@wix/himalaya-cli 0.1039.0 → 0.1041.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 +31 -19
- package/dist/preview-assets.generated.bin +0 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +42 -42
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/beauty-salon-owner/config.ts +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +794 -295
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/branded-lite/dev/index.ts +175 -175
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/branded-lite/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club/dev/index.ts +28 -28
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club-owner/config.ts +2 -2
- package/templates/community-club-owner/dev/index.ts +794 -295
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/community-club-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/events-meetups/dev/index.ts +21 -21
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/events-meetups-owner/config.ts +2 -2
- package/templates/events-meetups-owner/dev/index.ts +766 -243
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/events-meetups-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/gym-fitness/dev/index.ts +49 -49
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/gym-fitness-owner/config.ts +2 -2
- package/templates/gym-fitness-owner/dev/index.ts +801 -320
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/nonprofit-cause/dev/index.ts +28 -28
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/nonprofit-cause-owner/config.ts +2 -2
- package/templates/nonprofit-cause-owner/dev/index.ts +773 -268
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/owner-lite/config.ts +2 -2
- package/templates/owner-lite/dev/index.ts +802 -322
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/owner-lite/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/owner-lite/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/podcast-creator/dev/index.ts +28 -28
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/podcast-creator-owner/config.ts +2 -2
- package/templates/podcast-creator-owner/dev/index.ts +794 -295
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/portfolio-creative/dev/index.ts +35 -35
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/portfolio-creative-owner/config.ts +2 -2
- package/templates/portfolio-creative-owner/dev/index.ts +794 -295
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/restaurant/dev/index.ts +21 -21
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/restaurant-owner/config.ts +2 -2
- package/templates/restaurant-owner/dev/index.ts +801 -320
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/restaurant-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
- package/templates/retail-store/dev/index.ts +21 -21
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/retail-store/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/retail-store-owner/config.ts +2 -2
- package/templates/retail-store-owner/dev/index.ts +787 -270
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/retail-store-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
- package/templates/yoga-studio/dev/index.ts +42 -42
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/yoga-studio-owner/config.ts +2 -2
- package/templates/yoga-studio-owner/dev/index.ts +773 -268
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +237 -4
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +2 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -1
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-account.ts +45 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
|
@@ -7,75 +7,61 @@
|
|
|
7
7
|
// attention today — and none of that is knowable when the config is authored.
|
|
8
8
|
//
|
|
9
9
|
// Hosting the panel lets the worker answer it at runtime: the live site, per-site
|
|
10
|
-
// counts on the rows that have them, and a "needs attention"
|
|
11
|
-
// not there when
|
|
10
|
+
// counts on the rows that have them, and a "needs attention" section that simply
|
|
11
|
+
// is not there when nothing is waiting.
|
|
12
|
+
//
|
|
13
|
+
// The derived fields (initials, the attention total and header, each row's badge
|
|
14
|
+
// and spoken label, the clear-confirmation flags) live in ./logic.ts so they are
|
|
15
|
+
// unit-tested and can only ever agree with each other.
|
|
12
16
|
//
|
|
13
17
|
// Build: node core/dev-server/tier5-bundles-src/build-all.mjs himi-owner-lite-drawer
|
|
14
18
|
// → core/dev-server/tier5-bundles/himi-owner-lite-drawer.bundle.js
|
|
15
19
|
|
|
16
20
|
import { defineService } from "@himalaya/state";
|
|
17
21
|
import { serializeForAgent } from "@himalaya/agent";
|
|
22
|
+
import { activeScreenFromInput, attentionRow, navRow, withDerived, type DrawerState } from "./logic.js";
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
icon: string;
|
|
23
|
-
screenId: string;
|
|
24
|
-
/** Rendered only when non-empty — an owner does not need a "0". */
|
|
25
|
-
badge: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface DrawerState {
|
|
29
|
-
siteName: string;
|
|
30
|
-
siteInitials: string;
|
|
31
|
-
nav: Row[];
|
|
32
|
-
/** Rows an owner should look at today; empty ⇒ the section renders nothing. */
|
|
33
|
-
attention: Row[];
|
|
34
|
-
attentionLabel: string;
|
|
35
|
-
/** TRUE when there is nothing to chase. Feeds `hiddenPath`, which hides on a
|
|
36
|
-
* TRUTHY value — so the flag is phrased negatively on purpose. */
|
|
37
|
-
noAttention: boolean;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const nav = (id: string, title: string, icon: string, screenId: string, badge = ""): Row =>
|
|
41
|
-
({ id, title, icon, screenId, badge });
|
|
42
|
-
|
|
43
|
-
/** Recompute the derived attention summary from the rows. */
|
|
44
|
-
function withAttention(s: Omit<DrawerState, "attentionLabel" | "noAttention">): DrawerState {
|
|
45
|
-
const n = s.attention.length;
|
|
46
|
-
return {
|
|
47
|
-
...s,
|
|
48
|
-
noAttention: n === 0,
|
|
49
|
-
attentionLabel: n === 0 ? "All caught up" : `${n} need${n === 1 ? "s" : ""} attention`,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
24
|
+
/** The shell passes the screen it is showing as worker input, so the drawer can mark
|
|
25
|
+
* that destination selected the way the declarative drawer does. */
|
|
26
|
+
const hostActiveScreen = () => activeScreenFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input);
|
|
52
27
|
|
|
53
28
|
const drawer = defineService<DrawerState, {
|
|
54
|
-
/** Re-read the
|
|
55
|
-
|
|
56
|
-
|
|
29
|
+
/** Re-read the active screen from host input (root onAppear). An explicit
|
|
30
|
+
* `{ screenId }` wins, for a host that pushes the change instead. */
|
|
31
|
+
syncActive: (s: DrawerState, args?: { screenId?: string }) => DrawerState;
|
|
32
|
+
/** First tap on "Mark all handled": ask before hiding anything. */
|
|
33
|
+
askClearAttention: (s: DrawerState) => DrawerState;
|
|
34
|
+
/** The owner backed out of the confirmation. */
|
|
35
|
+
cancelClearAttention: (s: DrawerState) => DrawerState;
|
|
36
|
+
/** Confirmed: clear the attention list — the section disappears, not just empties. */
|
|
57
37
|
clearAttention: (s: DrawerState) => DrawerState;
|
|
58
38
|
}>("drawer", {
|
|
59
|
-
state:
|
|
39
|
+
state: withDerived({
|
|
60
40
|
siteName: "My Site",
|
|
61
|
-
siteInitials: "MS",
|
|
62
41
|
nav: [
|
|
63
|
-
|
|
42
|
+
navRow("drawer_home", "Home", "home", "himi_owner_lite_home"),
|
|
64
43
|
// Between Home and Manage deliberately: analytics answers "how is the
|
|
65
44
|
// business doing", which sits with the dashboard, not with the per-entity
|
|
66
45
|
// management areas below it.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
46
|
+
navRow("drawer_analytics", "Analytics", "chart", "himi_owner_lite_analytics"),
|
|
47
|
+
navRow("drawer_manage", "Manage", "grid", "himi_owner_lite_manage"),
|
|
48
|
+
navRow("drawer_account", "Account", "person", "himi_owner_lite_account"),
|
|
70
49
|
],
|
|
71
50
|
attention: [
|
|
72
|
-
|
|
73
|
-
|
|
51
|
+
attentionRow("att_orders", "Orders", "cart", "himi_owner_lite_orders", 3),
|
|
52
|
+
attentionRow("att_appts", "Appointments", "calendar", "himi_owner_lite_appointments", 1),
|
|
74
53
|
],
|
|
54
|
+
confirmingClear: false,
|
|
55
|
+
activeScreenId: hostActiveScreen(),
|
|
75
56
|
}),
|
|
76
57
|
actions: {
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
syncActive: (s, args) => snap(withDerived({
|
|
59
|
+
...s,
|
|
60
|
+
activeScreenId: typeof args?.screenId === "string" ? args.screenId : hostActiveScreen(),
|
|
61
|
+
})),
|
|
62
|
+
askClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: true })),
|
|
63
|
+
cancelClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: false })),
|
|
64
|
+
clearAttention: (s) => snap(withDerived({ ...s, attention: [], confirmingClear: false })),
|
|
79
65
|
},
|
|
80
66
|
});
|
|
81
67
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// himi-owner-lite DRAWER — pure derivations, kept out of index.ts so they are
|
|
2
|
+
// unit-testable without booting a worker.
|
|
3
|
+
//
|
|
4
|
+
// Everything a reader of the panel compares lives here, so the pieces can only ever
|
|
5
|
+
// agree: the header counts ITEMS summed from the same numbers the badges show (it
|
|
6
|
+
// used to count rows, and read "2 need attention" over badges of 3 and 1), and each
|
|
7
|
+
// row's spoken label is built from those same numbers.
|
|
8
|
+
|
|
9
|
+
export interface Row {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
/** Design-system icon name for the row's leading glyph. */
|
|
13
|
+
icon: string;
|
|
14
|
+
screenId: string;
|
|
15
|
+
/** Items waiting on this row; 0 for a plain destination. */
|
|
16
|
+
count: number;
|
|
17
|
+
/** The badge text — the count, or "" (a destination shows no badge). */
|
|
18
|
+
badge: string;
|
|
19
|
+
/** The badge read on its own, for a screen reader that keeps a labelled row's
|
|
20
|
+
* children reachable (iOS): "3 need attention". "" when there is no badge. */
|
|
21
|
+
badgeLabel: string;
|
|
22
|
+
/** What a screen reader announces for the whole row. */
|
|
23
|
+
a11yLabel: string;
|
|
24
|
+
/** This row is the screen the owner is on. Only destinations can be. */
|
|
25
|
+
selected: boolean;
|
|
26
|
+
/** `!selected`, for `hiddenPath` (which hides on a TRUTHY value). */
|
|
27
|
+
notSelected: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The authored part of the state; everything else is derived by `withDerived`. */
|
|
31
|
+
export interface DrawerCore {
|
|
32
|
+
siteName: string;
|
|
33
|
+
nav: Row[];
|
|
34
|
+
attention: Row[];
|
|
35
|
+
/** The owner tapped "Mark all handled" and has not confirmed or cancelled yet. */
|
|
36
|
+
confirmingClear: boolean;
|
|
37
|
+
/** The screen the shell is showing, passed in as worker input; "" when unknown. */
|
|
38
|
+
activeScreenId: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DrawerState extends DrawerCore {
|
|
42
|
+
siteInitials: string;
|
|
43
|
+
attentionTotal: number;
|
|
44
|
+
attentionLabel: string;
|
|
45
|
+
/** TRUE when there is nothing to chase. Feeds `hiddenPath`, which hides on a
|
|
46
|
+
* TRUTHY value — so the flag is phrased negatively on purpose. */
|
|
47
|
+
noAttention: boolean;
|
|
48
|
+
/** Hides the inline confirmation (same negative phrasing, same reason). */
|
|
49
|
+
noConfirmClear: boolean;
|
|
50
|
+
/** The confirmation question, naming how much will be hidden. */
|
|
51
|
+
clearPrompt: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const needs = (n: number) => `${n} need${n === 1 ? "s" : ""} attention`;
|
|
55
|
+
|
|
56
|
+
export const navRow = (id: string, title: string, icon: string, screenId: string): Row =>
|
|
57
|
+
({ id, title, icon, screenId, count: 0, badge: "", badgeLabel: "", a11yLabel: title, selected: false, notSelected: true });
|
|
58
|
+
|
|
59
|
+
export const attentionRow = (id: string, title: string, icon: string, screenId: string, count: number): Row => {
|
|
60
|
+
const n = Math.max(0, Math.floor(count));
|
|
61
|
+
return { id, title, icon, screenId, count: n, badge: n > 0 ? String(n) : "", badgeLabel: n > 0 ? needs(n) : "", a11yLabel: n > 0 ? `${title}, ${needs(n)}` : title, selected: false, notSelected: true };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** Up to two initials from the site name: "Pottery Studio" → "PS". */
|
|
65
|
+
export function initialsOf(name: string): string {
|
|
66
|
+
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
67
|
+
if (words.length === 0) return "?";
|
|
68
|
+
return words.slice(0, 2).map((w) => [...w][0]!.toUpperCase()).join("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Recompute every derived field. Rows with nothing waiting are dropped, so a
|
|
72
|
+
* cleared or all-zero section hides instead of listing zeros. */
|
|
73
|
+
export function withDerived(core: DrawerCore): DrawerState {
|
|
74
|
+
const attention = core.attention.filter((row) => row.count > 0);
|
|
75
|
+
const active = core.activeScreenId;
|
|
76
|
+
const nav = core.nav.map((row) => {
|
|
77
|
+
const selected = active !== "" && row.screenId === active;
|
|
78
|
+
return { ...row, selected, notSelected: !selected };
|
|
79
|
+
});
|
|
80
|
+
const total = attention.reduce((sum, row) => sum + row.count, 0);
|
|
81
|
+
const confirmingClear = core.confirmingClear && total > 0;
|
|
82
|
+
return {
|
|
83
|
+
...core,
|
|
84
|
+
nav,
|
|
85
|
+
attention,
|
|
86
|
+
confirmingClear,
|
|
87
|
+
siteInitials: initialsOf(core.siteName),
|
|
88
|
+
attentionTotal: total,
|
|
89
|
+
attentionLabel: total === 0
|
|
90
|
+
? "All caught up"
|
|
91
|
+
: `${total} item${total === 1 ? "" : "s"} ${total === 1 ? "needs" : "need"} attention`,
|
|
92
|
+
noAttention: total === 0,
|
|
93
|
+
noConfirmClear: !confirmingClear,
|
|
94
|
+
clearPrompt: total === 1 ? "Hide this reminder?" : `Hide these ${total} reminders?`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The active screen from the worker's host input (`himalaya.input.activeScreenId`). */
|
|
99
|
+
export function activeScreenFromInput(input: unknown): string {
|
|
100
|
+
const v = (input as { activeScreenId?: unknown } | undefined)?.activeScreenId;
|
|
101
|
+
return typeof v === "string" ? v : "";
|
|
102
|
+
}
|
|
@@ -10,7 +10,7 @@ import { defineService } from "@himalaya/state";
|
|
|
10
10
|
import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
|
|
11
11
|
import { normalizeEvent, eventDescription } from "../_shared/himi-events.js";
|
|
12
12
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
13
|
-
import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
|
|
13
|
+
import { DEMO_EVENTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
14
14
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
15
15
|
import { get as dbGet, upsert as dbUpsert, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
|
|
16
16
|
|
|
@@ -113,6 +113,12 @@ function shape(s: Partial<EventFormState>): EventFormState {
|
|
|
113
113
|
|
|
114
114
|
const startMode: Mode = "edit";
|
|
115
115
|
|
|
116
|
+
|
|
117
|
+
/** A demo-preview write refusal: see demoRefusalPatch (a mode, not an error). */
|
|
118
|
+
function demoRefusal(s: Parameters<typeof shape>[0]): ReturnType<typeof shape> {
|
|
119
|
+
return shape({ ...s, ...demoRefusalPatch(copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE)) });
|
|
120
|
+
}
|
|
121
|
+
|
|
116
122
|
const svc = defineService<EventFormState, {
|
|
117
123
|
load: (s: EventFormState, arg?: unknown) => Promise<EventFormState>;
|
|
118
124
|
submit: (s: EventFormState, values?: Record<string, unknown>) => Promise<EventFormState>;
|
|
@@ -185,7 +191,7 @@ const svc = defineService<EventFormState, {
|
|
|
185
191
|
}
|
|
186
192
|
},
|
|
187
193
|
submit: async (s, values) => {
|
|
188
|
-
if (isOwnerPreviewDemo() || s.demoNote) return
|
|
194
|
+
if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
|
|
189
195
|
const v = values ?? {};
|
|
190
196
|
const title = String(v.title ?? s.title).trim();
|
|
191
197
|
const description = String(v.description ?? s.description).trim();
|
|
@@ -221,7 +227,7 @@ const svc = defineService<EventFormState, {
|
|
|
221
227
|
// Demo / web-preview (no owner bridge; the record came from demo sample
|
|
222
228
|
// data, marked by demoNote): a queued write could never replay, so answer
|
|
223
229
|
// with the demo save note instead of the real-offline write-behind.
|
|
224
|
-
if (s.demoNote) return
|
|
230
|
+
if (s.demoNote) return demoRefusal(s);
|
|
225
231
|
// Real device offline: the optimistic cache row already shows; queue the
|
|
226
232
|
// write to replay (WriteQueue, idempotent) when connectivity returns.
|
|
227
233
|
await queueWrite("tier5.himiOwnerLiteEventForm.submit", { id: s.id, title, description });
|
|
@@ -9,7 +9,7 @@ import { wixMediaUrl, WIX_DETAIL_IMAGE } from "../_shared/himi-store.js";
|
|
|
9
9
|
import { defineService } from "@himalaya/state";
|
|
10
10
|
import { getDraftPost, createDraftPost, updateDraftPost, type WixDraftPostWrite, type WixRichContent } from "@himalaya/wix-blog";
|
|
11
11
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
12
|
-
import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
|
|
12
|
+
import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
|
|
13
13
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
14
14
|
import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
|
|
15
15
|
|
|
@@ -127,6 +127,12 @@ function shape(s: Partial<PostFormState>): PostFormState {
|
|
|
127
127
|
|
|
128
128
|
const startMode: Mode = initialInput.mode === "edit" || initialInput.id ? "edit" : "create";
|
|
129
129
|
|
|
130
|
+
|
|
131
|
+
/** A demo-preview write refusal: see demoRefusalPatch (a mode, not an error). */
|
|
132
|
+
function demoRefusal(s: Parameters<typeof shape>[0]): ReturnType<typeof shape> {
|
|
133
|
+
return shape({ ...s, ...demoRefusalPatch(copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE)) });
|
|
134
|
+
}
|
|
135
|
+
|
|
130
136
|
const svc = defineService<PostFormState, {
|
|
131
137
|
load: (s: PostFormState, arg?: unknown) => Promise<PostFormState>;
|
|
132
138
|
setCover: (s: PostFormState, arg?: unknown) => PostFormState;
|
|
@@ -140,7 +146,7 @@ const svc = defineService<PostFormState, {
|
|
|
140
146
|
// bakes no id/mode); the onAppear `id` param is the #570 suffixed-build
|
|
141
147
|
// fallback. An id ⇒ edit; no id anywhere ⇒ the create shell.
|
|
142
148
|
const id = navInputId() || idFrom(arg, s.id);
|
|
143
|
-
if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "" });
|
|
149
|
+
if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "", demoNote: isOwnerPreviewDemo() ? copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) : "" });
|
|
144
150
|
if (isOwnerPreviewDemo()) {
|
|
145
151
|
const d = DEMO_POSTS.find((x) => x.id === id);
|
|
146
152
|
if (d) {
|
|
@@ -203,7 +209,7 @@ const svc = defineService<PostFormState, {
|
|
|
203
209
|
return shape({ ...s, coverUrl, error: "" });
|
|
204
210
|
},
|
|
205
211
|
submit: async (s, values) => {
|
|
206
|
-
if (isOwnerPreviewDemo() || s.demoNote) return
|
|
212
|
+
if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
|
|
207
213
|
const v = values ?? {};
|
|
208
214
|
const title = String(v.title ?? s.title).trim();
|
|
209
215
|
const excerpt = String(v.excerpt ?? s.excerpt).trim();
|
|
@@ -272,7 +278,7 @@ const svc = defineService<PostFormState, {
|
|
|
272
278
|
// Demo / web-preview (no owner bridge; the record came from demo sample
|
|
273
279
|
// data, marked by demoNote): a queued write could never replay, so answer
|
|
274
280
|
// with the demo save note instead of the real-offline write-behind.
|
|
275
|
-
if (s.demoNote) return
|
|
281
|
+
if (s.demoNote) return demoRefusal(s);
|
|
276
282
|
// Real device offline: cache and queue the edit for reconnect.
|
|
277
283
|
await cachePut(localRow(localId));
|
|
278
284
|
await queueWrite("tier5.himiOwnerLitePostForm.submit", { id: targetId, title, excerpt, body, coverUrl, coverChanged: String(coverChanged), preserveBody: String(preserveBody), isFeatured: String(isFeatured), mode: editing ? "edit" : "create" });
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
|
|
19
19
|
import { plainText } from "../_shared/himi-store.js";
|
|
20
20
|
import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
|
|
21
|
-
import { DEMO_PRODUCTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
|
|
21
|
+
import { DEMO_PRODUCTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch, editablePrice } from "../_shared/owner-preview-demo.js";
|
|
22
22
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
23
23
|
|
|
24
24
|
type Mode = "create" | "edit";
|
|
@@ -95,9 +95,14 @@ function boolString(value: unknown, fallback: string): string {
|
|
|
95
95
|
// fall back to the raw numeric if the formatted string is missing.
|
|
96
96
|
function priceOf(p: WixProduct): string {
|
|
97
97
|
const pr = (p as any).price;
|
|
98
|
-
|
|
98
|
+
// The field is EDITABLE and renderer-validated as an amount, so prefill the
|
|
99
|
+
// raw number — never the display string ("€10.00"), which would fail the rule
|
|
100
|
+
// and disable Save before the owner typed anything.
|
|
101
|
+
if (typeof pr?.price === "number" && Number.isFinite(pr.price)) return pr.price.toFixed(2);
|
|
102
|
+
return editablePrice(String(pr?.formatted?.price ?? pr?.price ?? ""));
|
|
99
103
|
}
|
|
100
104
|
|
|
105
|
+
|
|
101
106
|
function shape(s: Partial<ProductFormState>): ProductFormState {
|
|
102
107
|
const mode: Mode = s.mode ?? "create";
|
|
103
108
|
const id = typeof s.id === "string" ? s.id : "";
|
|
@@ -128,6 +133,12 @@ function shape(s: Partial<ProductFormState>): ProductFormState {
|
|
|
128
133
|
|
|
129
134
|
const startMode: Mode = initialInput.mode === "edit" || initialInput.id ? "edit" : "create";
|
|
130
135
|
|
|
136
|
+
|
|
137
|
+
/** A demo-preview write refusal: see demoRefusalPatch (a mode, not an error). */
|
|
138
|
+
function demoRefusal(s: Parameters<typeof shape>[0]): ReturnType<typeof shape> {
|
|
139
|
+
return shape({ ...s, ...demoRefusalPatch(copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE)) });
|
|
140
|
+
}
|
|
141
|
+
|
|
131
142
|
const svc = defineService<ProductFormState, {
|
|
132
143
|
load: (s: ProductFormState, arg?: unknown) => Promise<ProductFormState>;
|
|
133
144
|
submit: (s: ProductFormState, values?: Record<string, unknown>) => Promise<ProductFormState>;
|
|
@@ -141,13 +152,13 @@ const svc = defineService<ProductFormState, {
|
|
|
141
152
|
// bakes no id/mode); the onAppear `id` param is the #570 suffixed-build
|
|
142
153
|
// fallback. An id ⇒ edit; no id anywhere ⇒ the create shell.
|
|
143
154
|
const id = navInputId() || idFrom(arg, s.id);
|
|
144
|
-
if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "" });
|
|
155
|
+
if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "", demoNote: isOwnerPreviewDemo() ? copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) : "" });
|
|
145
156
|
if (isOwnerPreviewDemo()) {
|
|
146
157
|
const d = DEMO_PRODUCTS.find((p) => p.id === id);
|
|
147
158
|
if (d) {
|
|
148
159
|
return shape({
|
|
149
160
|
...s, mode: "edit", id, heading: copyParam("ownerProductForm.headingEdit", "Edit product"),
|
|
150
|
-
name: d.name, price: d.price
|
|
161
|
+
name: d.name, price: editablePrice(d.price), sku: d.sku, description: d.description,
|
|
151
162
|
visible: d.visible ? "true" : "false",
|
|
152
163
|
status: "idle", error: "", savedMessage: "", demoNote: copyParam("ownerCommon.demoNote", DEMO_NOTE),
|
|
153
164
|
});
|
|
@@ -178,7 +189,7 @@ const svc = defineService<ProductFormState, {
|
|
|
178
189
|
if (cached) {
|
|
179
190
|
return shape({
|
|
180
191
|
...s, mode: "edit", id, heading: "Edit product",
|
|
181
|
-
name: String(cached.name ?? ""), price: String(cached.price ?? ""), sku: String(cached.sku ?? ""),
|
|
192
|
+
name: String(cached.name ?? ""), price: editablePrice(String(cached.price ?? "")), sku: String(cached.sku ?? ""),
|
|
182
193
|
visible: cached.visible === "false" ? "false" : "true",
|
|
183
194
|
status: "idle", error: "", savedMessage: "",
|
|
184
195
|
});
|
|
@@ -188,7 +199,7 @@ const svc = defineService<ProductFormState, {
|
|
|
188
199
|
if (d) {
|
|
189
200
|
return shape({
|
|
190
201
|
...s, mode: "edit", id, heading: copyParam("ownerProductForm.headingEdit", "Edit product"),
|
|
191
|
-
name: d.name, price: d.price
|
|
202
|
+
name: d.name, price: editablePrice(d.price), sku: d.sku, description: d.description,
|
|
192
203
|
visible: d.visible ? "true" : "false",
|
|
193
204
|
status: "idle", error: "", savedMessage: "", demoNote: copyParam("ownerCommon.demoNote", DEMO_NOTE),
|
|
194
205
|
});
|
|
@@ -198,17 +209,22 @@ const svc = defineService<ProductFormState, {
|
|
|
198
209
|
}
|
|
199
210
|
},
|
|
200
211
|
submit: async (s, values) => {
|
|
201
|
-
if (isOwnerPreviewDemo() || s.demoNote) return
|
|
212
|
+
if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
|
|
202
213
|
const v = values ?? {};
|
|
203
214
|
const name = String(v.name ?? s.name).trim();
|
|
204
|
-
const
|
|
215
|
+
const rawPrice = String(v.price ?? s.price).trim();
|
|
216
|
+
// "12,50" (comma-decimal keypad) is accepted and written as 12.50.
|
|
217
|
+
const price = editablePrice(rawPrice);
|
|
205
218
|
const sku = String(v.sku ?? s.sku).trim();
|
|
206
219
|
const description = String(v.description ?? s.description).trim();
|
|
207
220
|
const visible = boolString(v.visible, s.visible) === "true";
|
|
208
221
|
if (!name) return shape({ ...s, status: "error", error: copyParam("ownerProductForm.nameRequired", "Name is required"), savedMessage: "" });
|
|
209
222
|
const priceNum = Number(price);
|
|
210
|
-
|
|
211
|
-
|
|
223
|
+
// The renderer already blocks these (declarative `validation`); this is the
|
|
224
|
+
// worker's own guard for a host or queue replay that skips it. Checked on the
|
|
225
|
+
// RAW value, so "-5" is refused rather than normalized into "5".
|
|
226
|
+
if (!/^\d+([.,]\d{1,2})?$/.test(rawPrice) || !Number.isFinite(priceNum)) {
|
|
227
|
+
return shape({ ...s, name, price, sku, description, visible: visible ? "true" : "false", status: "error", error: copyParam("ownerProductForm.priceInvalid", "Enter a price of 0 or more."), savedMessage: "" });
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
const write: WixProductWrite = {
|
|
@@ -254,7 +270,7 @@ const svc = defineService<ProductFormState, {
|
|
|
254
270
|
// Demo / web-preview (no owner bridge; the record came from DEMO_PRODUCTS,
|
|
255
271
|
// marked by demoNote): a queued write could never replay, so answer with
|
|
256
272
|
// the demo save note instead of the real-offline write-behind.
|
|
257
|
-
if (s.demoNote) return
|
|
273
|
+
if (s.demoNote) return demoRefusal(s);
|
|
258
274
|
// Real device offline: the optimistic cache row already shows; queue the
|
|
259
275
|
// write to replay (WriteQueue, idempotent) when connectivity returns.
|
|
260
276
|
await queueWrite("tier5.himiOwnerLiteProductForm.submit", { id: s.id, name, price, sku, visible: String(visible), mode: s.mode });
|
|
@@ -269,7 +285,7 @@ const svc = defineService<ProductFormState, {
|
|
|
269
285
|
}
|
|
270
286
|
},
|
|
271
287
|
remove: async (s) => {
|
|
272
|
-
if (isOwnerPreviewDemo() || s.demoNote) return
|
|
288
|
+
if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
|
|
273
289
|
if (!s.id) return shape({ ...s, status: "error", error: copyParam("ownerProductForm.errorNoDelete", "No product to delete."), savedMessage: "" });
|
|
274
290
|
// Optimistic local delete — the list drops the row immediately.
|
|
275
291
|
await cacheDrop(s.id);
|
|
@@ -278,7 +294,7 @@ const svc = defineService<ProductFormState, {
|
|
|
278
294
|
return shape({ ...s, id: "", status: "saved", error: "", savedMessage: copyParam("ownerProductForm.deletedMessage", "Product deleted."), savedId: "" });
|
|
279
295
|
} catch (e) {
|
|
280
296
|
if (shouldQueueOwnerWrite(e)) {
|
|
281
|
-
if (s.demoNote) return
|
|
297
|
+
if (s.demoNote) return demoRefusal(s);
|
|
282
298
|
await queueWrite("tier5.himiOwnerLiteProductForm.remove", { id: s.id });
|
|
283
299
|
return shape({ ...s, id: "", status: "saved", error: "", savedMessage: copyParam("ownerProductForm.deletedOffline", "Deleted offline — will sync when you reconnect."), savedId: "" });
|
|
284
300
|
}
|
|
@@ -6889,7 +6889,7 @@ const SCREENS = runtimeSetting({
|
|
|
6889
6889
|
},
|
|
6890
6890
|
{
|
|
6891
6891
|
"id": "save",
|
|
6892
|
-
"label": "
|
|
6892
|
+
"label": "$item.collectionSaveLabel",
|
|
6893
6893
|
"icon": "bookmark",
|
|
6894
6894
|
"action": {
|
|
6895
6895
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -6961,7 +6961,7 @@ const SCREENS = runtimeSetting({
|
|
|
6961
6961
|
"rowSwipeActions": {
|
|
6962
6962
|
"trailing": [
|
|
6963
6963
|
{
|
|
6964
|
-
"label": "
|
|
6964
|
+
"label": "$item.collectionSaveLabel",
|
|
6965
6965
|
"icon": "bookmark",
|
|
6966
6966
|
"action": {
|
|
6967
6967
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -7245,7 +7245,7 @@ const SCREENS = runtimeSetting({
|
|
|
7245
7245
|
},
|
|
7246
7246
|
{
|
|
7247
7247
|
"id": "save",
|
|
7248
|
-
"label": "
|
|
7248
|
+
"label": "$item.collectionSaveLabel",
|
|
7249
7249
|
"icon": "bookmark",
|
|
7250
7250
|
"action": {
|
|
7251
7251
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -7564,7 +7564,7 @@ const SCREENS = runtimeSetting({
|
|
|
7564
7564
|
},
|
|
7565
7565
|
{
|
|
7566
7566
|
"id": "save",
|
|
7567
|
-
"label": "
|
|
7567
|
+
"label": "$item.collectionSaveLabel",
|
|
7568
7568
|
"icon": "bookmark",
|
|
7569
7569
|
"action": {
|
|
7570
7570
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -7636,7 +7636,7 @@ const SCREENS = runtimeSetting({
|
|
|
7636
7636
|
"rowSwipeActions": {
|
|
7637
7637
|
"trailing": [
|
|
7638
7638
|
{
|
|
7639
|
-
"label": "
|
|
7639
|
+
"label": "$item.collectionSaveLabel",
|
|
7640
7640
|
"icon": "bookmark",
|
|
7641
7641
|
"action": {
|
|
7642
7642
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -7920,7 +7920,7 @@ const SCREENS = runtimeSetting({
|
|
|
7920
7920
|
},
|
|
7921
7921
|
{
|
|
7922
7922
|
"id": "save",
|
|
7923
|
-
"label": "
|
|
7923
|
+
"label": "$item.collectionSaveLabel",
|
|
7924
7924
|
"icon": "bookmark",
|
|
7925
7925
|
"action": {
|
|
7926
7926
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -8611,7 +8611,7 @@ const SCREENS = runtimeSetting({
|
|
|
8611
8611
|
},
|
|
8612
8612
|
{
|
|
8613
8613
|
"id": "save",
|
|
8614
|
-
"label": "
|
|
8614
|
+
"label": "$item.collectionSaveLabel",
|
|
8615
8615
|
"icon": "bookmark",
|
|
8616
8616
|
"action": {
|
|
8617
8617
|
"actionId": "tier5.himiEvents.collectionSave",
|
|
@@ -10157,7 +10157,7 @@ const SCREENS = runtimeSetting({
|
|
|
10157
10157
|
},
|
|
10158
10158
|
{
|
|
10159
10159
|
"id": "save",
|
|
10160
|
-
"label": "
|
|
10160
|
+
"label": "$item.collectionSaveLabel",
|
|
10161
10161
|
"icon": "bookmark",
|
|
10162
10162
|
"action": {
|
|
10163
10163
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -10239,7 +10239,7 @@ const SCREENS = runtimeSetting({
|
|
|
10239
10239
|
"rowSwipeActions": {
|
|
10240
10240
|
"trailing": [
|
|
10241
10241
|
{
|
|
10242
|
-
"label": "
|
|
10242
|
+
"label": "$item.collectionSaveLabel",
|
|
10243
10243
|
"icon": "bookmark",
|
|
10244
10244
|
"action": {
|
|
10245
10245
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -10504,7 +10504,7 @@ const SCREENS = runtimeSetting({
|
|
|
10504
10504
|
},
|
|
10505
10505
|
{
|
|
10506
10506
|
"id": "save",
|
|
10507
|
-
"label": "
|
|
10507
|
+
"label": "$item.collectionSaveLabel",
|
|
10508
10508
|
"icon": "bookmark",
|
|
10509
10509
|
"action": {
|
|
10510
10510
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -10794,7 +10794,7 @@ const SCREENS = runtimeSetting({
|
|
|
10794
10794
|
},
|
|
10795
10795
|
{
|
|
10796
10796
|
"id": "save",
|
|
10797
|
-
"label": "
|
|
10797
|
+
"label": "$item.collectionSaveLabel",
|
|
10798
10798
|
"icon": "bookmark",
|
|
10799
10799
|
"action": {
|
|
10800
10800
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -10876,7 +10876,7 @@ const SCREENS = runtimeSetting({
|
|
|
10876
10876
|
"rowSwipeActions": {
|
|
10877
10877
|
"trailing": [
|
|
10878
10878
|
{
|
|
10879
|
-
"label": "
|
|
10879
|
+
"label": "$item.collectionSaveLabel",
|
|
10880
10880
|
"icon": "bookmark",
|
|
10881
10881
|
"action": {
|
|
10882
10882
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -11141,7 +11141,7 @@ const SCREENS = runtimeSetting({
|
|
|
11141
11141
|
},
|
|
11142
11142
|
{
|
|
11143
11143
|
"id": "save",
|
|
11144
|
-
"label": "
|
|
11144
|
+
"label": "$item.collectionSaveLabel",
|
|
11145
11145
|
"icon": "bookmark",
|
|
11146
11146
|
"action": {
|
|
11147
11147
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -11782,7 +11782,7 @@ const SCREENS = runtimeSetting({
|
|
|
11782
11782
|
},
|
|
11783
11783
|
{
|
|
11784
11784
|
"id": "save",
|
|
11785
|
-
"label": "
|
|
11785
|
+
"label": "$item.collectionSaveLabel",
|
|
11786
11786
|
"icon": "bookmark",
|
|
11787
11787
|
"action": {
|
|
11788
11788
|
"actionId": "tier5.himiBlogFeed.collectionSave",
|
|
@@ -13217,7 +13217,7 @@ const SCREENS = runtimeSetting({
|
|
|
13217
13217
|
},
|
|
13218
13218
|
{
|
|
13219
13219
|
"id": "save",
|
|
13220
|
-
"label": "
|
|
13220
|
+
"label": "$item.collectionSaveLabel",
|
|
13221
13221
|
"icon": "bookmark",
|
|
13222
13222
|
"action": {
|
|
13223
13223
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -13289,7 +13289,7 @@ const SCREENS = runtimeSetting({
|
|
|
13289
13289
|
"rowSwipeActions": {
|
|
13290
13290
|
"trailing": [
|
|
13291
13291
|
{
|
|
13292
|
-
"label": "
|
|
13292
|
+
"label": "$item.collectionSaveLabel",
|
|
13293
13293
|
"icon": "bookmark",
|
|
13294
13294
|
"action": {
|
|
13295
13295
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -13554,7 +13554,7 @@ const SCREENS = runtimeSetting({
|
|
|
13554
13554
|
},
|
|
13555
13555
|
{
|
|
13556
13556
|
"id": "save",
|
|
13557
|
-
"label": "
|
|
13557
|
+
"label": "$item.collectionSaveLabel",
|
|
13558
13558
|
"icon": "bookmark",
|
|
13559
13559
|
"action": {
|
|
13560
13560
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -13828,7 +13828,7 @@ const SCREENS = runtimeSetting({
|
|
|
13828
13828
|
},
|
|
13829
13829
|
{
|
|
13830
13830
|
"id": "save",
|
|
13831
|
-
"label": "
|
|
13831
|
+
"label": "$item.collectionSaveLabel",
|
|
13832
13832
|
"icon": "bookmark",
|
|
13833
13833
|
"action": {
|
|
13834
13834
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -13900,7 +13900,7 @@ const SCREENS = runtimeSetting({
|
|
|
13900
13900
|
"rowSwipeActions": {
|
|
13901
13901
|
"trailing": [
|
|
13902
13902
|
{
|
|
13903
|
-
"label": "
|
|
13903
|
+
"label": "$item.collectionSaveLabel",
|
|
13904
13904
|
"icon": "bookmark",
|
|
13905
13905
|
"action": {
|
|
13906
13906
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -14165,7 +14165,7 @@ const SCREENS = runtimeSetting({
|
|
|
14165
14165
|
},
|
|
14166
14166
|
{
|
|
14167
14167
|
"id": "save",
|
|
14168
|
-
"label": "
|
|
14168
|
+
"label": "$item.collectionSaveLabel",
|
|
14169
14169
|
"icon": "bookmark",
|
|
14170
14170
|
"action": {
|
|
14171
14171
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -14789,7 +14789,7 @@ const SCREENS = runtimeSetting({
|
|
|
14789
14789
|
},
|
|
14790
14790
|
{
|
|
14791
14791
|
"id": "save",
|
|
14792
|
-
"label": "
|
|
14792
|
+
"label": "$item.collectionSaveLabel",
|
|
14793
14793
|
"icon": "bookmark",
|
|
14794
14794
|
"action": {
|
|
14795
14795
|
"actionId": "tier5.himiBrandedGroups.collectionSave",
|
|
@@ -168,6 +168,8 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
|
|
|
168
168
|
items: visible.map(row => ({ ...row, collectionId: collectionId(row), collectionTitle: collectionTitle(row, config),
|
|
169
169
|
collectionMeta: [...new Set((config.metadata ?? ['total', 'statusLabel', 'price', 'priceLine', 'dateLine', 'meta']).filter(key => key !== config.subtitle).map(key => scalar(row[key])).filter(Boolean))].join(' · '),
|
|
170
170
|
collectionSelectLabel: prefs.selectedIds.includes(collectionId(row)) ? 'Selected' : 'Select',
|
|
171
|
+
// The save toggle names the action it WILL take (the service localizes it).
|
|
172
|
+
collectionSaveLabel: prefs.savedIds.includes(collectionId(row)) ? 'Remove from saved' : 'Save',
|
|
171
173
|
collectionSubtitle: scalar(row[config.subtitle ?? '']) || scalar(row.subtitle) || scalar(row.description) || scalar(row.meta),
|
|
172
174
|
collectionImage: scalar(row[config.image ?? '']), collectionNoImage: !scalar(row[config.image ?? '']), collectionHasImage: !!scalar(row[config.image ?? '']),
|
|
173
175
|
collectionSaved: prefs.savedIds.includes(collectionId(row)), collectionNotSaved: !prefs.savedIds.includes(collectionId(row)), collectionSelected: prefs.selectedIds.includes(collectionId(row)),
|