@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1254.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
|
|
6
6
|
"keywords": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "9813d2a1e8d42807e3ab7149876a5ba5a51589fc23547f6f8b66174d"
|
|
71
71
|
}
|
|
@@ -52895,7 +52895,7 @@ const SCREENS = runtimeSetting({
|
|
|
52895
52895
|
"contentType": "application/javascript+ts-bundle",
|
|
52896
52896
|
"declaredCapabilities": [
|
|
52897
52897
|
"wix_api",
|
|
52898
|
-
"
|
|
52898
|
+
"wix_platform"
|
|
52899
52899
|
],
|
|
52900
52900
|
"minBinaryVersion": "0.1.0",
|
|
52901
52901
|
"bundleVersion": "0.0.1"
|
|
@@ -52980,7 +52980,8 @@ const SCREENS = runtimeSetting({
|
|
|
52980
52980
|
"props": {
|
|
52981
52981
|
"text": "$service:himiOwnerLiteSearch.summary",
|
|
52982
52982
|
"style": "caption",
|
|
52983
|
-
"color": "textSecondary"
|
|
52983
|
+
"color": "textSecondary",
|
|
52984
|
+
"collapseWhenEmpty": true
|
|
52984
52985
|
}
|
|
52985
52986
|
}
|
|
52986
52987
|
},
|
|
@@ -53095,7 +53096,7 @@ const SCREENS = runtimeSetting({
|
|
|
53095
53096
|
"actionId": "himi.open",
|
|
53096
53097
|
"params": {
|
|
53097
53098
|
"screenId": "$item.screenId",
|
|
53098
|
-
"id": "$item.
|
|
53099
|
+
"id": "$item.recordId"
|
|
53099
53100
|
}
|
|
53100
53101
|
}
|
|
53101
53102
|
}
|
|
@@ -53201,8 +53202,9 @@ const SCREENS = runtimeSetting({
|
|
|
53201
53202
|
"package": "himalaya",
|
|
53202
53203
|
"type": "Text",
|
|
53203
53204
|
"props": {
|
|
53204
|
-
"text": "
|
|
53205
|
-
"style": "bodyEmphasis"
|
|
53205
|
+
"text": "$service:himiOwnerLiteSearch.emptyTitle",
|
|
53206
|
+
"style": "bodyEmphasis",
|
|
53207
|
+
"maxLines": 2
|
|
53206
53208
|
}
|
|
53207
53209
|
}
|
|
53208
53210
|
},
|
|
@@ -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
|
+
}
|
|
@@ -19,8 +19,277 @@
|
|
|
19
19
|
|
|
20
20
|
import { defineService } from "@himalaya/state";
|
|
21
21
|
import { serializeForAgent } from "@himalaya/agent";
|
|
22
|
+
import { searchOrders } from "@himalaya/wix-ecom";
|
|
23
|
+
import { queryBookings, type WixBooking } from "@himalaya/wix-bookings";
|
|
24
|
+
import { site } from "@himalaya/wix-platform";
|
|
22
25
|
import { activeScreenFromInput, attentionRow, navRow, withDerived, type DrawerState } from "./logic.js";
|
|
23
|
-
import {
|
|
26
|
+
import { UPCOMING_BOOKING_STATUSES, upcomingBookingCount } from "../_shared/owner-bookings.js";
|
|
27
|
+
import { ownerOrderAttentionFilter } from "../_shared/owner-order-attention.js";
|
|
28
|
+
import { fallbackNameFrom, readSelectedSite, selectedSiteName } from "../_shared/owner-selected-site.js";
|
|
29
|
+
|
|
30
|
+
const ATTENTION_PAGE = 50;
|
|
31
|
+
const DISMISSED_AT_KEY = "himi.ownerLite.drawer.attentionDismissedAt.v1";
|
|
32
|
+
const OBSERVED_ACTIVITY_AT_KEY = "himi.ownerLite.drawer.latestObservedActivityAt.v1";
|
|
33
|
+
const DISMISSED_BOOKINGS_KEY = "himi.ownerLite.drawer.dismissedBookings.v1";
|
|
34
|
+
const OBSERVED_BOOKINGS_KEY = "himi.ownerLite.drawer.observedBookings.v1";
|
|
35
|
+
const DISMISSED_ORDERS_KEY = "himi.ownerLite.drawer.dismissedOrders.v1";
|
|
36
|
+
const OBSERVED_ORDERS_KEY = "himi.ownerLite.drawer.observedOrders.v1";
|
|
37
|
+
let refreshRevision = 0;
|
|
38
|
+
|
|
39
|
+
type DrawerStorage = { getItem(key: string): string | null; setItem(key: string, value: string): void };
|
|
40
|
+
type DrawerHost = typeof globalThis & { himalaya?: { storage?: DrawerStorage; input?: Record<string, unknown> } };
|
|
41
|
+
|
|
42
|
+
function currentSiteId(): string {
|
|
43
|
+
const selected = readSelectedSite()?.id;
|
|
44
|
+
if (selected) return selected;
|
|
45
|
+
const business = (globalThis as DrawerHost).himalaya?.input?.business;
|
|
46
|
+
return typeof business === "string" && business.trim() !== "default" ? business.trim() : "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function dismissalKey(siteId = currentSiteId()): string {
|
|
50
|
+
const input = (globalThis as DrawerHost).himalaya?.input;
|
|
51
|
+
const key = siteId || (typeof input?.business === "string" ? input.business.trim() : "") || "default";
|
|
52
|
+
return DISMISSED_AT_KEY + ":" + encodeURIComponent(key);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function dismissedAt(key = dismissalKey()): string {
|
|
56
|
+
// If persistence is unavailable, fail the refresh instead of treating a failed
|
|
57
|
+
// read as “not dismissed” and unexpectedly restoring hidden reminders.
|
|
58
|
+
const value = (globalThis as DrawerHost).himalaya?.storage?.getItem(key) ?? "";
|
|
59
|
+
return value && Number.isFinite(Date.parse(value)) ? value : "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function latestActivityAt(record: { createdDate?: unknown; updatedDate?: unknown }): string {
|
|
63
|
+
const timestamps = [record.createdDate, record.updatedDate]
|
|
64
|
+
.filter((value): value is string => typeof value === "string" && Number.isFinite(Date.parse(value)))
|
|
65
|
+
.sort((a, b) => Date.parse(b) - Date.parse(a));
|
|
66
|
+
return timestamps[0] ?? "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function newerActivity(left: string, right: string): string {
|
|
70
|
+
return !left || (right && Date.parse(right) > Date.parse(left)) ? right : left;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function rememberObservedActivity(siteKey: string, value: string): void {
|
|
74
|
+
if (!value) return;
|
|
75
|
+
try {
|
|
76
|
+
const storage = (globalThis as DrawerHost).himalaya?.storage;
|
|
77
|
+
const key = `${OBSERVED_ACTIVITY_AT_KEY}:${encodeURIComponent(siteKey)}`;
|
|
78
|
+
const previous = storage?.getItem(key) ?? "";
|
|
79
|
+
const latest = newerActivity(previous, value);
|
|
80
|
+
if (latest) storage?.setItem(key, latest);
|
|
81
|
+
} catch { /* best-effort local preference */ }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type RecordSnapshot = Record<string, string>;
|
|
85
|
+
type BookingSnapshot = RecordSnapshot;
|
|
86
|
+
type OrderSnapshot = RecordSnapshot;
|
|
87
|
+
|
|
88
|
+
function bookingSnapshotKey(prefix: string, siteKey: string): string {
|
|
89
|
+
return `${prefix}:${encodeURIComponent(siteKey)}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function readRecordSnapshot(key: string): RecordSnapshot | undefined {
|
|
93
|
+
const raw = (globalThis as DrawerHost).himalaya?.storage?.getItem(key);
|
|
94
|
+
if (raw === null || raw === undefined) return undefined;
|
|
95
|
+
const value = JSON.parse(raw) as unknown;
|
|
96
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
97
|
+
throw new Error("Stored drawer reminder snapshot must be an object");
|
|
98
|
+
}
|
|
99
|
+
const entries = Object.entries(value);
|
|
100
|
+
if (entries.some((entry) => typeof entry[1] !== "string")) {
|
|
101
|
+
throw new Error("Stored drawer reminder snapshot values must be strings");
|
|
102
|
+
}
|
|
103
|
+
return Object.fromEntries(entries) as RecordSnapshot;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const readBookingSnapshot = (key: string): BookingSnapshot | undefined => readRecordSnapshot(key);
|
|
107
|
+
|
|
108
|
+
function orderSignature(order: {
|
|
109
|
+
archived?: unknown;
|
|
110
|
+
status?: unknown;
|
|
111
|
+
paymentStatus?: unknown;
|
|
112
|
+
fulfillmentStatus?: unknown;
|
|
113
|
+
createdDate?: unknown;
|
|
114
|
+
updatedDate?: unknown;
|
|
115
|
+
}): string {
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
archived: order.archived ?? null,
|
|
118
|
+
status: order.status ?? "",
|
|
119
|
+
paymentStatus: order.paymentStatus ?? "",
|
|
120
|
+
fulfillmentStatus: order.fulfillmentStatus ?? "",
|
|
121
|
+
createdDate: order.createdDate ?? "",
|
|
122
|
+
updatedDate: order.updatedDate ?? "",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function orderSnapshot(orders: Array<{ id?: string } & Parameters<typeof orderSignature>[0]>): OrderSnapshot {
|
|
127
|
+
return Object.fromEntries(orders.filter((order) => order.id)
|
|
128
|
+
.map((order) => [order.id!, orderSignature(order)]));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function rememberObservedOrders(siteKey: string, snapshot: OrderSnapshot): void {
|
|
132
|
+
try {
|
|
133
|
+
(globalThis as DrawerHost).himalaya?.storage?.setItem(
|
|
134
|
+
bookingSnapshotKey(OBSERVED_ORDERS_KEY, siteKey), JSON.stringify(snapshot),
|
|
135
|
+
);
|
|
136
|
+
} catch { /* best-effort local preference */ }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function bookingSignature(booking: WixBooking): string {
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
status: booking.status ?? "",
|
|
142
|
+
paymentStatus: booking.paymentStatus ?? "",
|
|
143
|
+
startDate: booking.startDate ?? "",
|
|
144
|
+
endDate: booking.endDate ?? "",
|
|
145
|
+
slotStartDate: booking.bookedEntity?.slot?.startDate ?? "",
|
|
146
|
+
slotEndDate: booking.bookedEntity?.slot?.endDate ?? "",
|
|
147
|
+
totalParticipants: booking.totalParticipants ?? null,
|
|
148
|
+
serviceId: booking.bookedEntity?.slot?.serviceId ?? "",
|
|
149
|
+
serviceTitle: booking.bookedEntity?.title ?? "",
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function bookingSnapshot(bookings: WixBooking[]): BookingSnapshot {
|
|
154
|
+
return Object.fromEntries(bookings.filter((booking) => booking.id)
|
|
155
|
+
.map((booking) => [booking.id, bookingSignature(booking)]));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function rememberObservedBookings(siteKey: string, snapshot: BookingSnapshot): void {
|
|
159
|
+
try {
|
|
160
|
+
(globalThis as DrawerHost).himalaya?.storage?.setItem(
|
|
161
|
+
bookingSnapshotKey(OBSERVED_BOOKINGS_KEY, siteKey), JSON.stringify(snapshot),
|
|
162
|
+
);
|
|
163
|
+
} catch { /* best-effort local preference */ }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function rememberDismissal(siteKey: string): void {
|
|
167
|
+
try {
|
|
168
|
+
const storage = (globalThis as DrawerHost).himalaya?.storage;
|
|
169
|
+
const observedKey = bookingSnapshotKey(OBSERVED_BOOKINGS_KEY, siteKey);
|
|
170
|
+
const dismissedKey = bookingSnapshotKey(DISMISSED_BOOKINGS_KEY, siteKey);
|
|
171
|
+
storage?.setItem(dismissedKey, storage?.getItem(observedKey) ?? "{}");
|
|
172
|
+
const observedOrdersKey = bookingSnapshotKey(OBSERVED_ORDERS_KEY, siteKey);
|
|
173
|
+
const dismissedOrdersKey = bookingSnapshotKey(DISMISSED_ORDERS_KEY, siteKey);
|
|
174
|
+
storage?.setItem(dismissedOrdersKey, storage?.getItem(observedOrdersKey) ?? "{}");
|
|
175
|
+
} catch { /* best-effort local preference */ }
|
|
176
|
+
try {
|
|
177
|
+
const storage = (globalThis as DrawerHost).himalaya?.storage;
|
|
178
|
+
const observed = storage?.getItem(`${OBSERVED_ACTIVITY_AT_KEY}:${encodeURIComponent(siteKey)}`) ?? "";
|
|
179
|
+
// Wix record timestamps are server-generated. Do not substitute device time:
|
|
180
|
+
// an ahead clock can hide later Wix activity. Order snapshots cover records
|
|
181
|
+
// without timestamps.
|
|
182
|
+
const previous = dismissedAt(siteKey);
|
|
183
|
+
storage?.setItem(siteKey, observed || previous);
|
|
184
|
+
} catch { /* best-effort local preference */ }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function countAttentionOrders(
|
|
188
|
+
filter: Record<string, unknown>, siteId: string, dismissed: string, dismissedSnapshot: OrderSnapshot | undefined,
|
|
189
|
+
): Promise<{ count: number; latestActivity: string; snapshot: OrderSnapshot }> {
|
|
190
|
+
const orders: Array<{ id?: string } & Parameters<typeof orderSignature>[0]> = [];
|
|
191
|
+
let latestActivity = "";
|
|
192
|
+
let cursor = "";
|
|
193
|
+
const seenIds = new Set<string>();
|
|
194
|
+
const seen = new Set<string>();
|
|
195
|
+
while (true) {
|
|
196
|
+
const response = await searchOrders({ search: {
|
|
197
|
+
filter,
|
|
198
|
+
sort: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "id", order: "ASC" }],
|
|
199
|
+
cursorPaging: { limit: ATTENTION_PAGE, ...(cursor ? { cursor } : {}) },
|
|
200
|
+
} }, siteId ? site.forSite(siteId) : undefined);
|
|
201
|
+
for (const order of response.orders ?? []) {
|
|
202
|
+
latestActivity = newerActivity(latestActivity, latestActivityAt(order));
|
|
203
|
+
if (order.id && seenIds.has(order.id)) continue;
|
|
204
|
+
if (order.id) seenIds.add(order.id);
|
|
205
|
+
orders.push(order);
|
|
206
|
+
}
|
|
207
|
+
const metadata = response.metadata as { hasNext?: boolean; cursors?: { next?: string | null } } | undefined;
|
|
208
|
+
const next = metadata?.cursors?.next ?? "";
|
|
209
|
+
if (metadata?.hasNext === false) break;
|
|
210
|
+
// Keep the count already observed if the API advertises another page without
|
|
211
|
+
// giving us a usable cursor. This is a lower bound, but still alerts the owner.
|
|
212
|
+
if (!next || seen.has(next)) break;
|
|
213
|
+
seen.add(next);
|
|
214
|
+
cursor = next;
|
|
215
|
+
}
|
|
216
|
+
const snapshot = orderSnapshot(orders);
|
|
217
|
+
const hasDismissal = Boolean(dismissed) || dismissedSnapshot !== undefined;
|
|
218
|
+
const count = hasDismissal
|
|
219
|
+
? orders.filter((order) => {
|
|
220
|
+
const activity = latestActivityAt(order);
|
|
221
|
+
if (dismissed && activity) return Date.parse(activity) > Date.parse(dismissed);
|
|
222
|
+
if (dismissedSnapshot !== undefined) {
|
|
223
|
+
return !order.id || dismissedSnapshot[order.id] === undefined
|
|
224
|
+
|| dismissedSnapshot[order.id] !== orderSignature(order);
|
|
225
|
+
}
|
|
226
|
+
return activity !== "" && Date.parse(activity) > Date.parse(dismissed);
|
|
227
|
+
}).length
|
|
228
|
+
: orders.length;
|
|
229
|
+
return { count, latestActivity, snapshot };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function countUpcomingBookings(
|
|
233
|
+
filter: Record<string, unknown>, now: number, dismissed: string, dismissedSnapshot: BookingSnapshot | undefined, siteId: string,
|
|
234
|
+
): Promise<{ count: number; latestActivity: string; snapshot: BookingSnapshot }> {
|
|
235
|
+
const bookings: WixBooking[] = [];
|
|
236
|
+
const seenIds = new Set<string>();
|
|
237
|
+
const seenPages = new Set<string>();
|
|
238
|
+
let offset = 0;
|
|
239
|
+
let latestActivity = "";
|
|
240
|
+
const result = () => {
|
|
241
|
+
const hasDismissal = Boolean(dismissed) || dismissedSnapshot !== undefined;
|
|
242
|
+
const visible = hasDismissal
|
|
243
|
+
? bookings.filter((booking) => {
|
|
244
|
+
const createdAfterDismissal = dismissed !== ""
|
|
245
|
+
&& Date.parse(booking.createdDate ?? "") > Date.parse(dismissed);
|
|
246
|
+
if (createdAfterDismissal) return true;
|
|
247
|
+
if (!dismissedSnapshot) return false; // Legacy timestamp-only dismissal.
|
|
248
|
+
if (!booking.id) return false;
|
|
249
|
+
return dismissedSnapshot[booking.id] === undefined
|
|
250
|
+
|| dismissedSnapshot[booking.id] !== bookingSignature(booking);
|
|
251
|
+
})
|
|
252
|
+
: bookings;
|
|
253
|
+
return {
|
|
254
|
+
count: upcomingBookingCount(visible, now),
|
|
255
|
+
latestActivity,
|
|
256
|
+
snapshot: bookingSnapshot(bookings),
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
while (true) {
|
|
260
|
+
const response = await queryBookings({ query: {
|
|
261
|
+
filter,
|
|
262
|
+
sort: [{ fieldName: "startDate", order: "ASC" }, { fieldName: "id", order: "ASC" }],
|
|
263
|
+
paging: { limit: ATTENTION_PAGE, offset },
|
|
264
|
+
} }, siteId ? site.forSite(siteId) : undefined);
|
|
265
|
+
const page = response.bookings ?? [];
|
|
266
|
+
const pageSignature = page.map((booking) => booking.id
|
|
267
|
+
? `id:${booking.id}`
|
|
268
|
+
: `record:${JSON.stringify(booking)}`).sort().join("\0");
|
|
269
|
+
if (pageSignature && seenPages.has(pageSignature)) {
|
|
270
|
+
return result();
|
|
271
|
+
}
|
|
272
|
+
if (pageSignature) seenPages.add(pageSignature);
|
|
273
|
+
for (const booking of page) {
|
|
274
|
+
latestActivity = newerActivity(latestActivity, latestActivityAt(booking));
|
|
275
|
+
if (booking.id && seenIds.has(booking.id)) continue;
|
|
276
|
+
if (booking.id) seenIds.add(booking.id);
|
|
277
|
+
bookings.push(booking);
|
|
278
|
+
}
|
|
279
|
+
// Some Wix responses mark a total as too large to count even though the
|
|
280
|
+
// committed SDK metadata type does not include that wire field.
|
|
281
|
+
const metadata = response.pagingMetadata as (typeof response.pagingMetadata & { tooManyToCount?: boolean }) | undefined;
|
|
282
|
+
const total = metadata?.total;
|
|
283
|
+
if (typeof total === "number" && Number.isFinite(total) && metadata?.tooManyToCount !== true) {
|
|
284
|
+
const expected = Math.max(0, Math.floor(total));
|
|
285
|
+
if (bookings.length >= expected || page.length === 0) return result();
|
|
286
|
+
offset += page.length;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (page.length < ATTENTION_PAGE) return result();
|
|
290
|
+
offset += page.length;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
24
293
|
|
|
25
294
|
/** The shell passes the screen it is showing as worker input, so the drawer can mark
|
|
26
295
|
* that destination selected the way the declarative drawer does. */
|
|
@@ -35,7 +304,7 @@ const currentSiteName = () => selectedSiteName(fallbackBusinessName);
|
|
|
35
304
|
const drawer = defineService<DrawerState, {
|
|
36
305
|
/** The panel opened (root onAppear): re-read the live site and the active screen.
|
|
37
306
|
* `arg.businessName` is the cold-start fallback for the site name. */
|
|
38
|
-
refresh: (s: DrawerState, arg?: unknown) => DrawerState
|
|
307
|
+
refresh: (s: DrawerState, arg?: unknown) => Promise<DrawerState>;
|
|
39
308
|
/** Re-read the active screen from host input. An explicit
|
|
40
309
|
* `{ screenId }` wins, for a host that pushes the change instead. */
|
|
41
310
|
syncActive: (s: DrawerState, args?: { screenId?: string }) => DrawerState;
|
|
@@ -57,20 +326,59 @@ const drawer = defineService<DrawerState, {
|
|
|
57
326
|
navRow("drawer_manage", "Manage", "grid", "himi_owner_lite_manage"),
|
|
58
327
|
navRow("drawer_account", "Account", "person", "himi_owner_lite_account"),
|
|
59
328
|
],
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
attentionRow("att_appts", "Appointments", "calendar", "himi_owner_lite_appointments", 1),
|
|
63
|
-
],
|
|
329
|
+
// Live rows arrive only after refresh. A cold/failed read never invents reminders.
|
|
330
|
+
attention: [],
|
|
64
331
|
confirmingClear: false,
|
|
65
332
|
activeScreenId: hostActiveScreen(),
|
|
66
333
|
}),
|
|
67
334
|
actions: {
|
|
68
|
-
refresh: (s, arg) => {
|
|
335
|
+
refresh: async (s, arg): Promise<DrawerState> => {
|
|
69
336
|
fallbackBusinessName = fallbackNameFrom(arg, fallbackBusinessName);
|
|
70
337
|
const screenId = (arg as { screenId?: unknown } | undefined)?.screenId;
|
|
338
|
+
const now = Date.now();
|
|
339
|
+
// Invalidate older work before any read that can fail, including persistence.
|
|
340
|
+
const revision = ++refreshRevision;
|
|
341
|
+
const siteId = currentSiteId();
|
|
342
|
+
const siteKey = dismissalKey(siteId);
|
|
343
|
+
const dismissed = dismissedAt(siteKey);
|
|
344
|
+
const dismissedSnapshot = readBookingSnapshot(bookingSnapshotKey(DISMISSED_BOOKINGS_KEY, siteKey));
|
|
345
|
+
const dismissedOrders = readRecordSnapshot(bookingSnapshotKey(DISMISSED_ORDERS_KEY, siteKey));
|
|
346
|
+
// Only the newest same-site refresh may apply, so a slower earlier request
|
|
347
|
+
// cannot replace a result from a newer refresh.
|
|
348
|
+
const [ordersResult, bookingsResult] = await Promise.allSettled([
|
|
349
|
+
countAttentionOrders(ownerOrderAttentionFilter(), siteId, dismissed, dismissedOrders),
|
|
350
|
+
countUpcomingBookings({
|
|
351
|
+
startDate: { $gte: new Date(now).toISOString() },
|
|
352
|
+
status: { $in: UPCOMING_BOOKING_STATUSES },
|
|
353
|
+
}, now, dismissed, dismissedSnapshot, siteId),
|
|
354
|
+
]);
|
|
355
|
+
// A user may dismiss while either request is in flight. Do not let this
|
|
356
|
+
// older result put the dismissed rows back into the current state.
|
|
357
|
+
if (revision !== refreshRevision || siteKey !== dismissalKey()) return drawer.getState();
|
|
358
|
+
const latestActivity = newerActivity(
|
|
359
|
+
ordersResult.status === "fulfilled" ? ordersResult.value.latestActivity : "",
|
|
360
|
+
bookingsResult.status === "fulfilled" ? bookingsResult.value.latestActivity : "",
|
|
361
|
+
);
|
|
362
|
+
rememberObservedActivity(siteKey, latestActivity);
|
|
363
|
+
if (ordersResult.status === "fulfilled") rememberObservedOrders(siteKey, ordersResult.value.snapshot);
|
|
364
|
+
if (bookingsResult.status === "fulfilled") rememberObservedBookings(siteKey, bookingsResult.value.snapshot);
|
|
365
|
+
const attention: DrawerState["attention"] = [];
|
|
366
|
+
if (ordersResult.status === "fulfilled") {
|
|
367
|
+
const count = ordersResult.value.count;
|
|
368
|
+
if (count > 0) attention.push(attentionRow("att_orders", "Orders", "cart", "himi_owner_lite_orders", count));
|
|
369
|
+
}
|
|
370
|
+
if (bookingsResult.status === "fulfilled") {
|
|
371
|
+
const count = bookingsResult.value.count;
|
|
372
|
+
if (count > 0) attention.push(attentionRow("att_appts", "Appointments", "calendar", "himi_owner_lite_appointments", count));
|
|
373
|
+
}
|
|
374
|
+
// Other synchronous drawer actions (such as opening the clear-confirmation
|
|
375
|
+
// prompt) may run while the requests are in flight. Keep those latest fields
|
|
376
|
+
// when committing the refreshed data instead of restoring this action's snapshot.
|
|
377
|
+
const latest = drawer.getState();
|
|
71
378
|
return snap(withDerived({
|
|
72
|
-
...
|
|
379
|
+
...latest,
|
|
73
380
|
siteName: currentSiteName(),
|
|
381
|
+
attention,
|
|
74
382
|
activeScreenId: typeof screenId === "string" ? screenId : hostActiveScreen(),
|
|
75
383
|
}));
|
|
76
384
|
},
|
|
@@ -80,7 +388,11 @@ const drawer = defineService<DrawerState, {
|
|
|
80
388
|
})),
|
|
81
389
|
askClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: true })),
|
|
82
390
|
cancelClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: false })),
|
|
83
|
-
clearAttention: (s) =>
|
|
391
|
+
clearAttention: (s) => {
|
|
392
|
+
refreshRevision += 1;
|
|
393
|
+
rememberDismissal(dismissalKey());
|
|
394
|
+
return snap(withDerived({ ...s, attention: [], confirmingClear: false }));
|
|
395
|
+
},
|
|
84
396
|
},
|
|
85
397
|
});
|
|
86
398
|
|
|
@@ -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. */
|