@wix/himalaya-cli 0.1253.0 → 0.1254.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +7 -5
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/community-club-owner/dev/index.ts +7 -5
- package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/community-club-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/community-club-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/community-club-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/events-meetups-owner/dev/index.ts +7 -5
- package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/events-meetups-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/gym-fitness-owner/dev/index.ts +7 -5
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/nonprofit-cause-owner/dev/index.ts +7 -5
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/owner-lite/dev/index.ts +7 -5
- package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/owner-lite/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/owner-lite/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/owner-lite/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/podcast-creator-owner/dev/index.ts +7 -5
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/portfolio-creative-owner/dev/index.ts +7 -5
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/restaurant-owner/dev/index.ts +7 -5
- package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/restaurant-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/retail-store-owner/dev/index.ts +7 -5
- package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/retail-store-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
- package/templates/yoga-studio-owner/dev/index.ts +7 -5
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +78 -7
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-order-attention.ts +18 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/index.ts +321 -9
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-home/index.ts +2 -10
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-search/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-search/logic.ts +36 -9
|
@@ -10,6 +10,7 @@ import { countSites } from "@himalaya/wix-site-list";
|
|
|
10
10
|
import { searchOrders, type WixOrder } from "@himalaya/wix-ecom";
|
|
11
11
|
import { queryBookings } from "@himalaya/wix-bookings";
|
|
12
12
|
import { UPCOMING_BOOKING_STATUSES, upcomingBookingCount } from "../_shared/owner-bookings.js";
|
|
13
|
+
import { ownerOrderAttentionFilter } from "../_shared/owner-order-attention.js";
|
|
13
14
|
import { queryMembers } from "@himalaya/wix-members";
|
|
14
15
|
import { queryReviews } from "@himalaya/wix-reviews";
|
|
15
16
|
import { queryReservations } from "@himalaya/wix-table-reservations";
|
|
@@ -102,8 +103,6 @@ const UPCOMING_RESERVATION_STATUSES = ["HELD", "RESERVED", "REQUESTED"];
|
|
|
102
103
|
const UPCOMING_EVENT_STATUSES = ["UPCOMING", "SCHEDULED"];
|
|
103
104
|
const ATTENTION_MEMBER_STATUSES = ["PENDING"];
|
|
104
105
|
const ATTENTION_REVIEW_STATUSES = ["IN_MODERATION", "SUBMITTED"];
|
|
105
|
-
const ATTENTION_PAYMENT_STATUSES = ["NOT_PAID", "PARTIALLY_PAID"];
|
|
106
|
-
const ATTENTION_FULFILLMENT_STATUSES = ["NOT_FULFILLED", "PARTIALLY_FULFILLED"];
|
|
107
106
|
const SOURCE_LABELS: Record<HomeSourceId, string> = {
|
|
108
107
|
products: "Products", posts: "Posts", events: "Events", sites: "Sites",
|
|
109
108
|
orders: "Orders", bookings: "Bookings", members: "Members",
|
|
@@ -469,14 +468,7 @@ async function loadSourcePart(source: HomeSourceId, part: SourcePart): Promise<S
|
|
|
469
468
|
case "sites": return countSites({}) as Promise<SourcePayload>;
|
|
470
469
|
case "orders": {
|
|
471
470
|
if (part === "primary") return searchOrders({ search: {
|
|
472
|
-
filter:
|
|
473
|
-
archived: false,
|
|
474
|
-
status: { $nin: ["CANCELED", "CANCELLED"] },
|
|
475
|
-
$or: [
|
|
476
|
-
{ paymentStatus: { $in: ATTENTION_PAYMENT_STATUSES } },
|
|
477
|
-
{ paymentStatus: "PAID", fulfillmentStatus: { $in: ATTENTION_FULFILLMENT_STATUSES } },
|
|
478
|
-
],
|
|
479
|
-
},
|
|
471
|
+
filter: ownerOrderAttentionFilter(),
|
|
480
472
|
sort: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "id", order: "ASC" }],
|
|
481
473
|
cursorPaging: { limit: TODAY_PAGE },
|
|
482
474
|
} }) as Promise<SourcePayload>;
|
|
@@ -213,7 +213,10 @@ function copyFromParams(): SearchCopy {
|
|
|
213
213
|
allFailed: copyParam("ownerSearch.allFailed", DEFAULT_COPY.allFailed),
|
|
214
214
|
loadedOnly: copyParam("ownerSearch.loadedOnly", DEFAULT_COPY.loadedOnly),
|
|
215
215
|
loadingSummary: copyParam("ownerSearch.loading", DEFAULT_COPY.loadingSummary),
|
|
216
|
-
|
|
216
|
+
// Keep the pre-worker-heading copy key alive for existing copy packs. The
|
|
217
|
+
// newer `empty` key takes precedence when present; both flow through the
|
|
218
|
+
// query interpolator so neither can hide what the owner searched.
|
|
219
|
+
emptySummary: copyParam("ownerSearch.empty", copyParam("ownerSearch.emptyTitle", DEFAULT_COPY.emptySummary)),
|
|
217
220
|
idleSummary: copyParam("ownerSearch.idle", DEFAULT_COPY.idleSummary),
|
|
218
221
|
permission: copyParam("ownerSearch.permission", DEFAULT_COPY.permission),
|
|
219
222
|
setup: copyParam("ownerSearch.setup", DEFAULT_COPY.setup),
|
|
@@ -240,6 +243,7 @@ const IDLE: State = {
|
|
|
240
243
|
anyLoading: false,
|
|
241
244
|
showEmpty: false,
|
|
242
245
|
hideEmpty: true,
|
|
246
|
+
emptyTitle: "",
|
|
243
247
|
};
|
|
244
248
|
|
|
245
249
|
/** Rebuilt on `load`, because the allowlist arrives with the descriptor. */
|
|
@@ -106,6 +106,14 @@ export interface SearchItem {
|
|
|
106
106
|
/** Stable across refreshes, so a re-fetch cannot re-key a row under the
|
|
107
107
|
* user's finger: `h:<source>`, `r:<source>:<id>`, `f:<source>`. */
|
|
108
108
|
key: string;
|
|
109
|
+
/** The row's LIST identity — always equal to `key`, never blank.
|
|
110
|
+
*
|
|
111
|
+
* A bound list keys its rows on `id` (the web BoundList does, verbatim). This
|
|
112
|
+
* carried the record id, which left every heading and footer at `""` and let
|
|
113
|
+
* two domains that reuse a record id collide — so React reconciled headings
|
|
114
|
+
* onto each other as groups landed and painted "Contacts" three times.
|
|
115
|
+
* What a record OPENS is `recordId`. */
|
|
116
|
+
id: string;
|
|
109
117
|
/** The source this row belongs to — a retry or a pager addresses it. */
|
|
110
118
|
sourceId: string;
|
|
111
119
|
// Exactly one kind is true. Pre-negated, because a `hiddenWhen` takes one path.
|
|
@@ -122,7 +130,9 @@ export interface SearchItem {
|
|
|
122
130
|
isLoading: boolean;
|
|
123
131
|
notLoading: boolean;
|
|
124
132
|
// Record.
|
|
125
|
-
id
|
|
133
|
+
/** The record this row opens — the destination loads it by this id. Blank on
|
|
134
|
+
* headings and footers, which open nothing. */
|
|
135
|
+
recordId: string;
|
|
126
136
|
title: string;
|
|
127
137
|
subtitle: string;
|
|
128
138
|
statusLabel: string;
|
|
@@ -163,6 +173,10 @@ export interface SearchState {
|
|
|
163
173
|
* nobody has typed into yet, and again over every result still in flight. */
|
|
164
174
|
showEmpty: boolean;
|
|
165
175
|
hideEmpty: boolean;
|
|
176
|
+
/** The empty state's heading, naming the query ("No records match “mug”").
|
|
177
|
+
* Blank unless `showEmpty` — and while it shows, `summary` is blank, because
|
|
178
|
+
* the two sat one above the other saying the same sentence. */
|
|
179
|
+
emptyTitle: string;
|
|
166
180
|
}
|
|
167
181
|
|
|
168
182
|
export interface SearchCopy {
|
|
@@ -192,7 +206,9 @@ export const DEFAULT_COPY: SearchCopy = {
|
|
|
192
206
|
allFailed: "No area could be searched",
|
|
193
207
|
loadedOnly: "{n} in loaded records",
|
|
194
208
|
loadingSummary: "Searching…",
|
|
195
|
-
|
|
209
|
+
/** The empty state's heading. `{q}` is the query the owner typed, so the
|
|
210
|
+
* state says WHAT matched nothing rather than only that something did not. */
|
|
211
|
+
emptySummary: "No records match “{q}”",
|
|
196
212
|
idleSummary: "Search your business records",
|
|
197
213
|
permission: "You don’t have access to this area",
|
|
198
214
|
setup: "This area isn’t set up on this site",
|
|
@@ -231,6 +247,14 @@ function fill(template: string, n: number, token = "{n}"): string {
|
|
|
231
247
|
return template.includes(token) ? template.split(token).join(String(n)) : `${template} ${n}`;
|
|
232
248
|
}
|
|
233
249
|
|
|
250
|
+
/** `{q}` → the query. Custom copy stays query-specific even when it omits the
|
|
251
|
+
* placeholder, so the empty state always identifies what was searched. */
|
|
252
|
+
function fillQuery(template: string, query: string): string {
|
|
253
|
+
return template.includes("{q}")
|
|
254
|
+
? template.split("{q}").join(query)
|
|
255
|
+
: `${template}: “${query}”`;
|
|
256
|
+
}
|
|
257
|
+
|
|
234
258
|
function idleGroup(adapter: SearchAdapter): SearchGroup {
|
|
235
259
|
return decorate({
|
|
236
260
|
id: adapter.id,
|
|
@@ -273,9 +297,9 @@ function baseItem(sourceId: string): SearchItem {
|
|
|
273
297
|
isHeader: false, notHeader: true,
|
|
274
298
|
isRecord: false, notRecord: true,
|
|
275
299
|
isFooter: false, notFooter: true,
|
|
276
|
-
label: "", countLabel: "",
|
|
300
|
+
id: "", label: "", countLabel: "",
|
|
277
301
|
isLoading: false, notLoading: true,
|
|
278
|
-
|
|
302
|
+
recordId: "", title: "", subtitle: "", statusLabel: "", image: "", screenId: "",
|
|
279
303
|
error: "", hasError: false, noError: true,
|
|
280
304
|
canLoadMore: false, cannotLoadMore: true, isLoadingMore: false,
|
|
281
305
|
};
|
|
@@ -302,7 +326,7 @@ export function projectItems(groups: SearchGroup[]): SearchItem[] {
|
|
|
302
326
|
if (silent) continue;
|
|
303
327
|
items.push({
|
|
304
328
|
...baseItem(group.id),
|
|
305
|
-
key: `h:${group.id}`,
|
|
329
|
+
key: `h:${group.id}`, id: `h:${group.id}`,
|
|
306
330
|
isHeader: true, notHeader: false,
|
|
307
331
|
label: group.label,
|
|
308
332
|
// A count is only shown once there is something to count: "0" under a
|
|
@@ -313,9 +337,9 @@ export function projectItems(groups: SearchGroup[]): SearchItem[] {
|
|
|
313
337
|
for (const row of group.rows) {
|
|
314
338
|
items.push({
|
|
315
339
|
...baseItem(group.id),
|
|
316
|
-
key: `r:${group.id}:${row.id}`,
|
|
340
|
+
key: `r:${group.id}:${row.id}`, id: `r:${group.id}:${row.id}`,
|
|
317
341
|
isRecord: true, notRecord: false,
|
|
318
|
-
|
|
342
|
+
recordId: row.id, title: row.title, subtitle: row.subtitle,
|
|
319
343
|
statusLabel: row.statusLabel, image: row.image, screenId: row.screenId,
|
|
320
344
|
});
|
|
321
345
|
}
|
|
@@ -325,7 +349,7 @@ export function projectItems(groups: SearchGroup[]): SearchItem[] {
|
|
|
325
349
|
if (group.hasError || group.problem !== "" || group.canLoadMore) {
|
|
326
350
|
items.push({
|
|
327
351
|
...baseItem(group.id),
|
|
328
|
-
key: `f:${group.id}`,
|
|
352
|
+
key: `f:${group.id}`, id: `f:${group.id}`,
|
|
329
353
|
isFooter: true, notFooter: false,
|
|
330
354
|
error: group.error,
|
|
331
355
|
hasError: group.error !== "", noError: group.error === "",
|
|
@@ -393,7 +417,9 @@ export function createGroupedSearch(
|
|
|
393
417
|
: anyLoading && total === 0
|
|
394
418
|
? copy.loadingSummary
|
|
395
419
|
: total === 0
|
|
396
|
-
|
|
420
|
+
// Empty and answered: the empty block carries the sentence, so the
|
|
421
|
+
// summary line above it steps aside rather than repeating it.
|
|
422
|
+
? (anyAnswered ? "" : copy.allFailed)
|
|
397
423
|
: total === 1
|
|
398
424
|
? copy.resultsOne
|
|
399
425
|
: areas === 1
|
|
@@ -416,6 +442,7 @@ export function createGroupedSearch(
|
|
|
416
442
|
anyLoading,
|
|
417
443
|
showEmpty: emptyAndAnswered,
|
|
418
444
|
hideEmpty: !emptyAndAnswered,
|
|
445
|
+
emptyTitle: emptyAndAnswered ? fillQuery(copy.emptySummary, query) : "",
|
|
419
446
|
};
|
|
420
447
|
};
|
|
421
448
|
|
|
@@ -52963,7 +52963,7 @@ const SCREENS = runtimeSetting({
|
|
|
52963
52963
|
"contentType": "application/javascript+ts-bundle",
|
|
52964
52964
|
"declaredCapabilities": [
|
|
52965
52965
|
"wix_api",
|
|
52966
|
-
"
|
|
52966
|
+
"wix_platform"
|
|
52967
52967
|
],
|
|
52968
52968
|
"minBinaryVersion": "0.1.0",
|
|
52969
52969
|
"bundleVersion": "0.0.1"
|
|
@@ -53048,7 +53048,8 @@ const SCREENS = runtimeSetting({
|
|
|
53048
53048
|
"props": {
|
|
53049
53049
|
"text": "$service:himiOwnerLiteSearch.summary",
|
|
53050
53050
|
"style": "caption",
|
|
53051
|
-
"color": "textSecondary"
|
|
53051
|
+
"color": "textSecondary",
|
|
53052
|
+
"collapseWhenEmpty": true
|
|
53052
53053
|
}
|
|
53053
53054
|
}
|
|
53054
53055
|
},
|
|
@@ -53163,7 +53164,7 @@ const SCREENS = runtimeSetting({
|
|
|
53163
53164
|
"actionId": "himi.open",
|
|
53164
53165
|
"params": {
|
|
53165
53166
|
"screenId": "$item.screenId",
|
|
53166
|
-
"id": "$item.
|
|
53167
|
+
"id": "$item.recordId"
|
|
53167
53168
|
}
|
|
53168
53169
|
}
|
|
53169
53170
|
}
|
|
@@ -53269,8 +53270,9 @@ const SCREENS = runtimeSetting({
|
|
|
53269
53270
|
"package": "himalaya",
|
|
53270
53271
|
"type": "Text",
|
|
53271
53272
|
"props": {
|
|
53272
|
-
"text": "
|
|
53273
|
-
"style": "bodyEmphasis"
|
|
53273
|
+
"text": "$service:himiOwnerLiteSearch.emptyTitle",
|
|
53274
|
+
"style": "bodyEmphasis",
|
|
53275
|
+
"maxLines": 2
|
|
53274
53276
|
}
|
|
53275
53277
|
}
|
|
53276
53278
|
},
|
|
@@ -49,6 +49,76 @@ function siteZone(): Promise<string | undefined> {
|
|
|
49
49
|
return zoneLookup;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/** A subscription order stores its buyer as an internal member/contact id. Resolve the
|
|
53
|
+
* display name so the owner can search subscriptions by the same person name shown in
|
|
54
|
+
* the dedicated subscriptions screen. Fetch member ids directly, and page the member
|
|
55
|
+
* directory only for contact ids. This is best-effort: missing member access must not
|
|
56
|
+
* hide otherwise readable plan orders. */
|
|
57
|
+
async function subscriptionMemberNames(orders: unknown[]): Promise<Map<string, string>> {
|
|
58
|
+
const memberIds = [...new Set(orders.map(order => field(order, 'buyer.memberId')).filter(Boolean))];
|
|
59
|
+
const unresolvedContacts = new Set(orders.map(order => field(order, 'buyer.contactId')).filter(Boolean));
|
|
60
|
+
const names = new Map<string, string>();
|
|
61
|
+
const remember = (member: unknown) => {
|
|
62
|
+
const emails = at(member, 'contact.emails');
|
|
63
|
+
const email = Array.isArray(emails) ? scalar(emails[0]) : '';
|
|
64
|
+
const displayName = name(member, 'contact.firstName', 'contact.lastName')
|
|
65
|
+
|| field(member, 'profile.nickname') || email || field(member, 'loginEmail');
|
|
66
|
+
if (!displayName) return;
|
|
67
|
+
const id = field(member, 'id'), contactId = field(member, 'contactId');
|
|
68
|
+
if (id) names.set(id, displayName);
|
|
69
|
+
if (contactId) {
|
|
70
|
+
names.set(contactId, displayName);
|
|
71
|
+
unresolvedContacts.delete(contactId);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// The first directory page is useful for buyers already in view. For a buyer outside
|
|
76
|
+
// that page, fetch the known member ID directly instead of walking the whole directory.
|
|
77
|
+
const pageSize = 100;
|
|
78
|
+
let offset = 0;
|
|
79
|
+
let canPageDirectory = false;
|
|
80
|
+
try {
|
|
81
|
+
const response = await queryMembers({ query: { paging: { limit: pageSize, offset } } });
|
|
82
|
+
const members = response.members ?? [];
|
|
83
|
+
for (const member of members) remember(member);
|
|
84
|
+
offset += members.length;
|
|
85
|
+
const total = response.metadata?.total;
|
|
86
|
+
canPageDirectory = members.length > 0
|
|
87
|
+
&& (typeof total === 'number' ? offset < total : members.length === pageSize);
|
|
88
|
+
} catch {
|
|
89
|
+
// Member details below can still work when the directory query is unavailable.
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const unresolvedMembers = memberIds.filter(id => !names.has(id));
|
|
93
|
+
for (let start = 0; start < unresolvedMembers.length; start += 10) {
|
|
94
|
+
const members = await Promise.all(unresolvedMembers.slice(start, start + 10).map(async id => {
|
|
95
|
+
try { return (await getMember(id)).member; } catch { return undefined; }
|
|
96
|
+
}));
|
|
97
|
+
for (const member of members) if (member) remember(member);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!unresolvedContacts.size || !canPageDirectory) return names;
|
|
101
|
+
try {
|
|
102
|
+
while (unresolvedContacts.size) {
|
|
103
|
+
const response = await queryMembers({ query: { paging: { limit: pageSize, offset } } });
|
|
104
|
+
const members = response.members ?? [];
|
|
105
|
+
for (const member of members) remember(member);
|
|
106
|
+
if (!members.length) break;
|
|
107
|
+
offset += members.length;
|
|
108
|
+
const total = response.metadata?.total;
|
|
109
|
+
if (typeof total === 'number' ? offset >= total : members.length < pageSize) break;
|
|
110
|
+
}
|
|
111
|
+
} catch {
|
|
112
|
+
// A later page can fail after earlier pages already resolved some buyers.
|
|
113
|
+
// Keep those names; this is best-effort context, never a reason to hide an order.
|
|
114
|
+
}
|
|
115
|
+
return names;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function subscriptionBuyerName(order: unknown, names: Map<string, string>): string {
|
|
119
|
+
return names.get(field(order, 'buyer.memberId')) || names.get(field(order, 'buyer.contactId')) || '';
|
|
120
|
+
}
|
|
121
|
+
|
|
52
122
|
/** A Wix date value as an instant. A calendar date with no time ("2026-11-07") is a DAY, not an
|
|
53
123
|
* instant: it is built at local midnight and formatted with no zone, so it never shifts across
|
|
54
124
|
* one — including on engines without Intl, where a zone could not be applied anyway. */
|
|
@@ -99,7 +169,8 @@ export async function queryOwnerModule(module: OwnerModuleId, cursor = ''): Prom
|
|
|
99
169
|
// The zone lookup runs beside the query, not after it: the two are independent, and serial
|
|
100
170
|
// they would double the time to a first paint.
|
|
101
171
|
const [raw, zone] = await Promise.all([queryRawOwnerModule(module, cursor), siteZone()]);
|
|
102
|
-
|
|
172
|
+
const subscribers = module === 'subscriptions' ? await subscriptionMemberNames(raw.items) : new Map<string, string>();
|
|
173
|
+
return { ...raw, items: raw.items.map(row => ownerRecord(module, row, zone, subscriptionBuyerName(row, subscribers))) };
|
|
103
174
|
}
|
|
104
175
|
|
|
105
176
|
async function queryRawOwnerModule(module: OwnerModuleId, cursor: string): Promise<CollectionPage<unknown>> {
|
|
@@ -171,7 +242,7 @@ async function queryRawOwnerModule(module: OwnerModuleId, cursor: string): Promi
|
|
|
171
242
|
}
|
|
172
243
|
}
|
|
173
244
|
|
|
174
|
-
export function ownerRecord(module: OwnerModuleId, raw: unknown, zone?: string): OwnerRecord {
|
|
245
|
+
export function ownerRecord(module: OwnerModuleId, raw: unknown, zone?: string, subscriberName = ''): OwnerRecord {
|
|
175
246
|
const when = (path: string, style?: DateStyle) => ownerDate(field(raw, path), zone, style);
|
|
176
247
|
const id = field(raw, 'id');
|
|
177
248
|
if (!id) throw new Error('Wix returned a record without an identity. Refresh this view.');
|
|
@@ -239,14 +310,13 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown, zone?: string):
|
|
|
239
310
|
details = [['Price', join(field(raw, 'pricing.price.value'), field(raw, 'pricing.price.currency'))], ['Free trial days', field(raw, 'pricing.freeTrialDays')]];
|
|
240
311
|
break;
|
|
241
312
|
case 'subscriptions':
|
|
242
|
-
// The buyer is only a member UUID here — not something an owner can read — so the
|
|
243
|
-
// row says when the plan started instead of echoing an internal id.
|
|
244
313
|
title = field(raw, 'planName'); statusLabel = words(field(raw, 'status'));
|
|
245
314
|
{
|
|
246
315
|
const started = when('startDate', 'short');
|
|
247
|
-
|
|
316
|
+
const startedLabel = started ? copyTemplate('ownerModules.startedOn', 'Started {date}', { date: started }) : '';
|
|
317
|
+
subtitle = join(subscriberName, startedLabel);
|
|
248
318
|
}
|
|
249
|
-
details = [['Started', when('startDate')], ['Ends', when('endDate')], ['Payment', words(field(raw, 'lastPaymentStatus'))]];
|
|
319
|
+
details = [['Subscriber', subscriberName], ['Started', when('startDate')], ['Ends', when('endDate')], ['Payment', words(field(raw, 'lastPaymentStatus'))]];
|
|
250
320
|
break;
|
|
251
321
|
case 'programs':
|
|
252
322
|
title = field(raw, 'description.title') || field(raw, 'details.title') || field(raw, 'name');
|
|
@@ -344,7 +414,8 @@ export async function getOwnerRecord(module: OwnerModuleId, id: string): Promise
|
|
|
344
414
|
}
|
|
345
415
|
case 'donationCampaigns': raw = (await getDonationCampaign(id)).donationCampaign; break;
|
|
346
416
|
}
|
|
347
|
-
const
|
|
417
|
+
const subscribers = module === 'subscriptions' ? await subscriptionMemberNames([raw]) : new Map<string, string>();
|
|
418
|
+
const record = ownerRecord(module, raw, await zone, subscriptionBuyerName(raw, subscribers));
|
|
348
419
|
if (record.id !== id) throw new Error('The returned record does not match this link. Refresh the collection.');
|
|
349
420
|
return record;
|
|
350
421
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The Home dashboard's canonical Orders attention filter. Keep drawer reminders on
|
|
2
|
+
* the same definition so the two surfaces never disagree about what needs review. */
|
|
3
|
+
export function ownerOrderAttentionFilter(updatedAfter?: string): Record<string, unknown> {
|
|
4
|
+
const filter: Record<string, unknown> = {
|
|
5
|
+
archived: false,
|
|
6
|
+
status: { $nin: ["CANCELED", "CANCELLED"] },
|
|
7
|
+
$or: [
|
|
8
|
+
{ paymentStatus: { $in: ["NOT_PAID", "PARTIALLY_PAID"] } },
|
|
9
|
+
{ paymentStatus: "PAID", fulfillmentStatus: { $in: ["NOT_FULFILLED", "PARTIALLY_FULFILLED"] } },
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
return updatedAfter
|
|
13
|
+
? { $and: [filter, { $or: [
|
|
14
|
+
{ createdDate: { $gt: updatedAfter } },
|
|
15
|
+
{ updatedDate: { $gt: updatedAfter } },
|
|
16
|
+
] }] }
|
|
17
|
+
: filter;
|
|
18
|
+
}
|