@juo/blocks 1.15.0 → 2.0.0-alpha.1

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 (171) hide show
  1. package/dist/{ExtensionRoot-C3lVJEBl.js → ExtensionRoot-DRpD5EcC.js} +24 -6
  2. package/dist/block-BjJztStr.js +993 -0
  3. package/dist/{block-VhmfGB3N.js → block-DnhzyZ1H.js} +38 -3
  4. package/dist/block.d.ts +56 -0
  5. package/dist/block.d.ts.map +1 -1
  6. package/dist/{bridge-xiKEzCGR.js → bridge-Mz0CqG6E.js} +39 -30
  7. package/dist/core.js +2429 -399
  8. package/dist/css-runtime-B7tZrBIp.js +749 -0
  9. package/dist/devtools/hook.d.ts +0 -5
  10. package/dist/devtools/hook.d.ts.map +1 -1
  11. package/dist/devtools/index.d.ts +0 -1
  12. package/dist/devtools/index.d.ts.map +1 -1
  13. package/dist/devtools/protocol.d.ts +15 -1
  14. package/dist/devtools/protocol.d.ts.map +1 -1
  15. package/dist/devtools.js +42 -9
  16. package/dist/editor/bridge.d.ts +7 -0
  17. package/dist/editor/bridge.d.ts.map +1 -1
  18. package/dist/editor/components.d.ts.map +1 -1
  19. package/dist/editor/main.d.ts +2 -1
  20. package/dist/editor/main.d.ts.map +1 -1
  21. package/dist/editor/messages.d.ts +13 -2
  22. package/dist/editor/messages.d.ts.map +1 -1
  23. package/dist/editor/overlays.d.ts +14 -0
  24. package/dist/editor/overlays.d.ts.map +1 -0
  25. package/dist/editor/overlays.test.d.ts +2 -0
  26. package/dist/editor/overlays.test.d.ts.map +1 -0
  27. package/dist/editor.js +52 -33
  28. package/dist/{gridstack-adapter-CDWbSpbt.js → gridstack-adapter-BGKGC-eO.js} +2 -2
  29. package/dist/main.d.ts +3 -1
  30. package/dist/main.d.ts.map +1 -1
  31. package/dist/{morph-n4LMxOYo.js → morph-CT5hc5Qp.js} +1 -1
  32. package/dist/overlay-wrapper.d.ts +69 -0
  33. package/dist/overlay-wrapper.d.ts.map +1 -0
  34. package/dist/overlay-wrapper.test.d.ts +2 -0
  35. package/dist/overlay-wrapper.test.d.ts.map +1 -0
  36. package/dist/overlays/OverlayService.d.ts +34 -0
  37. package/dist/overlays/OverlayService.d.ts.map +1 -0
  38. package/dist/overlays/OverlayService.test.d.ts +2 -0
  39. package/dist/overlays/OverlayService.test.d.ts.map +1 -0
  40. package/dist/preact/block.d.ts.map +1 -1
  41. package/dist/preact/context.d.ts.map +1 -1
  42. package/dist/preact/context.test.d.ts +2 -0
  43. package/dist/preact/context.test.d.ts.map +1 -0
  44. package/dist/preact/signals.d.ts +19 -0
  45. package/dist/preact/signals.d.ts.map +1 -1
  46. package/dist/preact/useContextSubscription.test.d.ts +2 -0
  47. package/dist/preact/useContextSubscription.test.d.ts.map +1 -0
  48. package/dist/preact.js +33 -7
  49. package/dist/{project-gridstack-CM783oKI.js → project-gridstack-PxN_DIfP.js} +1 -1
  50. package/dist/react/block.d.ts.map +1 -1
  51. package/dist/react/context.d.ts.map +1 -1
  52. package/dist/react/context.test.d.ts +2 -0
  53. package/dist/react/context.test.d.ts.map +1 -0
  54. package/dist/react/signals.d.ts +19 -0
  55. package/dist/react/signals.d.ts.map +1 -1
  56. package/dist/react/useContextSubscription.test.d.ts +2 -0
  57. package/dist/react/useContextSubscription.test.d.ts.map +1 -0
  58. package/dist/react.js +40 -8
  59. package/dist/services/customer/adapter.d.ts +8 -0
  60. package/dist/services/customer/adapter.d.ts.map +1 -0
  61. package/dist/services/customer/customerService.d.ts +3 -2
  62. package/dist/services/customer/customerService.d.ts.map +1 -1
  63. package/dist/services/customer/index.d.ts +1 -0
  64. package/dist/services/customer/index.d.ts.map +1 -1
  65. package/dist/services/customer/types.d.ts +4 -1
  66. package/dist/services/customer/types.d.ts.map +1 -1
  67. package/dist/services/index.d.ts +4 -1
  68. package/dist/services/index.d.ts.map +1 -1
  69. package/dist/services/login/adapter.d.ts +33 -0
  70. package/dist/services/login/adapter.d.ts.map +1 -0
  71. package/dist/services/login/index.d.ts +3 -1
  72. package/dist/services/login/index.d.ts.map +1 -1
  73. package/dist/services/login/loginService.d.ts +6 -2
  74. package/dist/services/login/loginService.d.ts.map +1 -1
  75. package/dist/services/order/adapter.d.ts +10 -0
  76. package/dist/services/order/adapter.d.ts.map +1 -0
  77. package/dist/services/order/index.d.ts +4 -0
  78. package/dist/services/order/index.d.ts.map +1 -0
  79. package/dist/services/order/orderService.d.ts +6 -0
  80. package/dist/services/order/orderService.d.ts.map +1 -0
  81. package/dist/services/order/orderService.test.d.ts +2 -0
  82. package/dist/services/order/orderService.test.d.ts.map +1 -0
  83. package/dist/services/{orders → order}/types.d.ts +3 -3
  84. package/dist/services/order/types.d.ts.map +1 -0
  85. package/dist/services/product/adapter.d.ts +27 -2
  86. package/dist/services/product/adapter.d.ts.map +1 -1
  87. package/dist/services/product/adapter.test.d.ts +2 -0
  88. package/dist/services/product/adapter.test.d.ts.map +1 -0
  89. package/dist/services/product/index.d.ts +1 -1
  90. package/dist/services/product/index.d.ts.map +1 -1
  91. package/dist/services/product/productService.d.ts.map +1 -1
  92. package/dist/services/product/types.d.ts +68 -0
  93. package/dist/services/product/types.d.ts.map +1 -1
  94. package/dist/services/schedules/adapter.d.ts +94 -0
  95. package/dist/services/schedules/adapter.d.ts.map +1 -0
  96. package/dist/services/schedules/adapter.test.d.ts +2 -0
  97. package/dist/services/schedules/adapter.test.d.ts.map +1 -0
  98. package/dist/services/schedules/apiSchedulesAdapter.test.d.ts +2 -0
  99. package/dist/services/schedules/apiSchedulesAdapter.test.d.ts.map +1 -0
  100. package/dist/services/schedules/index.d.ts +5 -0
  101. package/dist/services/schedules/index.d.ts.map +1 -0
  102. package/dist/services/schedules/schedulesService.d.ts +11 -0
  103. package/dist/services/schedules/schedulesService.d.ts.map +1 -0
  104. package/dist/services/schedules/schedulesService.test.d.ts +2 -0
  105. package/dist/services/schedules/schedulesService.test.d.ts.map +1 -0
  106. package/dist/services/schedules/types.d.ts +290 -0
  107. package/dist/services/schedules/types.d.ts.map +1 -0
  108. package/dist/services/settings/index.d.ts +3 -0
  109. package/dist/services/settings/index.d.ts.map +1 -0
  110. package/dist/services/settings/settingsService.d.ts +7 -0
  111. package/dist/services/settings/settingsService.d.ts.map +1 -0
  112. package/dist/services/settings/settingsService.test.d.ts +2 -0
  113. package/dist/services/settings/settingsService.test.d.ts.map +1 -0
  114. package/dist/services/settings/types.d.ts +35 -0
  115. package/dist/services/settings/types.d.ts.map +1 -0
  116. package/dist/services/shop/index.d.ts +3 -0
  117. package/dist/services/shop/index.d.ts.map +1 -0
  118. package/dist/services/shop/shopService.d.ts +12 -0
  119. package/dist/services/shop/shopService.d.ts.map +1 -0
  120. package/dist/services/shop/shopService.test.d.ts +2 -0
  121. package/dist/services/shop/shopService.test.d.ts.map +1 -0
  122. package/dist/services/shop/types.d.ts +7 -0
  123. package/dist/services/shop/types.d.ts.map +1 -0
  124. package/dist/services/subscription/adapter.d.ts +77 -2
  125. package/dist/services/subscription/adapter.d.ts.map +1 -1
  126. package/dist/services/subscription/adapter.test.d.ts +2 -0
  127. package/dist/services/subscription/adapter.test.d.ts.map +1 -0
  128. package/dist/services/subscription/index.d.ts +1 -1
  129. package/dist/services/subscription/index.d.ts.map +1 -1
  130. package/dist/services/subscription/subscriptionService.d.ts +1 -6
  131. package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
  132. package/dist/services/subscription/types.d.ts +220 -9
  133. package/dist/services/subscription/types.d.ts.map +1 -1
  134. package/dist/services/subscription/types.test.d.ts +2 -0
  135. package/dist/services/subscription/types.test.d.ts.map +1 -0
  136. package/dist/shared/context.d.ts +10 -3
  137. package/dist/shared/context.d.ts.map +1 -1
  138. package/dist/shared/touchSignals.d.ts +17 -0
  139. package/dist/shared/touchSignals.d.ts.map +1 -0
  140. package/dist/shared/touchSignals.test.d.ts +2 -0
  141. package/dist/shared/touchSignals.test.d.ts.map +1 -0
  142. package/dist/theme-state.d.ts +2 -1
  143. package/dist/theme-state.d.ts.map +1 -1
  144. package/dist/theme-state.params.test.d.ts +2 -0
  145. package/dist/theme-state.params.test.d.ts.map +1 -0
  146. package/dist/theme-state.translations.test.d.ts +2 -0
  147. package/dist/theme-state.translations.test.d.ts.map +1 -0
  148. package/dist/utils/index.d.ts +6 -0
  149. package/dist/utils/index.d.ts.map +1 -1
  150. package/dist/vue/block.d.ts.map +1 -1
  151. package/dist/vue/context.d.ts +1 -0
  152. package/dist/vue/context.d.ts.map +1 -1
  153. package/dist/vue.js +28 -15
  154. package/dist/web-components/ExtensionRoot.d.ts +1 -0
  155. package/dist/web-components/ExtensionRoot.d.ts.map +1 -1
  156. package/dist/web-components-editor.js +6 -5
  157. package/dist/web-components-runtime.js +3 -2
  158. package/package.json +1 -1
  159. package/dist/css-runtime-ClH0MEYr.js +0 -1570
  160. package/dist/devtools/instrumentation.d.ts +0 -47
  161. package/dist/devtools/instrumentation.d.ts.map +0 -1
  162. package/dist/index-JSv6UH_o.js +0 -169
  163. package/dist/services/orders/adapter.d.ts +0 -8
  164. package/dist/services/orders/adapter.d.ts.map +0 -1
  165. package/dist/services/orders/index.d.ts +0 -4
  166. package/dist/services/orders/index.d.ts.map +0 -1
  167. package/dist/services/orders/ordersService.d.ts +0 -6
  168. package/dist/services/orders/ordersService.d.ts.map +0 -1
  169. package/dist/services/orders/ordersService.test.d.ts +0 -2
  170. package/dist/services/orders/ordersService.test.d.ts.map +0 -1
  171. package/dist/services/orders/types.d.ts.map +0 -1
package/dist/core.js CHANGED
@@ -1,11 +1,13 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { f as createContext, g as d, h as w, j as registerBlock, E, u, k as getDefinedBlocks, l as createBlockInstance, m as defineBlock, s as subscribeContext, G as GRID_V1_ENGINE_ID, n as clampRect, p as applyFillExpansion, q as compactItems, t as findFreeRect, D as DEFAULT_GRID } from "./css-runtime-ClH0MEYr.js";
5
- import { B, v, x, y, z, L, P, S, c, T, A, d as d2, b, F, H, I, J, K, N, O, Q, R, U, V, W, X, Y, Z, i, _, a, $, a0, o, a1, a2, a3, e, a4, r, a5, a6, a7, w as w2 } from "./css-runtime-ClH0MEYr.js";
4
+ import { e as createContext, f as d, m as w, n as defineBlock, h as createBlockInstance, j as getDefinedBlocks, p as registerBlock, E, u, s as subscribeContext } from "./block-BjJztStr.js";
5
+ import { B, q, t, S, v, _, x, c, y, g, z, A, D, F, G, i, H, b, I, o, J, k, K, l, L, w as w2 } from "./block-BjJztStr.js";
6
+ import { G as GRID_V1_ENGINE_ID, d as clampRect, e as applyFillExpansion, f as compactItems, g as findFreeRect, D as DEFAULT_GRID } from "./css-runtime-B7tZrBIp.js";
7
+ import { h, i as i2, L as L2, P, a, T, b as b2, j, k as k2, l as l2, m, n, o as o2, p, q as q2, s, t as t2, u as u2, v as v2, c as c2, w as w3, r } from "./css-runtime-B7tZrBIp.js";
6
8
  import { reportBlockWarning, reportBlockError } from "./observability.js";
7
- import { a as createEditorBridge, D as createSelectBlockMessage } from "./bridge-xiKEzCGR.js";
8
- import { p } from "./project-gridstack-CM783oKI.js";
9
+ import { a as createEditorBridge, E as createSelectBlockMessage } from "./bridge-Mz0CqG6E.js";
10
+ import { p as p2 } from "./project-gridstack-PxN_DIfP.js";
9
11
  import { clearLayoutModeAdapter, getLayoutModeAdapter, registerLayoutModeAdapter } from "./layout-mode-adapter-BBd4JRQd.js";
10
12
  function createState() {
11
13
  const discountCode = d(null);
@@ -75,6 +77,111 @@ function createState() {
75
77
  };
76
78
  }
77
79
  const StateContext = createContext("state");
80
+ const OVERLAY_WRAPPER_SCHEMA = {
81
+ type: "object",
82
+ properties: {
83
+ props: {
84
+ type: "object",
85
+ properties: {
86
+ layout: {
87
+ type: "string",
88
+ enum: ["modal", "sidebar"]
89
+ },
90
+ canAbort: {
91
+ type: "boolean"
92
+ }
93
+ },
94
+ required: ["layout"],
95
+ additionalProperties: false
96
+ },
97
+ slots: {
98
+ type: "object",
99
+ properties: {
100
+ content: {}
101
+ },
102
+ additionalProperties: false
103
+ }
104
+ },
105
+ required: ["props", "slots"],
106
+ additionalProperties: false
107
+ };
108
+ function defineOverlayWrapper(opts) {
109
+ const layout = opts.layout ?? "sidebar";
110
+ const canAbort = opts.canAbort ?? true;
111
+ return defineBlock(opts.name, {
112
+ displayName: opts.title,
113
+ group: "internal",
114
+ schema: OVERLAY_WRAPPER_SCHEMA,
115
+ initialValue: () => ({
116
+ props: { layout, canAbort },
117
+ slots: {
118
+ content: [createBlockInstance(opts.content)]
119
+ }
120
+ }),
121
+ renderer: () => null
122
+ });
123
+ }
124
+ const OVERLAY_PREFIX$1 = "overlay:";
125
+ function callInitialValue(block) {
126
+ if (typeof block.initialValue !== "function") return block.initialValue;
127
+ try {
128
+ return block.initialValue();
129
+ } catch (error) {
130
+ console.warn(
131
+ `[overlays] initialValue() threw for "${block.name}"; its content block will not be hidden from the palette.`,
132
+ error
133
+ );
134
+ return void 0;
135
+ }
136
+ }
137
+ function buildOverlayRegistry(blocks) {
138
+ var _a, _b;
139
+ const registry = {};
140
+ for (const block of blocks) {
141
+ if (!block.name.startsWith(OVERLAY_PREFIX$1)) continue;
142
+ const id = block.name.slice(OVERLAY_PREFIX$1.length);
143
+ const initial = callInitialValue(block);
144
+ const first = (_b = (_a = initial == null ? void 0 : initial.slots) == null ? void 0 : _a.content) == null ? void 0 : _b[0];
145
+ const contentBlock = typeof (first == null ? void 0 : first.type) === "string" ? first.type : "";
146
+ registry[id] = {
147
+ block: block.name,
148
+ title: block.displayName ?? id,
149
+ contentBlock
150
+ };
151
+ }
152
+ return registry;
153
+ }
154
+ const OVERLAY_PREFIX = "overlay:";
155
+ const OverlayServiceContext = createContext("services/overlay");
156
+ function createOverlayService(themeState, options = {}) {
157
+ const getBlocks = options.getBlocks ?? getDefinedBlocks;
158
+ const current = w(() => {
159
+ const root = themeState.overlay.blocks.value[0];
160
+ const type = root == null ? void 0 : root.type;
161
+ if (typeof type !== "string" || !type.startsWith(OVERLAY_PREFIX)) {
162
+ return null;
163
+ }
164
+ const props = root.props ?? {};
165
+ return {
166
+ name: type,
167
+ // Defaults mirror defineOverlayWrapper's seeding in overlay-wrapper.ts — keep in sync.
168
+ layout: props.layout ?? "sidebar",
169
+ canAbort: Boolean(props.canAbort ?? true)
170
+ };
171
+ });
172
+ return {
173
+ getEditableOverlays() {
174
+ return buildOverlayRegistry(getBlocks());
175
+ },
176
+ open(name, params) {
177
+ void themeState.resolve("overlay", name, params);
178
+ },
179
+ close() {
180
+ themeState.overlay.blocks.value = [];
181
+ },
182
+ current
183
+ };
184
+ }
78
185
  function isLocalhostOrigin(origin) {
79
186
  try {
80
187
  const url = new URL(origin);
@@ -159,13 +266,43 @@ async function loadBlockRegistry(options = {}) {
159
266
  function createCustomerService(options) {
160
267
  const current = d(null);
161
268
  const accountOrdersUrl = d(options.getAccountOrdersUrl());
162
- const service = {
269
+ return {
163
270
  current,
164
- accountOrdersUrl
271
+ accountOrdersUrl,
272
+ async getCurrent() {
273
+ const result = await options.adapter.getCurrent();
274
+ if (result._tag === "Success") current.value = result.data;
275
+ return result;
276
+ }
165
277
  };
166
- return service;
167
278
  }
168
279
  const CustomerServiceContext = createContext("services/customer");
280
+ function createMockCustomerAdapter() {
281
+ const customer = { id: "customer-1", name: "Customer 1" };
282
+ return {
283
+ async getCurrent() {
284
+ return { _tag: "Success", data: customer };
285
+ }
286
+ };
287
+ }
288
+ function createApiCustomerAdapter(fetcher) {
289
+ return {
290
+ async getCurrent() {
291
+ try {
292
+ const res = await fetcher("/customer/info");
293
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
294
+ const data = await res.json();
295
+ const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
296
+ return {
297
+ _tag: "Success",
298
+ data: { id: data.id, name, email: data.email }
299
+ };
300
+ } catch (e) {
301
+ return { _tag: "Failure", error: e };
302
+ }
303
+ }
304
+ };
305
+ }
169
306
  const PARAMS = {
170
307
  DELETGATED_TOKEN: "delegated-token",
171
308
  REDIRECT_TO: "redirect-to",
@@ -179,9 +316,126 @@ const PARAMS = {
179
316
  function uuid() {
180
317
  return ("10000000-1000-4000-8000" + -1e11).replace(
181
318
  /[018]/g,
182
- (c2) => (c2 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c2 / 4).toString(16)
319
+ (c3) => (c3 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c3 / 4).toString(16)
183
320
  );
184
321
  }
322
+ function createMockLoginAdapter(opts = {}) {
323
+ const appUrl = opts.appUrl ?? "https://admin.juo.io";
324
+ return {
325
+ async passwordlessStart() {
326
+ return { _tag: "Success", data: void 0 };
327
+ },
328
+ async authenticate() {
329
+ return {
330
+ _tag: "Success",
331
+ data: {
332
+ access_token: "mock-access-token",
333
+ refresh_token: "mock-refresh-token",
334
+ expires_in: 3600
335
+ }
336
+ };
337
+ },
338
+ getAuthorizeUrl({ connection, state, shop }) {
339
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
340
+ url.searchParams.set("connection", connection);
341
+ url.searchParams.set("state", state);
342
+ url.searchParams.set("shop", shop);
343
+ return url.toString();
344
+ }
345
+ };
346
+ }
347
+ function createApiLoginAdapter(fetcher, opts) {
348
+ const { shopDomain, appUrl } = opts;
349
+ async function callAuthEndpoint(path, body, lang) {
350
+ const url = lang ? `${path}?lang=${lang}` : path;
351
+ const res = await fetcher(url, {
352
+ method: "POST",
353
+ headers: {
354
+ Accept: "application/json",
355
+ "Content-Type": "application/json",
356
+ "X-Shopify-Shop-Domain": shopDomain
357
+ },
358
+ body: JSON.stringify(body)
359
+ });
360
+ if (res.status === 403) {
361
+ const data = await res.json();
362
+ if ("error_description" in data) {
363
+ if (data.error_description === "Wrong email or verification code.") {
364
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
365
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
366
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
367
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
368
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
369
+ }
370
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
371
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
372
+ connection: data.connection
373
+ });
374
+ }
375
+ }
376
+ if (!res.ok) {
377
+ throw new Error(`Unexpected response: ${res.statusText}`);
378
+ }
379
+ return { _tag: "Success", data: void 0 };
380
+ }
381
+ return {
382
+ async passwordlessStart({ email, lang }) {
383
+ try {
384
+ await callAuthEndpoint(
385
+ "/auth/customer/passwordless/start",
386
+ { email },
387
+ lang
388
+ );
389
+ return { _tag: "Success", data: void 0 };
390
+ } catch (e) {
391
+ return { _tag: "Failure", error: e };
392
+ }
393
+ },
394
+ async authenticate(params) {
395
+ try {
396
+ const res = await fetcher("/auth/customer/token", {
397
+ method: "POST",
398
+ headers: {
399
+ Accept: "application/json",
400
+ "Content-Type": "application/json",
401
+ "X-Shopify-Shop-Domain": shopDomain
402
+ },
403
+ body: JSON.stringify(params)
404
+ });
405
+ if (res.status === 403) {
406
+ const data = await res.json();
407
+ if ("error_description" in data) {
408
+ if (data.error_description === "Wrong email or verification code.") {
409
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
410
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
411
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
412
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
413
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
414
+ }
415
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
416
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
417
+ connection: data.connection
418
+ });
419
+ }
420
+ }
421
+ if (!res.ok) {
422
+ throw new Error(`Unexpected response: ${res.statusText}`);
423
+ }
424
+ return { _tag: "Success", data: await res.json() };
425
+ } catch (e) {
426
+ return { _tag: "Failure", error: e };
427
+ }
428
+ },
429
+ getAuthorizeUrl({ connection, state, shop }) {
430
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
431
+ url.searchParams.set("connection", connection);
432
+ url.searchParams.set("state", state);
433
+ url.searchParams.set("shop", shop);
434
+ return url.toString();
435
+ }
436
+ };
437
+ }
438
+ const __vite_import_meta_env__ = {};
185
439
  var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
186
440
  AuthenticationErrorReason2["Expired"] = "expired";
187
441
  AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
@@ -200,6 +454,9 @@ class AuthenticationError extends Error {
200
454
  this.data = data ?? {};
201
455
  }
202
456
  }
457
+ const STORAGE_DELEGATED_TOKEN = "delegated-token";
458
+ const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
459
+ const STORAGE_STATE_KEY = "state";
203
460
  function storage(key, value) {
204
461
  const storageKey = `auth:${key}`;
205
462
  try {
@@ -215,83 +472,33 @@ function storage(key, value) {
215
472
  }
216
473
  return null;
217
474
  }
218
- const STORAGE_DELEGATED_TOKEN = "delegated-token";
219
- const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
220
- const STORAGE_STATE_KEY = "state";
221
- function createAuthFunctions(basePath, shop) {
222
- async function start(email, lang) {
223
- const response = await fetch(
224
- `${basePath}/auth/customer/passwordless/start?lang=${lang}`,
225
- {
226
- method: "POST",
227
- headers: {
228
- Accept: "application/json",
229
- "Content-Type": "application/json",
230
- "X-Shopify-Shop-Domain": shop
231
- },
232
- body: JSON.stringify({ email })
233
- }
234
- );
235
- if (response.status !== 200) {
236
- throw new Error(`Unexpected response: ${response.statusText}`);
237
- }
238
- }
239
- async function authenticate(data) {
240
- const response = await fetch(`${basePath}/auth/customer/token`, {
241
- method: "POST",
242
- headers: {
243
- Accept: "application/json",
244
- "Content-Type": "application/json",
245
- "X-Shopify-Shop-Domain": shop
246
- },
247
- body: JSON.stringify(data)
248
- });
249
- if (response.status === 403) {
250
- const data2 = await response.json();
251
- if ("error_description" in data2) {
252
- if (data2.error_description === "Wrong email or verification code.") {
253
- throw new AuthenticationError(
254
- "wrong-code"
255
- /* IncorrectCode */
256
- );
257
- } else if (data2.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
258
- throw new AuthenticationError(
259
- "too-many-tries"
260
- /* TooManyTries */
261
- );
262
- } else if (data2.error_description === "The verification code has expired. Please try to login again.") {
263
- throw new AuthenticationError(
264
- "expired"
265
- /* Expired */
266
- );
267
- }
268
- } else if ("code" in data2) {
269
- if (data2.code === "AUTH_ERROR") {
270
- throw new AuthenticationError("no-email", {
271
- connection: data2.connection
272
- });
273
- }
274
- }
275
- }
276
- if (response.status !== 200) {
277
- throw new Error(`Unexpected response: ${response.statusText}`);
278
- }
279
- return await response.json();
280
- }
281
- return { start, authenticate };
282
- }
283
475
  function nonce(len = 10) {
284
476
  return uuid().replace(/-/g, "").slice(0, len);
285
477
  }
286
- function createAuthService(apiUrl, shopDomain) {
287
- const { start: authStart, authenticate: authAuthenticate } = createAuthFunctions(apiUrl, shopDomain);
478
+ function loginService(deps) {
479
+ const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
480
+ const appUrl = (typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io";
481
+ const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
482
+ const adapter = createApiLoginAdapter(fetcher, {
483
+ shopDomain: deps.shop.domain,
484
+ appUrl
485
+ });
486
+ return createLoginService({
487
+ adapter,
488
+ router: deps.router,
489
+ formatError: deps.formatError,
490
+ shop: {
491
+ domain: deps.shop.domain,
492
+ locale: w(() => deps.getLocale())
493
+ },
494
+ onLoginSuccess: deps.onLoginSuccess
495
+ });
496
+ }
497
+ function createLoginService(init) {
498
+ const { adapter, formatError, router, shop, onLoginSuccess } = init;
288
499
  const accessToken = d(null);
289
- const delegatedToken = d(
290
- storage(STORAGE_DELEGATED_TOKEN)
291
- );
292
- const refreshToken = d(
293
- storage(STORAGE_REFRESH_TOKEN_KEY)
294
- );
500
+ const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
501
+ const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
295
502
  const expires = d(null);
296
503
  const state = d(storage(STORAGE_STATE_KEY));
297
504
  E(() => {
@@ -303,67 +510,28 @@ function createAuthService(apiUrl, shopDomain) {
303
510
  E(() => {
304
511
  storage(STORAGE_STATE_KEY, state.value);
305
512
  });
306
- function store(data) {
513
+ function storeTokens(data) {
307
514
  const { access_token, refresh_token, expires_in } = data;
308
515
  accessToken.value = access_token;
309
516
  refreshToken.value = refresh_token;
310
517
  expires.value = new Date(Date.now() + expires_in);
311
518
  }
312
- return {
313
- state,
314
- async start(email, lang) {
315
- await authStart(email, lang);
316
- },
317
- async authenticate(data) {
318
- store(await authAuthenticate(data));
319
- },
320
- getDelegatedToken() {
321
- return delegatedToken.value;
322
- },
323
- setDelegatedToken(token) {
324
- delegatedToken.value = token;
325
- },
326
- async getToken() {
327
- if (refreshToken.value == null) {
328
- return null;
329
- }
330
- if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
331
- accessToken.value = new Promise(
332
- (resolve, reject) => authAuthenticate({ refreshToken: refreshToken.value }).then((data) => data).then((data) => {
333
- store(data);
334
- resolve(data.access_token);
519
+ async function getToken() {
520
+ if (refreshToken.value == null) return null;
521
+ if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
522
+ accessToken.value = new Promise(
523
+ (resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
524
+ if (result._tag === "Failure") {
525
+ reject(result.error);
335
526
  return;
336
- }).catch(reject)
337
- );
338
- }
339
- return Promise.resolve(accessToken.value);
340
- },
341
- logout() {
342
- accessToken.value = null;
343
- delegatedToken.value = null;
344
- refreshToken.value = null;
345
- expires.value = null;
346
- },
347
- isAuthenticated: w(
348
- () => delegatedToken.value != null || refreshToken.value != null
349
- )
350
- };
351
- }
352
- function loginService(deps) {
353
- return createLoginService({
354
- apiUrl: `${deps.shop.rootUrl.replace(/\/$/, "")}/api`,
355
- router: deps.router,
356
- formatError: deps.formatError,
357
- shop: {
358
- domain: deps.shop.domain,
359
- locale: w(() => deps.getLocale())
360
- },
361
- onLoginSuccess: deps.onLoginSuccess
362
- });
363
- }
364
- function createLoginService(init) {
365
- const { formatError, router, shop, onLoginSuccess } = init;
366
- const auth = createAuthService(init.apiUrl.replace(/\/$/, ""), shop.domain);
527
+ }
528
+ storeTokens(result.data);
529
+ resolve(result.data.access_token);
530
+ }).catch(reject)
531
+ );
532
+ }
533
+ return Promise.resolve(accessToken.value);
534
+ }
367
535
  const userEmail = d(null);
368
536
  async function onSuccess(query) {
369
537
  await (onLoginSuccess == null ? void 0 : onLoginSuccess());
@@ -376,13 +544,9 @@ function createLoginService(init) {
376
544
  queryParams[key] = value;
377
545
  }
378
546
  });
379
- router.push({
380
- path: decodeURIComponent(redirect),
381
- query: queryParams
382
- });
547
+ router.push({ path: decodeURIComponent(redirect), query: queryParams });
383
548
  } else {
384
- const redirect = query.get(PARAMS.REDIRECT_TO);
385
- router.replace(decodeURIComponent(redirect));
549
+ router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
386
550
  }
387
551
  } else {
388
552
  router.replace("/");
@@ -394,16 +558,17 @@ function createLoginService(init) {
394
558
  async start(email) {
395
559
  try {
396
560
  userEmail.value = email;
397
- await auth.start(
398
- userEmail.value,
399
- shop.locale instanceof u ? shop.locale.value : shop.locale
400
- );
561
+ const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
562
+ const result = await adapter.passwordlessStart({
563
+ email: userEmail.value,
564
+ lang
565
+ });
566
+ if (result._tag === "Failure") {
567
+ return { _tag: "Failure", error: formatError(result.error) };
568
+ }
401
569
  return { _tag: "Success", data: void 0 };
402
570
  } catch (error) {
403
- return {
404
- _tag: "Failure",
405
- error: formatError(error)
406
- };
571
+ return { _tag: "Failure", error: formatError(error) };
407
572
  }
408
573
  },
409
574
  async verify(email, code) {
@@ -414,63 +579,57 @@ function createLoginService(init) {
414
579
  };
415
580
  }
416
581
  try {
417
- const result = await auth.authenticate({
582
+ const result = await adapter.authenticate({
418
583
  email: userEmail.value,
419
584
  code
420
585
  });
586
+ if (result._tag === "Failure") {
587
+ return { _tag: "Failure", error: formatError(result.error) };
588
+ }
589
+ storeTokens(result.data);
421
590
  await onSuccess();
422
- return { _tag: "Success", data: result };
591
+ return { _tag: "Success", data: void 0 };
423
592
  } catch (error) {
424
- return {
425
- _tag: "Failure",
426
- error: formatError(error)
427
- };
593
+ return { _tag: "Failure", error: formatError(error) };
428
594
  }
429
595
  }
430
596
  }
431
597
  },
432
598
  social: {
433
599
  async start(provider) {
434
- function socialUrl(key, shop2) {
435
- auth.state.value = nonce();
436
- const url = new URL(
437
- // TODO: double check admin url
438
- `${"https://admin.juo.io"}/auth/customer/authorize`
439
- );
440
- url.searchParams.append("connection", key);
441
- url.searchParams.append("state", auth.state.value ?? "");
442
- url.searchParams.append("shop", shop2);
443
- return url.toString();
444
- }
445
- window.location.href = socialUrl(
446
- provider === "Facebook" ? "facebook" : "google-oauth2",
447
- shop.domain
448
- );
449
- return {
450
- _tag: "Success",
451
- data: void 0
452
- };
600
+ state.value = nonce();
601
+ const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
602
+ window.location.href = adapter.getAuthorizeUrl({
603
+ connection,
604
+ state: state.value ?? "",
605
+ shop: shop.domain
606
+ });
607
+ return { _tag: "Success", data: void 0 };
453
608
  },
454
609
  async verify(code) {
455
610
  try {
456
- const result = await auth.authenticate({ code });
611
+ const result = await adapter.authenticate({ code });
612
+ if (result._tag === "Failure") {
613
+ return { _tag: "Failure", error: formatError(result.error) };
614
+ }
615
+ storeTokens(result.data);
457
616
  await onSuccess();
458
- return { _tag: "Success", data: result };
617
+ return { _tag: "Success", data: void 0 };
459
618
  } catch (error) {
460
- return {
461
- _tag: "Failure",
462
- error: formatError(error)
463
- };
619
+ return { _tag: "Failure", error: formatError(error) };
464
620
  }
465
621
  }
466
622
  },
467
623
  async logout() {
468
- await auth.logout();
624
+ accessToken.value = null;
625
+ delegatedToken.value = null;
626
+ refreshToken.value = null;
627
+ expires.value = null;
469
628
  return { _tag: "Success", data: void 0 };
470
629
  },
471
630
  async handleQueryParams(query) {
472
631
  if (query.has(PARAMS.DELETGATED_TOKEN)) {
473
- auth.setDelegatedToken(query.get(PARAMS.DELETGATED_TOKEN));
632
+ delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
474
633
  await onSuccess(query);
475
634
  return { _tag: "Success", data: void 0 };
476
635
  }
@@ -482,16 +641,13 @@ function createLoginService(init) {
482
641
  }
483
642
  const code = query.get(PARAMS.CODE);
484
643
  if (code != null) {
485
- if (query.get(PARAMS.STATE) !== auth.state.value) {
486
- return {
487
- _tag: "Failure",
488
- error: formatError("Invalid state")
489
- };
644
+ if (query.get(PARAMS.STATE) !== state.value) {
645
+ return { _tag: "Failure", error: formatError("Invalid state") };
490
646
  } else {
491
647
  return service.social.verify(code);
492
648
  }
493
649
  }
494
- if (query.get(PARAMS.REFRESH) === "1" && await auth.getToken() != null) {
650
+ if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
495
651
  await onSuccess();
496
652
  return { _tag: "Success", data: void 0 };
497
653
  }
@@ -508,13 +664,25 @@ function createLoginService(init) {
508
664
  }
509
665
  return { _tag: "Success", data: void 0 };
510
666
  },
511
- isAuthenticated: auth.isAuthenticated,
512
- getToken: () => auth.getToken()
667
+ isAuthenticated: w(
668
+ () => delegatedToken.value != null || refreshToken.value != null
669
+ ),
670
+ getToken,
671
+ async getAuthHeaders() {
672
+ if (delegatedToken.value) {
673
+ return { "X-Delegated-Token": delegatedToken.value };
674
+ }
675
+ const token = await getToken();
676
+ if (token) {
677
+ return { Authorization: `Bearer ${token}` };
678
+ }
679
+ return {};
680
+ }
513
681
  };
514
682
  return service;
515
683
  }
516
684
  const LoginServiceContext = createContext("services/login");
517
- function createMockOrdersAdapter() {
685
+ function createMockOrderAdapter() {
518
686
  const mockOrders = [
519
687
  {
520
688
  id: "10001",
@@ -575,7 +743,56 @@ function createMockOrdersAdapter() {
575
743
  }
576
744
  };
577
745
  }
578
- function createOrdersService(adapter) {
746
+ function mapOrderLine(item) {
747
+ var _a;
748
+ return {
749
+ id: item.id,
750
+ title: item.title,
751
+ quantity: item.quantity,
752
+ variantImage: ((_a = item.variantImage) == null ? void 0 : _a.url) ? { url: item.variantImage.url, altText: item.variantImage.altText ?? void 0 } : null,
753
+ customAttributes: item.customAttributes
754
+ };
755
+ }
756
+ function mapOrder(order) {
757
+ return {
758
+ id: order.id,
759
+ createdAt: order.createdAt,
760
+ placedAt: order.placedAt,
761
+ currencyCode: order.currencyCode,
762
+ totalPrice: order.totalPrice,
763
+ financialStatus: order.financialStatus,
764
+ fulfillmentStatus: order.fulfillmentStatus,
765
+ lines: order.items.map(mapOrderLine),
766
+ customAttributes: order.customAttributes
767
+ };
768
+ }
769
+ function createApiOrderAdapter(fetcher) {
770
+ return {
771
+ async getOrders(params) {
772
+ const query = new URLSearchParams();
773
+ if ((params == null ? void 0 : params.limit) != null) query.set("limit", String(params.limit));
774
+ if ((params == null ? void 0 : params.after) != null) query.set("after", params.after);
775
+ if ((params == null ? void 0 : params.before) != null) query.set("before", params.before);
776
+ if ((params == null ? void 0 : params.query) != null) query.set("query", params.query);
777
+ if ((params == null ? void 0 : params.includeOneTime) != null) query.set("includeOneTime", String(params.includeOneTime));
778
+ if ((params == null ? void 0 : params.sort) != null) query.set("sort", params.sort);
779
+ const qs = query.toString();
780
+ const res = await fetcher(`/customer/v1/orders${qs ? `?${qs}` : ""}`);
781
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
782
+ const body = await res.json();
783
+ return {
784
+ items: body.data.map(mapOrder),
785
+ pageInfo: {
786
+ hasNextPage: body.hasNextPage,
787
+ hasPrevPage: body.hasPrevPage,
788
+ startCursor: body.startCursor,
789
+ endCursor: body.endCursor
790
+ }
791
+ };
792
+ }
793
+ };
794
+ }
795
+ function createOrderService(adapter) {
579
796
  return {
580
797
  async getOrders(params) {
581
798
  try {
@@ -587,14 +804,14 @@ function createOrdersService(adapter) {
587
804
  }
588
805
  };
589
806
  }
590
- const OrdersServiceContext = createContext("services/orders");
807
+ const OrderServiceContext = createContext("services/order");
591
808
  function createMockProductAdapter() {
592
809
  const products = [
593
810
  {
594
811
  id: "sample-1",
595
812
  title: "Hydrating Gel Cleanser for Face and Eye Area",
596
813
  featuredImage: {
597
- url: "https://via.placeholder.com/400x400?text=Product+1"
814
+ url: "https://placehold.co/400x400?text=Product+1"
598
815
  },
599
816
  hasOnlyDefaultVariant: true,
600
817
  variants: [
@@ -602,7 +819,7 @@ function createMockProductAdapter() {
602
819
  id: "sample-1-1",
603
820
  title: "150 ml bottle",
604
821
  image: {
605
- url: "https://via.placeholder.com/400x400?text=Product+1"
822
+ url: "https://placehold.co/400x400?text=Product+1"
606
823
  },
607
824
  price: {
608
825
  amount: "12.49",
@@ -628,7 +845,7 @@ function createMockProductAdapter() {
628
845
  id: "sample-2",
629
846
  title: "More COLLAGEN",
630
847
  featuredImage: {
631
- url: "https://via.placeholder.com/400x400?text=Product+2"
848
+ url: "https://placehold.co/400x400?text=Product+2"
632
849
  },
633
850
  hasOnlyDefaultVariant: true,
634
851
  variants: [
@@ -636,7 +853,7 @@ function createMockProductAdapter() {
636
853
  id: "sample-2-1",
637
854
  title: "blackcurrant",
638
855
  image: {
639
- url: "https://via.placeholder.com/400x400?text=Product+2"
856
+ url: "https://placehold.co/400x400?text=Product+2"
640
857
  },
641
858
  price: {
642
859
  amount: "12.49",
@@ -657,7 +874,7 @@ function createMockProductAdapter() {
657
874
  id: "sample-3",
658
875
  title: "Gentle deodorant for underarms and bust",
659
876
  featuredImage: {
660
- url: "https://via.placeholder.com/400x400?text=Product+3"
877
+ url: "https://placehold.co/400x400?text=Product+3"
661
878
  },
662
879
  hasOnlyDefaultVariant: false,
663
880
  variants: [
@@ -665,7 +882,7 @@ function createMockProductAdapter() {
665
882
  id: "sample-3-1",
666
883
  title: "Green tea and jasmine",
667
884
  image: {
668
- url: "https://via.placeholder.com/400x400?text=Product+3a"
885
+ url: "https://placehold.co/400x400?text=Product+3a"
669
886
  },
670
887
  price: {
671
888
  amount: "6.7",
@@ -683,7 +900,7 @@ function createMockProductAdapter() {
683
900
  id: "sample-3-2",
684
901
  title: "Lavender and mint",
685
902
  image: {
686
- url: "https://via.placeholder.com/400x400?text=Product+3b"
903
+ url: "https://placehold.co/400x400?text=Product+3b"
687
904
  },
688
905
  price: {
689
906
  amount: "7.5",
@@ -715,6 +932,48 @@ function createMockProductAdapter() {
715
932
  }
716
933
  };
717
934
  }
935
+ function toAvailableProduct(product) {
936
+ return {
937
+ id: product.id,
938
+ title: product.title,
939
+ description: `Sample description for ${product.title}.`,
940
+ featuredImage: product.featuredImage ?? void 0,
941
+ hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
942
+ variants: product.variants.map((variant) => {
943
+ var _a, _b, _c;
944
+ const base = Number.parseFloat(variant.price.amount);
945
+ const sub = (_b = (_a = variant.purchaseOptions) == null ? void 0 : _a[0]) == null ? void 0 : _b.price.amount;
946
+ const current = sub == null ? base : Number.parseFloat(sub);
947
+ return {
948
+ id: variant.id,
949
+ availableForSale: true,
950
+ title: variant.title,
951
+ currentPrice: { amount: current, currencyCode: variant.price.currencyCode },
952
+ basePrice: { amount: base, currencyCode: variant.price.currencyCode },
953
+ image: variant.image ?? void 0,
954
+ recurring: (((_c = variant.purchaseOptions) == null ? void 0 : _c.length) ?? 0) > 0
955
+ };
956
+ })
957
+ };
958
+ }
959
+ const sampleBox = {
960
+ id: "sample-box-1",
961
+ title: "Build Your Own Box",
962
+ minProductsCount: 3,
963
+ parentProduct: "sample-1",
964
+ tiers: [
965
+ { value: 5, threshold: 3 },
966
+ { value: 10, threshold: 5 }
967
+ ],
968
+ variants: products.map((product) => ({
969
+ id: product.id,
970
+ variants: product.variants.map((variant) => ({ id: variant.id }))
971
+ })),
972
+ freeVariants: [],
973
+ thresholdType: "LINES_COUNT",
974
+ type: "PERCENTAGE",
975
+ restrictQuantityToTiers: false
976
+ };
718
977
  return {
719
978
  async getById(id) {
720
979
  await new Promise((resolve) => setTimeout(resolve, 100));
@@ -728,6 +987,27 @@ function createMockProductAdapter() {
728
987
  );
729
988
  return product == null ? null : processProduct(product);
730
989
  },
990
+ async getProductsByIds({ ids }) {
991
+ await new Promise((resolve) => setTimeout(resolve, 100));
992
+ return products.filter((product) => ids.includes(product.id)).map(({ variants: _variants, ...product }) => product);
993
+ },
994
+ async getBoxVariants({ ids }) {
995
+ await new Promise((resolve) => setTimeout(resolve, 100));
996
+ return products.filter((product) => ids.includes(product.id)).map(toAvailableProduct);
997
+ },
998
+ async getAvailableProducts({ q: q3 }) {
999
+ await new Promise((resolve) => setTimeout(resolve, 100));
1000
+ const queryLower = q3 == null ? void 0 : q3.toLowerCase();
1001
+ return products.filter(
1002
+ (product) => !queryLower || product.title.toLowerCase().includes(queryLower) || product.variants.some(
1003
+ (variant) => variant.title.toLowerCase().includes(queryLower)
1004
+ )
1005
+ ).map(toAvailableProduct);
1006
+ },
1007
+ async getBox({ id }) {
1008
+ await new Promise((resolve) => setTimeout(resolve, 100));
1009
+ return id === sampleBox.id ? sampleBox : null;
1010
+ },
731
1011
  async search(opts) {
732
1012
  await new Promise((resolve) => setTimeout(resolve, 100));
733
1013
  let filteredProducts = [...products];
@@ -768,23 +1048,206 @@ function createMockProductAdapter() {
768
1048
  }
769
1049
  };
770
1050
  }
771
- const ProductServiceContext = createContext("services/product");
772
- function createProductService(adapter) {
1051
+ function mapVariant(variant, currencyCode) {
1052
+ var _a;
773
1053
  return {
774
- async getById(id) {
775
- return adapter.getById(id);
776
- },
777
- async getByVariantId(variantId) {
778
- return adapter.getByVariantId(variantId);
1054
+ id: variant.id,
1055
+ title: variant.title,
1056
+ image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
1057
+ price: { amount: variant.price.toFixed(2), currencyCode },
1058
+ ...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
1059
+ purchaseOptions: variant.purchaseOptions.map((opt) => ({
1060
+ name: opt.name,
1061
+ price: { amount: opt.price.toFixed(2), currencyCode },
1062
+ interval: opt.interval
1063
+ }))
1064
+ } : {}
1065
+ };
1066
+ }
1067
+ function mapProduct(product, currencyCode) {
1068
+ return {
1069
+ id: product.id,
1070
+ title: product.title,
1071
+ featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
1072
+ hasOnlyDefaultVariant: product.variants.length <= 1,
1073
+ variants: {
1074
+ items: product.variants.map((v3) => mapVariant(v3, currencyCode)),
1075
+ pageInfo: {
1076
+ hasNextPage: false,
1077
+ hasPrevPage: false,
1078
+ startCursor: null,
1079
+ endCursor: null
1080
+ }
779
1081
  },
780
- async search(opts) {
781
- return adapter.search(opts);
782
- }
1082
+ collections: product.collections
783
1083
  };
784
1084
  }
785
- function routeNameTableFromPages(pages) {
786
- const table = {};
787
- for (const page of pages) {
1085
+ function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
1086
+ const { currencyCode } = opts;
1087
+ async function fetchProducts(params) {
1088
+ const query = params.toString() ? `?${params.toString()}` : "";
1089
+ const res = await fetcher(`/customer/v1/products${query}`);
1090
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
1091
+ return res.json();
1092
+ }
1093
+ return {
1094
+ async getById(id) {
1095
+ try {
1096
+ const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
1097
+ const body = await fetchProducts(params);
1098
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
1099
+ } catch {
1100
+ return null;
1101
+ }
1102
+ },
1103
+ async getByVariantId(variantId) {
1104
+ try {
1105
+ const params = new URLSearchParams({
1106
+ query: `variants.id:"${variantId}"`,
1107
+ limit: "1"
1108
+ });
1109
+ const body = await fetchProducts(params);
1110
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
1111
+ } catch {
1112
+ return null;
1113
+ }
1114
+ },
1115
+ async search(opts2) {
1116
+ try {
1117
+ const params = new URLSearchParams();
1118
+ if (opts2 == null ? void 0 : opts2.query) {
1119
+ params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
1120
+ }
1121
+ if (opts2 == null ? void 0 : opts2.pagination) {
1122
+ if ("after" in opts2.pagination)
1123
+ params.set("after", opts2.pagination.after);
1124
+ else if ("before" in opts2.pagination)
1125
+ params.set("before", opts2.pagination.before);
1126
+ }
1127
+ const body = await fetchProducts(params);
1128
+ return {
1129
+ items: body.data.map((p3) => mapProduct(p3, currencyCode)),
1130
+ pageInfo: {
1131
+ hasNextPage: body.hasNextPage,
1132
+ hasPrevPage: body.hasPrevPage,
1133
+ startCursor: body.startCursor,
1134
+ endCursor: body.endCursor
1135
+ }
1136
+ };
1137
+ } catch {
1138
+ return {
1139
+ items: [],
1140
+ pageInfo: {
1141
+ hasNextPage: false,
1142
+ hasPrevPage: false,
1143
+ startCursor: null,
1144
+ endCursor: null
1145
+ }
1146
+ };
1147
+ }
1148
+ },
1149
+ async getProductsByIds({ ids }) {
1150
+ const params = new URLSearchParams();
1151
+ for (const id of ids) params.append("ids", id);
1152
+ const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
1153
+ if (!res.ok) return [];
1154
+ const data = await res.json();
1155
+ return (data ?? []).map((p3) => ({
1156
+ id: p3.id,
1157
+ title: p3.title ?? "",
1158
+ featuredImage: p3.featuredImage ? { url: p3.featuredImage.url, altText: p3.featuredImage.altText } : null,
1159
+ hasOnlyDefaultVariant: true,
1160
+ collections: []
1161
+ }));
1162
+ },
1163
+ async getBoxVariants({ ids, subscriptionId }) {
1164
+ const params = new URLSearchParams();
1165
+ for (const id of ids) params.append("ids", id);
1166
+ params.set("subscriptionId", subscriptionId);
1167
+ const res = await fetcher(`/customer/products/variants?${params.toString()}`);
1168
+ if (!res.ok) return [];
1169
+ const data = await res.json();
1170
+ return (data ?? []).map((p3) => {
1171
+ var _a;
1172
+ return {
1173
+ id: p3.id,
1174
+ title: p3.title,
1175
+ description: p3.description ?? "",
1176
+ featuredImage: p3.featuredImage,
1177
+ hasOnlyDefaultVariant: p3.hasOnlyDefaultVariant ?? (((_a = p3.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
1178
+ variants: (p3.variants ?? []).map((v3) => ({
1179
+ id: v3.id,
1180
+ availableForSale: v3.availableForSale,
1181
+ title: v3.title,
1182
+ currentPrice: v3.currentPrice,
1183
+ basePrice: v3.basePrice,
1184
+ image: v3.image,
1185
+ recurring: v3.recurring
1186
+ }))
1187
+ };
1188
+ });
1189
+ },
1190
+ async getAvailableProducts({ subscriptionId, lineId, q: q3, mode, action, pagination }) {
1191
+ const params = new URLSearchParams();
1192
+ params.set("action", action);
1193
+ if (q3) params.set("q", q3);
1194
+ if (mode) params.set("mode", mode);
1195
+ if (pagination && "after" in pagination) params.set("after", pagination.after);
1196
+ if (pagination && "before" in pagination) params.set("before", pagination.before);
1197
+ const res = await fetcher(
1198
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
1199
+ );
1200
+ if (!res.ok) return [];
1201
+ const data = await res.json();
1202
+ return (data ?? []).map((p3) => {
1203
+ var _a;
1204
+ return {
1205
+ id: p3.id,
1206
+ title: p3.title,
1207
+ description: p3.description ?? "",
1208
+ featuredImage: p3.featuredImage,
1209
+ hasOnlyDefaultVariant: p3.hasOnlyDefaultVariant ?? (((_a = p3.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
1210
+ variants: (p3.variants ?? []).map((v3) => ({
1211
+ id: v3.id,
1212
+ availableForSale: v3.availableForSale,
1213
+ title: v3.title,
1214
+ currentPrice: v3.currentPrice,
1215
+ basePrice: v3.basePrice,
1216
+ image: v3.image,
1217
+ recurring: v3.recurring
1218
+ }))
1219
+ };
1220
+ });
1221
+ },
1222
+ async getBox({ id }) {
1223
+ const res = await fetcher(`/customer/boxes/${id}`);
1224
+ if (!res.ok) return null;
1225
+ const data = await res.json();
1226
+ return { id, ...data };
1227
+ }
1228
+ };
1229
+ }
1230
+ const ProductServiceContext = createContext("services/product");
1231
+ function createProductService(adapter) {
1232
+ return {
1233
+ async getById(id) {
1234
+ return adapter.getById(id);
1235
+ },
1236
+ async getByVariantId(variantId) {
1237
+ return adapter.getByVariantId(variantId);
1238
+ },
1239
+ async search(opts) {
1240
+ return adapter.search(opts);
1241
+ },
1242
+ getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
1243
+ getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
1244
+ getAvailableProducts: (params) => adapter.getAvailableProducts(params),
1245
+ getBox: (id) => adapter.getBox({ id })
1246
+ };
1247
+ }
1248
+ function routeNameTableFromPages(pages) {
1249
+ const table = {};
1250
+ for (const page of pages) {
788
1251
  const names = page.routeNames;
789
1252
  if (names.length === 0) continue;
790
1253
  for (const name of names) {
@@ -796,7 +1259,7 @@ function routeNameTableFromPages(pages) {
796
1259
  function createRouterService(options) {
797
1260
  const { pages, pushImplementation } = options;
798
1261
  const routeNameToPagePath = routeNameTableFromPages(pages);
799
- const pathSet = new Set(pages.map((p2) => p2.path));
1262
+ const pathSet = new Set(pages.map((p3) => p3.path));
800
1263
  const navigationSubscribers = [];
801
1264
  function notifySubscribers(logicalPath) {
802
1265
  for (const cb of navigationSubscribers) cb({ path: logicalPath });
@@ -833,7 +1296,85 @@ function createMockRouterService() {
833
1296
  }
834
1297
  }).routerService;
835
1298
  }
1299
+ const DISCOUNT_REJECTION_REASONS = [
1300
+ "INVALID_CODE",
1301
+ "INACTIVE",
1302
+ "NOT_APPLICABLE_TO_CUSTOMER",
1303
+ "ALREADY_ADDED",
1304
+ "ALREADY_USED",
1305
+ "NOT_APPLICABLE_TO_PRODUCTS",
1306
+ "NOT_APPLICABLE_TO_SUBSCRIPTION",
1307
+ "INVALID_COMBINATION",
1308
+ "QUANTITY_REQUIREMENT_NOT_MET"
1309
+ ];
1310
+ function toAttributeArray(next) {
1311
+ return Object.entries(next).map(([key, value]) => ({ key, value }));
1312
+ }
836
1313
  function createMockSubscriptionAdapter(options = {}) {
1314
+ const richBase = (id, itemId, renewalId) => ({
1315
+ id,
1316
+ customerId: "customer_1",
1317
+ items: [
1318
+ {
1319
+ id: itemId,
1320
+ // Aligned with the mock product catalog (createMockProductAdapter):
1321
+ // sample-3 "Gentle deodorant", variant sample-3-1 "Green tea and jasmine",
1322
+ // recurring price 5.36 EUR (10% off → 4.82 with SAVE10).
1323
+ productId: "sample-3",
1324
+ variantId: "sample-3-1",
1325
+ quantity: 2,
1326
+ price: { amount: "5.36", currencyCode: "EUR" },
1327
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1328
+ isUpsell: false,
1329
+ isVirtual: false,
1330
+ nextBillingDate: "2025-11-15T00:00:00Z",
1331
+ currentPrice: { amount: "5.36", currencyCode: "EUR" },
1332
+ lineDiscountedPrice: { amount: "4.82", currencyCode: "EUR" },
1333
+ variantAvailableForSale: true,
1334
+ title: "Gentle deodorant for underarms and bust",
1335
+ variantTitle: "Green tea and jasmine",
1336
+ variantImage: { url: "https://placehold.co/400x400?text=Product+3a" },
1337
+ billingPolicy: { intervalCount: 1, interval: "MONTH" },
1338
+ deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
1339
+ }
1340
+ ],
1341
+ discounts: [
1342
+ {
1343
+ id: "discount_1",
1344
+ type: "percentage",
1345
+ value: 10,
1346
+ code: "SAVE10",
1347
+ description: "10% off subscription"
1348
+ }
1349
+ ],
1350
+ status: "active",
1351
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1352
+ frequency: "1 MONTH",
1353
+ createdAt: "2024-10-01T00:00:00Z",
1354
+ updatedAt: "2024-10-14T00:00:00Z",
1355
+ billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
1356
+ deliveryPolicy: {
1357
+ intervalCount: 1,
1358
+ interval: "MONTH",
1359
+ anchors: [{ day: 15, type: "MONTHDAY" }]
1360
+ },
1361
+ nextBillingDate: "2025-11-15T00:00:00Z",
1362
+ nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
1363
+ lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
1364
+ nextFulfillment: "2025-11-17T00:00:00Z",
1365
+ estimatedOrderDate: "2025-11-13T00:00:00Z",
1366
+ customerPaymentMethod: {
1367
+ id: "pm_1",
1368
+ instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
1369
+ active: true,
1370
+ isRecurring: true
1371
+ },
1372
+ requiredBillingCyclesLeft: 3,
1373
+ renewal: { id: renewalId, isInActiveProcessState: false },
1374
+ lastPaymentStatus: "succeeded",
1375
+ pausePeriod: null,
1376
+ isEditable: true
1377
+ });
837
1378
  const mockSubscriptions = [
838
1379
  {
839
1380
  id: "sub_1",
@@ -841,14 +1382,27 @@ function createMockSubscriptionAdapter(options = {}) {
841
1382
  items: [
842
1383
  {
843
1384
  id: "item_1",
844
- productId: "product_1",
845
- variantId: "variant_1",
1385
+ // Aligned with the mock product catalog (createMockProductAdapter):
1386
+ // sample-1 "Hydrating Gel Cleanser" (default variant), recurring price
1387
+ // 9.99 EUR (10% off → 8.99 with SAVE10).
1388
+ productId: "sample-1",
1389
+ variantId: "sample-1-1",
846
1390
  quantity: 2,
847
- price: { amount: "29.99", currencyCode: "USD" },
1391
+ price: { amount: "9.99", currencyCode: "EUR" },
848
1392
  nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1393
+ isUpsell: false,
1394
+ isVirtual: false,
1395
+ nextBillingDate: "2025-11-15T00:00:00Z",
1396
+ currentPrice: { amount: "9.99", currencyCode: "EUR" },
1397
+ lineDiscountedPrice: { amount: "8.99", currencyCode: "EUR" },
1398
+ variantAvailableForSale: true,
1399
+ title: "Hydrating Gel Cleanser for Face and Eye Area",
1400
+ variantImage: { url: "https://placehold.co/400x400?text=Product+1" },
1401
+ billingPolicy: { intervalCount: 1, interval: "MONTH" },
1402
+ deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
849
1403
  customAttributes: [
850
- { key: "roast_level", value: "medium" },
851
- { key: "grind_type", value: "whole_bean" }
1404
+ { key: "skin_type", value: "combination" },
1405
+ { key: "fragrance", value: "fragrance_free" }
852
1406
  ]
853
1407
  }
854
1408
  ],
@@ -866,9 +1420,31 @@ function createMockSubscriptionAdapter(options = {}) {
866
1420
  frequency: "1 MONTH",
867
1421
  createdAt: "2024-10-01T00:00:00Z",
868
1422
  updatedAt: "2024-10-14T00:00:00Z",
1423
+ billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
1424
+ deliveryPolicy: {
1425
+ intervalCount: 1,
1426
+ interval: "MONTH",
1427
+ anchors: [{ day: 15, type: "MONTHDAY" }]
1428
+ },
1429
+ nextBillingDate: "2025-11-15T00:00:00Z",
1430
+ nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
1431
+ lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
1432
+ nextFulfillment: "2025-11-17T00:00:00Z",
1433
+ estimatedOrderDate: "2025-11-13T00:00:00Z",
1434
+ customerPaymentMethod: {
1435
+ id: "pm_1",
1436
+ instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
1437
+ active: true,
1438
+ isRecurring: true
1439
+ },
1440
+ requiredBillingCyclesLeft: 3,
1441
+ renewal: { id: "ren_1", isInActiveProcessState: false },
1442
+ lastPaymentStatus: "succeeded",
1443
+ pausePeriod: null,
1444
+ isEditable: true,
869
1445
  customAttributes: [
870
1446
  { key: "subscription_tier", value: "premium" },
871
- { key: "gift_message", value: "Enjoy your coffee!" }
1447
+ { key: "gift_message", value: "Enjoy your skincare routine!" }
872
1448
  ]
873
1449
  },
874
1450
  {
@@ -877,11 +1453,17 @@ function createMockSubscriptionAdapter(options = {}) {
877
1453
  items: [
878
1454
  {
879
1455
  id: "item_2",
880
- productId: "product_2",
881
- variantId: "variant_2",
1456
+ // Aligned with the mock product catalog (createMockProductAdapter):
1457
+ // sample-2 "More COLLAGEN" (default variant), recurring price 10.99 EUR.
1458
+ productId: "sample-2",
1459
+ variantId: "sample-2-1",
882
1460
  quantity: 1,
883
- price: { amount: "49.99", currencyCode: "USD" },
1461
+ price: { amount: "10.99", currencyCode: "EUR" },
884
1462
  nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
1463
+ title: "More COLLAGEN",
1464
+ variantImage: { url: "https://placehold.co/400x400?text=Product+2" },
1465
+ billingPolicy: { intervalCount: 2, interval: "WEEK" },
1466
+ deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
885
1467
  customAttributes: []
886
1468
  }
887
1469
  ],
@@ -892,17 +1474,66 @@ function createMockSubscriptionAdapter(options = {}) {
892
1474
  createdAt: "2024-09-15T00:00:00Z",
893
1475
  updatedAt: "2024-10-10T00:00:00Z",
894
1476
  customAttributes: []
1477
+ },
1478
+ // Per-status fixtures: groundwork for the deferred editor preview picker
1479
+ // (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
1480
+ // Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
1481
+ {
1482
+ ...richBase("sub_paused", "item_paused", "ren_paused"),
1483
+ status: "paused",
1484
+ pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
1485
+ },
1486
+ {
1487
+ ...richBase("sub_canceled", "item_canceled", "ren_canceled"),
1488
+ status: "canceled",
1489
+ pausePeriod: null
1490
+ },
1491
+ {
1492
+ ...richBase("sub_failed", "item_failed", "ren_failed"),
1493
+ status: "failed",
1494
+ lastPaymentStatus: "failed",
1495
+ pausePeriod: null
1496
+ },
1497
+ {
1498
+ ...richBase("sub_expired", "item_expired", "ren_expired"),
1499
+ status: "expired",
1500
+ pausePeriod: null
1501
+ }
1502
+ ];
1503
+ const mockUpsellProducts = [
1504
+ {
1505
+ id: "upsell-1",
1506
+ variantId: "upsell-v-1",
1507
+ title: "Retinol Night Cream",
1508
+ imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
1509
+ price: { amount: "38.00", currencyCode: "USD" },
1510
+ description: "Anti-aging night treatment"
1511
+ },
1512
+ {
1513
+ id: "upsell-2",
1514
+ variantId: "upsell-v-2",
1515
+ title: "SPF 50 Day Shield",
1516
+ imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
1517
+ price: { amount: "24.00", currencyCode: "USD" },
1518
+ description: "Lightweight daily sun protection"
895
1519
  }
896
1520
  ];
897
1521
  return {
898
1522
  async getById(id) {
899
1523
  await new Promise((resolve) => setTimeout(resolve, 300));
900
- const subscription = mockSubscriptions.find((sub) => sub.id === id) || null;
901
- return { _tag: "Success", data: subscription };
1524
+ const subscription = mockSubscriptions.find((sub) => sub.id === id);
1525
+ return {
1526
+ _tag: "Success",
1527
+ data: subscription ? {
1528
+ ...subscription,
1529
+ items: subscription.items.map((item) => ({ ...item })),
1530
+ discounts: subscription.discounts.map((d2) => ({ ...d2 }))
1531
+ } : null
1532
+ };
902
1533
  },
903
1534
  async search(options2 = {}) {
904
1535
  await new Promise((resolve) => setTimeout(resolve, 300));
905
- const pageSize = 1;
1536
+ const pageSize = 2;
906
1537
  const allItems = mockSubscriptions;
907
1538
  let startIndex = 0;
908
1539
  let endIndex = pageSize;
@@ -918,29 +1549,108 @@ function createMockSubscriptionAdapter(options = {}) {
918
1549
  }
919
1550
  }
920
1551
  const pageItems = allItems.slice(startIndex, endIndex);
921
- const paginatedList = {
922
- items: pageItems.map((sub) => ({
923
- ...sub,
924
- items: sub.items.map((item) => ({ ...item })),
925
- discounts: sub.discounts.map((discount) => ({ ...discount }))
926
- })),
927
- pageInfo: {
928
- hasNextPage: endIndex < allItems.length,
929
- hasPrevPage: startIndex > 0,
930
- startCursor: pageItems.length > 0 ? startIndex.toString() : null,
931
- endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
1552
+ return {
1553
+ _tag: "Success",
1554
+ data: {
1555
+ items: pageItems.map((sub) => ({
1556
+ ...sub,
1557
+ items: sub.items.map((item) => ({ ...item })),
1558
+ discounts: sub.discounts.map((discount) => ({ ...discount }))
1559
+ })),
1560
+ pageInfo: {
1561
+ hasNextPage: endIndex < allItems.length,
1562
+ hasPrevPage: startIndex > 0,
1563
+ startCursor: pageItems.length > 0 ? startIndex.toString() : null,
1564
+ endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
1565
+ }
932
1566
  }
933
1567
  };
934
- return { _tag: "Success", data: paginatedList };
935
1568
  },
936
- async addItem() {
1569
+ async addItem({ subscriptionId, item }) {
1570
+ var _a, _b, _c, _d, _e, _f, _g;
1571
+ await new Promise((resolve) => setTimeout(resolve, 300));
1572
+ const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
1573
+ if (!subscription) {
1574
+ return { _tag: "Failure", error: "Subscription not found" };
1575
+ }
1576
+ const currencyCode = ((_a = subscription.items[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD";
1577
+ const product = await ((_b = options.productAdapter) == null ? void 0 : _b.getByVariantId(item.variantId)) ?? await ((_c = options.productAdapter) == null ? void 0 : _c.getById(item.variantId)) ?? null;
1578
+ const variant = (product == null ? void 0 : product.variants.items.find((v3) => v3.id === item.variantId)) ?? (product == null ? void 0 : product.variants.items[0]);
1579
+ const linePrice = ((_e = (_d = variant == null ? void 0 : variant.purchaseOptions) == null ? void 0 : _d[0]) == null ? void 0 : _e.price) ?? (variant == null ? void 0 : variant.price);
1580
+ subscription.items.push({
1581
+ id: `item_${Date.now()}_${subscription.items.length}`,
1582
+ productId: (product == null ? void 0 : product.id) ?? "",
1583
+ variantId: item.variantId,
1584
+ quantity: item.quantity,
1585
+ price: linePrice ? { ...linePrice } : { amount: "0.00", currencyCode },
1586
+ ...linePrice ? { currentPrice: { ...linePrice } } : {},
1587
+ title: (product == null ? void 0 : product.title) ?? `Product ${item.variantId}`,
1588
+ ...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
1589
+ ...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
1590
+ variantAvailableForSale: true,
1591
+ billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
1592
+ deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
1593
+ });
1594
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1595
+ return { _tag: "Success", data: void 0 };
1596
+ },
1597
+ async removeItem({ subscriptionId, itemId }) {
1598
+ await new Promise((resolve) => setTimeout(resolve, 300));
1599
+ const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
1600
+ if (!subscription) {
1601
+ return { _tag: "Failure", error: "Subscription not found" };
1602
+ }
1603
+ subscription.items = subscription.items.filter((i3) => i3.id !== itemId);
1604
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1605
+ return { _tag: "Success", data: void 0 };
1606
+ },
1607
+ async updateItem({ subscriptionId, lineId, update }) {
1608
+ await new Promise((resolve) => setTimeout(resolve, 300));
1609
+ const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
1610
+ if (!subscription) {
1611
+ return { _tag: "Failure", error: "Subscription not found" };
1612
+ }
1613
+ const item = subscription.items.find((i3) => i3.id === lineId);
1614
+ if (!item) {
1615
+ return { _tag: "Failure", error: "Line item not found" };
1616
+ }
1617
+ if (update.variantId !== void 0) item.variantId = update.variantId;
1618
+ if (update.quantity !== void 0) item.quantity = update.quantity;
1619
+ if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
1620
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1621
+ return { _tag: "Success", data: void 0 };
1622
+ },
1623
+ async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
1624
+ await new Promise((resolve) => setTimeout(resolve, 300));
1625
+ const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
1626
+ if (!subscription) {
1627
+ return { _tag: "Failure", error: "Subscription not found" };
1628
+ }
1629
+ const item = subscription.items.find((i3) => i3.id === lineId);
1630
+ if (!item) {
1631
+ return { _tag: "Failure", error: "Line item not found" };
1632
+ }
1633
+ item.customAttributes = toAttributeArray(customAttributes);
1634
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1635
+ return { _tag: "Success", data: void 0 };
1636
+ },
1637
+ async updateAttributes({ subscriptionId, customAttributes }) {
937
1638
  await new Promise((resolve) => setTimeout(resolve, 300));
1639
+ const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
1640
+ if (!subscription) {
1641
+ return { _tag: "Failure", error: "Subscription not found" };
1642
+ }
1643
+ subscription.customAttributes = toAttributeArray(customAttributes);
1644
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
938
1645
  return { _tag: "Success", data: void 0 };
939
1646
  },
940
- async removeItem() {
1647
+ async skipLineItem() {
941
1648
  await new Promise((resolve) => setTimeout(resolve, 300));
942
1649
  return { _tag: "Success", data: void 0 };
943
1650
  },
1651
+ async updateBoxItems() {
1652
+ return { _tag: "Success", data: void 0 };
1653
+ },
944
1654
  async addDiscount() {
945
1655
  await new Promise((resolve) => setTimeout(resolve, 300));
946
1656
  const behavior = options.addDiscountBehavior ?? { kind: "success" };
@@ -969,24 +1679,29 @@ function createMockSubscriptionAdapter(options = {}) {
969
1679
  (sub) => sub.id === params.subscriptionId
970
1680
  );
971
1681
  if (!subscription) {
972
- return {
973
- _tag: "Failure",
974
- error: "Subscription not found"
975
- };
1682
+ return { _tag: "Failure", error: "Subscription not found" };
976
1683
  }
977
1684
  subscription.nextDeliveryDate = new Date(params.newDate);
978
1685
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
979
- subscription.items.forEach((item) => {
980
- item.nextDeliveryDate = new Date(params.newDate);
981
- });
982
- return {
983
- _tag: "Success",
984
- data: {
985
- ...subscription,
986
- items: subscription.items.map((item) => ({ ...item })),
987
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
988
- }
989
- };
1686
+ return { _tag: "Success", data: { ...subscription } };
1687
+ },
1688
+ async updateCustomAttributes(params) {
1689
+ await new Promise((resolve) => setTimeout(resolve, 300));
1690
+ const subscription = mockSubscriptions.find(
1691
+ (sub) => sub.id === params.subscriptionId
1692
+ );
1693
+ if (!subscription) {
1694
+ return { _tag: "Failure", error: "Subscription not found" };
1695
+ }
1696
+ const preserved = (subscription.customAttributes ?? []).filter(
1697
+ (attr) => attr.key.startsWith("_")
1698
+ );
1699
+ const next = Object.entries(params.customAttributes).map(
1700
+ ([key, value]) => ({ key, value })
1701
+ );
1702
+ subscription.customAttributes = [...preserved, ...next];
1703
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1704
+ return { _tag: "Success", data: { ...subscription } };
990
1705
  },
991
1706
  async resume(params) {
992
1707
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -994,21 +1709,11 @@ function createMockSubscriptionAdapter(options = {}) {
994
1709
  (sub) => sub.id === params.subscriptionId
995
1710
  );
996
1711
  if (!subscription) {
997
- return {
998
- _tag: "Failure",
999
- error: "Subscription not found"
1000
- };
1712
+ return { _tag: "Failure", error: "Subscription not found" };
1001
1713
  }
1002
1714
  subscription.status = "active";
1003
1715
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1004
- return {
1005
- _tag: "Success",
1006
- data: {
1007
- ...subscription,
1008
- items: subscription.items.map((item) => ({ ...item })),
1009
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
1010
- }
1011
- };
1716
+ return { _tag: "Success", data: { ...subscription } };
1012
1717
  },
1013
1718
  async pause(params) {
1014
1719
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1016,21 +1721,11 @@ function createMockSubscriptionAdapter(options = {}) {
1016
1721
  (sub) => sub.id === params.subscriptionId
1017
1722
  );
1018
1723
  if (!subscription) {
1019
- return {
1020
- _tag: "Failure",
1021
- error: "Subscription not found"
1022
- };
1724
+ return { _tag: "Failure", error: "Subscription not found" };
1023
1725
  }
1024
1726
  subscription.status = "paused";
1025
1727
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1026
- return {
1027
- _tag: "Success",
1028
- data: {
1029
- ...subscription,
1030
- items: subscription.items.map((item) => ({ ...item })),
1031
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
1032
- }
1033
- };
1728
+ return { _tag: "Success", data: { ...subscription } };
1034
1729
  },
1035
1730
  async reactivate(params) {
1036
1731
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1038,21 +1733,11 @@ function createMockSubscriptionAdapter(options = {}) {
1038
1733
  (sub) => sub.id === params.subscriptionId
1039
1734
  );
1040
1735
  if (!subscription) {
1041
- return {
1042
- _tag: "Failure",
1043
- error: "Subscription not found"
1044
- };
1736
+ return { _tag: "Failure", error: "Subscription not found" };
1045
1737
  }
1046
1738
  subscription.status = "active";
1047
1739
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1048
- return {
1049
- _tag: "Success",
1050
- data: {
1051
- ...subscription,
1052
- items: subscription.items.map((item) => ({ ...item })),
1053
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
1054
- }
1055
- };
1740
+ return { _tag: "Success", data: { ...subscription } };
1056
1741
  },
1057
1742
  async cancel(params) {
1058
1743
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1060,21 +1745,11 @@ function createMockSubscriptionAdapter(options = {}) {
1060
1745
  (sub) => sub.id === params.subscriptionId
1061
1746
  );
1062
1747
  if (!subscription) {
1063
- return {
1064
- _tag: "Failure",
1065
- error: "Subscription not found"
1066
- };
1748
+ return { _tag: "Failure", error: "Subscription not found" };
1067
1749
  }
1068
1750
  subscription.status = "canceled";
1069
1751
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1070
- return {
1071
- _tag: "Success",
1072
- data: {
1073
- ...subscription,
1074
- items: subscription.items.map((item) => ({ ...item })),
1075
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
1076
- }
1077
- };
1752
+ return { _tag: "Success", data: { ...subscription } };
1078
1753
  },
1079
1754
  async getAvailablePlans() {
1080
1755
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1102,70 +1777,1410 @@ function createMockSubscriptionAdapter(options = {}) {
1102
1777
  ]
1103
1778
  };
1104
1779
  },
1780
+ async getLastOrder(params) {
1781
+ await new Promise((resolve) => setTimeout(resolve, 200));
1782
+ const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
1783
+ if (!subscription) return { _tag: "Success", data: null };
1784
+ return {
1785
+ _tag: "Success",
1786
+ data: {
1787
+ id: `mock_last_order_${params.subscriptionId}`,
1788
+ createdAt: "2026-04-22T12:00:00Z"
1789
+ }
1790
+ };
1791
+ },
1792
+ async renewNow(_params) {
1793
+ await new Promise((resolve) => setTimeout(resolve, 300));
1794
+ return { _tag: "Success", data: { result: "success" } };
1795
+ },
1796
+ async changeFrequency(params) {
1797
+ await new Promise((resolve) => setTimeout(resolve, 300));
1798
+ const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
1799
+ if (!subscription) {
1800
+ return { _tag: "Failure", error: "Subscription not found" };
1801
+ }
1802
+ if (typeof params.plan === "string") {
1803
+ subscription.frequency = `1 ${params.plan}`;
1804
+ } else {
1805
+ subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
1806
+ if (subscription.billingPolicy) {
1807
+ subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
1808
+ }
1809
+ if (subscription.deliveryPolicy) {
1810
+ subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
1811
+ }
1812
+ }
1813
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1814
+ return {
1815
+ _tag: "Success",
1816
+ data: {
1817
+ ...subscription,
1818
+ items: subscription.items.map((item) => ({ ...item })),
1819
+ discounts: subscription.discounts.map((d2) => ({ ...d2 }))
1820
+ }
1821
+ };
1822
+ },
1105
1823
  async getDefaultSubscriptionId() {
1106
1824
  var _a;
1107
1825
  await new Promise((resolve) => setTimeout(resolve, 300));
1108
1826
  return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
1827
+ },
1828
+ async getUpsellProducts() {
1829
+ await new Promise((resolve) => setTimeout(resolve, 300));
1830
+ return { _tag: "Success", data: [...mockUpsellProducts] };
1831
+ },
1832
+ async addUpsellProduct() {
1833
+ await new Promise((resolve) => setTimeout(resolve, 400));
1834
+ return { _tag: "Success", data: void 0 };
1109
1835
  }
1110
1836
  };
1111
1837
  }
1112
- const SubscriptionServiceContext = createContext("services/subscription");
1113
- function getCurrentSubscriptionId() {
1114
- if (typeof window === "undefined") {
1115
- return null;
1116
- }
1117
- const pathname = window.location.pathname;
1118
- const segments = pathname.split("/").filter(Boolean);
1119
- const subscriptionIndex = segments.findIndex(
1120
- (segment) => segment === "subscriptions" || segment === "subscription"
1121
- );
1122
- if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
1123
- return segments[subscriptionIndex + 1];
1838
+ function normalizeInstrumentType(type) {
1839
+ return type.replace(/-/g, "_");
1840
+ }
1841
+ function mapPaymentMethod(rawPm) {
1842
+ if (!rawPm) return null;
1843
+ if (typeof rawPm === "string") {
1844
+ return { id: rawPm };
1124
1845
  }
1125
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1126
- const lastSegment = segments[segments.length - 1];
1127
- if (lastSegment && uuidPattern.test(lastSegment)) {
1128
- return lastSegment;
1846
+ return {
1847
+ id: rawPm.id ?? "",
1848
+ ...rawPm.instrument != null ? {
1849
+ instrument: {
1850
+ ...rawPm.instrument,
1851
+ type: normalizeInstrumentType(rawPm.instrument.type)
1852
+ }
1853
+ } : {},
1854
+ ...rawPm.active != null ? { active: rawPm.active } : {},
1855
+ ...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
1856
+ };
1857
+ }
1858
+ function mapSubscription(raw) {
1859
+ var _a, _b;
1860
+ const status = raw.status === "merged" ? "canceled" : raw.status;
1861
+ const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
1862
+ const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
1863
+ return {
1864
+ id: raw.id,
1865
+ customerId: "",
1866
+ serial: raw.serial,
1867
+ items: (raw.items ?? []).map(
1868
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1869
+ (item) => ({
1870
+ id: item.id,
1871
+ productId: item.productId ?? item.product ?? "",
1872
+ variantId: item.variantId ?? item.variant ?? "",
1873
+ quantity: item.quantity,
1874
+ price: item.price ?? {
1875
+ amount: String(item.totalPrice ?? 0),
1876
+ currencyCode: raw.currencyCode ?? ""
1877
+ },
1878
+ nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
1879
+ billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
1880
+ deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
1881
+ // Map human-readable titles from the v1 SubscriptionItemResource
1882
+ title: item.title,
1883
+ variantTitle: item.subtitle ?? void 0,
1884
+ // recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
1885
+ recurringCycleLimit: item.recurringCycleLimit ?? null,
1886
+ customAttributes: Array.isArray(item.customAttributes) ? item.customAttributes : []
1887
+ })
1888
+ ),
1889
+ discounts: (raw.discounts ?? []).map(
1890
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1891
+ (d2) => {
1892
+ var _a2, _b2, _c;
1893
+ return {
1894
+ id: d2.id,
1895
+ type: ((_a2 = d2.value) == null ? void 0 : _a2.type) === "fixed-amount" || d2.type === "fixed_amount" ? "fixed_amount" : "percentage",
1896
+ value: ((_b2 = d2.value) == null ? void 0 : _b2.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? d2.value ?? 0,
1897
+ code: d2.title ?? d2.code ?? "",
1898
+ description: d2.description ?? ""
1899
+ };
1900
+ }
1901
+ ),
1902
+ status,
1903
+ nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
1904
+ // Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
1905
+ nextBillingDate: raw.nextBillingDate ?? null,
1906
+ // Map customerPaymentMethod — expanded to an object (id + instrument) via the
1907
+ // adapter's ?expand=paymentMethod request; mapPaymentMethod tolerates a bare id too.
1908
+ customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
1909
+ frequency,
1910
+ createdAt: raw.createdAt,
1911
+ updatedAt: raw.updatedAt,
1912
+ billingPolicy: raw.billingPolicy ?? null,
1913
+ deliveryPolicy: raw.deliveryPolicy ?? null,
1914
+ customAttributes: Array.isArray(raw.customAttributes) ? raw.customAttributes : []
1915
+ };
1916
+ }
1917
+ async function readErrorBody(res) {
1918
+ try {
1919
+ const body = await res.json();
1920
+ return body != null && typeof body === "object" ? body : {};
1921
+ } catch {
1922
+ return {};
1129
1923
  }
1130
- return null;
1131
1924
  }
1132
- function createSubscriptionService(adapter) {
1925
+ function createApiSubscriptionAdapter(fetcher) {
1926
+ const adapter = {
1927
+ async search(options) {
1928
+ var _a;
1929
+ try {
1930
+ const params = new URLSearchParams();
1931
+ if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
1932
+ const groups = options.filters.filter((f) => Object.keys(f).length > 0).map((f) => Object.entries(f).filter(([, v3]) => Boolean(v3))).filter((entries) => entries.length > 0).map(
1933
+ (entries) => "(" + entries.map(([k3, v3]) => `${k3}:'${v3}'`).join(" AND ") + ")"
1934
+ );
1935
+ if (groups.length) params.set("query", groups.join(" OR "));
1936
+ }
1937
+ if (options == null ? void 0 : options.includeCanceledItems) {
1938
+ params.set("includeCanceledItems", "true");
1939
+ }
1940
+ params.set("expand", "paymentMethod");
1941
+ if (options == null ? void 0 : options.pagination) {
1942
+ if ("after" in options.pagination)
1943
+ params.set("after", options.pagination.after);
1944
+ else if ("before" in options.pagination)
1945
+ params.set("before", options.pagination.before);
1946
+ }
1947
+ const query = params.toString() ? `?${params.toString()}` : "";
1948
+ const res = await fetcher(`/customer/v1/subscriptions${query}`);
1949
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1950
+ const body = await res.json();
1951
+ return {
1952
+ _tag: "Success",
1953
+ data: {
1954
+ items: body.data.map(mapSubscription),
1955
+ pageInfo: {
1956
+ hasNextPage: body.hasNextPage,
1957
+ hasPrevPage: body.hasPrevPage,
1958
+ startCursor: body.startCursor,
1959
+ endCursor: body.endCursor
1960
+ }
1961
+ }
1962
+ };
1963
+ } catch (e) {
1964
+ return { _tag: "Failure", error: e };
1965
+ }
1966
+ },
1967
+ async getById(id) {
1968
+ try {
1969
+ const res = await fetcher(
1970
+ `/customer/v1/subscriptions?query=id:"${id}"&limit=1&expand=paymentMethod`
1971
+ );
1972
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1973
+ const body = await res.json();
1974
+ const raw = body.data[0];
1975
+ return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
1976
+ } catch (e) {
1977
+ return { _tag: "Failure", error: e };
1978
+ }
1979
+ },
1980
+ async addItem({ subscriptionId, item }) {
1981
+ try {
1982
+ const res = await fetcher(
1983
+ `/customer/v1/subscriptions/${subscriptionId}/items`,
1984
+ {
1985
+ method: "POST",
1986
+ headers: { "Content-Type": "application/json" },
1987
+ body: JSON.stringify({ variant: item.variantId, quantity: item.quantity })
1988
+ }
1989
+ );
1990
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1991
+ return { _tag: "Success", data: void 0 };
1992
+ } catch (e) {
1993
+ return { _tag: "Failure", error: e };
1994
+ }
1995
+ },
1996
+ async removeItem({ subscriptionId, itemId }) {
1997
+ try {
1998
+ const res = await fetcher(
1999
+ `/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
2000
+ { method: "DELETE" }
2001
+ );
2002
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2003
+ return { _tag: "Success", data: void 0 };
2004
+ } catch (e) {
2005
+ return { _tag: "Failure", error: e };
2006
+ }
2007
+ },
2008
+ async updateItem({ subscriptionId, lineId, update }) {
2009
+ try {
2010
+ const body = {
2011
+ ...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
2012
+ ...update.quantity !== void 0 ? { quantity: update.quantity } : {},
2013
+ ...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
2014
+ ...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
2015
+ };
2016
+ const res = await fetcher(
2017
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
2018
+ {
2019
+ method: "PUT",
2020
+ headers: { "Content-Type": "application/json" },
2021
+ body: JSON.stringify(body)
2022
+ }
2023
+ );
2024
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2025
+ return { _tag: "Success", data: void 0 };
2026
+ } catch (e) {
2027
+ return { _tag: "Failure", error: e };
2028
+ }
2029
+ },
2030
+ async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
2031
+ try {
2032
+ const res = await fetcher(
2033
+ `/customer/v1/subscriptions/${subscriptionId}/items/${lineId}`,
2034
+ {
2035
+ method: "PATCH",
2036
+ headers: { "Content-Type": "application/json" },
2037
+ body: JSON.stringify({ customAttributes })
2038
+ }
2039
+ );
2040
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2041
+ return { _tag: "Success", data: void 0 };
2042
+ } catch (e) {
2043
+ return { _tag: "Failure", error: e };
2044
+ }
2045
+ },
2046
+ async updateAttributes({ subscriptionId, customAttributes }) {
2047
+ try {
2048
+ const res = await fetcher(
2049
+ `/customer/v1/subscriptions/${subscriptionId}`,
2050
+ {
2051
+ method: "PATCH",
2052
+ headers: { "Content-Type": "application/json" },
2053
+ body: JSON.stringify({ customAttributes })
2054
+ }
2055
+ );
2056
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2057
+ return { _tag: "Success", data: void 0 };
2058
+ } catch (e) {
2059
+ return { _tag: "Failure", error: e };
2060
+ }
2061
+ },
2062
+ async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
2063
+ try {
2064
+ const res = await fetcher(
2065
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
2066
+ {
2067
+ method: "PUT",
2068
+ headers: { "Content-Type": "application/json" },
2069
+ body: JSON.stringify({ nextBillingDate })
2070
+ }
2071
+ );
2072
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2073
+ return { _tag: "Success", data: void 0 };
2074
+ } catch (e) {
2075
+ return { _tag: "Failure", error: e };
2076
+ }
2077
+ },
2078
+ async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
2079
+ try {
2080
+ const res = await fetcher(
2081
+ `/customer/subscriptions/${subscriptionId}/update-box-products`,
2082
+ {
2083
+ method: "POST",
2084
+ headers: { "Content-Type": "application/json" },
2085
+ body: JSON.stringify({ parentId, items, shippingMethodHandle })
2086
+ }
2087
+ );
2088
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2089
+ return { _tag: "Success", data: void 0 };
2090
+ } catch (e) {
2091
+ return { _tag: "Failure", error: e };
2092
+ }
2093
+ },
2094
+ async addDiscount({ subscriptionId, code }) {
2095
+ try {
2096
+ const res = await fetcher(
2097
+ `/customer/v1/subscriptions/${subscriptionId}/discounts`,
2098
+ {
2099
+ method: "POST",
2100
+ headers: { "Content-Type": "application/json" },
2101
+ body: JSON.stringify({ code })
2102
+ }
2103
+ );
2104
+ if (!res.ok) {
2105
+ const body = await readErrorBody(res);
2106
+ if (res.status === 400 && body.code === "DISCOUNT_CODE_REJECTED") {
2107
+ const raw = body.rejectionReason;
2108
+ const reason = typeof raw === "string" && DISCOUNT_REJECTION_REASONS.includes(raw) ? raw : "INVALID_CODE";
2109
+ return { _tag: "Failure", error: { kind: "REJECTED", reason } };
2110
+ }
2111
+ if (res.status === 404) {
2112
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
2113
+ }
2114
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
2115
+ }
2116
+ return { _tag: "Success", data: void 0 };
2117
+ } catch (e) {
2118
+ return {
2119
+ _tag: "Failure",
2120
+ error: { kind: "NETWORK", message: e instanceof Error ? e.message : void 0 }
2121
+ };
2122
+ }
2123
+ },
2124
+ async removeDiscount({ subscriptionId, discountId }) {
2125
+ try {
2126
+ const res = await fetcher(
2127
+ `/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
2128
+ { method: "DELETE" }
2129
+ );
2130
+ if (!res.ok) {
2131
+ if (res.status === 404) {
2132
+ const body = await readErrorBody(res);
2133
+ const message = typeof body.message === "string" ? body.message : "";
2134
+ if (message.toLowerCase().includes("discount")) {
2135
+ return { _tag: "Failure", error: { kind: "DISCOUNT_NOT_FOUND" } };
2136
+ }
2137
+ return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
2138
+ }
2139
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
2140
+ }
2141
+ return { _tag: "Success", data: void 0 };
2142
+ } catch (e) {
2143
+ return {
2144
+ _tag: "Failure",
2145
+ error: { kind: "NETWORK", message: e instanceof Error ? e.message : void 0 }
2146
+ };
2147
+ }
2148
+ },
2149
+ // Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
2150
+ async reschedule({ subscriptionId, newDate }) {
2151
+ try {
2152
+ const res = await fetcher(
2153
+ `/customer/v1/subscriptions/${subscriptionId}`,
2154
+ {
2155
+ method: "PATCH",
2156
+ headers: { "Content-Type": "application/json" },
2157
+ body: JSON.stringify({ nextBillingDate: newDate })
2158
+ }
2159
+ );
2160
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2161
+ const data = await res.json();
2162
+ return { _tag: "Success", data: mapSubscription(data) };
2163
+ } catch (e) {
2164
+ return { _tag: "Failure", error: e };
2165
+ }
2166
+ },
2167
+ // PATCH /customer/v1/subscriptions/{id} with the full customer-visible set.
2168
+ async updateCustomAttributes({ subscriptionId, customAttributes }) {
2169
+ try {
2170
+ const res = await fetcher(
2171
+ `/customer/v1/subscriptions/${subscriptionId}`,
2172
+ {
2173
+ method: "PATCH",
2174
+ headers: { "Content-Type": "application/json" },
2175
+ body: JSON.stringify({ customAttributes })
2176
+ }
2177
+ );
2178
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2179
+ const data = await res.json();
2180
+ return { _tag: "Success", data: mapSubscription(data) };
2181
+ } catch (e) {
2182
+ return { _tag: "Failure", error: e };
2183
+ }
2184
+ },
2185
+ async resume({ subscriptionId }) {
2186
+ try {
2187
+ const res = await fetcher(
2188
+ `/customer/v1/subscriptions/${subscriptionId}/resume`,
2189
+ { method: "POST" }
2190
+ );
2191
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2192
+ const data = await res.json();
2193
+ return { _tag: "Success", data: mapSubscription(data) };
2194
+ } catch (e) {
2195
+ return { _tag: "Failure", error: e };
2196
+ }
2197
+ },
2198
+ async pause({ subscriptionId }) {
2199
+ try {
2200
+ const res = await fetcher(
2201
+ `/customer/v1/subscriptions/${subscriptionId}/pause`,
2202
+ { method: "POST" }
2203
+ );
2204
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2205
+ const data = await res.json();
2206
+ return { _tag: "Success", data: mapSubscription(data) };
2207
+ } catch (e) {
2208
+ return { _tag: "Failure", error: e };
2209
+ }
2210
+ },
2211
+ async reactivate({ subscriptionId }) {
2212
+ try {
2213
+ const res = await fetcher(
2214
+ `/customer/v1/subscriptions/${subscriptionId}/reactivate`,
2215
+ { method: "POST" }
2216
+ );
2217
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2218
+ const data = await res.json();
2219
+ return { _tag: "Success", data: mapSubscription(data) };
2220
+ } catch (e) {
2221
+ return { _tag: "Failure", error: e };
2222
+ }
2223
+ },
2224
+ async cancel({ subscriptionId, reason }) {
2225
+ try {
2226
+ const res = await fetcher(
2227
+ `/customer/v1/subscriptions/${subscriptionId}/cancel`,
2228
+ {
2229
+ method: "POST",
2230
+ headers: { "Content-Type": "application/json" },
2231
+ body: JSON.stringify({ cancellationReason: reason })
2232
+ }
2233
+ );
2234
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2235
+ const data = await res.json();
2236
+ return { _tag: "Success", data: mapSubscription(data) };
2237
+ } catch (e) {
2238
+ return { _tag: "Failure", error: e };
2239
+ }
2240
+ },
2241
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
2242
+ async getAvailablePlans({ subscriptionId }) {
2243
+ try {
2244
+ const res = await fetcher(
2245
+ `/customer/subscriptions/${subscriptionId}/plans`
2246
+ );
2247
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2248
+ const data = await res.json();
2249
+ return { _tag: "Success", data };
2250
+ } catch (e) {
2251
+ return { _tag: "Failure", error: e };
2252
+ }
2253
+ },
2254
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
2255
+ async getDefaultSubscriptionId() {
2256
+ try {
2257
+ const res = await fetcher(
2258
+ `/customer/subscriptions/one-by-next-billing-date`
2259
+ );
2260
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2261
+ const data = await res.json();
2262
+ return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
2263
+ } catch (e) {
2264
+ return { _tag: "Failure", error: e };
2265
+ }
2266
+ },
2267
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
2268
+ async getUpsellProducts({ subscriptionId }) {
2269
+ try {
2270
+ const res = await fetcher(
2271
+ `/customer/subscriptions/${subscriptionId}/upsell-products`
2272
+ );
2273
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2274
+ const data = await res.json();
2275
+ const products = data.map((p3) => {
2276
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2277
+ return {
2278
+ id: p3.id,
2279
+ variantId: ((_b = (_a = p3.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
2280
+ title: p3.title ?? "",
2281
+ imageUrl: ((_c = p3.featuredImage) == null ? void 0 : _c.url) ?? void 0,
2282
+ price: {
2283
+ amount: String(((_f = (_e = (_d = p3.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
2284
+ currencyCode: ((_i = (_h = (_g = p3.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
2285
+ },
2286
+ // recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
2287
+ recurring: (_k = (_j = p3.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
2288
+ };
2289
+ });
2290
+ return { _tag: "Success", data: products };
2291
+ } catch (e) {
2292
+ return { _tag: "Failure", error: e };
2293
+ }
2294
+ },
2295
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
2296
+ async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
2297
+ try {
2298
+ const res = await fetcher(
2299
+ `/customer/subscriptions/${subscriptionId}/upsell`,
2300
+ {
2301
+ method: "POST",
2302
+ headers: { "Content-Type": "application/json" },
2303
+ body: JSON.stringify({
2304
+ productVariantId: variantId,
2305
+ quantity,
2306
+ ...oneTime ? { renewalLimit: 1 } : {}
2307
+ })
2308
+ }
2309
+ );
2310
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2311
+ return { _tag: "Success", data: void 0 };
2312
+ } catch (e) {
2313
+ return { _tag: "Failure", error: e };
2314
+ }
2315
+ },
2316
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
2317
+ async getLastOrder({ subscriptionId }) {
2318
+ try {
2319
+ const res = await fetcher(
2320
+ `/customer/subscriptions/${subscriptionId}/last-order`
2321
+ );
2322
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2323
+ const data = await res.json();
2324
+ if (!data) return { _tag: "Success", data: null };
2325
+ return {
2326
+ _tag: "Success",
2327
+ data: { id: data.id, createdAt: data.createdAt }
2328
+ };
2329
+ } catch (e) {
2330
+ return { _tag: "Failure", error: e };
2331
+ }
2332
+ },
2333
+ // Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
2334
+ // retry → POST /customer/subscriptions/{id}/retry
2335
+ // renewalId → POST /customer/renewals/{renewalId}/renew
2336
+ // default → POST /customer/subscriptions/{id}/order-now
2337
+ async renewNow({ subscriptionId, renewalId, retry }) {
2338
+ try {
2339
+ const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
2340
+ const res = await fetcher(url, { method: "POST" });
2341
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2342
+ const body = res.status === 204 ? null : await res.json();
2343
+ return {
2344
+ _tag: "Success",
2345
+ data: {
2346
+ nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
2347
+ result: (body == null ? void 0 : body.result) ?? "success"
2348
+ }
2349
+ };
2350
+ } catch (e) {
2351
+ return { _tag: "Failure", error: e };
2352
+ }
2353
+ },
2354
+ // Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
2355
+ async changeFrequency({ subscriptionId, plan }) {
2356
+ try {
2357
+ const res = await fetcher(
2358
+ `/customer/subscriptions/${subscriptionId}/plan`,
2359
+ {
2360
+ method: "POST",
2361
+ headers: { "Content-Type": "application/json" },
2362
+ body: JSON.stringify({ plan })
2363
+ }
2364
+ );
2365
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2366
+ const refreshed = await adapter.getById(subscriptionId);
2367
+ if (refreshed._tag === "Failure") return refreshed;
2368
+ if (refreshed.data) return { _tag: "Success", data: refreshed.data };
2369
+ return {
2370
+ _tag: "Failure",
2371
+ error: "Subscription not found after frequency change"
2372
+ };
2373
+ } catch (e) {
2374
+ return { _tag: "Failure", error: e };
2375
+ }
2376
+ }
2377
+ };
2378
+ return adapter;
2379
+ }
2380
+ function expandableId(field) {
2381
+ if (typeof field === "string") return field;
2382
+ if (field != null && typeof field === "object" && "id" in field) {
2383
+ const id = field.id;
2384
+ return typeof id === "string" ? id : void 0;
2385
+ }
2386
+ return void 0;
2387
+ }
2388
+ function mapScheduleOrder(order) {
2389
+ return {
2390
+ id: order.id,
2391
+ customerId: order.customerId,
2392
+ subscriptionId: order.subscriptionId,
2393
+ date: order.date,
2394
+ skipped: order.skipped ?? false,
2395
+ currencyCode: order.currencyCode,
2396
+ subtotal: order.subtotal,
2397
+ deliveryPrice: order.deliveryPrice,
2398
+ cycleIndex: order.cycleIndex,
2399
+ deliveryDate: order.deliveryDate ?? order.date,
2400
+ status: order.status ?? (order.skipped ? "skipped" : "scheduled"),
2401
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2402
+ items: (order.items ?? []).map((item) => {
2403
+ var _a;
2404
+ return {
2405
+ id: item.id,
2406
+ title: item.title,
2407
+ subtitle: item.subtitle ?? null,
2408
+ imageUrl: item.imageUrl ?? ((_a = item.variantImage) == null ? void 0 : _a.url) ?? void 0,
2409
+ variantId: expandableId(item.variant) ?? "",
2410
+ quantity: item.quantity,
2411
+ price: item.price,
2412
+ totalPrice: item.totalPrice,
2413
+ skipped: item.skipped ?? false,
2414
+ variantImage: item.variantImage != null ? { url: item.variantImage.url ?? "", altText: item.variantImage.altText ?? void 0 } : null,
2415
+ variantAvailableForSale: item.variantAvailableForSale ?? void 0,
2416
+ subscriptionId: expandableId(item.subscription) ?? "",
2417
+ subscriptionLineId: expandableId(item.subscriptionItem) ?? "",
2418
+ productId: expandableId(item.product),
2419
+ parentProductId: item.parentProductId ?? null,
2420
+ parentLineId: item.parentLineId ?? null,
2421
+ isVirtual: item.isVirtual ?? void 0,
2422
+ isUpsell: item.isUpsell ?? void 0,
2423
+ recurringCycleLimit: item.recurringCycleLimit ?? null,
2424
+ customAttributes: item.customAttributes ?? [],
2425
+ sellingPlanId: item.sellingPlanId ?? null,
2426
+ sellingPlanName: item.sellingPlanName ?? null,
2427
+ nextBillingDate: item.nextBillingDate ?? null,
2428
+ billingPolicy: item.billingPolicy ?? null,
2429
+ subscriptionStatus: item.subscriptionStatus ?? null,
2430
+ subscriptionIsEditable: item.subscriptionIsEditable ?? null,
2431
+ requiredBillingCyclesLeft: item.requiredBillingCyclesLeft ?? null
2432
+ };
2433
+ }),
2434
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2435
+ discounts: (order.discounts ?? []).map((d2) => {
2436
+ var _a, _b, _c;
2437
+ return {
2438
+ id: d2.id,
2439
+ title: d2.title ?? void 0,
2440
+ type: ((_a = d2.value) == null ? void 0 : _a.type) === "fixed-amount" ? "fixed_amount" : "percentage",
2441
+ value: ((_b = d2.value) == null ? void 0 : _b.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? 0
2442
+ };
2443
+ })
2444
+ };
2445
+ }
2446
+ function toIsoOrNull(value) {
2447
+ const date = new Date(value);
2448
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
2449
+ }
2450
+ function buildMatcher(params) {
2451
+ const { cycle, date } = params;
2452
+ let iso;
2453
+ if (date != null) {
2454
+ const parsed = toIsoOrNull(date);
2455
+ if (parsed == null) return { ok: false, error: "date must be a valid date" };
2456
+ iso = parsed;
2457
+ }
2458
+ if (cycle != null && iso != null) {
2459
+ return { ok: true, matcher: { type: "CYCLE_AND_DATE", input: { cycle, date: iso } } };
2460
+ }
2461
+ if (cycle != null) return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
2462
+ if (iso != null) return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
2463
+ return { ok: false, error: "Either cycle or date is required" };
2464
+ }
2465
+ function buildSkipItemMatcher(params) {
2466
+ const { cycle, date } = params;
2467
+ if (cycle != null) {
2468
+ return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
2469
+ }
2470
+ if (date != null) {
2471
+ const iso = toIsoOrNull(date);
2472
+ if (iso == null) return { ok: false, error: "date must be a valid date" };
2473
+ return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
2474
+ }
2475
+ return { ok: false, error: "Either cycle or date is required" };
2476
+ }
2477
+ const DELIVERY_PRICE = 5;
2478
+ const DELIVERY_OFFSET_DAYS = 2;
2479
+ function toPrice(amount) {
2480
+ const n2 = Number.parseFloat(amount ?? "0");
2481
+ return Number.isFinite(n2) ? n2 : 0;
2482
+ }
2483
+ function addInterval(date, interval, units) {
2484
+ const d2 = new Date(date);
2485
+ switch (interval) {
2486
+ case "DAY":
2487
+ d2.setDate(d2.getDate() + units);
2488
+ break;
2489
+ case "WEEK":
2490
+ d2.setDate(d2.getDate() + units * 7);
2491
+ break;
2492
+ case "YEAR":
2493
+ d2.setFullYear(d2.getFullYear() + units);
2494
+ break;
2495
+ default:
2496
+ d2.setMonth(d2.getMonth() + units);
2497
+ break;
2498
+ }
2499
+ return d2;
2500
+ }
2501
+ function mapItem(sub, item, cycle) {
2502
+ var _a, _b;
2503
+ const price = toPrice(((_a = item.currentPrice) == null ? void 0 : _a.amount) ?? item.price.amount);
2504
+ return {
2505
+ id: `${sub.id}_cycle_${cycle}_${item.id}`,
2506
+ title: item.title ?? `Product ${item.variantId}`,
2507
+ subtitle: item.variantTitle ?? null,
2508
+ imageUrl: (_b = item.variantImage) == null ? void 0 : _b.url,
2509
+ variantId: item.variantId,
2510
+ quantity: item.quantity,
2511
+ price,
2512
+ totalPrice: price * item.quantity,
2513
+ skipped: false,
2514
+ variantImage: item.variantImage ?? null,
2515
+ variantAvailableForSale: item.variantAvailableForSale ?? true,
2516
+ subscriptionId: sub.id,
2517
+ subscriptionLineId: item.id,
2518
+ productId: item.productId,
2519
+ parentProductId: item.parentProductId ?? null,
2520
+ parentLineId: item.parentLineId ?? null,
2521
+ isVirtual: item.isVirtual ?? false,
2522
+ isUpsell: item.isUpsell ?? false,
2523
+ recurringCycleLimit: item.recurringCycleLimit ?? null,
2524
+ customAttributes: item.customAttributes ?? []
2525
+ };
2526
+ }
2527
+ function projectUpcomingOrders(sub, count) {
2528
+ var _a, _b;
2529
+ if (sub.status !== "active") return [];
2530
+ const billing = sub.billingPolicy ?? ((_a = sub.items[0]) == null ? void 0 : _a.billingPolicy) ?? { interval: "MONTH", intervalCount: 1 };
2531
+ const baseStr = sub.nextBillingDate ?? (sub.nextDeliveryDate ? sub.nextDeliveryDate.toISOString() : null);
2532
+ const base = baseStr ? new Date(baseStr) : /* @__PURE__ */ new Date();
2533
+ const currencyCode = ((_b = sub.items[0]) == null ? void 0 : _b.price.currencyCode) ?? "USD";
2534
+ const orders = [];
2535
+ for (let cycle = 0; cycle < count; cycle++) {
2536
+ const billingDate = addInterval(
2537
+ base,
2538
+ billing.interval,
2539
+ cycle * billing.intervalCount
2540
+ );
2541
+ const deliveryDate = addInterval(billingDate, "DAY", DELIVERY_OFFSET_DAYS);
2542
+ const items = sub.items.map((item) => mapItem(sub, item, cycle));
2543
+ const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
2544
+ orders.push({
2545
+ id: `${sub.id}_cycle_${cycle}`,
2546
+ customerId: sub.customerId,
2547
+ subscriptionId: sub.id,
2548
+ date: billingDate.toISOString(),
2549
+ skipped: false,
2550
+ currencyCode,
2551
+ subtotal,
2552
+ deliveryPrice: DELIVERY_PRICE,
2553
+ cycleIndex: cycle,
2554
+ deliveryDate: deliveryDate.toISOString(),
2555
+ status: "scheduled",
2556
+ items,
2557
+ discounts: sub.discounts.map((d2) => ({
2558
+ id: d2.id,
2559
+ title: d2.code,
2560
+ type: d2.type,
2561
+ value: d2.value
2562
+ }))
2563
+ });
2564
+ }
2565
+ return orders;
2566
+ }
2567
+ function applyAdjustments(orders, stored) {
2568
+ return orders.map((order) => {
2569
+ let next = order;
2570
+ for (const { adjustment, subscriptionId, cycle } of stored) {
2571
+ if (subscriptionId !== order.subscriptionId) continue;
2572
+ if (cycle !== order.cycleIndex) continue;
2573
+ const { action } = adjustment;
2574
+ if (action.type === "SKIP_ORDER") {
2575
+ next = { ...next, skipped: true, status: "skipped" };
2576
+ } else if (action.type === "CHANGE_DATE") {
2577
+ next = {
2578
+ ...next,
2579
+ date: action.input.newDate,
2580
+ deliveryDate: action.input.newDate
2581
+ };
2582
+ } else if (action.type === "SKIP_ITEM") {
2583
+ const lineIds = new Set(action.input.lineIds);
2584
+ const items = next.items.map(
2585
+ (item) => lineIds.has(item.subscriptionLineId) ? { ...item, skipped: true, totalPrice: 0 } : item
2586
+ );
2587
+ const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
2588
+ next = { ...next, items, subtotal };
2589
+ if (items.length > 0 && items.every((i3) => i3.skipped)) {
2590
+ next = { ...next, skipped: true, status: "skipped", deliveryPrice: 0 };
2591
+ }
2592
+ }
2593
+ }
2594
+ return next;
2595
+ });
2596
+ }
2597
+ function createMockSchedulesAdapter(subscriptions = createMockSubscriptionAdapter()) {
2598
+ const RESOLVE_DEPTH = 12;
2599
+ let stored = [];
2600
+ let idSeq = 0;
2601
+ async function subscriptionsToProject(subscriptionId) {
2602
+ if (subscriptionId) {
2603
+ const res = await subscriptions.getById(subscriptionId);
2604
+ return res._tag === "Success" && res.data ? [res.data] : [];
2605
+ }
2606
+ const all = [];
2607
+ let pagination;
2608
+ for (let guard = 0; guard < 50; guard++) {
2609
+ const res = await subscriptions.search(pagination ? { pagination } : {});
2610
+ if (res._tag !== "Success") break;
2611
+ all.push(...res.data.items);
2612
+ const { hasNextPage, endCursor } = res.data.pageInfo;
2613
+ if (!hasNextPage || !endCursor) break;
2614
+ pagination = { after: endCursor };
2615
+ }
2616
+ return all;
2617
+ }
2618
+ function resolveCycle(orders, opts) {
2619
+ if (opts.cycle != null) return opts.cycle;
2620
+ if (opts.date) {
2621
+ const iso = toIsoOrNull(opts.date);
2622
+ const target = iso == null ? void 0 : iso.split("T")[0];
2623
+ const match = target ? orders.find(
2624
+ (o3) => o3.deliveryDate.split("T")[0] === target || o3.date.split("T")[0] === target
2625
+ ) : void 0;
2626
+ return match ? match.cycleIndex : null;
2627
+ }
2628
+ const firstScheduled = orders.find((o3) => o3.status === "scheduled");
2629
+ return (firstScheduled == null ? void 0 : firstScheduled.cycleIndex) ?? null;
2630
+ }
2631
+ async function recordAdjustment(subscriptionId, opts, buildAction, matcherOverride) {
2632
+ const res = await subscriptions.getById(subscriptionId);
2633
+ const sub = res._tag === "Success" ? res.data : null;
2634
+ if (!sub) return { _tag: "Failure", error: "No scheduled order found" };
2635
+ const orders = applyAdjustments(
2636
+ projectUpcomingOrders(sub, RESOLVE_DEPTH),
2637
+ stored
2638
+ );
2639
+ const cycle = resolveCycle(orders, opts);
2640
+ if (cycle == null) {
2641
+ return { _tag: "Failure", error: "No scheduled order found" };
2642
+ }
2643
+ const built = buildMatcher({ cycle, date: opts.date });
2644
+ const matcher = matcherOverride ?? (built.ok ? built.matcher : { type: "CYCLE", input: { cycle } });
2645
+ stored = [
2646
+ ...stored,
2647
+ {
2648
+ adjustment: { id: `adj-${idSeq++}`, matcher, action: buildAction() },
2649
+ subscriptionId,
2650
+ cycle
2651
+ }
2652
+ ];
2653
+ return { _tag: "Success", data: void 0 };
2654
+ }
2655
+ return {
2656
+ async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
2657
+ await new Promise((r2) => setTimeout(r2, 200));
2658
+ const subs = await subscriptionsToProject(subscriptionId);
2659
+ const skipCount = new Set(
2660
+ stored.filter(
2661
+ (s2) => s2.adjustment.action.type === "SKIP_ORDER" || s2.adjustment.action.type === "SKIP_ITEM"
2662
+ ).map((s2) => `${s2.subscriptionId}:${s2.cycle}`)
2663
+ ).size;
2664
+ const depth = excludeSkipped ? count + skipCount : count;
2665
+ const orders = subs.flatMap(
2666
+ (sub) => applyAdjustments(projectUpcomingOrders(sub, depth), stored)
2667
+ ).filter((o3) => excludeSkipped ? !o3.skipped : true).map(
2668
+ (o3) => (
2669
+ // Mirror backend removeSkippedItems: the billable view also strips
2670
+ // skipped lines from surviving (partially-skipped) orders. No
2671
+ // recompute needed — skipped lines already carry totalPrice 0 (R4).
2672
+ excludeSkipped && o3.items.some((i3) => i3.skipped) ? { ...o3, items: o3.items.filter((i3) => !i3.skipped) } : o3
2673
+ )
2674
+ ).sort((a2, b3) => a2.date.localeCompare(b3.date));
2675
+ return { _tag: "Success", data: orders.slice(0, count) };
2676
+ },
2677
+ async getAdjustments() {
2678
+ await new Promise((r2) => setTimeout(r2, 200));
2679
+ return { _tag: "Success", data: stored.map((s2) => s2.adjustment) };
2680
+ },
2681
+ async skipOrder({ subscriptionId, cycle, date }) {
2682
+ await new Promise((r2) => setTimeout(r2, 300));
2683
+ return recordAdjustment(subscriptionId, { cycle, date }, () => ({
2684
+ type: "SKIP_ORDER",
2685
+ input: { reason: "" }
2686
+ }));
2687
+ },
2688
+ async skipItem({ subscriptionId, lineId, cycle, date, reason }) {
2689
+ await new Promise((r2) => setTimeout(r2, 300));
2690
+ const built = buildSkipItemMatcher({ cycle, date });
2691
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2692
+ return recordAdjustment(
2693
+ subscriptionId,
2694
+ { cycle, date },
2695
+ () => ({
2696
+ type: "SKIP_ITEM",
2697
+ input: { lineIds: [lineId], reason: reason ?? "" }
2698
+ }),
2699
+ built.matcher
2700
+ );
2701
+ },
2702
+ async changeDateAdjustment({ subscriptionId, newDate, cycle, currentDate }) {
2703
+ await new Promise((r2) => setTimeout(r2, 300));
2704
+ const newIso = toIsoOrNull(newDate);
2705
+ if (newIso == null) {
2706
+ return { _tag: "Failure", error: "newDate must be a valid date" };
2707
+ }
2708
+ return recordAdjustment(
2709
+ subscriptionId,
2710
+ { cycle, date: currentDate },
2711
+ () => ({
2712
+ type: "CHANGE_DATE",
2713
+ input: { newDate: newIso }
2714
+ })
2715
+ );
2716
+ },
2717
+ async updateShippingAdjustment({ subscriptionId, address, cycle, date }) {
2718
+ await new Promise((r2) => setTimeout(r2, 300));
2719
+ return recordAdjustment(subscriptionId, { cycle, date }, () => ({
2720
+ type: "UPDATE_SHIPPING",
2721
+ input: { address }
2722
+ }));
2723
+ },
2724
+ async updatePaymentMethodAdjustment({
2725
+ subscriptionId,
2726
+ paymentMethodId,
2727
+ cycle,
2728
+ date
2729
+ }) {
2730
+ await new Promise((r2) => setTimeout(r2, 300));
2731
+ return recordAdjustment(subscriptionId, { cycle, date }, () => ({
2732
+ type: "UPDATE_PAYMENT_METHOD",
2733
+ input: { paymentMethodId }
2734
+ }));
2735
+ },
2736
+ async updateProductsAdjustment({ subscriptionId, lines, cycle, date }) {
2737
+ await new Promise((r2) => setTimeout(r2, 300));
2738
+ return recordAdjustment(subscriptionId, { cycle, date }, () => ({
2739
+ type: "UPDATE_PRODUCTS",
2740
+ input: { lines }
2741
+ }));
2742
+ },
2743
+ async applyDiscountAdjustment({ subscriptionId, discountCode, cycle }) {
2744
+ await new Promise((r2) => setTimeout(r2, 300));
2745
+ return recordAdjustment(subscriptionId, { cycle }, () => ({
2746
+ type: "APPLY_DISCOUNT",
2747
+ input: { discountSource: "code", discountCode }
2748
+ }));
2749
+ },
2750
+ async removeAdjustment({ adjustmentId }) {
2751
+ await new Promise((r2) => setTimeout(r2, 200));
2752
+ stored = stored.filter((s2) => s2.adjustment.id !== adjustmentId);
2753
+ return { _tag: "Success", data: void 0 };
2754
+ }
2755
+ };
2756
+ }
2757
+ function createApiSchedulesAdapter(fetcher) {
2758
+ async function postAdjustment(matcher, action) {
2759
+ try {
2760
+ const res = await fetcher(`/customer/v1/schedules/adjustments`, {
2761
+ method: "POST",
2762
+ headers: { "Content-Type": "application/json" },
2763
+ body: JSON.stringify({ matcher, action })
2764
+ });
2765
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2766
+ return { _tag: "Success", data: void 0 };
2767
+ } catch (e) {
2768
+ return { _tag: "Failure", error: e };
2769
+ }
2770
+ }
2771
+ return {
2772
+ async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
2773
+ try {
2774
+ const params = new URLSearchParams({ count: String(count) });
2775
+ params.set("excludeSkipped", String(excludeSkipped));
2776
+ if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
2777
+ const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
2778
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2779
+ const data = await res.json();
2780
+ return { _tag: "Success", data: (data.orders ?? []).map(mapScheduleOrder) };
2781
+ } catch (e) {
2782
+ return { _tag: "Failure", error: e };
2783
+ }
2784
+ },
2785
+ async getAdjustments() {
2786
+ try {
2787
+ const all = [];
2788
+ let after;
2789
+ for (let guard = 0; guard < 50; guard++) {
2790
+ const params = new URLSearchParams({ limit: "100" });
2791
+ if (after) params.set("after", after);
2792
+ const res = await fetcher(
2793
+ `/customer/v1/schedules/adjustments?${params.toString()}`
2794
+ );
2795
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2796
+ const body = await res.json();
2797
+ all.push(
2798
+ ...body.data.map((a2) => ({
2799
+ id: a2.id,
2800
+ matcher: a2.matcher,
2801
+ action: a2.action
2802
+ }))
2803
+ );
2804
+ if (!body.hasNextPage) return { _tag: "Success", data: all };
2805
+ if (!body.endCursor || body.endCursor === after) {
2806
+ return {
2807
+ _tag: "Failure",
2808
+ error: "Adjustments pagination cursor did not advance"
2809
+ };
2810
+ }
2811
+ after = body.endCursor;
2812
+ }
2813
+ return {
2814
+ _tag: "Failure",
2815
+ error: "Adjustments pagination exceeded 50 pages"
2816
+ };
2817
+ } catch (e) {
2818
+ return { _tag: "Failure", error: e };
2819
+ }
2820
+ },
2821
+ async skipOrder({ cycle, date }) {
2822
+ const built = buildMatcher({ cycle, date });
2823
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2824
+ return postAdjustment(built.matcher, {
2825
+ type: "SKIP_ORDER",
2826
+ input: { reason: "" }
2827
+ });
2828
+ },
2829
+ async skipItem({ lineId, cycle, date, reason }) {
2830
+ const built = buildSkipItemMatcher({ cycle, date });
2831
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2832
+ return postAdjustment(built.matcher, {
2833
+ type: "SKIP_ITEM",
2834
+ input: { lineIds: [lineId], reason: reason ?? "" }
2835
+ });
2836
+ },
2837
+ async changeDateAdjustment({ newDate, cycle }) {
2838
+ if (cycle == null) {
2839
+ return {
2840
+ _tag: "Failure",
2841
+ error: "cycle is required for changeDateAdjustment"
2842
+ };
2843
+ }
2844
+ const newIso = toIsoOrNull(newDate);
2845
+ if (newIso == null) {
2846
+ return { _tag: "Failure", error: "newDate must be a valid date" };
2847
+ }
2848
+ return postAdjustment(
2849
+ { type: "CYCLE", input: { cycle } },
2850
+ { type: "CHANGE_DATE", input: { newDate: newIso } }
2851
+ );
2852
+ },
2853
+ async updateShippingAdjustment({ address, cycle, date }) {
2854
+ const built = buildMatcher({ cycle, date });
2855
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2856
+ return postAdjustment(built.matcher, {
2857
+ type: "UPDATE_SHIPPING",
2858
+ input: { address }
2859
+ });
2860
+ },
2861
+ async updatePaymentMethodAdjustment({ paymentMethodId, cycle, date }) {
2862
+ const built = buildMatcher({ cycle, date });
2863
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2864
+ return postAdjustment(built.matcher, {
2865
+ type: "UPDATE_PAYMENT_METHOD",
2866
+ input: { paymentMethodId }
2867
+ });
2868
+ },
2869
+ async updateProductsAdjustment({ lines, cycle, date }) {
2870
+ const built = buildMatcher({ cycle, date });
2871
+ if (!built.ok) return { _tag: "Failure", error: built.error };
2872
+ return postAdjustment(built.matcher, {
2873
+ type: "UPDATE_PRODUCTS",
2874
+ input: { lines }
2875
+ });
2876
+ },
2877
+ async applyDiscountAdjustment({ discountCode, cycle }) {
2878
+ return postAdjustment(
2879
+ { type: "CYCLE", input: { cycle } },
2880
+ {
2881
+ type: "APPLY_DISCOUNT",
2882
+ input: { discountSource: "code", discountCode }
2883
+ }
2884
+ );
2885
+ },
2886
+ async removeAdjustment({ adjustmentId }) {
2887
+ try {
2888
+ const res = await fetcher(
2889
+ `/customer/v1/schedules/adjustments/${adjustmentId}`,
2890
+ { method: "DELETE" }
2891
+ );
2892
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2893
+ return { _tag: "Success", data: void 0 };
2894
+ } catch (e) {
2895
+ return { _tag: "Failure", error: e };
2896
+ }
2897
+ }
2898
+ };
2899
+ }
2900
+ const SchedulesServiceContext = createContext("services/schedules");
2901
+ function isSameCalendarDay(a2, b3) {
2902
+ const da = new Date(a2);
2903
+ const db = new Date(b3);
2904
+ if (Number.isNaN(da.getTime()) || Number.isNaN(db.getTime())) return false;
2905
+ return da.toISOString().slice(0, 10) === db.toISOString().slice(0, 10);
2906
+ }
2907
+ function matcherArms(matcher, target) {
2908
+ const cycleArm = (matcher.type === "CYCLE" || matcher.type === "CYCLE_AND_DATE") && matcher.input.cycle === target.cycle;
2909
+ const dateArm = (matcher.type === "DATE" || matcher.type === "CYCLE_AND_DATE") && isSameCalendarDay(matcher.input.date, target.date);
2910
+ return { cycleArm, dateArm };
2911
+ }
2912
+ function matcherAffects(matcher, target) {
2913
+ const { cycleArm, dateArm } = matcherArms(matcher, target);
2914
+ return cycleArm || dateArm;
2915
+ }
2916
+ function matcherExactlyTargets(matcher, target) {
2917
+ const { cycleArm, dateArm } = matcherArms(matcher, target);
2918
+ if (matcher.type === "CYCLE") return cycleArm;
2919
+ if (matcher.type === "DATE") return dateArm;
2920
+ return cycleArm && dateArm;
2921
+ }
2922
+ function createSchedulesService(adapter, opts) {
1133
2923
  const current = d(null);
1134
- const discountsChangedAtSignal = d(0);
1135
- const itemsChangedAtSignal = d(0);
1136
- async function refreshCurrent(subscriptionId) {
1137
- const result = await adapter.getById(subscriptionId);
1138
- if (result._tag === "Success" && result.data != null) {
2924
+ const currentLoading = d(false);
2925
+ const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
2926
+ const schedulesChangedAt = d(0);
2927
+ let reloadToken = 0;
2928
+ let currentSubscriptionId;
2929
+ async function getUpcomingOrder() {
2930
+ const result = await adapter.getUpcomingOrders({ count: 1 });
2931
+ if (result._tag === "Failure") return result;
2932
+ return { _tag: "Success", data: result.data[0] ?? null };
2933
+ }
2934
+ async function getUpcomingOrders(count, opts2) {
2935
+ return adapter.getUpcomingOrders({ count, ...opts2 });
2936
+ }
2937
+ async function reloadCurrent(subscriptionId) {
2938
+ var _a;
2939
+ const scopeChanged = currentSubscriptionId !== subscriptionId;
2940
+ currentSubscriptionId = subscriptionId;
2941
+ const token = ++reloadToken;
2942
+ currentLoading.value = true;
2943
+ const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
2944
+ if (scopeChanged) current.value = null;
2945
+ const result = await adapter.getUpcomingOrders({
2946
+ count,
2947
+ subscriptionId
2948
+ });
2949
+ if (token !== reloadToken) return;
2950
+ currentLoading.value = false;
2951
+ if (result._tag === "Success") {
1139
2952
  current.value = result.data;
1140
2953
  }
1141
2954
  }
1142
- async function refreshAfterDiscountChange(subscriptionId) {
1143
- await refreshCurrent(subscriptionId);
1144
- discountsChangedAtSignal.value = discountsChangedAtSignal.value + 1;
2955
+ async function notifyChanged(subscriptionId) {
2956
+ if (subscriptionId) await reloadCurrent(subscriptionId);
2957
+ schedulesChangedAt.value++;
1145
2958
  }
1146
- async function refreshAfterItemChange(subscriptionId) {
1147
- await refreshCurrent(subscriptionId);
1148
- itemsChangedAtSignal.value = itemsChangedAtSignal.value + 1;
2959
+ return {
2960
+ current,
2961
+ currentLoading,
2962
+ hasPendingBilling,
2963
+ schedulesChangedAt,
2964
+ getUpcomingOrder,
2965
+ getUpcomingOrders,
2966
+ async getAdjustments() {
2967
+ return adapter.getAdjustments();
2968
+ },
2969
+ async skipOrder(params) {
2970
+ const result = await adapter.skipOrder(params);
2971
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
2972
+ return result;
2973
+ },
2974
+ async skipItem(params) {
2975
+ const result = await adapter.skipItem(params);
2976
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
2977
+ return result;
2978
+ },
2979
+ async unskipItem({ subscriptionId, lineId, cycle, date }) {
2980
+ const adjustments = await adapter.getAdjustments();
2981
+ if (adjustments._tag === "Failure") return adjustments;
2982
+ const covering = adjustments.data.filter(
2983
+ (a2) => a2.action.type === "SKIP_ITEM" && a2.action.input.lineIds.includes(lineId) && matcherAffects(a2.matcher, { cycle, date })
2984
+ );
2985
+ if (covering.length === 0) {
2986
+ return { _tag: "Failure", error: "No skip found for this item" };
2987
+ }
2988
+ if (covering.some((a2) => a2.action.input.lineIds.length > 1)) {
2989
+ return {
2990
+ _tag: "Failure",
2991
+ error: "This item was skipped together with other items and can't be unskipped individually"
2992
+ };
2993
+ }
2994
+ if (covering.some((a2) => !matcherExactlyTargets(a2.matcher, { cycle, date }))) {
2995
+ return {
2996
+ _tag: "Failure",
2997
+ error: "This skip also affects another order and can't be removed from here"
2998
+ };
2999
+ }
3000
+ let deleted = 0;
3001
+ for (const a2 of covering) {
3002
+ const result = await adapter.removeAdjustment({ adjustmentId: a2.id });
3003
+ if (result._tag === "Failure") {
3004
+ if (deleted > 0) await notifyChanged(subscriptionId);
3005
+ return result;
3006
+ }
3007
+ deleted++;
3008
+ }
3009
+ await notifyChanged(subscriptionId);
3010
+ return { _tag: "Success", data: void 0 };
3011
+ },
3012
+ async changeDateAdjustment(params) {
3013
+ const result = await adapter.changeDateAdjustment(params);
3014
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
3015
+ return result;
3016
+ },
3017
+ async updateShippingAdjustment(params) {
3018
+ const result = await adapter.updateShippingAdjustment(params);
3019
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
3020
+ return result;
3021
+ },
3022
+ async updatePaymentMethodAdjustment(params) {
3023
+ const result = await adapter.updatePaymentMethodAdjustment(params);
3024
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
3025
+ return result;
3026
+ },
3027
+ async updateProductsAdjustment(params) {
3028
+ const result = await adapter.updateProductsAdjustment(params);
3029
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
3030
+ return result;
3031
+ },
3032
+ async applyDiscountAdjustment(params) {
3033
+ const result = await adapter.applyDiscountAdjustment(params);
3034
+ if (result._tag === "Success") await notifyChanged(params.subscriptionId);
3035
+ return result;
3036
+ },
3037
+ async removeAdjustment(params) {
3038
+ var _a, _b, _c;
3039
+ const result = await adapter.removeAdjustment(params);
3040
+ if (result._tag === "Success") {
3041
+ const order = (_a = current.value) == null ? void 0 : _a[0];
3042
+ const subscriptionId = currentSubscriptionId ?? (order == null ? void 0 : order.subscriptionId) ?? ((_c = (_b = order == null ? void 0 : order.items) == null ? void 0 : _b[0]) == null ? void 0 : _c.subscriptionId);
3043
+ await notifyChanged(subscriptionId);
3044
+ }
3045
+ return result;
3046
+ },
3047
+ reloadCurrent
3048
+ };
3049
+ }
3050
+ const SettingsServiceContext = createContext("services/settings");
3051
+ function createSettingsService(initial = {}) {
3052
+ const current = d(initial);
3053
+ return {
3054
+ current,
3055
+ get(key) {
3056
+ return this.current.value[key];
3057
+ }
3058
+ };
3059
+ }
3060
+ function createShopService(options) {
3061
+ const contactEmail = d(options.getContactEmail());
3062
+ const service = { contactEmail };
3063
+ return service;
3064
+ }
3065
+ const ShopServiceContext = createContext("services/shop");
3066
+ const SubscriptionServiceContext = createContext("services/subscription");
3067
+ function getCurrentSubscriptionId() {
3068
+ if (typeof window === "undefined") {
3069
+ return null;
3070
+ }
3071
+ const pathname = window.location.pathname;
3072
+ const segments = pathname.split("/").filter(Boolean);
3073
+ const subscriptionIndex = segments.findIndex(
3074
+ (segment) => segment === "subscriptions" || segment === "subscription"
3075
+ );
3076
+ if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
3077
+ return segments[subscriptionIndex + 1];
3078
+ }
3079
+ const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3080
+ const lastSegment = segments[segments.length - 1];
3081
+ if (lastSegment && uuidPattern.test(lastSegment)) {
3082
+ return lastSegment;
3083
+ }
3084
+ return null;
3085
+ }
3086
+ function createSubscriptionService(adapter) {
3087
+ const current = d(null);
3088
+ const hasPendingBilling = w(
3089
+ () => {
3090
+ var _a, _b, _c, _d;
3091
+ return ((_b = (_a = current.value) == null ? void 0 : _a.lastBillingAttempt) == null ? void 0 : _b.ready) === false || ((_d = (_c = current.value) == null ? void 0 : _c.renewal) == null ? void 0 : _d.isInActiveProcessState) === true;
3092
+ }
3093
+ );
3094
+ const discountsChangedAt = d(0);
3095
+ const itemsChangedAt = d(0);
3096
+ const lastOrder = d(null);
3097
+ async function refreshAfterDiscountChange() {
3098
+ await reload();
3099
+ discountsChangedAt.value = discountsChangedAt.value + 1;
3100
+ }
3101
+ async function refreshAfterItemChange() {
3102
+ await reload();
3103
+ itemsChangedAt.value = itemsChangedAt.value + 1;
1149
3104
  }
1150
3105
  function subscriptionIdFromCurrent() {
1151
3106
  var _a;
1152
3107
  return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
1153
3108
  }
3109
+ async function reload() {
3110
+ var _a, _b;
3111
+ const id = (_a = current.value) == null ? void 0 : _a.id;
3112
+ if (!id) return;
3113
+ const [subResult, lastOrderResult] = await Promise.all([
3114
+ adapter.getById(id),
3115
+ adapter.getLastOrder({ subscriptionId: id })
3116
+ ]);
3117
+ if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
3118
+ if (subResult._tag === "Success") current.value = subResult.data;
3119
+ if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
3120
+ }
1154
3121
  const service = {
3122
+ current,
3123
+ hasPendingBilling,
3124
+ discountsChangedAt,
3125
+ itemsChangedAt,
3126
+ lastOrder,
1155
3127
  search: adapter.search,
3128
+ // Prefer the adapter's rich cross-subscription list (legacy endpoint, active-first).
3129
+ // Fall back to a single `search` page for adapters that don't implement it
3130
+ // (e.g. the editor mock) so the cross-subscription views still render.
3131
+ listAll: adapter.listAll ? adapter.listAll.bind(adapter) : async () => {
3132
+ const result = await adapter.search();
3133
+ if (result._tag !== "Success") return result;
3134
+ return { _tag: "Success", data: result.data.items };
3135
+ },
1156
3136
  getById: adapter.getById,
1157
3137
  getCurrentSubscriptionId,
1158
3138
  addItem: async (subscriptionId, item) => {
1159
3139
  const result = await adapter.addItem({ subscriptionId, item });
1160
3140
  if (result._tag === "Success") {
1161
- await refreshAfterItemChange(subscriptionId);
3141
+ await refreshAfterItemChange();
1162
3142
  }
1163
3143
  return result;
1164
3144
  },
1165
3145
  removeItem: async (subscriptionId, itemId) => {
1166
3146
  const result = await adapter.removeItem({ subscriptionId, itemId });
1167
3147
  if (result._tag === "Success") {
1168
- await refreshAfterItemChange(subscriptionId);
3148
+ await refreshAfterItemChange();
3149
+ }
3150
+ return result;
3151
+ },
3152
+ updateItem: async (subscriptionId, lineId, update) => {
3153
+ const result = await adapter.updateItem({ subscriptionId, lineId, update });
3154
+ if (result._tag === "Success") {
3155
+ await refreshAfterItemChange();
3156
+ }
3157
+ return result;
3158
+ },
3159
+ updateItemAttributes: async (subscriptionId, lineId, customAttributes) => {
3160
+ const result = await adapter.updateItemAttributes({ subscriptionId, lineId, customAttributes });
3161
+ if (result._tag === "Success") {
3162
+ await refreshAfterItemChange();
3163
+ }
3164
+ return result;
3165
+ },
3166
+ updateAttributes: async (subscriptionId, customAttributes) => {
3167
+ const result = await adapter.updateAttributes({ subscriptionId, customAttributes });
3168
+ if (result._tag === "Success") {
3169
+ await reload();
3170
+ }
3171
+ return result;
3172
+ },
3173
+ skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
3174
+ const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
3175
+ if (result._tag === "Success") {
3176
+ await refreshAfterItemChange();
3177
+ }
3178
+ return result;
3179
+ },
3180
+ updateBoxItems: async (subscriptionId, params) => {
3181
+ const result = await adapter.updateBoxItems({ subscriptionId, ...params });
3182
+ if (result._tag === "Success") {
3183
+ await refreshAfterItemChange();
1169
3184
  }
1170
3185
  return result;
1171
3186
  },
@@ -1179,7 +3194,7 @@ function createSubscriptionService(adapter) {
1179
3194
  }
1180
3195
  const result = await adapter.addDiscount({ subscriptionId: id, code });
1181
3196
  if (result._tag === "Success") {
1182
- await refreshAfterDiscountChange(id);
3197
+ await refreshAfterDiscountChange();
1183
3198
  }
1184
3199
  return result;
1185
3200
  },
@@ -1196,38 +3211,30 @@ function createSubscriptionService(adapter) {
1196
3211
  discountId
1197
3212
  });
1198
3213
  if (result._tag === "Success") {
1199
- await refreshAfterDiscountChange(id);
3214
+ await refreshAfterDiscountChange();
1200
3215
  }
1201
3216
  return result;
1202
3217
  },
1203
3218
  reschedule: (subscriptionId, newDate) => adapter.reschedule({ subscriptionId, newDate }),
3219
+ updateCustomAttributes: (subscriptionId, customAttributes) => adapter.updateCustomAttributes({ subscriptionId, customAttributes }),
1204
3220
  resume: (subscriptionId) => adapter.resume({ subscriptionId }),
1205
3221
  pause: (subscriptionId) => adapter.pause({ subscriptionId }),
1206
3222
  reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
1207
3223
  cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
3224
+ renewNow: (params) => adapter.renewNow(params),
3225
+ changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
3226
+ reload,
1208
3227
  getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
1209
3228
  getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
1210
- current,
1211
- discountsChangedAt: discountsChangedAtSignal,
1212
- itemsChangedAt: itemsChangedAtSignal
3229
+ getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
3230
+ addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
1213
3231
  };
1214
3232
  return service;
1215
3233
  }
1216
- const DISCOUNT_REJECTION_REASONS = [
1217
- "INVALID_CODE",
1218
- "INACTIVE",
1219
- "NOT_APPLICABLE_TO_CUSTOMER",
1220
- "ALREADY_ADDED",
1221
- "ALREADY_USED",
1222
- "NOT_APPLICABLE_TO_PRODUCTS",
1223
- "NOT_APPLICABLE_TO_SUBSCRIPTION",
1224
- "INVALID_COMBINATION",
1225
- "QUANTITY_REQUIREMENT_NOT_MET"
1226
- ];
1227
3234
  function getDefaultBlockForAction(actionType, actionConfig, blockId) {
1228
3235
  const blockName = `action:${actionType}`;
1229
3236
  const definedBlocks = getDefinedBlocks();
1230
- const exists = definedBlocks.some((b2) => b2.name === blockName);
3237
+ const exists = definedBlocks.some((b3) => b3.name === blockName);
1231
3238
  if (!exists) {
1232
3239
  throw new Error(
1233
3240
  `No default block registered for action type "${actionType}". Expected a block named "${blockName}" to be registered in blocks-extensions.`
@@ -1237,7 +3244,7 @@ function getDefaultBlockForAction(actionType, actionConfig, blockId) {
1237
3244
  }
1238
3245
  function hasDefaultBlockForAction(actionType) {
1239
3246
  const blockName = `action:${actionType}`;
1240
- return getDefinedBlocks().some((b2) => b2.name === blockName);
3247
+ return getDefinedBlocks().some((b3) => b3.name === blockName);
1241
3248
  }
1242
3249
  const WorkflowServiceContext = createContext("services/workflow");
1243
3250
  const RUN_SCOPED_RESET = {
@@ -1375,7 +3382,7 @@ function createWorkflowService(deps) {
1375
3382
  outcome: null
1376
3383
  });
1377
3384
  if (editor.isEditorMode) {
1378
- const displayStepId = isInitialResponse && previewStepId && ((_e = data.interactiveSteps) == null ? void 0 : _e.some((s) => s.stepId === previewStepId)) ? previewStepId : currentStepId;
3385
+ const displayStepId = isInitialResponse && previewStepId && ((_e = data.interactiveSteps) == null ? void 0 : _e.some((s2) => s2.stepId === previewStepId)) ? previewStepId : currentStepId;
1379
3386
  const overlayBlocks = deps.themeState.overlay.blocks.value;
1380
3387
  const rootBlock = overlayBlocks[0];
1381
3388
  const stepSlot = displayStepId ? (_f = rootBlock == null ? void 0 : rootBlock.slots) == null ? void 0 : _f[displayStepId] : null;
@@ -1470,7 +3477,7 @@ function createWorkflowService(deps) {
1470
3477
  const presets = {};
1471
3478
  const baseLabelFor = (step) => {
1472
3479
  var _a;
1473
- return ((_a = getDefinedBlocks().find((b2) => b2.name === `action:${step.actionType}`)) == null ? void 0 : _a.displayName) ?? step.stepId;
3480
+ return ((_a = getDefinedBlocks().find((b3) => b3.name === `action:${step.actionType}`)) == null ? void 0 : _a.displayName) ?? step.stepId;
1474
3481
  };
1475
3482
  const totalCounts = /* @__PURE__ */ new Map();
1476
3483
  for (const step of allSteps) {
@@ -2065,9 +4072,9 @@ const gridV1Engine = {
2065
4072
  case "resize": {
2066
4073
  const current = layout.items[cmd.blockId];
2067
4074
  if (current) {
2068
- const w3 = Math.max(1, Math.min(cmd.size.w, ctx2.cols - current.c));
2069
- const h = Math.max(1, cmd.size.h);
2070
- layout.items[cmd.blockId] = { ...current, w: w3, h };
4075
+ const w4 = Math.max(1, Math.min(cmd.size.w, ctx2.cols - current.c));
4076
+ const h2 = Math.max(1, cmd.size.h);
4077
+ layout.items[cmd.blockId] = { ...current, w: w4, h: h2 };
2071
4078
  }
2072
4079
  break;
2073
4080
  }
@@ -2134,7 +4141,7 @@ function insertRectRelative(doc, args) {
2134
4141
  if (!layout) continue;
2135
4142
  const target = layout.items[args.targetId];
2136
4143
  if (!target) continue;
2137
- const cols = ((_a = doc.breakpoints.find((b2) => b2.id === breakpointId)) == null ? void 0 : _a.cols) ?? ((_b = layout.grid) == null ? void 0 : _b.cols) ?? DEFAULT_GRID.cols;
4144
+ const cols = ((_a = doc.breakpoints.find((b3) => b3.id === breakpointId)) == null ? void 0 : _a.cols) ?? ((_b = layout.grid) == null ? void 0 : _b.cols) ?? DEFAULT_GRID.cols;
2138
4145
  const insertRow = args.position === "before" ? target.r : target.r + target.h;
2139
4146
  for (const [id, rect] of Object.entries(layout.items)) {
2140
4147
  if (id === args.blockId) continue;
@@ -2185,105 +4192,128 @@ export {
2185
4192
  AuthenticationError,
2186
4193
  AuthenticationErrorReason,
2187
4194
  B as BlockWrapperEvent,
2188
- v as ContextProvideEvent,
2189
- x as ContextRequestEvent,
4195
+ q as ContextProvideEvent,
4196
+ t as ContextRequestEvent,
2190
4197
  CustomerServiceContext,
2191
- y as DEFAULT_BREAKPOINTS,
2192
- z as DEFAULT_GLOBAL_STYLES,
4198
+ h as DEFAULT_BREAKPOINTS,
4199
+ i2 as DEFAULT_GLOBAL_STYLES,
2193
4200
  DEFAULT_GRID,
2194
4201
  DEFAULT_NEW_BLOCK_HEIGHT,
2195
4202
  DISCOUNT_REJECTION_REASONS,
2196
4203
  GRID_V1_ENGINE_ID,
2197
- L as LAYOUT_SCOPE_ATTR,
4204
+ L2 as LAYOUT_SCOPE_ATTR,
2198
4205
  LoginServiceContext,
2199
- OrdersServiceContext,
4206
+ OrderServiceContext,
4207
+ OverlayServiceContext,
2200
4208
  P as PAGE_ROOT_SLOT,
2201
4209
  ProductServiceContext,
2202
4210
  RouterServiceContext,
4211
+ SchedulesServiceContext,
2203
4212
  S as SelectedBlockContext,
4213
+ SettingsServiceContext,
4214
+ ShopServiceContext,
2204
4215
  u as Signal,
2205
4216
  StateContext,
2206
4217
  SubscriptionServiceContext,
2207
- c as ThemeStateContext,
4218
+ a as ThemeStateContext,
2208
4219
  T as TranslationContext,
2209
4220
  WorkflowServiceContext,
2210
- A as attachEmptyTracking,
2211
- d2 as backfillInstanceDefaults,
2212
- b as buildGridLayout,
2213
- F as buildLayoutCss,
4221
+ v as __recordDevtoolsInstance,
4222
+ _ as __recordDevtoolsPropSetter,
4223
+ x as attachEmptyTracking,
4224
+ c as backfillInstanceDefaults,
4225
+ b2 as buildGridLayout,
4226
+ j as buildLayoutCss,
2214
4227
  canvasMode,
2215
4228
  clampRect,
2216
- H as classifyRowDividers,
4229
+ k2 as classifyRowDividers,
2217
4230
  clearAllSurfaceContexts,
2218
4231
  clearLayoutModeAdapter,
2219
4232
  compactItems,
2220
- I as compactLayoutDocument,
4233
+ l2 as compactLayoutDocument,
2221
4234
  w as computed,
4235
+ createApiCustomerAdapter,
4236
+ createApiLoginAdapter,
4237
+ createApiOrderAdapter,
4238
+ createApiProductAdapter,
4239
+ createApiSchedulesAdapter,
4240
+ createApiSubscriptionAdapter,
2222
4241
  createBlockInstance,
2223
- J as createBlockInstanceFromJSON,
2224
- K as createBlockInstanceFromObject,
2225
- N as createCeRenderer,
4242
+ y as createBlockInstanceFromJSON,
4243
+ g as createBlockInstanceFromObject,
4244
+ z as createCeRenderer,
2226
4245
  createContext,
2227
4246
  createCustomerService,
2228
- O as createDefaultLayoutDocument,
2229
- createMockOrdersAdapter,
4247
+ m as createDefaultLayoutDocument,
4248
+ createLoginService,
4249
+ createMockCustomerAdapter,
4250
+ createMockLoginAdapter,
4251
+ createMockOrderAdapter,
2230
4252
  createMockProductAdapter,
2231
4253
  createMockRouterService,
4254
+ createMockSchedulesAdapter,
2232
4255
  createMockSubscriptionAdapter,
2233
4256
  createMockWorkflowApiAdapter,
2234
- createOrdersService,
4257
+ createOrderService,
4258
+ createOverlayService,
2235
4259
  createProductService,
2236
4260
  createRouterService,
2237
- Q as createSelectedBlock,
4261
+ createSchedulesService,
4262
+ A as createSelectedBlock,
4263
+ createSettingsService,
4264
+ createShopService,
2238
4265
  createState,
2239
4266
  createSubscriptionService,
2240
- R as createThemeState,
2241
- U as createTranslationService,
4267
+ n as createThemeState,
4268
+ o2 as createTranslationService,
2242
4269
  createWorkflowApiAdapter,
2243
4270
  createWorkflowService,
2244
4271
  defineBlock,
2245
- V as defineBlockRuntime,
2246
- W as deriveLayout,
4272
+ D as defineBlockRuntime,
4273
+ defineOverlayWrapper,
4274
+ p as deriveLayout,
2247
4275
  E as effect,
2248
4276
  emitLayoutCommand,
2249
4277
  emitLayoutSetRects,
2250
- X as expandFillLayoutDocument,
4278
+ q2 as expandFillLayoutDocument,
2251
4279
  findFreeRect,
2252
4280
  getActiveSurfaceContext,
2253
4281
  getDefaultBlockForAction,
2254
4282
  getDefinedBlocks,
4283
+ F as getDevtoolsBlockState,
2255
4284
  getLayoutModeAdapter,
2256
4285
  gridV1Engine,
2257
4286
  hasDefaultBlockForAction,
2258
- Y as hasRenderedContent,
2259
- Z as indexOverrides,
4287
+ G as hasRenderedContent,
4288
+ s as indexOverrides,
2260
4289
  i as injectContext,
2261
4290
  insertRectRelative,
2262
- _ as isEditorPreview,
2263
- a as isStubDefinition,
2264
- $ as layoutScopeSelector,
2265
- a0 as layoutSystemEnabled,
4291
+ H as isEditorPreview,
4292
+ b as isStubDefinition,
4293
+ t2 as layoutScopeSelector,
4294
+ I as layoutSystemEnabled,
2266
4295
  loadBlockRegistry,
2267
4296
  loginService,
2268
4297
  o as onRegisterBlock,
2269
- a1 as projectToCss,
2270
- p as projectToGridStack,
2271
- a2 as provideContext,
2272
- a3 as readingOrder,
2273
- e as reconcileLayout,
2274
- a4 as rectsOverlap,
4298
+ u2 as projectToCss,
4299
+ p2 as projectToGridStack,
4300
+ J as provideContext,
4301
+ v2 as readingOrder,
4302
+ c2 as reconcileLayout,
4303
+ w3 as rectsOverlap,
2275
4304
  registerBlock,
2276
4305
  registerLayoutModeAdapter,
2277
4306
  resetBreakpoint,
2278
4307
  r as resolveLayout,
2279
- a5 as serializeBlocks,
4308
+ k as serializeBlocks,
2280
4309
  setActiveSurfaceContext,
4310
+ K as setDevtoolsBlockProp,
2281
4311
  setLayoutCommandDispatcher,
2282
4312
  setLayoutCommandEmitter,
2283
- a6 as setLayoutSystemEnabled,
4313
+ l as setLayoutSystemEnabled,
2284
4314
  d as signal,
2285
4315
  subscribeContext,
2286
- a7 as untracked,
4316
+ L as untracked,
2287
4317
  waitForDevBlocksUrl,
2288
4318
  w2 as withWrapper
2289
4319
  };