@juo/blocks 1.14.0 → 1.16.0

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