@wix/himalaya-cli 0.1032.0 → 0.1034.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.
Files changed (128) hide show
  1. package/dist/cli.mjs +0 -2
  2. package/guides/getting-started.md +6 -2
  3. package/guides/wix-site-to-himi-app.md +9 -1
  4. package/package.json +2 -2
  5. package/templates/beauty-salon/dev/index.ts +398 -1
  6. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +4 -1
  7. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +36 -5
  8. package/templates/beauty-salon/tier5-src/_shared/empty-flags.ts +33 -0
  9. package/templates/beauty-salon/tier5-src/himi-store-saved/index.ts +3 -2
  10. package/templates/beauty-salon/tier5-src/wix-spaces-collections/index.ts +3 -2
  11. package/templates/beauty-salon/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  12. package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  13. package/templates/beauty-salon-owner/dev/index.ts +1216 -0
  14. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +4 -1
  15. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +36 -5
  16. package/templates/branded-lite/dev/index.ts +2239 -343
  17. package/templates/branded-lite/tier5-src/_shared/branded-load-state.ts +106 -0
  18. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +4 -1
  19. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +36 -5
  20. package/templates/branded-lite/tier5-src/_shared/empty-flags.ts +33 -0
  21. package/templates/branded-lite/tier5-src/himi-branded-lite-badges/index.ts +20 -16
  22. package/templates/branded-lite/tier5-src/himi-branded-lite-coupons/index.ts +20 -20
  23. package/templates/branded-lite/tier5-src/himi-branded-lite-donations/index.ts +20 -16
  24. package/templates/branded-lite/tier5-src/himi-branded-lite-forms/index.ts +23 -31
  25. package/templates/branded-lite/tier5-src/himi-branded-lite-galleries/index.ts +19 -14
  26. package/templates/branded-lite/tier5-src/himi-branded-lite-giftcards/index.ts +20 -16
  27. package/templates/branded-lite/tier5-src/himi-branded-lite-inbox/index.ts +20 -15
  28. package/templates/branded-lite/tier5-src/himi-branded-lite-invoices/index.ts +20 -16
  29. package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +20 -17
  30. package/templates/branded-lite/tier5-src/himi-branded-lite-notifications/index.ts +20 -18
  31. package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +23 -19
  32. package/templates/branded-lite/tier5-src/himi-branded-lite-podcasts/index.ts +1 -1
  33. package/templates/branded-lite/tier5-src/himi-branded-lite-reservations/index.ts +24 -20
  34. package/templates/branded-lite/tier5-src/himi-branded-lite-reviews/index.ts +20 -17
  35. package/templates/branded-lite/tier5-src/himi-branded-lite-videos/index.ts +19 -15
  36. package/templates/branded-lite/tier5-src/himi-store-saved/index.ts +3 -2
  37. package/templates/branded-lite/tier5-src/wix-spaces-collections/index.ts +3 -2
  38. package/templates/branded-lite/tier5-src/wix-spaces-forum/index.ts +3 -2
  39. package/templates/branded-lite/tier5-src/wix-spaces-members/index.ts +19 -6
  40. package/templates/branded-lite/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  41. package/templates/branded-lite/tier5-src/wix-spaces-new-message/index.ts +3 -2
  42. package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  43. package/templates/community-club/dev/index.ts +283 -2
  44. package/templates/community-club/tier5-src/_shared/collection-contract.ts +4 -1
  45. package/templates/community-club/tier5-src/_shared/collection-service.ts +36 -5
  46. package/templates/community-club/tier5-src/_shared/empty-flags.ts +33 -0
  47. package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  48. package/templates/community-club-owner/dev/index.ts +1216 -0
  49. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +4 -1
  50. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +36 -5
  51. package/templates/events-meetups/dev/index.ts +197 -1
  52. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +4 -1
  53. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +36 -5
  54. package/templates/events-meetups/tier5-src/_shared/empty-flags.ts +33 -0
  55. package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  56. package/templates/events-meetups-owner/dev/index.ts +1088 -0
  57. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +4 -1
  58. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +36 -5
  59. package/templates/gym-fitness/dev/index.ts +462 -1
  60. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +4 -1
  61. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +36 -5
  62. package/templates/gym-fitness/tier5-src/_shared/empty-flags.ts +33 -0
  63. package/templates/gym-fitness/tier5-src/himi-store-saved/index.ts +3 -2
  64. package/templates/gym-fitness/tier5-src/wix-spaces-collections/index.ts +3 -2
  65. package/templates/gym-fitness/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  66. package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  67. package/templates/gym-fitness-owner/dev/index.ts +1280 -0
  68. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +4 -1
  69. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +36 -5
  70. package/templates/nonprofit-cause/dev/index.ts +264 -1
  71. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +4 -1
  72. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +36 -5
  73. package/templates/nonprofit-cause/tier5-src/_shared/empty-flags.ts +33 -0
  74. package/templates/nonprofit-cause/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  75. package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  76. package/templates/nonprofit-cause-owner/dev/index.ts +1152 -0
  77. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +4 -1
  78. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +36 -5
  79. package/templates/owner-lite/dev/index.ts +1280 -0
  80. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +4 -1
  81. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +36 -5
  82. package/templates/podcast-creator/dev/index.ts +286 -23
  83. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +4 -1
  84. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +36 -5
  85. package/templates/podcast-creator/tier5-src/_shared/empty-flags.ts +33 -0
  86. package/templates/podcast-creator/tier5-src/himi-branded-lite-podcasts/index.ts +1 -1
  87. package/templates/podcast-creator/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  88. package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  89. package/templates/podcast-creator-owner/dev/index.ts +1216 -0
  90. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +4 -1
  91. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +36 -5
  92. package/templates/portfolio-creative/dev/index.ts +353 -23
  93. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +4 -1
  94. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +36 -5
  95. package/templates/portfolio-creative/tier5-src/_shared/empty-flags.ts +33 -0
  96. package/templates/portfolio-creative/tier5-src/himi-store-saved/index.ts +3 -2
  97. package/templates/portfolio-creative/tier5-src/wix-spaces-collections/index.ts +3 -2
  98. package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  99. package/templates/portfolio-creative-owner/dev/index.ts +1216 -0
  100. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +4 -1
  101. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +36 -5
  102. package/templates/restaurant/dev/index.ts +197 -1
  103. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +4 -1
  104. package/templates/restaurant/tier5-src/_shared/collection-service.ts +36 -5
  105. package/templates/restaurant/tier5-src/_shared/empty-flags.ts +33 -0
  106. package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  107. package/templates/restaurant-owner/dev/index.ts +1280 -0
  108. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +4 -1
  109. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +36 -5
  110. package/templates/retail-store/dev/index.ts +203 -1
  111. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +4 -1
  112. package/templates/retail-store/tier5-src/_shared/collection-service.ts +36 -5
  113. package/templates/retail-store/tier5-src/_shared/empty-flags.ts +33 -0
  114. package/templates/retail-store/tier5-src/himi-store-saved/index.ts +3 -2
  115. package/templates/retail-store/tier5-src/wix-spaces-collections/index.ts +3 -2
  116. package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  117. package/templates/retail-store-owner/dev/index.ts +1152 -0
  118. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +4 -1
  119. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +36 -5
  120. package/templates/yoga-studio/dev/index.ts +392 -1
  121. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +4 -1
  122. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +36 -5
  123. package/templates/yoga-studio/tier5-src/_shared/empty-flags.ts +33 -0
  124. package/templates/yoga-studio/tier5-src/wix-spaces-my-subscriptions/index.ts +11 -2
  125. package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +3 -2
  126. package/templates/yoga-studio-owner/dev/index.ts +1152 -0
  127. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +4 -1
  128. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +36 -5
@@ -1,15 +1,14 @@
1
- // himi-branded-lite-giftcards — branded-lite "Gift Cards" worker with a SAMPLE-DATA
2
- // fallback (same pattern as the other branded-lite verticals). Reads the site's real
3
- // Wix Gift Cards (queryGiftCards); when the site has no Gift Cards app / none are
4
- // readable (the query errors — e.g. 403/404/428 — or returns empty), it substitutes
5
- // representative SAMPLE gift cards and flags `isMock`, so the screen shows a clearly-
6
- // labelled "Sample content" banner. Real gift cards always win the moment any exist.
1
+ // himi-branded-lite-giftcards — reads the visitor's real Wix Gift Cards (queryGiftCards).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
7
6
  import { defineService } from "../_shared/collection-service.js";
7
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
8
8
  import { queryGiftCards, type WixGiftCard } from "@himalaya/wix-gift-cards";
9
9
 
10
10
  interface Row { id: string; title: string; subtitle: string }
11
- interface S {
12
- status: "loading" | "ready";
11
+ interface S extends LoadFlags {
13
12
  cards: Row[]; emptyText: string;
14
13
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
14
  * shows only when the site really has none, and the unavailable one only when the
@@ -44,21 +43,26 @@ const MOCK: Row[] = [
44
43
  { id: "sample-3", title: "Gift card ····3312", subtitle: "Balance $0.00" },
45
44
  ];
46
45
 
47
- function mockState(s: S, reason: "empty" | "unavailable"): S {
48
- return { ...s, status: "ready", cards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
49
- }
46
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
47
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
48
+ const COPY: LoadCopy<Row> = {
49
+ prefix: "giftCards",
50
+ empty: "You don't have any gift cards yet.",
51
+ error: "Your gift cards couldn't be loaded.",
52
+ signIn: "Sign in to see your gift cards.",
53
+ sample: () => MOCK.map((r) => ({ ...r })),
54
+ };
50
55
 
51
56
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedGiftCards", {
52
- state: { status: "loading", cards: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
57
+ state: { ...LOADING, cards: [], emptyText: "" },
53
58
  actions: {
54
59
  load: async (s) => {
55
60
  try {
56
61
  const r = await queryGiftCards({ query: { cursorPaging: { limit: 20 } } });
57
62
  const cards = (r.giftCards ?? []).map(norm);
58
- if (cards.length) return { ...s, status: "ready", cards, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
59
- return mockState(s, "empty");
60
- } catch {
61
- return mockState(s, "unavailable");
63
+ return { ...s, ...loaded("cards", cards, COPY) };
64
+ } catch (err) {
65
+ return { ...s, ...loadFailed("cards", err, COPY) };
62
66
  }
63
67
  },
64
68
  },
@@ -1,17 +1,17 @@
1
- // himi-branded-lite-inbox — branded-lite "Inbox" worker (Wix Inbox / member↔business
2
- // chat). Reads the site's real inbox conversations (listConversations); when the site
3
- // has no Inbox app / no conversations (the query errors — e.g. 403/404/428 — or returns
4
- // empty), it substitutes representative SAMPLE conversations and flags `isMock`, so the
5
- // screen shows a clearly-labelled "Sample content" banner. Real conversations always win.
1
+ // himi-branded-lite-inbox — reads the visitor's real Wix Inbox conversations (listConversations).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
6
6
  //
7
7
  // Inbox exposes no list-messages endpoint (only listConversations + single getMessage),
8
8
  // so this is a conversation LIST — same read-view scope as the Galleries/Forms verticals.
9
9
  import { defineService } from "../_shared/collection-service.js";
10
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
10
11
  import { listConversations, type WixConversation } from "@himalaya/wix-inbox";
11
12
 
12
13
  interface Row { id: string; title: string; subtitle: string }
13
- interface S {
14
- status: "loading" | "ready";
14
+ interface S extends LoadFlags {
15
15
  conversations: Row[]; emptyText: string;
16
16
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
17
  * shows only when the site really has none, and the unavailable one only when the
@@ -43,21 +43,26 @@ const MOCK: Row[] = [
43
43
  { id: "sample-team", title: "The Team", subtitle: "Welcome 👋 Thanks for joining us!" },
44
44
  ];
45
45
 
46
- function mockState(s: S, reason: "empty" | "unavailable"): S {
47
- return { ...s, status: "ready", conversations: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
48
- }
46
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
47
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
48
+ const COPY: LoadCopy<Row> = {
49
+ prefix: "inbox",
50
+ empty: "No messages yet.",
51
+ error: "Your messages couldn't be loaded.",
52
+ signIn: "Sign in to see your messages.",
53
+ sample: () => MOCK.map((r) => ({ ...r })),
54
+ };
49
55
 
50
56
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInbox", {
51
- state: { status: "loading", conversations: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
57
+ state: { ...LOADING, conversations: [], emptyText: "" },
52
58
  actions: {
53
59
  load: async (s) => {
54
60
  try {
55
61
  const r = await listConversations({ paging: { limit: 50 } });
56
62
  const conversations = (r.conversations ?? []).map(norm);
57
- if (conversations.length) return { ...s, status: "ready", conversations, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
58
- return mockState(s, "empty");
59
- } catch {
60
- return mockState(s, "unavailable");
63
+ return { ...s, ...loaded("conversations", conversations, COPY) };
64
+ } catch (err) {
65
+ return { ...s, ...loadFailed("conversations", err, COPY) };
61
66
  }
62
67
  },
63
68
  },
@@ -1,15 +1,14 @@
1
- // himi-branded-lite-invoices — branded-lite "My Invoices" worker with a SAMPLE-DATA
2
- // fallback (same pattern as the other branded-lite verticals). Reads the member's
3
- // real Wix Invoices (queryInvoices); when the site has no Invoices app / the member
4
- // has none (the query errors — e.g. 404/428 — or returns empty), it substitutes
5
- // representative SAMPLE invoices and flags `isMock`, so the screen shows a clearly-
6
- // labelled "Sample content" banner. Real invoices always win the moment any exist.
1
+ // himi-branded-lite-invoices — reads the member's real Wix Invoices (queryInvoices).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
7
6
  import { defineService } from "../_shared/collection-service.js";
7
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
8
8
  import { queryInvoices, type WixInvoice } from "@himalaya/wix-invoices";
9
9
 
10
10
  interface Row { id: string; title: string; subtitle: string }
11
- interface S {
12
- status: "loading" | "ready";
11
+ interface S extends LoadFlags {
13
12
  invoices: Row[]; emptyText: string;
14
13
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
14
  * shows only when the site really has none, and the unavailable one only when the
@@ -45,21 +44,26 @@ const MOCK: Row[] = [
45
44
  { id: "sample-1042", title: "Invoice #1042", subtitle: "Overdue · $45.00" },
46
45
  ];
47
46
 
48
- function mockState(s: S, reason: "empty" | "unavailable"): S {
49
- return { ...s, status: "ready", invoices: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
50
- }
47
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
48
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
49
+ const COPY: LoadCopy<Row> = {
50
+ prefix: "invoices",
51
+ empty: "You don't have any invoices yet.",
52
+ error: "Your invoices couldn't be loaded.",
53
+ signIn: "Sign in to see your invoices.",
54
+ sample: () => MOCK.map((r) => ({ ...r })),
55
+ };
51
56
 
52
57
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInvoices", {
53
- state: { status: "loading", invoices: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
58
+ state: { ...LOADING, invoices: [], emptyText: "" },
54
59
  actions: {
55
60
  load: async (s) => {
56
61
  try {
57
62
  const r = await queryInvoices({ query: { paging: { limit: 20 } } });
58
63
  const invoices = (r.invoices ?? []).filter((v) => v.archived !== true).map(norm);
59
- if (invoices.length) return { ...s, status: "ready", invoices, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
60
- return mockState(s, "empty");
61
- } catch {
62
- return mockState(s, "unavailable");
64
+ return { ...s, ...loaded("invoices", invoices, COPY) };
65
+ } catch (err) {
66
+ return { ...s, ...loadFailed("invoices", err, COPY) };
63
67
  }
64
68
  },
65
69
  },
@@ -1,16 +1,14 @@
1
- // himi-branded-lite-loyalty — branded-lite "Rewards" worker with a SAMPLE-DATA
2
- // fallback (same pattern as himi-branded-lite-coupons/-notifications). Reads the
3
- // real Wix Loyalty program + rewards catalog (getLoyaltyProgram + listLoyaltyRewards);
4
- // when the site has no Loyalty program / no rewards (the query errors — e.g.
5
- // 404/428 — or returns empty), it substitutes representative SAMPLE rewards and
6
- // flags `isMock`, so the screen shows a clearly-labelled "Sample content" banner.
7
- // Real rewards always win the moment the program is active with rewards.
1
+ // himi-branded-lite-loyalty — reads the real Wix Loyalty program + rewards (getLoyaltyProgram + listLoyaltyRewards).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
8
6
  import { defineService } from "../_shared/collection-service.js";
7
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
9
8
  import { getLoyaltyProgram, listLoyaltyRewards, type WixLoyaltyReward } from "@himalaya/wix-loyalty";
10
9
 
11
10
  interface Row { id: string; name: string; subtitle: string }
12
- interface S {
13
- status: "loading" | "ready";
11
+ interface S extends LoadFlags {
14
12
  programName: string; unit: string;
15
13
  rewards: Row[]; emptyText: string;
16
14
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
@@ -39,12 +37,18 @@ const MOCK: Row[] = [
39
37
  { id: "sample-gift", name: "Free welcome gift", subtitle: "1,000 points" },
40
38
  ];
41
39
 
42
- function mockState(s: S, reason: "empty" | "unavailable"): S {
43
- return { ...s, status: "ready", programName: "Rewards", unit: "points", rewards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
44
- }
40
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
41
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
42
+ const COPY: LoadCopy<Row> = {
43
+ prefix: "loyalty",
44
+ empty: "No rewards to redeem yet.",
45
+ error: "The rewards program couldn't be loaded.",
46
+ signIn: "Sign in to see your points and rewards.",
47
+ sample: () => MOCK.map((r) => ({ ...r })),
48
+ };
45
49
 
46
50
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedLoyalty", {
47
- state: { status: "loading", programName: "Rewards", unit: "points", rewards: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
51
+ state: { ...LOADING, programName: "Rewards", unit: "points", rewards: [], emptyText: "" },
48
52
  actions: {
49
53
  load: async (s) => {
50
54
  try {
@@ -54,10 +58,9 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedLoyalty
54
58
  const programName = (p?.name || "Rewards").trim() || "Rewards";
55
59
  const r = await listLoyaltyRewards();
56
60
  const rewards = (r.rewards ?? []).filter((x) => x.active !== false).map((x) => norm(x, unit));
57
- if (rewards.length) return { ...s, status: "ready", programName, unit, rewards, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
58
- return mockState(s, "empty");
59
- } catch {
60
- return mockState(s, "unavailable");
61
+ return { ...s, programName, unit, ...loaded("rewards", rewards, COPY) };
62
+ } catch (err) {
63
+ return { ...s, ...loadFailed("rewards", err, COPY) };
61
64
  }
62
65
  },
63
66
  },
@@ -1,13 +1,12 @@
1
- // himi-branded-lite-notifications — branded-lite member-inbox worker with a
2
- // SAMPLE-DATA fallback (same pattern as himi-branded-lite-coupons). Reads the
3
- // signed-in member's real notifications via the Members Feed Viewer service
4
- // (queryNotifications); when the member has no notifications yet (empty) or the
5
- // query errors (e.g. app not installed / 428), it substitutes representative
6
- // SAMPLE notifications and flags `isMock`, so the screen shows a clearly-labelled
7
- // "Sample content" banner. Real notifications always win the moment any exist.
1
+ // himi-branded-lite-notifications — reads the member's notifications via Members Feed Viewer (queryNotifications).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
8
6
  // mark-one-read / mark-all-read stay wired: they update state optimistically and
9
7
  // best-effort the real write (a no-op on sample rows).
10
8
  import { defineService } from "../_shared/collection-service.js";
9
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
11
10
  import { queryNotifications, markRead, markAllRead, type MfvNotification } from "@himalaya/wix-members-feed-viewer";
12
11
 
13
12
  interface Row {
@@ -22,8 +21,7 @@ interface Row {
22
21
  initial: string;
23
22
  dest: string;
24
23
  }
25
- interface S {
26
- status: "loading" | "ready";
24
+ interface S extends LoadFlags {
27
25
  items: Row[]; emptyText: string;
28
26
  isMock: boolean; notMock: boolean;
29
27
  /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
@@ -92,25 +90,29 @@ const MOCK: Row[] = [
92
90
  { id: "sample-offer", name: "New offer just for you", subtitle: "15% off your next order · 3d ago", unread: false, unreadDot: "", photo: "", initial: "%", dest: "" },
93
91
  ];
94
92
 
95
- function mockState(s: S, reason: "empty" | "unavailable"): S {
96
- return { ...s, status: "ready", items: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false,
97
- notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable" };
98
- }
93
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
94
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
95
+ const COPY: LoadCopy<Row> = {
96
+ prefix: "notifications",
97
+ empty: "You're all caught up — no notifications yet.",
98
+ error: "Notifications couldn't be loaded.",
99
+ signIn: "Sign in to see your notifications.",
100
+ sample: () => MOCK.map((r) => ({ ...r })),
101
+ };
99
102
 
100
103
  const svc = defineService<S, {
101
104
  load: (s: S) => Promise<S>;
102
105
  markOneRead: (s: S, id: string) => Promise<S>;
103
106
  markAll: (s: S) => Promise<S>;
104
107
  }>("himiBrandedNotifications", {
105
- state: { status: "loading", items: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true },
108
+ state: { ...LOADING, items: [], emptyText: "" },
106
109
  actions: {
107
110
  load: async (s) => {
108
111
  try {
109
112
  const items = (await queryNotifications(25)).map(norm);
110
- if (items.length) return { ...s, status: "ready", items, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true };
111
- return mockState(s, "empty");
112
- } catch {
113
- return mockState(s, "unavailable");
113
+ return { ...s, ...loaded("items", items, COPY) };
114
+ } catch (err) {
115
+ return { ...s, ...loadFailed("items", err, COPY) };
114
116
  }
115
117
  },
116
118
  // Mark one read; optimistically clear its dot. Real write is best-effort and
@@ -1,13 +1,13 @@
1
- // himi-branded-lite-notifsettings — branded-lite "Notification Settings" worker
2
- // with a SAMPLE-DATA fallback (same pattern as the other branded-lite verticals).
3
- // Reads the site's real notification topics (getNotificationTopics) + the member's
4
- // per-topic delivery preferences (getMyNotificationPreferences); when the site has
5
- // no topics configured (the query errors — e.g. 404/428 — or returns empty), it
6
- // substitutes representative SAMPLE topics and flags `isMock`, so the screen shows
7
- // a clearly-labelled "Sample content" banner. Real topics always win the moment
1
+ // himi-branded-lite-notifsettings — reads the site's notification topics + the member's preferences.
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
8
6
  // any exist. INTERACTIVE: tapping a row toggles that topic On/Off — optimistic +
9
7
  // best-effort upsertMyNotificationPreferences (a no-op on sample rows).
10
8
  import { defineService } from "@himalaya/state";
9
+ import { withEmptyFlags } from "../_shared/empty-flags.js";
10
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
11
11
  import {
12
12
  getNotificationTopics,
13
13
  getMyNotificationPreferences,
@@ -30,8 +30,7 @@ const CHANNEL = "MOBILE";
30
30
  interface Row { id: string; name: string; subtitle: string; note: string; on: boolean; locked: boolean;
31
31
  /** Inverse of `locked` (hiddenWhen hides on TRUE) and the plain On/Off a locked row shows. */
32
32
  notLocked: boolean; stateLabel: string }
33
- interface S {
34
- status: "loading" | "ready";
33
+ interface S extends LoadFlags {
35
34
  topics: Row[]; emptyText: string;
36
35
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
37
36
  * shows only when the site really has none, and the unavailable one only when the
@@ -82,15 +81,21 @@ const MOCK: Row[] = [
82
81
  row("sample-community", "Community activity", true),
83
82
  ];
84
83
 
85
- function mockState(s: S, reason: "empty" | "unavailable"): S {
86
- return { ...s, status: "ready", topics: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
87
- }
84
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
85
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
86
+ const COPY: LoadCopy<Row> = {
87
+ prefix: "notifsettings",
88
+ empty: "No notification topics to manage yet.",
89
+ error: "Notification settings couldn't be loaded.",
90
+ signIn: "Sign in to manage your notifications.",
91
+ sample: () => MOCK.map((r) => ({ ...r })),
92
+ };
88
93
 
89
94
  const svc = defineService<S, {
90
95
  load: (s: S) => Promise<S>;
91
96
  toggle: (s: S, p?: { id?: string; value?: unknown }) => Promise<S>;
92
- }>("himiBrandedNotifSettings", {
93
- state: { status: "loading", topics: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
97
+ }>("himiBrandedNotifSettings", withEmptyFlags({
98
+ state: { ...LOADING, topics: [], emptyText: "" },
94
99
  actions: {
95
100
  load: async (s) => {
96
101
  try {
@@ -100,10 +105,9 @@ const svc = defineService<S, {
100
105
  ]);
101
106
  const prefs = readPreferences(prefsRes);
102
107
  const topics = readTopics(topicsRes).map((t) => norm(t, prefs));
103
- if (topics.length) return { ...s, status: "ready", topics, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true };
104
- return mockState(s, "empty");
105
- } catch {
106
- return mockState(s, "unavailable");
108
+ return { ...s, ...loaded("topics", topics, COPY) };
109
+ } catch (err) {
110
+ return { ...s, ...loadFailed("topics", err, COPY) };
107
111
  }
108
112
  },
109
113
  // Flip one topic's On/Off. Optimistic (updates the row immediately); the real
@@ -132,6 +136,6 @@ const svc = defineService<S, {
132
136
  return { ...s, topics };
133
137
  },
134
138
  },
135
- });
139
+ }, { topics: "noTopics" }));
136
140
  svc.load();
137
141
  export { svc };
@@ -154,7 +154,7 @@ const svc = defineService<S, {
154
154
  load: async (s, arg) => {
155
155
  const configured = typeof (arg as { feedUrl?: unknown } | undefined)?.feedUrl === "string" ? String((arg as { feedUrl: string }).feedUrl).trim() : "";
156
156
  if (!configured) {
157
- return derive({ ...s, status: "ready", episodes: [], emptyText: "Set the podcast feed URL for this app before building or deploying.", isMock: false, notMock: true, mockBanner: "" });
157
+ return derive({ ...s, status: "ready", episodes: [], emptyText: "No episodes yet — check back soon.", isMock: false, notMock: true, mockBanner: "" });
158
158
  }
159
159
  try {
160
160
  const api = host().wixApi;
@@ -1,12 +1,10 @@
1
- // himi-branded-lite-reservations — branded-lite "My Reservations" worker with a
2
- // SAMPLE-DATA fallback (same pattern as the other branded-lite verticals). Reads
3
- // the member's real Wix Table Reservations (queryReservations); when the site has
4
- // no Table Reservations app / the member has none (the query errors — e.g. 404/428
5
- // — or returns empty), it substitutes representative SAMPLE reservations and flags
6
- // `isMock`, so the screen shows a clearly-labelled "Sample content" banner. Real
7
- // reservations always win the moment any exist. INTERACTIVE: tapping an upcoming
8
- // reservation cancels it — optimistic + best-effort cancelReservation (no-op on
9
- // sample rows).
1
+ // himi-branded-lite-reservations — branded-lite "My Reservations" worker. Reads
2
+ // the member's real Wix Table Reservations (queryReservations). A failed query is an
3
+ // error with Retry (or Sign in when Wix refuses the visitor), an empty one is a real
4
+ // empty state — never invented bookings labelled "tap to cancel". Sample rows
5
+ // appear only in the host's explicit sample preview (branded-load-state.ts).
6
+ // INTERACTIVE: tapping an upcoming reservation cancels it — optimistic + best-effort
7
+ // cancelReservation (no-op on sample rows).
10
8
  //
11
9
  // RFC 0052 consumer: the list is a `himalaya.Agenda` — chronological, grouped
12
10
  // by LOCAL day under sticky headers. A reservation list is a diary, and the
@@ -14,12 +12,12 @@
14
12
  // no row knew what day the row above it was on. `Agenda` owns the day headers
15
13
  // and the time column, so a row carries only what is actually per-reservation.
16
14
  import { defineService } from "@himalaya/state";
15
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
17
16
  import { queryReservations, cancelReservation, type WixReservation } from "@himalaya/wix-table-reservations";
18
17
  import { type CalendarEvent } from "@himalaya/time";
19
18
 
20
19
  interface Row { id: string; revision: string; title: string; subtitle: string; cancellable: boolean; canceled: boolean; start: string; raw: string }
21
- interface S {
22
- status: "loading" | "ready";
20
+ interface S extends LoadFlags {
23
21
  reservations: Row[];
24
22
  /** The Agenda wire shape, derived from `reservations` on every transition. */
25
23
  events: CalendarEvent[];
@@ -122,8 +120,17 @@ function mockRows(): Row[] {
122
120
  ];
123
121
  }
124
122
 
125
- function mockState(s: S, reason: "empty" | "unavailable"): S {
126
- return withEvents({ ...s, status: "ready", emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"}, mockRows());
123
+ const COPY: LoadCopy<Row> = {
124
+ prefix: "reservations",
125
+ empty: "You don't have any reservations yet.",
126
+ error: "Your reservations couldn't be loaded.",
127
+ signIn: "Sign in to see your reservations.",
128
+ sample: mockRows,
129
+ };
130
+
131
+ /** Every settled load goes through withEvents so the Agenda never drifts from the rows. */
132
+ function settle(s: S, next: Pick<S, "reservations" | "emptyText"> & LoadFlags): S {
133
+ return withEvents({ ...s, ...next }, next.reservations);
127
134
  }
128
135
 
129
136
  /** The reservation id out of a tap. `Agenda.onSelectEvent` dispatches
@@ -141,7 +148,7 @@ const svc = defineService<S, {
141
148
  load: (s: S) => Promise<S>;
142
149
  cancel: (s: S, p?: unknown) => Promise<S>;
143
150
  }>("himiBrandedReservations", {
144
- state: { status: "loading", reservations: [], events: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
151
+ state: { ...LOADING, reservations: [], events: [], emptyText: "" },
145
152
  actions: {
146
153
  load: async (s) => {
147
154
  try {
@@ -149,12 +156,9 @@ const svc = defineService<S, {
149
156
  // to hand it chronological order rather than newest-first.
150
157
  const r = await queryReservations({ cursorPaging: { limit: 20 }, sort: [{ fieldName: "details.startDate", order: "ASC" }] });
151
158
  const reservations = (r.reservations ?? []).map(norm);
152
- if (reservations.length) {
153
- return withEvents({ ...s, status: "ready", emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true}, reservations);
154
- }
155
- return mockState(s, "empty");
156
- } catch {
157
- return mockState(s, "unavailable");
159
+ return settle(s, loaded("reservations", reservations, COPY));
160
+ } catch (err) {
161
+ return settle(s, loadFailed("reservations", err, COPY));
158
162
  }
159
163
  },
160
164
  // Cancel one upcoming reservation. Optimistic (marks it Canceled immediately);
@@ -1,17 +1,15 @@
1
- // himi-branded-lite-reviews — branded-lite "Reviews" worker with a SAMPLE-DATA
2
- // fallback (same pattern as himi-branded-lite-coupons/-notifications/-loyalty).
3
- // Reads the real Wix Reviews (queryReviews, public/approved only); when the site
4
- // has no Reviews app / no reviews (the query errors — e.g. 404/428 — or returns
5
- // empty), it substitutes representative SAMPLE reviews and flags `isMock`, so the
6
- // screen shows a clearly-labelled "Sample content" banner. Real reviews always
7
- // win the moment any approved review exists.
1
+ // himi-branded-lite-reviews — reads the site's approved Wix Reviews (queryReviews).
2
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
3
+ // visitor) and an empty one is a real empty state — never sample rows presented as
4
+ // the customer's own. Samples appear only in the host's explicit sample preview
5
+ // (_shared/branded-load-state.ts).
8
6
  import { defineService } from "../_shared/collection-service.js";
7
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
9
8
  import { queryReviews, type WixReview } from "@himalaya/wix-reviews";
10
9
 
11
10
  /** `rating` (0–5) feeds the row's Rating control (spoken "N out of 5"); `stars` stays for older descriptors. */
12
11
  interface Row { id: string; author: string; stars: string; rating: number; title: string; body: string }
13
- interface S {
14
- status: "loading" | "ready";
12
+ interface S extends LoadFlags {
15
13
  reviews: Row[]; emptyText: string;
16
14
  isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
15
  * shows only when the site really has none, and the unavailable one only when the
@@ -48,22 +46,27 @@ const MOCK: Row[] = [
48
46
  { id: "sample-4", author: "Amit L.", stars: "★★★★☆", rating: 4, title: "Solid", body: "Good quality and quick service. Would order again." },
49
47
  ];
50
48
 
51
- function mockState(s: S, reason: "empty" | "unavailable"): S {
52
- return { ...s, status: "ready", reviews: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
53
- }
49
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
50
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
51
+ const COPY: LoadCopy<Row> = {
52
+ prefix: "reviews",
53
+ empty: "No reviews yet.",
54
+ error: "Reviews couldn't be loaded.",
55
+ signIn: "Sign in to see reviews.",
56
+ sample: () => MOCK.map((r) => ({ ...r })),
57
+ };
54
58
 
55
59
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedReviews", {
56
- state: { status: "loading", reviews: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
60
+ state: { ...LOADING, reviews: [], emptyText: "" },
57
61
  actions: {
58
62
  load: async (s) => {
59
63
  try {
60
64
  const r = await queryReviews({ cursorPaging: { limit: 20 } });
61
65
  const raw = (r.reviews ?? r.results ?? []) as WixReview[];
62
66
  const reviews = raw.map(norm).filter((x) => x.title || x.body);
63
- if (reviews.length) return { ...s, status: "ready", reviews, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
64
- return mockState(s, "empty");
65
- } catch {
66
- return mockState(s, "unavailable");
67
+ return { ...s, ...loaded("reviews", reviews, COPY) };
68
+ } catch (err) {
69
+ return { ...s, ...loadFailed("reviews", err, COPY) };
67
70
  }
68
71
  },
69
72
  },