@wix/himalaya-cli 0.1378.0 → 0.1379.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 (176) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/app-info/src/index.d.ts +4 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +113 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/app-info.mjs +2 -1
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +2 -1
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +2 -1
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +77 -0
  7. package/dist/cli.mjs +23 -23
  8. package/dist/preview-assets.generated.bin +0 -0
  9. package/guides/importable-sdks.md +1 -1
  10. package/guides/sdk-reference.json +128 -2
  11. package/guides/wix-api-usage.md +2 -0
  12. package/package.json +2 -2
  13. package/templates/beauty-salon/config.ts +23 -0
  14. package/templates/beauty-salon/dev/index.ts +2611 -1
  15. package/templates/beauty-salon/himalaya.content.json +9 -0
  16. package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  17. package/templates/beauty-salon/tier5-src/_shared/member-access.ts +14 -0
  18. package/templates/beauty-salon/tier5-src/_shared/member-account-routes.ts +43 -0
  19. package/templates/beauty-salon/tier5-src/_shared/member-address.ts +129 -0
  20. package/templates/beauty-salon/tier5-src/_shared/saved-card.ts +20 -0
  21. package/templates/beauty-salon/tier5-src/himi-branded-lite-address/index.ts +223 -0
  22. package/templates/beauty-salon/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  23. package/templates/beauty-salon/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  24. package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +17 -0
  25. package/templates/beauty-salon-owner/dev/index.ts +5 -27
  26. package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +99 -40
  27. package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  28. package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  29. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  30. package/templates/branded-lite/config.ts +23 -0
  31. package/templates/branded-lite/dev/index.ts +2614 -2
  32. package/templates/branded-lite/himalaya.content.json +9 -0
  33. package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  34. package/templates/branded-lite/tier5-src/_shared/member-access.ts +14 -0
  35. package/templates/branded-lite/tier5-src/_shared/member-account-routes.ts +43 -0
  36. package/templates/branded-lite/tier5-src/_shared/member-address.ts +129 -0
  37. package/templates/branded-lite/tier5-src/_shared/saved-card.ts +20 -0
  38. package/templates/branded-lite/tier5-src/himi-branded-lite-address/index.ts +223 -0
  39. package/templates/branded-lite/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  40. package/templates/branded-lite/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  41. package/templates/branded-lite/tier5-src/himi-store-account/index.ts +17 -0
  42. package/templates/community-club/dev/index.ts +3 -1
  43. package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  44. package/templates/community-club/tier5-src/_shared/member-access.ts +14 -0
  45. package/templates/community-club/tier5-src/_shared/member-account-routes.ts +43 -0
  46. package/templates/community-club/tier5-src/himi-store-account/index.ts +17 -0
  47. package/templates/community-club-owner/dev/index.ts +5 -27
  48. package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +99 -40
  49. package/templates/community-club-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  50. package/templates/community-club-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  51. package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  52. package/templates/events-meetups/dev/index.ts +3 -1
  53. package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  54. package/templates/events-meetups/tier5-src/_shared/member-access.ts +14 -0
  55. package/templates/events-meetups/tier5-src/_shared/member-account-routes.ts +43 -0
  56. package/templates/events-meetups/tier5-src/himi-store-account/index.ts +17 -0
  57. package/templates/events-meetups-owner/dev/index.ts +5 -27
  58. package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +99 -40
  59. package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  60. package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  61. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  62. package/templates/gym-fitness/config.ts +23 -0
  63. package/templates/gym-fitness/dev/index.ts +2611 -1
  64. package/templates/gym-fitness/himalaya.content.json +9 -0
  65. package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  66. package/templates/gym-fitness/tier5-src/_shared/member-access.ts +14 -0
  67. package/templates/gym-fitness/tier5-src/_shared/member-account-routes.ts +43 -0
  68. package/templates/gym-fitness/tier5-src/_shared/member-address.ts +129 -0
  69. package/templates/gym-fitness/tier5-src/_shared/saved-card.ts +20 -0
  70. package/templates/gym-fitness/tier5-src/himi-branded-lite-address/index.ts +223 -0
  71. package/templates/gym-fitness/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  72. package/templates/gym-fitness/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  73. package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +17 -0
  74. package/templates/gym-fitness-owner/dev/index.ts +5 -27
  75. package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +99 -40
  76. package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  77. package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  78. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  79. package/templates/nonprofit-cause/config.ts +7 -0
  80. package/templates/nonprofit-cause/dev/index.ts +762 -1
  81. package/templates/nonprofit-cause/himalaya.content.json +3 -0
  82. package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  83. package/templates/nonprofit-cause/tier5-src/_shared/member-access.ts +14 -0
  84. package/templates/nonprofit-cause/tier5-src/_shared/member-account-routes.ts +43 -0
  85. package/templates/nonprofit-cause/tier5-src/_shared/saved-card.ts +20 -0
  86. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  87. package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +17 -0
  88. package/templates/nonprofit-cause-owner/dev/index.ts +5 -27
  89. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +99 -40
  90. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  91. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  92. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  93. package/templates/owner-lite/dev/index.ts +5 -27
  94. package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +99 -40
  95. package/templates/owner-lite/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  96. package/templates/owner-lite/tier5-src/_shared/aria-widget-view.ts +6 -0
  97. package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  98. package/templates/podcast-creator/config.ts +7 -0
  99. package/templates/podcast-creator/dev/index.ts +762 -1
  100. package/templates/podcast-creator/himalaya.content.json +3 -0
  101. package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  102. package/templates/podcast-creator/tier5-src/_shared/member-access.ts +14 -0
  103. package/templates/podcast-creator/tier5-src/_shared/member-account-routes.ts +43 -0
  104. package/templates/podcast-creator/tier5-src/_shared/saved-card.ts +20 -0
  105. package/templates/podcast-creator/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  106. package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +17 -0
  107. package/templates/podcast-creator-owner/dev/index.ts +5 -27
  108. package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +99 -40
  109. package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  110. package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  111. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  112. package/templates/portfolio-creative/config.ts +23 -0
  113. package/templates/portfolio-creative/dev/index.ts +2611 -1
  114. package/templates/portfolio-creative/himalaya.content.json +9 -0
  115. package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  116. package/templates/portfolio-creative/tier5-src/_shared/member-access.ts +14 -0
  117. package/templates/portfolio-creative/tier5-src/_shared/member-account-routes.ts +43 -0
  118. package/templates/portfolio-creative/tier5-src/_shared/member-address.ts +129 -0
  119. package/templates/portfolio-creative/tier5-src/_shared/saved-card.ts +20 -0
  120. package/templates/portfolio-creative/tier5-src/himi-branded-lite-address/index.ts +223 -0
  121. package/templates/portfolio-creative/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  122. package/templates/portfolio-creative/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  123. package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +17 -0
  124. package/templates/portfolio-creative-owner/dev/index.ts +5 -27
  125. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +99 -40
  126. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  127. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  128. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  129. package/templates/restaurant/config.ts +23 -0
  130. package/templates/restaurant/dev/index.ts +2611 -1
  131. package/templates/restaurant/himalaya.content.json +9 -0
  132. package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  133. package/templates/restaurant/tier5-src/_shared/member-access.ts +14 -0
  134. package/templates/restaurant/tier5-src/_shared/member-account-routes.ts +43 -0
  135. package/templates/restaurant/tier5-src/_shared/member-address.ts +129 -0
  136. package/templates/restaurant/tier5-src/_shared/saved-card.ts +20 -0
  137. package/templates/restaurant/tier5-src/himi-branded-lite-address/index.ts +223 -0
  138. package/templates/restaurant/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  139. package/templates/restaurant/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  140. package/templates/restaurant/tier5-src/himi-store-account/index.ts +17 -0
  141. package/templates/restaurant-owner/dev/index.ts +5 -27
  142. package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +99 -40
  143. package/templates/restaurant-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  144. package/templates/restaurant-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  145. package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  146. package/templates/retail-store/config.ts +23 -0
  147. package/templates/retail-store/dev/index.ts +2611 -1
  148. package/templates/retail-store/himalaya.content.json +9 -0
  149. package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  150. package/templates/retail-store/tier5-src/_shared/member-access.ts +14 -0
  151. package/templates/retail-store/tier5-src/_shared/member-account-routes.ts +43 -0
  152. package/templates/retail-store/tier5-src/_shared/member-address.ts +129 -0
  153. package/templates/retail-store/tier5-src/_shared/saved-card.ts +20 -0
  154. package/templates/retail-store/tier5-src/himi-branded-lite-address/index.ts +223 -0
  155. package/templates/retail-store/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
  156. package/templates/retail-store/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  157. package/templates/retail-store/tier5-src/himi-store-account/index.ts +17 -0
  158. package/templates/retail-store-owner/dev/index.ts +5 -27
  159. package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +99 -40
  160. package/templates/retail-store-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  161. package/templates/retail-store-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  162. package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
  163. package/templates/yoga-studio/config.ts +7 -0
  164. package/templates/yoga-studio/dev/index.ts +762 -1
  165. package/templates/yoga-studio/himalaya.content.json +3 -0
  166. package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +3 -0
  167. package/templates/yoga-studio/tier5-src/_shared/member-access.ts +14 -0
  168. package/templates/yoga-studio/tier5-src/_shared/member-account-routes.ts +43 -0
  169. package/templates/yoga-studio/tier5-src/_shared/saved-card.ts +20 -0
  170. package/templates/yoga-studio/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
  171. package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +17 -0
  172. package/templates/yoga-studio-owner/dev/index.ts +5 -27
  173. package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +99 -40
  174. package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
  175. package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
  176. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
@@ -67,6 +67,9 @@
67
67
  "himi_view_profile": {
68
68
  "displayName": "View Profile"
69
69
  },
70
+ "himi_wallet": {
71
+ "displayName": "Wallet"
72
+ },
70
73
  "himi_whats_new": {
71
74
  "displayName": "Whats New"
72
75
  }
@@ -65,6 +65,8 @@ export const VERTICAL_ROOT_SCREENS: Readonly<Record<string, readonly string[]>>
65
65
  * (`brandedLiteScreenOwners`, checked by the app's hosted-mode tests), so a screen added
66
66
  * to a vertical without being listed here fails the build. */
67
67
  export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLiteVertical[]>> = {
68
+ himi_address: ["restaurants", "store"],
69
+ himi_addresses: ["restaurants", "store"],
68
70
  himi_badge: ["badges"],
69
71
  himi_badges: ["badges"],
70
72
  himi_blog: ["blog"],
@@ -127,6 +129,7 @@ export const HOSTED_SCREEN_VERTICALS: Readonly<Record<string, readonly BrandedLi
127
129
  himi_video: ["videos"],
128
130
  himi_video_channel: ["videos"],
129
131
  himi_videos: ["videos"],
132
+ himi_wallet: ["bookings", "plans", "restaurants", "store"],
130
133
  };
131
134
 
132
135
  /** The templated (`<prefix>[_/]<id>`) detail routes each vertical owns, pinned the same way. */
@@ -36,3 +36,17 @@ export async function assertMemberAccess(member: MemberAccess): Promise<void> {
36
36
  export function memberAccessDenied(error: unknown): boolean {
37
37
  return (error as { name?: string } | null)?.name === "MemberAccessChanged" || /(?:HTTP\s*(?:401|403)|READ_ORDER_FORBIDDEN|permission denied|forbidden)/i.test(String(error));
38
38
  }
39
+
40
+ /** Did this write fail because the member changed (signed out, or another account)? Then
41
+ * the screen reloads for whoever is signed in now. A 401/403 for the SAME member is only a
42
+ * failed write: the caller keeps what the member typed and says so. */
43
+ export async function memberChanged(member: MemberAccess | undefined, error: unknown): Promise<boolean> {
44
+ if ((error as { name?: string } | null)?.name === "MemberAccessChanged") return true;
45
+ if (!member || !memberAccessDenied(error)) return false;
46
+ try {
47
+ await assertMemberAccess(member);
48
+ return false;
49
+ } catch {
50
+ return true;
51
+ }
52
+ }
@@ -0,0 +1,43 @@
1
+ // Can this host reach the member account routes My Addresses and My Wallet need?
2
+ //
3
+ // Both are served OFF www.wixapis.com (the address book on ecom.wix.com, saved cards on
4
+ // the member's own site), so only a native runtime whose WixApiCapability pins them can
5
+ // call them: HimalayaCore 1.4.0 and later. The web bridges refuse absolute URLs, and a
6
+ // hosted app (Spaces) signs its calls with a capability pinned to www.wixapis.com. Saved
7
+ // cards also need the shell to have bound its own site (app_info `memberSiteBound`): a
8
+ // multi-app host such as Himi Player binds none, so its wallet route has no host. The
9
+ // Account screen hides a link whose route is unreachable, and the screen says so instead
10
+ // of failing a load.
11
+ import { get as getAppInfo } from "@himalaya/app-info";
12
+ import { isHostedWorker } from "./hosted-checkout.js";
13
+
14
+ /** The HimalayaCore version whose WixApiCapability pins these routes. Checked here, at run
15
+ * time: the screens declare no `minBinaryVersion`, because a release takes its highest
16
+ * screen floor as its own and would then refuse every older binary. */
17
+ export const MEMBER_ACCOUNT_ROUTES_MIN_BINARY = "1.4.0";
18
+
19
+ function atLeast(version: string, floor: string): boolean {
20
+ const parts = (v: string) => v.split("-")[0].split(".").map((p) => Number.parseInt(p, 10) || 0);
21
+ const a = parts(version), b = parts(floor);
22
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
23
+ const x = a[i] ?? 0, y = b[i] ?? 0;
24
+ if (x !== y) return x > y;
25
+ }
26
+ return true;
27
+ }
28
+
29
+ export interface MemberAccountRoutes {
30
+ /** The address book on ecom.wix.com (My Addresses). */
31
+ addresses: boolean;
32
+ /** Saved cards on the member's own site (My Wallet). */
33
+ wallet: boolean;
34
+ }
35
+
36
+ export async function memberAccountRoutes(): Promise<MemberAccountRoutes> {
37
+ if (isHostedWorker()) return { addresses: false, wallet: false };
38
+ const info = await getAppInfo().catch(() => undefined);
39
+ if (!info) return { addresses: false, wallet: false };
40
+ const native = info.platform === "ios" || info.platform === "android" || info.platform === "macos";
41
+ const addresses = native && atLeast(info.binaryVersion ?? "", MEMBER_ACCOUNT_ROUTES_MIN_BINARY);
42
+ return { addresses, wallet: addresses && info.memberSiteBound === true };
43
+ }
@@ -0,0 +1,20 @@
1
+ // A saved card's network from its BIN (the first 4-6 digits Wix returns with a saved
2
+ // payment method), for My Wallet's row titles. Wix returns the network only as a brand
3
+ // GUID, so this is the readable name. Brand names are not copy.
4
+ //
5
+ // Pure + Hermes-safe: no host globals, no Node APIs.
6
+
7
+ /** The card network from its BIN, for the row title. Brand names are not copy. */
8
+ export function cardBrand(bin: string): string {
9
+ const d = bin.replace(/\D/g, "");
10
+ const n = (len: number) => Number(d.slice(0, len));
11
+ if (!d) return "";
12
+ if (d.startsWith("4")) return "Visa";
13
+ if ((n(2) >= 51 && n(2) <= 55) || (n(4) >= 2221 && n(4) <= 2720)) return "Mastercard";
14
+ if (d.startsWith("34") || d.startsWith("37")) return "American Express";
15
+ if (d.startsWith("6011") || d.startsWith("65") || (n(3) >= 644 && n(3) <= 649)) return "Discover";
16
+ if (d.startsWith("35")) return "JCB";
17
+ if (d.startsWith("36") || d.startsWith("38") || (n(3) >= 300 && n(3) <= 305)) return "Diners Club";
18
+ if (d.startsWith("62")) return "UnionPay";
19
+ return "";
20
+ }
@@ -0,0 +1,217 @@
1
+ // Tier-5 bundle: Branded Lite — My Wallet (the member's saved payment methods).
2
+ //
3
+ // The cards a member chose to save at checkout, read from Wix Payments on the member's
4
+ // own site (@himalaya/wix-ecom listMySavedPaymentMethods — the native transport routes it there). Like the site's My Wallet
5
+ // page, a member can make one the default for checkout or remove one — there is no
6
+ // "add a card" here: a card is saved while paying. Only the last four digits, the
7
+ // expiry and the cardholder name ever reach the app.
8
+ import { defineService } from "@himalaya/state";
9
+ import {
10
+ deleteSavedPaymentMethod,
11
+ listMySavedPaymentMethods,
12
+ markSavedPaymentMethodPrimary,
13
+ type WixSavedPaymentMethod,
14
+ } from "@himalaya/wix-ecom";
15
+ import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
16
+ import { assertMemberAccess, memberAccess, memberAccessDenied, memberChanged, type MemberAccess } from "../_shared/member-access.js";
17
+ import { memberAccountRoutes } from "../_shared/member-account-routes.js";
18
+ import { cardBrand } from "../_shared/saved-card.js";
19
+
20
+ interface CardRow {
21
+ id: string;
22
+ title: string;
23
+ detail: string;
24
+ isPrimary: boolean;
25
+ notPrimary: boolean;
26
+ confirming: boolean;
27
+ notConfirming: boolean;
28
+ hideActions: boolean;
29
+ }
30
+
31
+ interface State {
32
+ status: "loading" | "ready" | "error";
33
+ rows: CardRow[];
34
+ message: string;
35
+ errorText: string;
36
+ needsSignIn: boolean;
37
+ unavailable: boolean;
38
+ noUnavailable: boolean;
39
+ isLoading: boolean;
40
+ notLoading: boolean;
41
+ notErrored: boolean;
42
+ noSignIn: boolean;
43
+ hideList: boolean;
44
+ noEmpty: boolean;
45
+ }
46
+
47
+ let member: MemberAccess | undefined;
48
+ let methods: WixSavedPaymentMethod[] = [];
49
+ let confirmId = "";
50
+ let busy = false;
51
+
52
+ function rowOf(method: WixSavedPaymentMethod, now: Date): CardRow {
53
+ const id = method.id ?? "";
54
+ const card = method.paymentMethod?.cardInfo;
55
+ const last4 = card?.lastFourDigits?.trim() ?? "";
56
+ const isPayPal = (method.paymentMethod?.typeId ?? "").toLowerCase() === "paypal";
57
+ const name = isPayPal ? "PayPal" : cardBrand(card?.bin ?? "") || copyParam("wallet.cardLabel", "Card");
58
+ const title = last4 ? `${name} •••• ${last4}` : name;
59
+ const month = card?.expirationMonth, year = card?.expirationYear;
60
+ let expiry = "";
61
+ if (month && year) {
62
+ const mmyy = `${String(month).padStart(2, "0")}/${String(year).slice(-2)}`;
63
+ // A card is good through the last day of its expiry month.
64
+ const expired = year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1);
65
+ expiry = expired
66
+ ? copyTemplate("wallet.expiredLabel", "Expired {date}", { date: mmyy })
67
+ : copyTemplate("wallet.expiresLabel", "Expires {date}", { date: mmyy });
68
+ }
69
+ const isPrimary = method.primary === true;
70
+ const confirming = id === confirmId;
71
+ return {
72
+ id,
73
+ title,
74
+ detail: [expiry, card?.cardholderName?.trim()].filter(Boolean).join(" · "),
75
+ isPrimary,
76
+ notPrimary: !isPrimary,
77
+ confirming,
78
+ notConfirming: !confirming,
79
+ hideActions: confirming,
80
+ };
81
+ }
82
+
83
+ function derive(state: State): State {
84
+ const ready = state.status === "ready" && !state.unavailable;
85
+ const now = new Date();
86
+ // The default card leads, like the site's own page.
87
+ const ordered = [...methods].sort((a, b) => Number(b.primary === true) - Number(a.primary === true));
88
+ const rows = ready ? ordered.filter((m) => !!m.id).map((m) => rowOf(m, now)) : [];
89
+ return {
90
+ ...state,
91
+ rows,
92
+ noUnavailable: !state.unavailable,
93
+ isLoading: state.status === "loading",
94
+ notLoading: state.status !== "loading",
95
+ notErrored: state.status !== "error",
96
+ noSignIn: !ready || !state.needsSignIn,
97
+ hideList: !ready || state.needsSignIn || rows.length === 0,
98
+ noEmpty: !ready || state.needsSignIn || rows.length > 0,
99
+ };
100
+ }
101
+
102
+ const INITIAL: State = derive({
103
+ status: "loading", rows: [], message: "", errorText: "", needsSignIn: false, unavailable: false, noUnavailable: true,
104
+ isLoading: true, notLoading: false, notErrored: true, noSignIn: true, hideList: true, noEmpty: true,
105
+ });
106
+
107
+ // 100 per page is the service maximum; the page cap only stops a cursor that never ends.
108
+ const MAX_PAGES = 10;
109
+
110
+ async function allSavedPaymentMethods(): Promise<WixSavedPaymentMethod[]> {
111
+ const all: WixSavedPaymentMethod[] = [];
112
+ let cursor = "";
113
+ for (let page = 0; page < MAX_PAGES; page++) {
114
+ const response = await listMySavedPaymentMethods({ limit: 100, ...(cursor ? { cursor } : {}) });
115
+ all.push(...(response.savedPaymentMethods ?? []));
116
+ const next = response.pagingMetadata?.cursors?.next ?? "";
117
+ if (response.pagingMetadata?.hasNext === false || !next || next === cursor) break;
118
+ cursor = next;
119
+ }
120
+ return all;
121
+ }
122
+
123
+ async function load(state: State, arg?: unknown): Promise<State> {
124
+ stashCopy(arg);
125
+ confirmId = "";
126
+ if (!(await memberAccountRoutes()).wallet) {
127
+ methods = [];
128
+ return derive({ ...state, status: "ready", unavailable: true, needsSignIn: false, message: "", errorText: "" });
129
+ }
130
+ try {
131
+ member = await memberAccess();
132
+ if (!member.email) {
133
+ methods = [];
134
+ return derive({ ...state, status: "ready", unavailable: false, needsSignIn: true, message: "", errorText: "" });
135
+ }
136
+ const all = await allSavedPaymentMethods();
137
+ await assertMemberAccess(member);
138
+ methods = all;
139
+ // A fresh load drops the last write's confirmation; write() sets it again after this.
140
+ return derive({ ...state, status: "ready", unavailable: false, needsSignIn: false, message: "", errorText: "" });
141
+ } catch (error) {
142
+ methods = [];
143
+ return derive({
144
+ ...state,
145
+ status: "error",
146
+ unavailable: false,
147
+ message: "",
148
+ errorText: memberAccessDenied(error)
149
+ ? copyParam("wallet.accessErrorText", "Your wallet is unavailable for this account. Try signing in again.")
150
+ : copyParam("wallet.loadErrorText", "Your saved cards could not be loaded."),
151
+ });
152
+ }
153
+ }
154
+
155
+ function argId(arg: unknown): string {
156
+ const id = (arg as { id?: unknown } | null)?.id;
157
+ return typeof id === "string" ? id : "";
158
+ }
159
+
160
+ async function write(state: State, run: () => Promise<unknown>, done: string, failed: string): Promise<State> {
161
+ if (busy || !member?.email) return state;
162
+ busy = true;
163
+ try {
164
+ await assertMemberAccess(member);
165
+ await run();
166
+ const next = await load(state);
167
+ return next.status === "ready" ? { ...next, message: done } : next;
168
+ } catch (error) {
169
+ if (await memberChanged(member, error)) return load(state);
170
+ return derive({ ...state, message: failed });
171
+ } finally {
172
+ busy = false;
173
+ }
174
+ }
175
+
176
+ const svc = defineService<State, {
177
+ load: (state: State, arg?: unknown) => Promise<State>;
178
+ retry: (state: State) => Promise<State>;
179
+ setPrimary: (state: State, arg?: unknown) => Promise<State>;
180
+ askRemove: (state: State, arg?: unknown) => State;
181
+ keep: (state: State) => State;
182
+ remove: (state: State, arg?: unknown) => Promise<State>;
183
+ }>("himiBrandedWallet", {
184
+ state: INITIAL,
185
+ actions: {
186
+ load,
187
+ retry: (state) => load(derive({ ...state, status: "loading", message: "" })),
188
+ setPrimary: (state, arg) => {
189
+ const id = argId(arg);
190
+ const method = methods.find((m) => m.id === id);
191
+ if (!id || !method || method.primary) return Promise.resolve(state);
192
+ return write(state, () => markSavedPaymentMethodPrimary(id),
193
+ copyParam("wallet.primaryDone", "Default card updated."),
194
+ copyParam("wallet.primaryFailed", "The default card could not be changed. Try again."));
195
+ },
196
+ askRemove: (state, arg) => {
197
+ const id = argId(arg);
198
+ if (!methods.some((m) => m.id === id)) return state;
199
+ confirmId = id;
200
+ return derive({ ...state, message: "" });
201
+ },
202
+ keep: (state) => {
203
+ confirmId = "";
204
+ return derive(state);
205
+ },
206
+ remove: (state, arg) => {
207
+ // Only the card the member is confirming — a stale row tap removes nothing.
208
+ const id = argId(arg);
209
+ if (!id || id !== confirmId) return Promise.resolve(state);
210
+ return write(state, () => deleteSavedPaymentMethod(id),
211
+ copyParam("wallet.removeDone", "Card removed."),
212
+ copyParam("wallet.removeFailed", "The card could not be removed. Try again."));
213
+ },
214
+ },
215
+ });
216
+
217
+ export { svc };
@@ -13,6 +13,7 @@ import { dispatch } from "@himalaya/dispatch";
13
13
  import { searchOrders, type WixOrder } from "@himalaya/wix-ecom";
14
14
  import { guestProfile, loadMemberProfile, strOf, fmtDate, titleCase, type AccountActivityRow } from "../_shared/himi-account";
15
15
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
16
+ import { memberAccountRoutes } from "../_shared/member-account-routes.js";
16
17
 
17
18
  interface AuthRouteInput {
18
19
  intent?: string;
@@ -205,6 +206,14 @@ interface S {
205
206
  activityEmptyText: string;
206
207
  hasActivity: boolean;
207
208
  noActivity: boolean;
209
+ /** Set once a descriptor asks for the member-account links (appear param
210
+ * `memberAccountLinks: "1"`). Only then does the probe read app_info, so a descriptor
211
+ * that renders no such link need not declare that capability. */
212
+ memberAccountLinks: boolean;
213
+ /** Hides My Addresses where this runtime cannot reach the address book. */
214
+ noMemberAddresses: boolean;
215
+ /** Hides My Wallet where this runtime cannot reach saved cards. */
216
+ noMemberWallet: boolean;
208
217
  }
209
218
 
210
219
  function derive(s: S): S {
@@ -278,6 +287,8 @@ async function orderRows(memberEmail: string): Promise<AccountActivityRow[]> {
278
287
  async function probe(s: S, arg?: unknown): Promise<S> {
279
288
  const intent = authIntentFromArg(arg, s);
280
289
  const profile = await loadMemberProfile(copyParam("storeAccount.guestDetail", "Sign in to keep your cart and orders connected."));
290
+ const memberAccountLinks = s.memberAccountLinks || strField(arg, "memberAccountLinks") === "1";
291
+ const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
281
292
  let activityRows: AccountActivityRow[] = [];
282
293
  if (profile.isMember) {
283
294
  try {
@@ -301,6 +312,9 @@ async function probe(s: S, arg?: unknown): Promise<S> {
301
312
  noMemberImage: profile.noMemberImage,
302
313
  email: profile.memberEmail || s.email,
303
314
  message: "",
315
+ memberAccountLinks,
316
+ noMemberAddresses: !routes.addresses,
317
+ noMemberWallet: !routes.wallet,
304
318
  activityRows,
305
319
  activityTitle: copyParam("storeAccount.activityTitle", "Previous orders"),
306
320
  activitySummary: activityRows.length
@@ -394,6 +408,9 @@ const svc = defineService<S, {
394
408
  activityEmptyText: "No previous orders for this member yet.",
395
409
  hasActivity: false,
396
410
  noActivity: true,
411
+ memberAccountLinks: false,
412
+ noMemberAddresses: true,
413
+ noMemberWallet: true,
397
414
  }),
398
415
  actions: {
399
416
  load: (s, arg) => { stashCopy(arg); return probe(s, arg); },
@@ -60653,22 +60653,6 @@ const SCREENS = runtimeSetting({
60653
60653
  "props": {
60654
60654
  "spacing": 8,
60655
60655
  "children": [
60656
- {
60657
- "id": "",
60658
- "body": {
60659
- "package": "himalaya",
60660
- "type": "Text",
60661
- "props": {
60662
- "text": "$service:himiOwnerLiteAria.widget.badge",
60663
- "style": "footnote",
60664
- "color": "textSecondary",
60665
- "maxLines": 12
60666
- }
60667
- },
60668
- "modifiers": {
60669
- "hiddenPath": "$service:himiOwnerLiteAria.widget.badgeHidden"
60670
- }
60671
- },
60672
60656
  {
60673
60657
  "id": "",
60674
60658
  "body": {
@@ -60705,21 +60689,21 @@ const SCREENS = runtimeSetting({
60705
60689
  "package": "himalaya",
60706
60690
  "type": "Text",
60707
60691
  "props": {
60708
- "text": "$service:himiOwnerLiteAria.widget.bottomText",
60692
+ "text": "$service:himiOwnerLiteAria.widget.notice",
60709
60693
  "style": "footnote",
60710
60694
  "color": "textSecondary",
60711
60695
  "maxLines": 12
60712
60696
  }
60713
60697
  },
60714
60698
  "modifiers": {
60715
- "hiddenPath": "$service:himiOwnerLiteAria.widget.bottomTextHidden"
60699
+ "hiddenPath": "$service:himiOwnerLiteAria.widget.noticeHidden"
60716
60700
  }
60717
60701
  },
60718
60702
  {
60719
60703
  "id": "himi_owner_lite_aria.widget.OAUTH_REQUIRED.footer",
60720
60704
  "body": {
60721
60705
  "package": "himalaya",
60722
- "type": "HStack",
60706
+ "type": "VStack",
60723
60707
  "props": {
60724
60708
  "spacing": 8,
60725
60709
  "children": [
@@ -60731,6 +60715,7 @@ const SCREENS = runtimeSetting({
60731
60715
  "props": {
60732
60716
  "title": "$service:himiOwnerLiteAria.widget.primaryLabel",
60733
60717
  "style": "primary",
60718
+ "fullWidth": true,
60734
60719
  "action": {
60735
60720
  "actionId": "tier5.himiOwnerLiteAria.widgetPrimary"
60736
60721
  }
@@ -60748,6 +60733,7 @@ const SCREENS = runtimeSetting({
60748
60733
  "props": {
60749
60734
  "title": "$service:himiOwnerLiteAria.widget.secondaryLabel",
60750
60735
  "style": "secondary",
60736
+ "fullWidth": true,
60751
60737
  "action": {
60752
60738
  "actionId": "tier5.himiOwnerLiteAria.widgetSecondary"
60753
60739
  }
@@ -60756,14 +60742,6 @@ const SCREENS = runtimeSetting({
60756
60742
  "modifiers": {
60757
60743
  "hiddenPath": "$service:himiOwnerLiteAria.widget.secondaryHidden"
60758
60744
  }
60759
- },
60760
- {
60761
- "id": "",
60762
- "body": {
60763
- "package": "himalaya",
60764
- "type": "Spacer",
60765
- "props": {}
60766
- }
60767
60745
  }
60768
60746
  ]
60769
60747
  }