@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
|
@@ -79,6 +79,7 @@ import { MENTION_PAGE_SIZE, allowlisted, emptyMentionModel, mentionView,
|
|
|
79
79
|
type MentionItemRow, type MentionLabels, type MentionModel, type MentionProvider, type MentionView } from "./aria-mentions.js";
|
|
80
80
|
import { isTransportFailure, ownerTransportFailureText } from "./owner-load-error.js";
|
|
81
81
|
import { LOCAL_ID_PREFIX, createRowMemo, mintLocalId, transcriptKeys, type RowMemo } from "./aria-hot-path.js";
|
|
82
|
+
import { calendarDay, formatClock, formatDateMedium, formatMonthDay } from "./engine-intl.js";
|
|
82
83
|
|
|
83
84
|
// ---------------------------------------------------------------------------
|
|
84
85
|
// Shapes
|
|
@@ -166,6 +167,9 @@ export interface AriaChatState {
|
|
|
166
167
|
* is computed once in `gated` rather than maintained by every reducer. */
|
|
167
168
|
replyActionsHidden: boolean;
|
|
168
169
|
messages: AriaBubble[];
|
|
170
|
+
/** Collapses the transcript list while it is empty — an empty list still costs
|
|
171
|
+
* its parent stack a gap. Derived in `gated`, like `stopHidden`. */
|
|
172
|
+
messagesHidden: boolean;
|
|
169
173
|
/** Non-empty ONLY on the greeting-only empty state. */
|
|
170
174
|
prompts: AriaPromptChip[];
|
|
171
175
|
_convId: string;
|
|
@@ -1499,7 +1503,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1499
1503
|
|
|
1500
1504
|
const initialState: AriaChatState = {
|
|
1501
1505
|
status: "loading", statusText: "", error: "", input: "", copyNote: "", replyActionsHidden: true,
|
|
1502
|
-
messages: [], prompts: [], _convId: "", _siteId: "", _pending: "", _pendingRefs: "", _pendingAttach: [],
|
|
1506
|
+
messages: [], messagesHidden: true, prompts: [], _convId: "", _siteId: "", _pending: "", _pendingRefs: "", _pendingAttach: [],
|
|
1503
1507
|
_pendingAfter: 0, _pendingId: "", _replyAfter: 0, _srvUsers: 0, _srvAssistants: 0,
|
|
1504
1508
|
_abortId: "", _turnId: "", stopHidden: true, ok: false,
|
|
1505
1509
|
listening: false, micHidden: !o.dictation, listeningHidden: true,
|
|
@@ -2460,10 +2464,12 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2460
2464
|
const listening = s.listening === true;
|
|
2461
2465
|
const micHidden = !o.dictation || dictationAvailable === false || listening;
|
|
2462
2466
|
const listeningHidden = !listening;
|
|
2467
|
+
// And the transcript list: hidden while empty, so it costs its stack no gap.
|
|
2468
|
+
const messagesHidden = s.messages.length === 0;
|
|
2463
2469
|
const actioned = s.stopHidden === hidden && s.replyActionsHidden === replyActionsHidden
|
|
2464
|
-
&& s.micHidden === micHidden && s.listeningHidden === listeningHidden
|
|
2470
|
+
&& s.micHidden === micHidden && s.listeningHidden === listeningHidden && s.messagesHidden === messagesHidden
|
|
2465
2471
|
? s
|
|
2466
|
-
: { ...s, stopHidden: hidden, replyActionsHidden, micHidden, listeningHidden };
|
|
2472
|
+
: { ...s, stopHidden: hidden, replyActionsHidden, micHidden, listeningHidden, messagesHidden };
|
|
2467
2473
|
// The attachment projection rides the same exit, for the same reason: one
|
|
2468
2474
|
// place that every action's result passes through, so no reducer can hand
|
|
2469
2475
|
// back a stale copy of the chips. Seeded first, so the very first action of
|
|
@@ -3493,6 +3499,9 @@ export interface AriaHistoryState {
|
|
|
3493
3499
|
items: AriaHistoryRow[];
|
|
3494
3500
|
/** Non-empty ONLY when the list is empty (collapses otherwise). */
|
|
3495
3501
|
emptyText: string;
|
|
3502
|
+
/** Collapses the (empty) row list, which would otherwise still cost its parent
|
|
3503
|
+
* stack a gap. True until a load lands rows. */
|
|
3504
|
+
itemsHidden: boolean;
|
|
3496
3505
|
/** The business the rows in `items` belong to. A kept list is only safe to
|
|
3497
3506
|
* keep while this still matches, so it is state rather than a closure — the
|
|
3498
3507
|
* chat service tracks its own `_siteId` for the same reason. */
|
|
@@ -3512,6 +3521,8 @@ export interface AriaHistoryLabels {
|
|
|
3512
3521
|
last7?: Resolvable<string>;
|
|
3513
3522
|
last30?: Resolvable<string>;
|
|
3514
3523
|
older?: Resolvable<string>;
|
|
3524
|
+
/** Prefix for a conversation from today: "Today · 9:12 AM". */
|
|
3525
|
+
today?: Resolvable<string>;
|
|
3515
3526
|
}
|
|
3516
3527
|
|
|
3517
3528
|
export interface AriaHistoryOptions {
|
|
@@ -3525,8 +3536,8 @@ export interface AriaHistoryOptions {
|
|
|
3525
3536
|
localHistory?: boolean;
|
|
3526
3537
|
}
|
|
3527
3538
|
|
|
3528
|
-
interface BucketLabels { last7: string; last30: string; older: string; }
|
|
3529
|
-
const STOCK_BUCKETS: BucketLabels = { last7: "Last 7 days", last30: "Last 30 days", older: "Older" };
|
|
3539
|
+
interface BucketLabels { last7: string; last30: string; older: string; today: string; }
|
|
3540
|
+
const STOCK_BUCKETS: BucketLabels = { last7: "Last 7 days", last30: "Last 30 days", older: "Older", today: "Today" };
|
|
3530
3541
|
|
|
3531
3542
|
/** Parse an ISO-8601 string (serialized Timestamp) or epoch-ms into ms. 0 when
|
|
3532
3543
|
* absent or unparseable. */
|
|
@@ -3539,15 +3550,17 @@ export function toMs(v: unknown): number {
|
|
|
3539
3550
|
return 0;
|
|
3540
3551
|
}
|
|
3541
3552
|
|
|
3542
|
-
|
|
3553
|
+
/** A history card's date, through the shared engine-intl shapes: "Today · 9:12 AM" for today,
|
|
3554
|
+
* "Sep 21" earlier this year, "Sep 21, 2025" before that. It was `toLocaleString()` —
|
|
3555
|
+
* "9/24/2026, 9:12:00 AM", seconds and all. These are the owner's own
|
|
3556
|
+
* conversations, so the device's calendar is the right one. */
|
|
3557
|
+
function formatTs(ms: number, now: number, today: string): string {
|
|
3543
3558
|
if (!ms) return "";
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
return "";
|
|
3550
|
-
}
|
|
3559
|
+
const d = new Date(ms);
|
|
3560
|
+
if (isNaN(d.getTime())) return "";
|
|
3561
|
+
const day = calendarDay(d), current = calendarDay(new Date(now));
|
|
3562
|
+
if (day === current) return `${today} · ${formatClock(d)}`;
|
|
3563
|
+
return day.slice(0, 4) === current.slice(0, 4) ? formatMonthDay(d) : formatDateMedium(d);
|
|
3551
3564
|
}
|
|
3552
3565
|
|
|
3553
3566
|
const DAY = 24 * 60 * 60 * 1000;
|
|
@@ -3568,7 +3581,7 @@ export function bucketRows(entries: Array<{ id: string; title: string; ms: numbe
|
|
|
3568
3581
|
const b = bucketOf(e.ms, now, labels);
|
|
3569
3582
|
const section = b === lastBucket ? "" : b;
|
|
3570
3583
|
lastBucket = b;
|
|
3571
|
-
return { id: e.id, title: e.title, subtitle: formatTs(e.ms), section };
|
|
3584
|
+
return { id: e.id, title: e.title, subtitle: formatTs(e.ms, now, labels.today), section };
|
|
3572
3585
|
});
|
|
3573
3586
|
}
|
|
3574
3587
|
|
|
@@ -3601,9 +3614,10 @@ export function createAriaHistory(o: AriaHistoryOptions) {
|
|
|
3601
3614
|
last7: res(o.labels?.last7 ?? STOCK_BUCKETS.last7),
|
|
3602
3615
|
last30: res(o.labels?.last30 ?? STOCK_BUCKETS.last30),
|
|
3603
3616
|
older: res(o.labels?.older ?? STOCK_BUCKETS.older),
|
|
3617
|
+
today: res(o.labels?.today ?? STOCK_BUCKETS.today),
|
|
3604
3618
|
});
|
|
3605
3619
|
const initialState: AriaHistoryState = {
|
|
3606
|
-
status: "loading", items: [], emptyText: "", _siteId: "", failed: false,
|
|
3620
|
+
status: "loading", items: [], itemsHidden: true, emptyText: "", _siteId: "", failed: false,
|
|
3607
3621
|
};
|
|
3608
3622
|
return {
|
|
3609
3623
|
initialState,
|
|
@@ -3631,7 +3645,7 @@ export function createAriaHistory(o: AriaHistoryOptions) {
|
|
|
3631
3645
|
// owner the previous business's conversation titles, which is precisely
|
|
3632
3646
|
// the leak `localHistory: false` is set to avoid.
|
|
3633
3647
|
if (fetched === null && s.items.length && s._siteId === site) {
|
|
3634
|
-
return { ...s, status: "ready", failed: true };
|
|
3648
|
+
return { ...s, status: "ready", itemsHidden: false, failed: true };
|
|
3635
3649
|
}
|
|
3636
3650
|
|
|
3637
3651
|
// A SUCCESSFUL empty answer is authoritative and DOES clear. The tempting
|
|
@@ -3651,6 +3665,7 @@ export function createAriaHistory(o: AriaHistoryOptions) {
|
|
|
3651
3665
|
...s,
|
|
3652
3666
|
status: "ready",
|
|
3653
3667
|
items,
|
|
3668
|
+
itemsHidden: items.length === 0,
|
|
3654
3669
|
emptyText: items.length ? "" : emptyText(),
|
|
3655
3670
|
_siteId: site,
|
|
3656
3671
|
failed: fetched === null,
|
|
@@ -48,6 +48,36 @@ const MONTHS_LONG = [
|
|
|
48
48
|
"July", "August", "September", "October", "November", "December",
|
|
49
49
|
] as const;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* A DateTimeFormat for `timeZone` when one is given and this engine knows it. An unknown or
|
|
53
|
+
* malformed IANA id is a RangeError in ECMA-402; these helpers never throw, so it degrades to
|
|
54
|
+
* the device zone rather than killing the screen. Without Intl there is no zone database at all,
|
|
55
|
+
* and the fallbacks below render in the device zone for the same reason.
|
|
56
|
+
*
|
|
57
|
+
* Formatters are memoised per (locale, zone, options): a management page formats several dates
|
|
58
|
+
* per row, and building a DateTimeFormat (or throwing on a bad zone) each time is not free on
|
|
59
|
+
* JSC or QuickJS. Only call it where `hasIntl()` holds — every caller does.
|
|
60
|
+
*/
|
|
61
|
+
const FORMATTERS = new Map<string, Intl.DateTimeFormat>();
|
|
62
|
+
function dateFormat(locale: string, options: Intl.DateTimeFormatOptions, timeZone?: string): Intl.DateTimeFormat {
|
|
63
|
+
const key = `${locale}|${timeZone ?? ""}|${JSON.stringify(options)}`;
|
|
64
|
+
let formatter = FORMATTERS.get(key);
|
|
65
|
+
if (!formatter) {
|
|
66
|
+
if (hasIntl()) {
|
|
67
|
+
if (timeZone) {
|
|
68
|
+
try {
|
|
69
|
+
formatter = new Intl.DateTimeFormat(locale, { ...options, timeZone });
|
|
70
|
+
} catch {
|
|
71
|
+
/* unknown zone: device zone below, remembered under this key */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
formatter ??= new Intl.DateTimeFormat(locale, options);
|
|
75
|
+
FORMATTERS.set(key, formatter);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return formatter as Intl.DateTimeFormat;
|
|
79
|
+
}
|
|
80
|
+
|
|
51
81
|
// The four shapes below are not invented — they are the option bags this corpus actually uses,
|
|
52
82
|
// counted across every bundle: "Jan 5, 2026" (33 sites), "Jan 5" (11), "Mon, Jan 5, 3:07 PM" (8)
|
|
53
83
|
// and "Jan 5, 3:07 PM" (4). Naming them means a conversion is a one-line swap rather than a
|
|
@@ -55,46 +85,46 @@ const MONTHS_LONG = [
|
|
|
55
85
|
// ECMA-402 in engine-intl-fallback.test.ts.
|
|
56
86
|
|
|
57
87
|
/** `{ month: "short", day: "numeric", year: "numeric" }` — "Jan 5, 2026". */
|
|
58
|
-
export function formatDateMedium(date: Date, locale = "en-US"): string {
|
|
88
|
+
export function formatDateMedium(date: Date, locale = "en-US", timeZone?: string): string {
|
|
59
89
|
if (hasIntl()) {
|
|
60
|
-
return
|
|
90
|
+
return dateFormat(locale, { month: "short", day: "numeric", year: "numeric" }, timeZone).format(date);
|
|
61
91
|
}
|
|
62
92
|
return `${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}, ${date.getFullYear()}`;
|
|
63
93
|
}
|
|
64
94
|
|
|
65
95
|
/** `{ month: "short", day: "numeric" }` — "Jan 5". */
|
|
66
|
-
export function formatMonthDay(date: Date, locale = "en-US"): string {
|
|
96
|
+
export function formatMonthDay(date: Date, locale = "en-US", timeZone?: string): string {
|
|
67
97
|
if (hasIntl()) {
|
|
68
|
-
return
|
|
98
|
+
return dateFormat(locale, { month: "short", day: "numeric" }, timeZone).format(date);
|
|
69
99
|
}
|
|
70
100
|
return `${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}`;
|
|
71
101
|
}
|
|
72
102
|
|
|
73
103
|
/** `{ month: "long", day: "numeric" }` — "January 5". */
|
|
74
|
-
export function formatMonthDayLong(date: Date, locale = "en-US"): string {
|
|
104
|
+
export function formatMonthDayLong(date: Date, locale = "en-US", timeZone?: string): string {
|
|
75
105
|
if (hasIntl()) {
|
|
76
|
-
return
|
|
106
|
+
return dateFormat(locale, { month: "long", day: "numeric" }, timeZone).format(date);
|
|
77
107
|
}
|
|
78
108
|
return `${MONTHS_LONG[date.getMonth()]} ${date.getDate()}`;
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
/** `{ weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }`
|
|
82
112
|
* — "Mon, Jan 5, 3:07 PM". */
|
|
83
|
-
export function formatWeekdayDateTime(date: Date, locale = "en-US"): string {
|
|
113
|
+
export function formatWeekdayDateTime(date: Date, locale = "en-US", timeZone?: string): string {
|
|
84
114
|
if (hasIntl()) {
|
|
85
|
-
return
|
|
115
|
+
return dateFormat(locale, {
|
|
86
116
|
weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit",
|
|
87
|
-
}).format(date);
|
|
117
|
+
}, timeZone).format(date);
|
|
88
118
|
}
|
|
89
119
|
return `${WEEKDAYS_SHORT[date.getDay()]}, ${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}, ${clock12(date)}`;
|
|
90
120
|
}
|
|
91
121
|
|
|
92
122
|
/** `{ month: "short", day: "numeric", hour: "numeric", minute: "2-digit" }` — "Jan 5, 3:07 PM". */
|
|
93
|
-
export function formatMonthDayTime(date: Date, locale = "en-US"): string {
|
|
123
|
+
export function formatMonthDayTime(date: Date, locale = "en-US", timeZone?: string): string {
|
|
94
124
|
if (hasIntl()) {
|
|
95
|
-
return
|
|
125
|
+
return dateFormat(locale, {
|
|
96
126
|
month: "short", day: "numeric", hour: "numeric", minute: "2-digit",
|
|
97
|
-
}).format(date);
|
|
127
|
+
}, timeZone).format(date);
|
|
98
128
|
}
|
|
99
129
|
return `${MONTHS_SHORT[date.getMonth()]} ${date.getDate()}, ${clock12(date)}`;
|
|
100
130
|
}
|
|
@@ -380,9 +410,9 @@ export function normalizeFormattedPrice(formatted: string): string {
|
|
|
380
410
|
return `${m[1]}${m[3]}${m[2]}`;
|
|
381
411
|
}
|
|
382
412
|
|
|
383
|
-
function clockParts(d: Date, locale: string): { time: string; meridiem: string } {
|
|
413
|
+
function clockParts(d: Date, locale: string, timeZone?: string): { time: string; meridiem: string } {
|
|
384
414
|
const full = hasIntl()
|
|
385
|
-
?
|
|
415
|
+
? dateFormat(locale, { hour: "numeric", minute: "2-digit", hour12: true }, timeZone).format(d)
|
|
386
416
|
: clock12(d);
|
|
387
417
|
// ICU puts a narrow no-break space before the meridiem; normalize so callers can split on it.
|
|
388
418
|
const clean = full.replace(/[ ]/g, " ");
|
|
@@ -391,8 +421,8 @@ function clockParts(d: Date, locale: string): { time: string; meridiem: string }
|
|
|
391
421
|
}
|
|
392
422
|
|
|
393
423
|
/** "8:00 PM" — the one clock every customer screen uses (12-hour, en-US shape). */
|
|
394
|
-
export function formatClock(date: Date, locale = "en-US"): string {
|
|
395
|
-
const { time, meridiem } = clockParts(date, locale);
|
|
424
|
+
export function formatClock(date: Date, locale = "en-US", timeZone?: string): string {
|
|
425
|
+
const { time, meridiem } = clockParts(date, locale, timeZone);
|
|
396
426
|
return meridiem ? `${time} ${meridiem}` : time;
|
|
397
427
|
}
|
|
398
428
|
|
|
@@ -403,3 +433,14 @@ export function isPastDate(iso: string | undefined | null, now: number = Date.no
|
|
|
403
433
|
const t = Date.parse(iso);
|
|
404
434
|
return !Number.isNaN(t) && t < now;
|
|
405
435
|
}
|
|
436
|
+
|
|
437
|
+
/** The calendar day `date` falls on in `timeZone` (device zone when absent, unknown, or when
|
|
438
|
+
* the engine has no Intl), as `YYYY-MM-DD`. For "is this today / this year" comparisons. */
|
|
439
|
+
export function calendarDay(date: Date, timeZone?: string): string {
|
|
440
|
+
if (hasIntl()) {
|
|
441
|
+
const parts = dateFormat("en-CA", { year: "numeric", month: "2-digit", day: "2-digit" }, timeZone).formatToParts(date);
|
|
442
|
+
const get = (type: string) => parts.find((p) => p.type === type)?.value ?? "";
|
|
443
|
+
return `${get("year")}-${get("month")}-${get("day")}`;
|
|
444
|
+
}
|
|
445
|
+
return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())}`;
|
|
446
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Shared display-text helpers for himi cards: trim catalog artifacts from titles and
|
|
2
|
+
// derive up-to-two-letter placeholder initials so same-first-letter lists still vary.
|
|
3
|
+
|
|
4
|
+
/** Trim trailing artifact markers (stray asterisks / footnote daggers) and whitespace
|
|
5
|
+
* that leak from test/seed catalogs, without touching interior punctuation. Falls back
|
|
6
|
+
* to the original when trimming would empty the string. */
|
|
7
|
+
export function cleanTitle(value?: string): string {
|
|
8
|
+
const raw = (value ?? "").trim();
|
|
9
|
+
const cleaned = raw.replace(/[\s*†‡]+$/u, "").trim();
|
|
10
|
+
return cleaned || raw;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Up to two significant initials for a placeholder avatar. Two-plus words → first
|
|
14
|
+
* letter of the first two; a single word → its first two alphanumerics. Keeps a list
|
|
15
|
+
* of similar names (e.g. many "Booking…") from collapsing to one identical glyph. */
|
|
16
|
+
export function initials(value?: string): string {
|
|
17
|
+
const words = (value ?? "").trim().split(/\s+/).filter(Boolean);
|
|
18
|
+
if (words.length === 0) return "#";
|
|
19
|
+
if (words.length === 1) {
|
|
20
|
+
const alnum = words[0].replace(/[^\p{L}\p{N}]/gu, "");
|
|
21
|
+
return (alnum.slice(0, 2) || "#").toUpperCase();
|
|
22
|
+
}
|
|
23
|
+
return ((words[0][0] ?? "") + (words[1][0] ?? "")).toUpperCase();
|
|
24
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
export interface AccountState {
|
|
5
5
|
status: "loading" | "ready";
|
|
6
|
+
/** The business the "Business" row names: the one picked on Sites, else the
|
|
7
|
+
* build-time fallback (see `owner-selected-site.ts`). */
|
|
8
|
+
businessName: string;
|
|
6
9
|
/** "Name · email", the email alone, or "" (the line collapses). */
|
|
7
10
|
ownerLine: string;
|
|
8
11
|
/** "Version 1.2.0 (34)", or "" when the host cannot say. */
|
|
@@ -36,6 +39,7 @@ export function shapeAccount(s: Partial<AccountState>): AccountState {
|
|
|
36
39
|
const supportUrl = safeUrl(s.supportUrl), privacyUrl = safeUrl(s.privacyUrl), termsUrl = safeUrl(s.termsUrl);
|
|
37
40
|
return {
|
|
38
41
|
status: s.status ?? "loading",
|
|
42
|
+
businessName: s.businessName ?? "",
|
|
39
43
|
ownerLine: s.ownerLine ?? "",
|
|
40
44
|
versionLine: s.versionLine ?? "",
|
|
41
45
|
error: s.error ?? "",
|
|
@@ -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
|
}
|