@wix/himalaya-cli 0.1209.0 → 0.1211.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/dist/cli.mjs +112 -18
- package/dist/preview-assets.generated.bin +0 -0
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/beauty-salon-owner/dev/index.ts +80 -50
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/beauty-salon-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/beauty-salon-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/branded-lite/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/community-club/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/community-club-owner/dev/index.ts +80 -50
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/community-club-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/community-club-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/community-club-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/community-club-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/community-club-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/events-meetups/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/events-meetups-owner/dev/index.ts +80 -50
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/events-meetups-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/events-meetups-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/events-meetups-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/gym-fitness/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/gym-fitness-owner/dev/index.ts +80 -50
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/gym-fitness-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/gym-fitness-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/nonprofit-cause/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/nonprofit-cause-owner/dev/index.ts +80 -50
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/nonprofit-cause-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/nonprofit-cause-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/owner-lite/dev/index.ts +81 -51
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/owner-lite/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/owner-lite/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/owner-lite/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/owner-lite/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/owner-lite/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-faq/index.ts +8 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/owner-lite/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/podcast-creator/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/podcast-creator-owner/dev/index.ts +80 -50
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/podcast-creator-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/podcast-creator-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/portfolio-creative/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/portfolio-creative-owner/dev/index.ts +80 -50
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/portfolio-creative-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/portfolio-creative-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/restaurant/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/restaurant-owner/dev/index.ts +80 -50
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/restaurant-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/restaurant-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/restaurant-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/retail-store/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/retail-store-owner/dev/index.ts +80 -50
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/retail-store-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/retail-store-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/retail-store-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
- package/templates/yoga-studio/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/yoga-studio-owner/dev/index.ts +80 -50
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +31 -16
- package/templates/yoga-studio-owner/tier5-src/_shared/engine-intl.ts +57 -16
- package/templates/yoga-studio-owner/tier5-src/_shared/himi-text.ts +24 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-account.ts +4 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +105 -37
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-selected-site.ts +103 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-site-pick.ts +74 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-account/index.ts +12 -2
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria-history/index.ts +1 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/index.ts +21 -2
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-home/index.ts +29 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +25 -5
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-overview/index.ts +8 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-sites/index.ts +14 -31
|
@@ -13,9 +13,11 @@ import { queryGiftCards, getGiftCard } from '@himalaya/wix-gift-cards';
|
|
|
13
13
|
import { queryDonationCampaigns, getDonationCampaign } from '@himalaya/wix-donations';
|
|
14
14
|
import { query as queryBadges, get as getBadge } from '@himalaya/wix-badges';
|
|
15
15
|
import { wixMediaUrl } from '@himalaya/wix-media';
|
|
16
|
+
import { getSiteTimeZone } from '@himalaya/wix-analytics';
|
|
16
17
|
import { type OwnerModuleId } from './owner-modules.js';
|
|
17
18
|
import { type CollectionPage } from './collection-query.js';
|
|
18
|
-
import { copyParam } from './himi-copy.js';
|
|
19
|
+
import { copyParam, copyTemplate } from './himi-copy.js';
|
|
20
|
+
import { calendarDay, formatDateMedium, formatMonthDay, formatWeekdayDateTime } from './engine-intl.js';
|
|
19
21
|
import { eligibleOwnerOperations } from './owner-operations.js';
|
|
20
22
|
|
|
21
23
|
export interface OwnerRecord {
|
|
@@ -33,91 +35,144 @@ const join = (...values: string[]) => values.filter(Boolean).join(' · ');
|
|
|
33
35
|
const name = (value: unknown, first: string, last: string) => [field(value, first), field(value, last)].filter(Boolean).join(' ');
|
|
34
36
|
const yesNo = (value: unknown) => typeof value === 'boolean' ? value ? 'Yes' : 'No' : '';
|
|
35
37
|
const LIMIT = 50;
|
|
38
|
+
|
|
39
|
+
/** The site's IANA zone, resolved once per worker. Wix hands every date here as a UTC instant;
|
|
40
|
+
* formatting it in the viewer's device zone (or leaving it raw) shows an owner outside UTC the
|
|
41
|
+
* wrong hour and sometimes the wrong day. A failed or unset lookup degrades to the
|
|
42
|
+
* device zone — a zone is garnish on a date, never a reason to fail the collection. The
|
|
43
|
+
* answer, failure included, is kept for the worker's life: a role that cannot read site
|
|
44
|
+
* properties would otherwise re-send the failing request for every page a search walks. */
|
|
45
|
+
let zoneLookup: Promise<string | undefined> | undefined;
|
|
46
|
+
function siteZone(): Promise<string | undefined> {
|
|
47
|
+
// `Promise.resolve().then` so a bridge that throws synchronously is caught here too.
|
|
48
|
+
zoneLookup ??= Promise.resolve().then(getSiteTimeZone).catch(() => undefined);
|
|
49
|
+
return zoneLookup;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A Wix date value as an instant. A calendar date with no time ("2026-11-07") is a DAY, not an
|
|
53
|
+
* instant: it is built at local midnight and formatted with no zone, so it never shifts across
|
|
54
|
+
* one — including on engines without Intl, where a zone could not be applied anyway. */
|
|
55
|
+
function ownerInstant(value: string): { date: Date; dayOnly: boolean } | undefined {
|
|
56
|
+
if (!value) return undefined;
|
|
57
|
+
const day = /^(\d{4})-(\d\d)-(\d\d)$/.exec(value);
|
|
58
|
+
if (day) return { date: new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3])), dayOnly: true };
|
|
59
|
+
const ms = /^\d{13}$/.test(value) ? Number(value) : Date.parse(value);
|
|
60
|
+
return Number.isFinite(ms) ? { date: new Date(ms), dayOnly: false } : undefined;
|
|
61
|
+
}
|
|
62
|
+
/** Has the instant (or, for a calendar date, the whole day) passed? */
|
|
63
|
+
function ownerPast(value: string, now = Date.now()): boolean {
|
|
64
|
+
const parsed = ownerInstant(value);
|
|
65
|
+
if (!parsed) return false;
|
|
66
|
+
const { date, dayOnly } = parsed;
|
|
67
|
+
return (dayOnly ? new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1).getTime() : date.getTime()) <= now;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
type DateStyle = 'date' | 'dateTime' | 'short';
|
|
71
|
+
/** A Wix date as owner copy, through the shared engine-intl shapes: `date` "Jan 5, 2026",
|
|
72
|
+
* `dateTime` "Mon, Jan 5, 3:07 PM", `short` "Jan 5" (with the year when it is not this year).
|
|
73
|
+
* A value that is not a date at all is kept as Wix sent it. */
|
|
74
|
+
function ownerDate(value: string, zone: string | undefined, style: DateStyle = 'date'): string {
|
|
75
|
+
if (!value) return '';
|
|
76
|
+
const parsed = ownerInstant(value);
|
|
77
|
+
if (!parsed) return value;
|
|
78
|
+
const { date } = parsed, tz = parsed.dayOnly ? undefined : zone;
|
|
79
|
+
if (style === 'dateTime') return formatWeekdayDateTime(date, undefined, tz);
|
|
80
|
+
if (style === 'short' && calendarDay(date, tz).slice(0, 4) === calendarDay(new Date(), tz).slice(0, 4)) return formatMonthDay(date, undefined, tz);
|
|
81
|
+
return formatDateMedium(date, undefined, tz);
|
|
82
|
+
}
|
|
36
83
|
function offsetOf(cursor: string): number {
|
|
37
84
|
if (cursor && !/^\d{1,9}$/.test(cursor)) throw new Error('Invalid collection cursor. Refresh this view.');
|
|
38
85
|
return Number(cursor || 0);
|
|
39
86
|
}
|
|
40
|
-
function page(
|
|
41
|
-
return { items: rows
|
|
87
|
+
function page(rows: unknown[], nextCursor: string, total?: number, complete?: boolean): CollectionPage<unknown> {
|
|
88
|
+
return { items: rows, nextCursor, total, ...(complete === undefined ? {} : { complete }) };
|
|
42
89
|
}
|
|
43
|
-
function offsetPage(
|
|
90
|
+
function offsetPage(rows: unknown[], offset: number, metadata: unknown) {
|
|
44
91
|
const total = at(metadata, 'total');
|
|
45
92
|
const more = rows.length > 0 && (typeof total === 'number' ? offset + rows.length < total : rows.length === LIMIT);
|
|
46
|
-
return page(
|
|
93
|
+
return page(rows, more ? String(offset + rows.length) : '', typeof total === 'number' ? total : undefined);
|
|
47
94
|
}
|
|
48
95
|
|
|
49
96
|
/** Domain query shapes stay here, shared by list and detail workers. Unsupported
|
|
50
97
|
* server name queries remain explicitly labelled searches of loaded records. */
|
|
51
98
|
export async function queryOwnerModule(module: OwnerModuleId, cursor = ''): Promise<CollectionPage<OwnerRecord>> {
|
|
99
|
+
// The zone lookup runs beside the query, not after it: the two are independent, and serial
|
|
100
|
+
// they would double the time to a first paint.
|
|
101
|
+
const [raw, zone] = await Promise.all([queryRawOwnerModule(module, cursor), siteZone()]);
|
|
102
|
+
return { ...raw, items: raw.items.map(row => ownerRecord(module, row, zone)) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function queryRawOwnerModule(module: OwnerModuleId, cursor: string): Promise<CollectionPage<unknown>> {
|
|
52
106
|
const cursorPaging = { limit: LIMIT, ...(cursor ? { cursor } : {}) };
|
|
53
107
|
switch (module) {
|
|
54
108
|
case 'invoices': {
|
|
55
109
|
const offset = offsetOf(cursor), r = await queryInvoices({ query: { paging: { limit: LIMIT, offset } } });
|
|
56
|
-
return offsetPage(
|
|
110
|
+
return offsetPage(r.invoices ?? [], offset, r.pagingMetadata);
|
|
57
111
|
}
|
|
58
112
|
case 'coupons': {
|
|
59
113
|
const offset = offsetOf(cursor), r = await queryCoupons({ paging: { limit: LIMIT, offset } });
|
|
60
|
-
return offsetPage(
|
|
114
|
+
return offsetPage(r.coupons ?? [], offset, r.metadata);
|
|
61
115
|
}
|
|
62
116
|
case 'giftCards': {
|
|
63
117
|
const r = await queryGiftCards({ query: { cursorPaging, sort: [{ fieldName: 'createdDate', order: 'DESC' }] } });
|
|
64
|
-
return page(
|
|
118
|
+
return page(r.giftCards ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
65
119
|
}
|
|
66
120
|
case 'contacts': {
|
|
67
121
|
const offset = offsetOf(cursor), r = await queryContacts({ query: { paging: { limit: LIMIT, offset } } });
|
|
68
|
-
return offsetPage(
|
|
122
|
+
return offsetPage(r.contacts ?? [], offset, r.pagingMetadata);
|
|
69
123
|
}
|
|
70
124
|
case 'members': {
|
|
71
125
|
const offset = offsetOf(cursor), r = await queryMembers({ query: { paging: { limit: LIMIT, offset } } });
|
|
72
|
-
return offsetPage(
|
|
126
|
+
return offsetPage(r.members ?? [], offset, r.metadata);
|
|
73
127
|
}
|
|
74
128
|
case 'groups': {
|
|
75
129
|
const offset = offsetOf(cursor), r = await queryGroups({ paging: { limit: LIMIT, offset } });
|
|
76
|
-
return offsetPage(
|
|
130
|
+
return offsetPage(r.groups ?? [], offset, r.metaData);
|
|
77
131
|
}
|
|
78
132
|
case 'reviews': {
|
|
79
133
|
const r = await queryReviews({ cursorPaging, sort: [{ fieldName: 'createdDate', order: 'DESC' }] }, true);
|
|
80
|
-
return page(
|
|
134
|
+
return page(r.reviews ?? r.results ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
81
135
|
}
|
|
82
136
|
case 'programs': {
|
|
83
137
|
const r = await queryPrograms({ cursorPaging });
|
|
84
|
-
return page(
|
|
138
|
+
return page(r.programs ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
85
139
|
}
|
|
86
140
|
case 'badges': {
|
|
87
141
|
const r = await queryBadges({ query: { cursorPaging } });
|
|
88
|
-
return page(
|
|
142
|
+
return page(r.badges ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
89
143
|
}
|
|
90
144
|
case 'reservations': {
|
|
91
145
|
const r = await queryReservations({ cursorPaging, sort: [{ fieldName: 'details.startDate', order: 'ASC' }] });
|
|
92
|
-
return page(
|
|
146
|
+
return page(r.reservations ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
93
147
|
}
|
|
94
148
|
case 'portfolio': {
|
|
95
149
|
const r = await listProjects(LIMIT, cursor || undefined);
|
|
96
|
-
return page(
|
|
150
|
+
return page(r.projects ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
97
151
|
}
|
|
98
152
|
case 'donationCampaigns': {
|
|
99
153
|
const r = await queryDonationCampaigns({ query: { cursorPaging, sort: [{ fieldName: 'updatedDate', order: 'DESC' }] } });
|
|
100
|
-
return page(
|
|
154
|
+
return page(r.donationCampaigns ?? [], r.pagingMetadata?.cursors?.next ?? '');
|
|
101
155
|
}
|
|
102
156
|
case 'forms': {
|
|
103
157
|
// This collection is Wix Forms. Booking/event registration forms belong
|
|
104
158
|
// to their respective operational records, rather than a mixed union
|
|
105
159
|
// that silently drops namespaces when one permission check fails.
|
|
106
160
|
const offset = offsetOf(cursor), r = await queryForms({ filter: { namespace: 'wix.form_app.form' }, paging: { limit: LIMIT, offset } });
|
|
107
|
-
return offsetPage(
|
|
161
|
+
return offsetPage(r.forms ?? [], offset, r.metaData);
|
|
108
162
|
}
|
|
109
163
|
case 'plans': {
|
|
110
164
|
const r = await listPlans();
|
|
111
|
-
return page(
|
|
165
|
+
return page(r.plans ?? [], '', r.pagingMetadata?.total, false);
|
|
112
166
|
}
|
|
113
167
|
case 'subscriptions': {
|
|
114
168
|
const r = await listPlanOrders();
|
|
115
|
-
return page(
|
|
169
|
+
return page(r.orders ?? [], '', r.pagingMetadata?.total, false);
|
|
116
170
|
}
|
|
117
171
|
}
|
|
118
172
|
}
|
|
119
173
|
|
|
120
|
-
export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
174
|
+
export function ownerRecord(module: OwnerModuleId, raw: unknown, zone?: string): OwnerRecord {
|
|
175
|
+
const when = (path: string, style?: DateStyle) => ownerDate(field(raw, path), zone, style);
|
|
121
176
|
const id = field(raw, 'id');
|
|
122
177
|
if (!id) throw new Error('Wix returned a record without an identity. Refresh this view.');
|
|
123
178
|
let title = '', subtitle = '', image = '', statusLabel = '';
|
|
@@ -127,7 +182,7 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
127
182
|
title = field(raw, 'specification.name') || field(raw, 'displayData.name');
|
|
128
183
|
subtitle = field(raw, 'specification.code');
|
|
129
184
|
statusLabel = at(raw, 'expired') === true ? 'Expired' : at(raw, 'specification.active') === true ? 'Active' : 'Inactive';
|
|
130
|
-
details = [['Code', subtitle], ['Uses', field(raw, 'numberOfUsages')], ['Offer', words(field(raw, 'specification.type'))], ['Expires',
|
|
185
|
+
details = [['Code', subtitle], ['Uses', field(raw, 'numberOfUsages')], ['Offer', words(field(raw, 'specification.type'))], ['Expires', when('specification.expirationTime')]];
|
|
131
186
|
break;
|
|
132
187
|
case 'giftCards': {
|
|
133
188
|
const suffix = field(raw, 'codeSuffix');
|
|
@@ -147,7 +202,7 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
147
202
|
['Initial value', field(raw, 'initialValue.formattedAmount') || join(field(raw, 'initialValue.amount'), field(raw, 'currency'))],
|
|
148
203
|
['Recipient', recipient],
|
|
149
204
|
['Order number', field(raw, 'orderInfo.orderNumber')],
|
|
150
|
-
['Expires',
|
|
205
|
+
['Expires', when('expirationDate')],
|
|
151
206
|
['Source', words(field(raw, 'source'))],
|
|
152
207
|
];
|
|
153
208
|
break;
|
|
@@ -156,7 +211,7 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
156
211
|
title = field(raw, 'number') ? `#${field(raw, 'number')}` : field(raw, 'title');
|
|
157
212
|
subtitle = field(raw, 'customerInfo.contactDetails.fullName') || name(raw, 'customerInfo.contactDetails.firstName', 'customerInfo.contactDetails.lastName') || field(raw, 'customerInfo.contactDetails.email');
|
|
158
213
|
statusLabel = words(field(raw, 'status'));
|
|
159
|
-
details = [['Customer', subtitle], ['Total', join(field(raw, 'totals.total'), field(raw, 'currency'))], ['Due',
|
|
214
|
+
details = [['Customer', subtitle], ['Total', join(field(raw, 'totals.total'), field(raw, 'currency'))], ['Due', when('dueDate')]];
|
|
160
215
|
break;
|
|
161
216
|
case 'contacts':
|
|
162
217
|
title = name(raw, 'info.name.first', 'info.name.last') || field(raw, 'primaryInfo.email');
|
|
@@ -167,12 +222,12 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
167
222
|
case 'members':
|
|
168
223
|
title = field(raw, 'profile.nickname') || name(raw, 'contact.firstName', 'contact.lastName') || field(raw, 'loginEmail');
|
|
169
224
|
subtitle = field(raw, 'loginEmail'); image = field(raw, 'profile.photo.url'); statusLabel = words(field(raw, 'status'));
|
|
170
|
-
details = [['Email', subtitle], ['Last login',
|
|
225
|
+
details = [['Email', subtitle], ['Last login', when('lastLoginDate')]];
|
|
171
226
|
break;
|
|
172
227
|
case 'groups':
|
|
173
228
|
title = field(raw, 'name'); subtitle = field(raw, 'description') || field(raw, 'teaser');
|
|
174
229
|
image = field(raw, 'coverImage.imageInfo.url'); statusLabel = words(field(raw, 'privacyStatus'));
|
|
175
|
-
details = [['Members', field(raw, 'memberCount') || field(raw, 'membersCount')], ['Last activity',
|
|
230
|
+
details = [['Members', field(raw, 'memberCount') || field(raw, 'membersCount')], ['Last activity', when('lastActivityDate')]];
|
|
176
231
|
break;
|
|
177
232
|
case 'reviews':
|
|
178
233
|
title = field(raw, 'content.title'); subtitle = field(raw, 'content.body'); statusLabel = words(field(raw, 'moderation.moderationStatus'));
|
|
@@ -184,8 +239,14 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
184
239
|
details = [['Price', join(field(raw, 'pricing.price.value'), field(raw, 'pricing.price.currency'))], ['Free trial days', field(raw, 'pricing.freeTrialDays')]];
|
|
185
240
|
break;
|
|
186
241
|
case 'subscriptions':
|
|
187
|
-
|
|
188
|
-
|
|
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
|
+
title = field(raw, 'planName'); statusLabel = words(field(raw, 'status'));
|
|
245
|
+
{
|
|
246
|
+
const started = when('startDate', 'short');
|
|
247
|
+
subtitle = started ? copyTemplate('ownerModules.startedOn', 'Started {date}', { date: started }) : '';
|
|
248
|
+
}
|
|
249
|
+
details = [['Started', when('startDate')], ['Ends', when('endDate')], ['Payment', words(field(raw, 'lastPaymentStatus'))]];
|
|
189
250
|
break;
|
|
190
251
|
case 'programs':
|
|
191
252
|
title = field(raw, 'description.title') || field(raw, 'details.title') || field(raw, 'name');
|
|
@@ -203,13 +264,14 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
203
264
|
break;
|
|
204
265
|
}
|
|
205
266
|
case 'reservations':
|
|
206
|
-
title = name(raw, 'reservee.firstName', 'reservee.lastName'); subtitle =
|
|
267
|
+
title = name(raw, 'reservee.firstName', 'reservee.lastName'); subtitle = when('details.startDate', 'dateTime'); statusLabel = words(field(raw, 'status'));
|
|
207
268
|
details = [['Arrival', subtitle], ['Party size', field(raw, 'details.partySize')], ['Email', field(raw, 'reservee.email')], ['Phone', field(raw, 'reservee.phone')]];
|
|
208
269
|
break;
|
|
209
270
|
case 'forms':
|
|
210
|
-
|
|
271
|
+
// The list is filtered to one namespace (`wix.form_app.form`), so the namespace is an
|
|
272
|
+
// internal constant, not information — it never reaches copy.
|
|
273
|
+
title = field(raw, 'name') || copyParam('ownerModules.untitledForm', 'Untitled form');
|
|
211
274
|
statusLabel = typeof at(raw, 'enabled') === 'boolean' ? at(raw, 'enabled') ? 'Enabled' : 'Disabled' : '';
|
|
212
|
-
details = [['Namespace', subtitle]];
|
|
213
275
|
break;
|
|
214
276
|
case 'portfolio':
|
|
215
277
|
title = field(raw, 'title'); subtitle = field(raw, 'description'); image = portfolioCoverRef(at(raw, 'coverImage') as WixMediaImage | string | undefined);
|
|
@@ -217,20 +279,25 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
217
279
|
break;
|
|
218
280
|
case 'donationCampaigns':
|
|
219
281
|
title = field(raw, 'name');
|
|
220
|
-
|
|
282
|
+
{
|
|
283
|
+
const end = when('campaignGoal.endDate', 'short');
|
|
284
|
+
subtitle = join(field(raw, 'campaignGoal.targetAmount.formattedAmount'), !end ? ''
|
|
285
|
+
: ownerPast(field(raw, 'campaignGoal.endDate')) ? copyTemplate('ownerModules.endedOn', 'ended {date}', { date: end })
|
|
286
|
+
: copyTemplate('ownerModules.endsOn', 'ends {date}', { date: end }));
|
|
287
|
+
}
|
|
221
288
|
image = field(raw, 'coverImage');
|
|
222
289
|
statusLabel = at(raw, 'archived') === true ? 'Archived' : words(field(raw, 'status'));
|
|
223
290
|
details = [
|
|
224
291
|
['Goal', field(raw, 'campaignGoal.targetAmount.formattedAmount') || field(raw, 'campaignGoal.targetAmount.amount')],
|
|
225
|
-
['End date',
|
|
292
|
+
['End date', when('campaignGoal.endDate')],
|
|
226
293
|
['Frequencies', Array.isArray(at(raw, 'donationFrequencies')) ? (at(raw, 'donationFrequencies') as unknown[]).map(String).join(', ') : ''],
|
|
227
294
|
['Custom amount', yesNo(at(raw, 'customAmountEnabled'))],
|
|
228
295
|
];
|
|
229
296
|
break;
|
|
230
297
|
}
|
|
231
298
|
details.push(
|
|
232
|
-
['Created',
|
|
233
|
-
['Updated',
|
|
299
|
+
['Created', when('createdDate') || when('dateCreated') || when('createDate')],
|
|
300
|
+
['Updated', when('updatedDate') || when('updateDate')],
|
|
234
301
|
);
|
|
235
302
|
return { id, moduleId: module, title: title || copyParam('ownerModules.untitled', 'Untitled record'), subtitle, image: wixMediaUrl(image), statusLabel,
|
|
236
303
|
detailScreenId: `himi_owner_lite_record_${module}`,
|
|
@@ -240,6 +307,7 @@ export function ownerRecord(module: OwnerModuleId, raw: unknown): OwnerRecord {
|
|
|
240
307
|
|
|
241
308
|
export async function getOwnerRecord(module: OwnerModuleId, id: string): Promise<OwnerRecord> {
|
|
242
309
|
if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(id)) throw new Error('This record link is invalid. Return to the collection.');
|
|
310
|
+
const zone = siteZone(); // started now, beside the record fetch; it never rejects
|
|
243
311
|
let raw: unknown;
|
|
244
312
|
switch (module) {
|
|
245
313
|
case 'coupons': raw = (await getCoupon(id)).coupon; break;
|
|
@@ -270,13 +338,13 @@ export async function getOwnerRecord(module: OwnerModuleId, id: string): Promise
|
|
|
270
338
|
seen.add(next); cursor = next;
|
|
271
339
|
}
|
|
272
340
|
if (!raw) throw new Error('This project could not be located. Open the portfolio in Wix to check its availability.');
|
|
273
|
-
const record = ownerRecord(module, raw);
|
|
341
|
+
const record = ownerRecord(module, raw, await zone);
|
|
274
342
|
const media = await listProjectItems(id, 100);
|
|
275
343
|
return { ...record, fields: [...record.fields, { id: 'media', label: 'Loaded media items', value: String(media.items?.length ?? 0) }] };
|
|
276
344
|
}
|
|
277
345
|
case 'donationCampaigns': raw = (await getDonationCampaign(id)).donationCampaign; break;
|
|
278
346
|
}
|
|
279
|
-
const record = ownerRecord(module, raw);
|
|
347
|
+
const record = ownerRecord(module, raw, await zone);
|
|
280
348
|
if (record.id !== id) throw new Error('The returned record does not match this link. Refresh the collection.');
|
|
281
349
|
return record;
|
|
282
350
|
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// The business the owner is managing RIGHT NOW, shared by every __HIMI_APP_NAME__ worker.
|
|
2
|
+
//
|
|
3
|
+
// Home, Manage, Account and the drawer used to name the business from the build-time
|
|
4
|
+
// `provisioning.json` (`identity.businessName`), so after picking another site on the
|
|
5
|
+
// Sites list the chrome still named the provisioning one — three different names for
|
|
6
|
+
// "the current business" in one session, none of them the site actually picked.
|
|
7
|
+
//
|
|
8
|
+
// The picker (`owner-site-pick.ts`) is the one place that knows both the id and the
|
|
9
|
+
// display name of the business being switched to, so it records them here; every
|
|
10
|
+
// other worker reads them back. The channel is `himalaya.storage` — the app-scoped KV
|
|
11
|
+
// every worker of the app shares on every host (UserDefaults / SharedPreferences / one
|
|
12
|
+
// localStorage namespace) and that needs no capability.
|
|
13
|
+
//
|
|
14
|
+
// ONE key, not one per owner: the host only tells a worker who the owner is once a
|
|
15
|
+
// business is scoped, so the picker (which runs before that) and the screens after it
|
|
16
|
+
// would otherwise land on different keys. The owner is recorded INSIDE the entry
|
|
17
|
+
// instead, and an entry written for a different owner is never shown.
|
|
18
|
+
//
|
|
19
|
+
// The build-time name is only ever the cold-start fallback, passed in by the
|
|
20
|
+
// descriptor: a worker never hardcodes it.
|
|
21
|
+
|
|
22
|
+
import { initials } from "./himi-text.js";
|
|
23
|
+
|
|
24
|
+
type StorageLike = { getItem(key: string): string | null; setItem(key: string, value: string): void };
|
|
25
|
+
type Host = typeof globalThis & { himalaya?: { storage?: StorageLike; input?: Record<string, unknown> } };
|
|
26
|
+
|
|
27
|
+
export interface SelectedSite {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const KEY = "himi.ownerLite.selectedSite.v1";
|
|
33
|
+
|
|
34
|
+
function host(): Host["himalaya"] {
|
|
35
|
+
return (globalThis as Host).himalaya;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inputString(key: string): string {
|
|
39
|
+
const v = host()?.input?.[key];
|
|
40
|
+
return typeof v === "string" && v !== "default" ? v.trim() : "";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The business the owner last picked, or null when none has been picked, the host
|
|
44
|
+
* has no storage, or the entry does not apply to this worker. Never throws. */
|
|
45
|
+
export function readSelectedSite(): SelectedSite | null {
|
|
46
|
+
try {
|
|
47
|
+
const raw = host()?.storage?.getItem(KEY);
|
|
48
|
+
if (!raw) return null;
|
|
49
|
+
const parsed = JSON.parse(raw) as Partial<SelectedSite> & { account?: unknown };
|
|
50
|
+
const id = typeof parsed?.id === "string" ? parsed.id.trim() : "";
|
|
51
|
+
const name = typeof parsed?.name === "string" ? parsed.name.trim() : "";
|
|
52
|
+
if (!id || !name) return null;
|
|
53
|
+
// Another owner's pick on a shared device is never this owner's business. Fail
|
|
54
|
+
// CLOSED: an owner-tagged entry is shown only to a worker that knows it IS that
|
|
55
|
+
// owner. A worker that cannot tell who the owner is (the account-level flow before
|
|
56
|
+
// a business is scoped, or a host that cannot supply the identity) gets the
|
|
57
|
+
// fallback, never a name that may be another owner's.
|
|
58
|
+
const account = typeof parsed.account === "string" ? parsed.account : "";
|
|
59
|
+
const me = inputString("__himiPreferenceAccount");
|
|
60
|
+
if (account && account !== me) return null;
|
|
61
|
+
// An entry the picker wrote before the host knew the owner is CLAIMED by the first
|
|
62
|
+
// worker that does know it, so from then on no other owner on the device reads it.
|
|
63
|
+
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({ id, name, account: me }));
|
|
64
|
+
// A screen opened FOR a specific business (a deep link or push carrying
|
|
65
|
+
// `business`) names that business's data; a name recorded for another one would
|
|
66
|
+
// sit over the wrong numbers, so it falls back instead.
|
|
67
|
+
const opened = inputString("business");
|
|
68
|
+
if (opened && opened !== id) return null;
|
|
69
|
+
return { id, name };
|
|
70
|
+
} catch {
|
|
71
|
+
return null; // a corrupt entry reads as "nothing picked", never as a crash
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Record the business the owner just picked. Best-effort: a host without storage
|
|
76
|
+
* simply keeps showing the fallback. */
|
|
77
|
+
export function rememberSelectedSite(site: SelectedSite): void {
|
|
78
|
+
const id = site.id.trim();
|
|
79
|
+
const name = site.name.trim();
|
|
80
|
+
if (!id || !name) return;
|
|
81
|
+
const account = inputString("__himiPreferenceAccount");
|
|
82
|
+
try {
|
|
83
|
+
host()?.storage?.setItem(KEY, JSON.stringify(account ? { id, name, account } : { id, name }));
|
|
84
|
+
} catch {
|
|
85
|
+
// Quota / storage disabled: the chrome falls back to the build-time name.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Display name for "the current business": the picked one, else `fallback`. */
|
|
90
|
+
export function selectedSiteName(fallback: string): string {
|
|
91
|
+
return readSelectedSite()?.name || fallback;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Badge letters for a business name ("Riverbend Market" → "RM"); Unicode-aware. */
|
|
95
|
+
export function siteInitials(name: string): string {
|
|
96
|
+
return initials(name);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The fallback name a descriptor passes in its `onAppear` params (`businessName`). */
|
|
100
|
+
export function fallbackNameFrom(arg: unknown, current = ""): string {
|
|
101
|
+
const v = (arg as { businessName?: unknown } | undefined)?.businessName;
|
|
102
|
+
return typeof v === "string" && v.trim() ? v.trim() : current;
|
|
103
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Switching the business an __HIMI_APP_NAME__ session manages — the one implementation
|
|
2
|
+
// behind every "pick a business" row (the Sites list and the cross-business Overview).
|
|
3
|
+
//
|
|
4
|
+
// Previously those rows were declarative `himi.open{business}` taps: the shell scoped
|
|
5
|
+
// the bridge and PUSHED Home, and nothing recorded which business was picked, so the
|
|
6
|
+
// chrome kept naming the build-time one and a `‹ Back` led to the list just left. As a
|
|
7
|
+
// worker action the row can record the name first, then hand the shell the same
|
|
8
|
+
// `business`-carrying open — now as a `replace` onto the Home root.
|
|
9
|
+
//
|
|
10
|
+
// Scoping stays with the shell's router (SP-1: a `business` nav param scopes the Wix
|
|
11
|
+
// bridge BEFORE navigating, on iOS and Android). It is deliberately not done here with
|
|
12
|
+
// `site.setActiveSite`: owner-lite's native shells do not register the worker-facing
|
|
13
|
+
// `siteSetActive` capability, so the call throws and the tap would go nowhere.
|
|
14
|
+
|
|
15
|
+
import { navigate, type NavDirective } from "@himalaya/state";
|
|
16
|
+
import { forgetCatalogVersion } from "@himalaya/wix-stores";
|
|
17
|
+
import { rememberSelectedSite } from "./owner-selected-site.js";
|
|
18
|
+
|
|
19
|
+
export const OWNER_HOME = "himi_owner_lite_home";
|
|
20
|
+
|
|
21
|
+
/** Set by a pick, cleared by `settleBusinessSwitch` once the new business is scoped. */
|
|
22
|
+
const SWITCH_PENDING_KEY = "himi.ownerLite.businessSwitchPending.v1";
|
|
23
|
+
|
|
24
|
+
type StorageLike = { getItem(key: string): string | null; setItem(key: string, value: string): void };
|
|
25
|
+
const storage = (): StorageLike | undefined =>
|
|
26
|
+
(globalThis as { himalaya?: { storage?: StorageLike } }).himalaya?.storage;
|
|
27
|
+
|
|
28
|
+
/** `{ id | business, name }` from a row's action params (or a bare msid string). */
|
|
29
|
+
export function pickedSite(arg: unknown): { msid: string; name: string } {
|
|
30
|
+
if (typeof arg === "string") return { msid: arg.trim(), name: "" };
|
|
31
|
+
const a = (arg ?? {}) as { business?: unknown; id?: unknown; name?: unknown };
|
|
32
|
+
const msid = typeof a.business === "string" && a.business ? a.business : typeof a.id === "string" ? a.id : "";
|
|
33
|
+
return { msid: msid.trim(), name: typeof a.name === "string" ? a.name.trim() : "" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Record `name` as the business being managed, then return `state` carrying a
|
|
38
|
+
* `replace` onto the Home root that asks the shell to scope to `msid`. Returns `state`
|
|
39
|
+
* untouched (no navigation) when there is no msid.
|
|
40
|
+
*/
|
|
41
|
+
export function pickSite<S extends object>(state: S, msid: string, name: string): S | (S & { _nav: NavDirective }) {
|
|
42
|
+
if (!msid) return state;
|
|
43
|
+
// Another business may run the other Stores catalog. The resolved version is
|
|
44
|
+
// persisted app-wide and routes WRITES, which have no 428 retry, so it is dropped
|
|
45
|
+
// before the switch rather than left to be re-used against the new site.
|
|
46
|
+
forgetCatalogVersion();
|
|
47
|
+
// ...but the shell scopes the bridge to `msid` only AFTER this returns, so a warm
|
|
48
|
+
// Stores worker resolving in that gap still talks to the OLD site and can persist
|
|
49
|
+
// its version over the tombstone above. Leave a marker so the first worker that
|
|
50
|
+
// runs after scoping drops the catalog version once more (`settleBusinessSwitch`).
|
|
51
|
+
try { storage()?.setItem(SWITCH_PENDING_KEY, msid); } catch { /* no storage: the first forget stands */ }
|
|
52
|
+
if (name) rememberSelectedSite({ id: msid, name });
|
|
53
|
+
// `replace` onto Home, the tab root the pickers are reached from: the core router
|
|
54
|
+
// lands ON the root instead of stacking a second Home above the list.
|
|
55
|
+
return navigate(state, { screenId: OWNER_HOME, presentation: "replace", args: { business: msid } });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Finish a business switch: if a pick is pending, drop the Stores catalog version
|
|
60
|
+
* again and clear the marker. Call it first in a worker that runs only after the shell
|
|
61
|
+
* has scoped the bridge to the picked business (Home's `load`: the pick `replace`s
|
|
62
|
+
* onto Home, and the router scopes before it navigates), so a version persisted from
|
|
63
|
+
* the old site during the scoping gap is discarded. Never throws.
|
|
64
|
+
*/
|
|
65
|
+
export function settleBusinessSwitch(): void {
|
|
66
|
+
try {
|
|
67
|
+
const s = storage();
|
|
68
|
+
if (!s?.getItem(SWITCH_PENDING_KEY)) return;
|
|
69
|
+
forgetCatalogVersion();
|
|
70
|
+
s.setItem(SWITCH_PENDING_KEY, "");
|
|
71
|
+
} catch {
|
|
72
|
+
// No storage, or it failed: nothing pending can be recorded either.
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
// from provisioning.json `identity` — so a branded template ships ITS links,
|
|
12
12
|
// and `openLink` can only ever open one of those three URLs.
|
|
13
13
|
//
|
|
14
|
+
// • which business this is — the one the owner picked on Sites (see
|
|
15
|
+
// `_shared/owner-selected-site.ts`), not the build-time provisioning name,
|
|
16
|
+
// which arrives only as the cold-start `businessName` param. A local read,
|
|
17
|
+
// so it is set synchronously in the initial state and again on every load.
|
|
18
|
+
//
|
|
14
19
|
// Every read is best-effort: an identity or version the host cannot answer
|
|
15
20
|
// collapses its line rather than rendering an error on a settings screen.
|
|
16
21
|
|
|
@@ -20,19 +25,24 @@ import { displayVersion } from "@himalaya/app-info";
|
|
|
20
25
|
import { open } from "@himalaya/open-url";
|
|
21
26
|
import { stashCopy, copyTemplate } from "../_shared/himi-copy.js";
|
|
22
27
|
import { ownerLineOf, shapeAccount, type AccountState } from "../_shared/owner-account.js";
|
|
28
|
+
import { fallbackNameFrom, selectedSiteName } from "../_shared/owner-selected-site.js";
|
|
23
29
|
|
|
24
30
|
type LinkKind = "support" | "privacy" | "terms";
|
|
25
31
|
|
|
26
32
|
const obj = (v: unknown): Record<string, unknown> => (v && typeof v === "object" ? v as Record<string, unknown> : {});
|
|
27
33
|
|
|
34
|
+
let fallbackBusinessName = "";
|
|
35
|
+
|
|
28
36
|
const svc = defineService<AccountState, {
|
|
29
37
|
load: (s: AccountState, arg?: unknown) => Promise<AccountState>;
|
|
30
38
|
openLink: (s: AccountState, arg?: unknown) => Promise<AccountState>;
|
|
31
39
|
}>("himiOwnerLiteAccount", {
|
|
32
|
-
state: shapeAccount({}),
|
|
40
|
+
state: shapeAccount({ businessName: selectedSiteName(fallbackBusinessName) }),
|
|
33
41
|
actions: {
|
|
34
42
|
load: async (s, arg) => {
|
|
35
43
|
stashCopy(arg);
|
|
44
|
+
fallbackBusinessName = fallbackNameFrom(arg, fallbackBusinessName);
|
|
45
|
+
const businessName = selectedSiteName(fallbackBusinessName);
|
|
36
46
|
const p = obj(arg);
|
|
37
47
|
const links = { supportUrl: String(p.supportUrl ?? s.supportUrl), privacyUrl: String(p.privacyUrl ?? s.privacyUrl), termsUrl: String(p.termsUrl ?? s.termsUrl) };
|
|
38
48
|
let ownerLine = "";
|
|
@@ -54,7 +64,7 @@ const svc = defineService<AccountState, {
|
|
|
54
64
|
const v = await displayVersion();
|
|
55
65
|
versionLine = v ? copyTemplate("ownerAccount.version", "Version {version}", { version: v }) : "";
|
|
56
66
|
} catch { /* host without app_info — the row collapses */ }
|
|
57
|
-
return shapeAccount({ ...s, ...links, status: "ready", ownerLine, versionLine, error });
|
|
67
|
+
return shapeAccount({ ...s, ...links, status: "ready", businessName, ownerLine, versionLine, error });
|
|
58
68
|
},
|
|
59
69
|
openLink: async (s, arg) => {
|
|
60
70
|
const kind = String(obj(arg).kind ?? arg ?? "") as LinkKind;
|
|
@@ -26,6 +26,7 @@ const core = createAriaHistory({
|
|
|
26
26
|
last7: () => copyParam("ownerAriaHistory.last7", "Last 7 days"),
|
|
27
27
|
last30: () => copyParam("ownerAriaHistory.last30", "Last 30 days"),
|
|
28
28
|
older: () => copyParam("ownerAriaHistory.older", "Older"),
|
|
29
|
+
today: () => copyParam("ownerAriaHistory.today", "Today"),
|
|
29
30
|
},
|
|
30
31
|
});
|
|
31
32
|
|
|
@@ -20,13 +20,23 @@
|
|
|
20
20
|
import { defineService } from "@himalaya/state";
|
|
21
21
|
import { serializeForAgent } from "@himalaya/agent";
|
|
22
22
|
import { activeScreenFromInput, attentionRow, navRow, withDerived, type DrawerState } from "./logic.js";
|
|
23
|
+
import { fallbackNameFrom, selectedSiteName } from "../_shared/owner-selected-site.js";
|
|
23
24
|
|
|
24
25
|
/** The shell passes the screen it is showing as worker input, so the drawer can mark
|
|
25
26
|
* that destination selected the way the declarative drawer does. */
|
|
26
27
|
const hostActiveScreen = () => activeScreenFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input);
|
|
27
28
|
|
|
29
|
+
/** Cold-start name from the descriptor's onAppear params (provisioning identity). */
|
|
30
|
+
let fallbackBusinessName = "";
|
|
31
|
+
|
|
32
|
+
/** "Which site am I on": the business picked on Sites, else the fallback. */
|
|
33
|
+
const currentSiteName = () => selectedSiteName(fallbackBusinessName);
|
|
34
|
+
|
|
28
35
|
const drawer = defineService<DrawerState, {
|
|
29
|
-
/**
|
|
36
|
+
/** The panel opened (root onAppear): re-read the live site and the active screen.
|
|
37
|
+
* `arg.businessName` is the cold-start fallback for the site name. */
|
|
38
|
+
refresh: (s: DrawerState, arg?: unknown) => DrawerState;
|
|
39
|
+
/** Re-read the active screen from host input. An explicit
|
|
30
40
|
* `{ screenId }` wins, for a host that pushes the change instead. */
|
|
31
41
|
syncActive: (s: DrawerState, args?: { screenId?: string }) => DrawerState;
|
|
32
42
|
/** First tap on "Mark all handled": ask before hiding anything. */
|
|
@@ -37,7 +47,7 @@ const drawer = defineService<DrawerState, {
|
|
|
37
47
|
clearAttention: (s: DrawerState) => DrawerState;
|
|
38
48
|
}>("drawer", {
|
|
39
49
|
state: withDerived({
|
|
40
|
-
siteName:
|
|
50
|
+
siteName: currentSiteName(),
|
|
41
51
|
nav: [
|
|
42
52
|
navRow("drawer_home", "Home", "home", "himi_owner_lite_home"),
|
|
43
53
|
// Between Home and Manage deliberately: analytics answers "how is the
|
|
@@ -55,6 +65,15 @@ const drawer = defineService<DrawerState, {
|
|
|
55
65
|
activeScreenId: hostActiveScreen(),
|
|
56
66
|
}),
|
|
57
67
|
actions: {
|
|
68
|
+
refresh: (s, arg) => {
|
|
69
|
+
fallbackBusinessName = fallbackNameFrom(arg, fallbackBusinessName);
|
|
70
|
+
const screenId = (arg as { screenId?: unknown } | undefined)?.screenId;
|
|
71
|
+
return snap(withDerived({
|
|
72
|
+
...s,
|
|
73
|
+
siteName: currentSiteName(),
|
|
74
|
+
activeScreenId: typeof screenId === "string" ? screenId : hostActiveScreen(),
|
|
75
|
+
}));
|
|
76
|
+
},
|
|
58
77
|
syncActive: (s, args) => snap(withDerived({
|
|
59
78
|
...s,
|
|
60
79
|
activeScreenId: typeof args?.screenId === "string" ? args.screenId : hostActiveScreen(),
|