@juo/blocks 1.11.0 → 1.12.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/{ExtensionRoot-9XfhG8YM.js → ExtensionRoot-Z2DlF9X6.js} +14 -3
  2. package/dist/block-Bh3z0B3y.js +971 -0
  3. package/dist/block.d.ts +50 -0
  4. package/dist/block.d.ts.map +1 -1
  5. package/dist/block.devtools.test.d.ts +2 -0
  6. package/dist/block.devtools.test.d.ts.map +1 -0
  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 +1677 -330
  11. package/dist/editor/components.d.ts.map +1 -1
  12. package/dist/editor/main.d.ts +4 -0
  13. package/dist/editor/main.d.ts.map +1 -1
  14. package/dist/editor/main.test.d.ts +2 -0
  15. package/dist/editor/main.test.d.ts.map +1 -0
  16. package/dist/editor.js +19 -2
  17. package/dist/{juo-workflow-CkyZRq60.js → juo-workflow-D3c29mxb.js} +52 -871
  18. package/dist/main.d.ts +2 -1
  19. package/dist/main.d.ts.map +1 -1
  20. package/dist/overlay-wrapper.d.ts +68 -0
  21. package/dist/overlay-wrapper.d.ts.map +1 -0
  22. package/dist/registry.devtools.test.d.ts +2 -0
  23. package/dist/registry.devtools.test.d.ts.map +1 -0
  24. package/dist/services/customer/adapter.d.ts +8 -0
  25. package/dist/services/customer/adapter.d.ts.map +1 -0
  26. package/dist/services/customer/customerService.d.ts +3 -2
  27. package/dist/services/customer/customerService.d.ts.map +1 -1
  28. package/dist/services/customer/index.d.ts +1 -0
  29. package/dist/services/customer/index.d.ts.map +1 -1
  30. package/dist/services/customer/types.d.ts +4 -1
  31. package/dist/services/customer/types.d.ts.map +1 -1
  32. package/dist/services/index.d.ts +3 -0
  33. package/dist/services/index.d.ts.map +1 -1
  34. package/dist/services/login/adapter.d.ts +32 -0
  35. package/dist/services/login/adapter.d.ts.map +1 -0
  36. package/dist/services/login/index.d.ts +3 -1
  37. package/dist/services/login/index.d.ts.map +1 -1
  38. package/dist/services/login/loginService.d.ts +6 -2
  39. package/dist/services/login/loginService.d.ts.map +1 -1
  40. package/dist/services/order/adapter.d.ts +9 -0
  41. package/dist/services/order/adapter.d.ts.map +1 -0
  42. package/dist/services/order/index.d.ts +5 -0
  43. package/dist/services/order/index.d.ts.map +1 -0
  44. package/dist/services/order/orderService.d.ts +7 -0
  45. package/dist/services/order/orderService.d.ts.map +1 -0
  46. package/dist/services/order/types.d.ts +21 -0
  47. package/dist/services/order/types.d.ts.map +1 -0
  48. package/dist/services/orders/adapter.d.ts +2 -1
  49. package/dist/services/orders/adapter.d.ts.map +1 -1
  50. package/dist/services/orders/types.d.ts +8 -0
  51. package/dist/services/orders/types.d.ts.map +1 -1
  52. package/dist/services/product/adapter.d.ts +27 -2
  53. package/dist/services/product/adapter.d.ts.map +1 -1
  54. package/dist/services/product/adapter.test.d.ts +2 -0
  55. package/dist/services/product/adapter.test.d.ts.map +1 -0
  56. package/dist/services/product/index.d.ts +1 -1
  57. package/dist/services/product/index.d.ts.map +1 -1
  58. package/dist/services/product/productService.d.ts.map +1 -1
  59. package/dist/services/product/types.d.ts +68 -0
  60. package/dist/services/product/types.d.ts.map +1 -1
  61. package/dist/services/schedules/adapter.d.ts +25 -0
  62. package/dist/services/schedules/adapter.d.ts.map +1 -0
  63. package/dist/services/schedules/adapter.test.d.ts +2 -0
  64. package/dist/services/schedules/adapter.test.d.ts.map +1 -0
  65. package/dist/services/schedules/index.d.ts +5 -0
  66. package/dist/services/schedules/index.d.ts.map +1 -0
  67. package/dist/services/schedules/schedulesService.d.ts +11 -0
  68. package/dist/services/schedules/schedulesService.d.ts.map +1 -0
  69. package/dist/services/schedules/schedulesService.test.d.ts +2 -0
  70. package/dist/services/schedules/schedulesService.test.d.ts.map +1 -0
  71. package/dist/services/schedules/types.d.ts +101 -0
  72. package/dist/services/schedules/types.d.ts.map +1 -0
  73. package/dist/services/settings/index.d.ts +3 -0
  74. package/dist/services/settings/index.d.ts.map +1 -0
  75. package/dist/services/settings/settingsService.d.ts +7 -0
  76. package/dist/services/settings/settingsService.d.ts.map +1 -0
  77. package/dist/services/settings/settingsService.test.d.ts +2 -0
  78. package/dist/services/settings/settingsService.test.d.ts.map +1 -0
  79. package/dist/services/settings/types.d.ts +35 -0
  80. package/dist/services/settings/types.d.ts.map +1 -0
  81. package/dist/services/shop/index.d.ts +3 -0
  82. package/dist/services/shop/index.d.ts.map +1 -0
  83. package/dist/services/shop/shopService.d.ts +12 -0
  84. package/dist/services/shop/shopService.d.ts.map +1 -0
  85. package/dist/services/shop/shopService.test.d.ts +2 -0
  86. package/dist/services/shop/shopService.test.d.ts.map +1 -0
  87. package/dist/services/shop/types.d.ts +7 -0
  88. package/dist/services/shop/types.d.ts.map +1 -0
  89. package/dist/services/subscription/adapter.d.ts +40 -2
  90. package/dist/services/subscription/adapter.d.ts.map +1 -1
  91. package/dist/services/subscription/adapter.test.d.ts +2 -0
  92. package/dist/services/subscription/adapter.test.d.ts.map +1 -0
  93. package/dist/services/subscription/index.d.ts +1 -1
  94. package/dist/services/subscription/index.d.ts.map +1 -1
  95. package/dist/services/subscription/subscriptionService.d.ts +1 -6
  96. package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
  97. package/dist/services/subscription/types.d.ts +186 -9
  98. package/dist/services/subscription/types.d.ts.map +1 -1
  99. package/dist/services/subscription/types.test.d.ts +2 -0
  100. package/dist/services/subscription/types.test.d.ts.map +1 -0
  101. package/dist/theme-state.d.ts +2 -1
  102. package/dist/theme-state.d.ts.map +1 -1
  103. package/dist/theme-state.params.test.d.ts +2 -0
  104. package/dist/theme-state.params.test.d.ts.map +1 -0
  105. package/dist/theme-state.translations.test.d.ts +2 -0
  106. package/dist/theme-state.translations.test.d.ts.map +1 -0
  107. package/dist/utils/index.d.ts +6 -0
  108. package/dist/utils/index.d.ts.map +1 -1
  109. package/dist/vue/block.d.ts.map +1 -1
  110. package/dist/vue.js +23 -3
  111. package/dist/web-components/ExtensionRoot.d.ts +1 -0
  112. package/dist/web-components/ExtensionRoot.d.ts.map +1 -1
  113. package/dist/web-components-editor.js +3 -2
  114. package/dist/web-components-runtime.js +3 -2
  115. package/package.json +3 -2
package/dist/core.js CHANGED
@@ -1,8 +1,9 @@
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 } from "./juo-workflow-CkyZRq60.js";
5
- import { B, C, a, D, S, T, b, W, e, f, g, h, i, j, k, l, m, n, o, p, q, s, t, v, x, y, z, A, F, G, H, I, J, K, L } from "./juo-workflow-CkyZRq60.js";
4
+ import { c as createContext, d, w, a as defineBlock, b as createBlockInstance, r as registerBlock, E, u } from "./block-Bh3z0B3y.js";
5
+ import { B, C, e, S, _, f, g, h, i, j, k, l, m, n, o, p, q, s, t, v, x, y, z, A, D, F } from "./block-Bh3z0B3y.js";
6
+ import { D as D2, T, a, W, c, b, d as d2, e as e2, g as g2, h as h2, i as i2 } from "./juo-workflow-D3c29mxb.js";
6
7
  import { reportBlockWarning, reportBlockError } from "./observability.js";
7
8
  function createState() {
8
9
  const discountCode = d(null);
@@ -72,6 +73,49 @@ function createState() {
72
73
  };
73
74
  }
74
75
  const StateContext = createContext("state");
76
+ const OVERLAY_WRAPPER_SCHEMA = {
77
+ type: "object",
78
+ properties: {
79
+ props: {
80
+ type: "object",
81
+ properties: {
82
+ layout: {
83
+ type: "string",
84
+ enum: ["modal", "sidebar"]
85
+ },
86
+ canAbort: {
87
+ type: "boolean"
88
+ }
89
+ },
90
+ required: ["layout"],
91
+ additionalProperties: false
92
+ },
93
+ slots: {
94
+ type: "object",
95
+ properties: {
96
+ content: {}
97
+ },
98
+ additionalProperties: false
99
+ }
100
+ },
101
+ required: ["props", "slots"],
102
+ additionalProperties: false
103
+ };
104
+ function defineOverlayWrapper(opts) {
105
+ const layout = opts.layout ?? "sidebar";
106
+ const canAbort = opts.canAbort ?? true;
107
+ return defineBlock(opts.name, {
108
+ group: "internal",
109
+ schema: OVERLAY_WRAPPER_SCHEMA,
110
+ initialValue: () => ({
111
+ props: { layout, canAbort },
112
+ slots: {
113
+ content: [createBlockInstance(opts.content)]
114
+ }
115
+ }),
116
+ renderer: () => null
117
+ });
118
+ }
75
119
  function isLocalhostOrigin(origin) {
76
120
  try {
77
121
  const url = new URL(origin);
@@ -132,7 +176,7 @@ async function loadBlockRegistry(options = {}) {
132
176
  if ("registerBlocks" in module && typeof module.registerBlocks === "function") {
133
177
  module.registerBlocks();
134
178
  } else {
135
- Object.entries(module).forEach(([_, item]) => {
179
+ Object.entries(module).forEach(([_2, item]) => {
136
180
  if (typeof item === "object" && item != null) {
137
181
  registerBlock(item);
138
182
  }
@@ -156,13 +200,43 @@ async function loadBlockRegistry(options = {}) {
156
200
  function createCustomerService(options) {
157
201
  const current = d(null);
158
202
  const accountOrdersUrl = d(options.getAccountOrdersUrl());
159
- const service = {
203
+ return {
160
204
  current,
161
- accountOrdersUrl
205
+ accountOrdersUrl,
206
+ async getCurrent() {
207
+ const result = await options.adapter.getCurrent();
208
+ if (result._tag === "Success") current.value = result.data;
209
+ return result;
210
+ }
162
211
  };
163
- return service;
164
212
  }
165
213
  const CustomerServiceContext = createContext("services/customer");
214
+ function createMockCustomerAdapter() {
215
+ const customer = { id: "customer-1", name: "Customer 1" };
216
+ return {
217
+ async getCurrent() {
218
+ return { _tag: "Success", data: customer };
219
+ }
220
+ };
221
+ }
222
+ function createApiCustomerAdapter(fetcher) {
223
+ return {
224
+ async getCurrent() {
225
+ try {
226
+ const res = await fetcher("/customer/info");
227
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
228
+ const data = await res.json();
229
+ const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
230
+ return {
231
+ _tag: "Success",
232
+ data: { id: data.id, name, email: data.email }
233
+ };
234
+ } catch (e3) {
235
+ return { _tag: "Failure", error: e3 };
236
+ }
237
+ }
238
+ };
239
+ }
166
240
  const PARAMS = {
167
241
  DELETGATED_TOKEN: "delegated-token",
168
242
  REDIRECT_TO: "redirect-to",
@@ -176,9 +250,125 @@ const PARAMS = {
176
250
  function uuid() {
177
251
  return ("10000000-1000-4000-8000" + -1e11).replace(
178
252
  /[018]/g,
179
- (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
253
+ (c2) => (c2 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c2 / 4).toString(16)
180
254
  );
181
255
  }
256
+ function createMockLoginAdapter() {
257
+ return {
258
+ async passwordlessStart() {
259
+ return { _tag: "Success", data: void 0 };
260
+ },
261
+ async authenticate() {
262
+ return {
263
+ _tag: "Success",
264
+ data: {
265
+ access_token: "mock-access-token",
266
+ refresh_token: "mock-refresh-token",
267
+ expires_in: 3600
268
+ }
269
+ };
270
+ },
271
+ getAuthorizeUrl({ connection, state, shop, appUrl }) {
272
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
273
+ url.searchParams.set("connection", connection);
274
+ url.searchParams.set("state", state);
275
+ url.searchParams.set("shop", shop);
276
+ return url.toString();
277
+ }
278
+ };
279
+ }
280
+ function createApiLoginAdapter(fetcher, opts) {
281
+ const { shopDomain, appUrl } = opts;
282
+ async function callAuthEndpoint(path, body, lang) {
283
+ const url = lang ? `${path}?lang=${lang}` : path;
284
+ const res = await fetcher(url, {
285
+ method: "POST",
286
+ headers: {
287
+ Accept: "application/json",
288
+ "Content-Type": "application/json",
289
+ "X-Shopify-Shop-Domain": shopDomain
290
+ },
291
+ body: JSON.stringify(body)
292
+ });
293
+ if (res.status === 403) {
294
+ const data = await res.json();
295
+ if ("error_description" in data) {
296
+ if (data.error_description === "Wrong email or verification code.") {
297
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
298
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
299
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
300
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
301
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
302
+ }
303
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
304
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
305
+ connection: data.connection
306
+ });
307
+ }
308
+ }
309
+ if (!res.ok) {
310
+ throw new Error(`Unexpected response: ${res.statusText}`);
311
+ }
312
+ return { _tag: "Success", data: void 0 };
313
+ }
314
+ return {
315
+ async passwordlessStart({ email, lang }) {
316
+ try {
317
+ await callAuthEndpoint(
318
+ "/auth/customer/passwordless/start",
319
+ { email },
320
+ lang
321
+ );
322
+ return { _tag: "Success", data: void 0 };
323
+ } catch (e3) {
324
+ return { _tag: "Failure", error: e3 };
325
+ }
326
+ },
327
+ async authenticate(params) {
328
+ try {
329
+ const res = await fetcher("/auth/customer/token", {
330
+ method: "POST",
331
+ headers: {
332
+ Accept: "application/json",
333
+ "Content-Type": "application/json",
334
+ "X-Shopify-Shop-Domain": shopDomain
335
+ },
336
+ body: JSON.stringify(params)
337
+ });
338
+ if (res.status === 403) {
339
+ const data = await res.json();
340
+ if ("error_description" in data) {
341
+ if (data.error_description === "Wrong email or verification code.") {
342
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
343
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
344
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
345
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
346
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
347
+ }
348
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
349
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
350
+ connection: data.connection
351
+ });
352
+ }
353
+ }
354
+ if (!res.ok) {
355
+ throw new Error(`Unexpected response: ${res.statusText}`);
356
+ }
357
+ return { _tag: "Success", data: await res.json() };
358
+ } catch (e3) {
359
+ return { _tag: "Failure", error: e3 };
360
+ }
361
+ },
362
+ getAuthorizeUrl({ connection, state, shop }) {
363
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
364
+ url.searchParams.set("connection", connection);
365
+ url.searchParams.set("state", state);
366
+ url.searchParams.set("shop", shop);
367
+ return url.toString();
368
+ }
369
+ };
370
+ }
371
+ const __vite_import_meta_env__ = {};
182
372
  var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
183
373
  AuthenticationErrorReason2["Expired"] = "expired";
184
374
  AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
@@ -197,6 +387,9 @@ class AuthenticationError extends Error {
197
387
  this.data = data ?? {};
198
388
  }
199
389
  }
390
+ const STORAGE_DELEGATED_TOKEN = "delegated-token";
391
+ const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
392
+ const STORAGE_STATE_KEY = "state";
200
393
  function storage(key, value) {
201
394
  const storageKey = `auth:${key}`;
202
395
  try {
@@ -212,83 +405,33 @@ function storage(key, value) {
212
405
  }
213
406
  return null;
214
407
  }
215
- const STORAGE_DELEGATED_TOKEN = "delegated-token";
216
- const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
217
- const STORAGE_STATE_KEY = "state";
218
- function createAuthFunctions(basePath, shop) {
219
- async function start(email, lang) {
220
- const response = await fetch(
221
- `${basePath}/auth/customer/passwordless/start?lang=${lang}`,
222
- {
223
- method: "POST",
224
- headers: {
225
- Accept: "application/json",
226
- "Content-Type": "application/json",
227
- "X-Shopify-Shop-Domain": shop
228
- },
229
- body: JSON.stringify({ email })
230
- }
231
- );
232
- if (response.status !== 200) {
233
- throw new Error(`Unexpected response: ${response.statusText}`);
234
- }
235
- }
236
- async function authenticate(data) {
237
- const response = await fetch(`${basePath}/auth/customer/token`, {
238
- method: "POST",
239
- headers: {
240
- Accept: "application/json",
241
- "Content-Type": "application/json",
242
- "X-Shopify-Shop-Domain": shop
243
- },
244
- body: JSON.stringify(data)
245
- });
246
- if (response.status === 403) {
247
- const data2 = await response.json();
248
- if ("error_description" in data2) {
249
- if (data2.error_description === "Wrong email or verification code.") {
250
- throw new AuthenticationError(
251
- "wrong-code"
252
- /* IncorrectCode */
253
- );
254
- } else if (data2.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
255
- throw new AuthenticationError(
256
- "too-many-tries"
257
- /* TooManyTries */
258
- );
259
- } else if (data2.error_description === "The verification code has expired. Please try to login again.") {
260
- throw new AuthenticationError(
261
- "expired"
262
- /* Expired */
263
- );
264
- }
265
- } else if ("code" in data2) {
266
- if (data2.code === "AUTH_ERROR") {
267
- throw new AuthenticationError("no-email", {
268
- connection: data2.connection
269
- });
270
- }
271
- }
272
- }
273
- if (response.status !== 200) {
274
- throw new Error(`Unexpected response: ${response.statusText}`);
275
- }
276
- return await response.json();
277
- }
278
- return { start, authenticate };
279
- }
280
408
  function nonce(len = 10) {
281
409
  return uuid().replace(/-/g, "").slice(0, len);
282
410
  }
283
- function createAuthService(apiUrl, shopDomain) {
284
- const { start: authStart, authenticate: authAuthenticate } = createAuthFunctions(apiUrl, shopDomain);
411
+ function loginService(deps) {
412
+ const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
413
+ 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";
414
+ const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
415
+ const adapter = createApiLoginAdapter(fetcher, {
416
+ shopDomain: deps.shop.domain,
417
+ appUrl
418
+ });
419
+ return createLoginService({
420
+ adapter,
421
+ router: deps.router,
422
+ formatError: deps.formatError,
423
+ shop: {
424
+ domain: deps.shop.domain,
425
+ locale: w(() => deps.getLocale())
426
+ },
427
+ onLoginSuccess: deps.onLoginSuccess
428
+ });
429
+ }
430
+ function createLoginService(init) {
431
+ const { adapter, formatError, router, shop, onLoginSuccess } = init;
285
432
  const accessToken = d(null);
286
- const delegatedToken = d(
287
- storage(STORAGE_DELEGATED_TOKEN)
288
- );
289
- const refreshToken = d(
290
- storage(STORAGE_REFRESH_TOKEN_KEY)
291
- );
433
+ const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
434
+ const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
292
435
  const expires = d(null);
293
436
  const state = d(storage(STORAGE_STATE_KEY));
294
437
  E(() => {
@@ -300,67 +443,28 @@ function createAuthService(apiUrl, shopDomain) {
300
443
  E(() => {
301
444
  storage(STORAGE_STATE_KEY, state.value);
302
445
  });
303
- function store(data) {
446
+ function storeTokens(data) {
304
447
  const { access_token, refresh_token, expires_in } = data;
305
448
  accessToken.value = access_token;
306
449
  refreshToken.value = refresh_token;
307
450
  expires.value = new Date(Date.now() + expires_in);
308
451
  }
309
- return {
310
- state,
311
- async start(email, lang) {
312
- await authStart(email, lang);
313
- },
314
- async authenticate(data) {
315
- store(await authAuthenticate(data));
316
- },
317
- getDelegatedToken() {
318
- return delegatedToken.value;
319
- },
320
- setDelegatedToken(token) {
321
- delegatedToken.value = token;
322
- },
323
- async getToken() {
324
- if (refreshToken.value == null) {
325
- return null;
326
- }
327
- if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
328
- accessToken.value = new Promise(
329
- (resolve, reject) => authAuthenticate({ refreshToken: refreshToken.value }).then((data) => data).then((data) => {
330
- store(data);
331
- resolve(data.access_token);
452
+ async function getToken() {
453
+ if (refreshToken.value == null) return null;
454
+ if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
455
+ accessToken.value = new Promise(
456
+ (resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
457
+ if (result._tag === "Failure") {
458
+ reject(result.error);
332
459
  return;
333
- }).catch(reject)
334
- );
335
- }
336
- return Promise.resolve(accessToken.value);
337
- },
338
- logout() {
339
- accessToken.value = null;
340
- delegatedToken.value = null;
341
- refreshToken.value = null;
342
- expires.value = null;
343
- },
344
- isAuthenticated: w(
345
- () => delegatedToken.value != null || refreshToken.value != null
346
- )
347
- };
348
- }
349
- function loginService(deps) {
350
- return createLoginService({
351
- apiUrl: `${deps.shop.rootUrl.replace(/\/$/, "")}/api`,
352
- router: deps.router,
353
- formatError: deps.formatError,
354
- shop: {
355
- domain: deps.shop.domain,
356
- locale: w(() => deps.getLocale())
357
- },
358
- onLoginSuccess: deps.onLoginSuccess
359
- });
360
- }
361
- function createLoginService(init) {
362
- const { formatError, router, shop, onLoginSuccess } = init;
363
- const auth = createAuthService(init.apiUrl.replace(/\/$/, ""), shop.domain);
460
+ }
461
+ storeTokens(result.data);
462
+ resolve(result.data.access_token);
463
+ }).catch(reject)
464
+ );
465
+ }
466
+ return Promise.resolve(accessToken.value);
467
+ }
364
468
  const userEmail = d(null);
365
469
  async function onSuccess(query) {
366
470
  await (onLoginSuccess == null ? void 0 : onLoginSuccess());
@@ -373,13 +477,9 @@ function createLoginService(init) {
373
477
  queryParams[key] = value;
374
478
  }
375
479
  });
376
- router.push({
377
- path: decodeURIComponent(redirect),
378
- query: queryParams
379
- });
480
+ router.push({ path: decodeURIComponent(redirect), query: queryParams });
380
481
  } else {
381
- const redirect = query.get(PARAMS.REDIRECT_TO);
382
- router.replace(decodeURIComponent(redirect));
482
+ router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
383
483
  }
384
484
  } else {
385
485
  router.replace("/");
@@ -391,16 +491,17 @@ function createLoginService(init) {
391
491
  async start(email) {
392
492
  try {
393
493
  userEmail.value = email;
394
- await auth.start(
395
- userEmail.value,
396
- shop.locale instanceof u ? shop.locale.value : shop.locale
397
- );
494
+ const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
495
+ const result = await adapter.passwordlessStart({
496
+ email: userEmail.value,
497
+ lang
498
+ });
499
+ if (result._tag === "Failure") {
500
+ return { _tag: "Failure", error: formatError(result.error) };
501
+ }
398
502
  return { _tag: "Success", data: void 0 };
399
503
  } catch (error) {
400
- return {
401
- _tag: "Failure",
402
- error: formatError(error)
403
- };
504
+ return { _tag: "Failure", error: formatError(error) };
404
505
  }
405
506
  },
406
507
  async verify(email, code) {
@@ -411,63 +512,61 @@ function createLoginService(init) {
411
512
  };
412
513
  }
413
514
  try {
414
- const result = await auth.authenticate({
515
+ const result = await adapter.authenticate({
415
516
  email: userEmail.value,
416
517
  code
417
518
  });
519
+ if (result._tag === "Failure") {
520
+ return { _tag: "Failure", error: formatError(result.error) };
521
+ }
522
+ storeTokens(result.data);
418
523
  await onSuccess();
419
- return { _tag: "Success", data: result };
524
+ return { _tag: "Success", data: void 0 };
420
525
  } catch (error) {
421
- return {
422
- _tag: "Failure",
423
- error: formatError(error)
424
- };
526
+ return { _tag: "Failure", error: formatError(error) };
425
527
  }
426
528
  }
427
529
  }
428
530
  },
429
531
  social: {
430
532
  async start(provider) {
431
- function socialUrl(key, shop2) {
432
- auth.state.value = nonce();
433
- const url = new URL(
434
- // TODO: double check admin url
435
- `${"https://admin.juo.io"}/auth/customer/authorize`
436
- );
437
- url.searchParams.append("connection", key);
438
- url.searchParams.append("state", auth.state.value ?? "");
439
- url.searchParams.append("shop", shop2);
440
- return url.toString();
441
- }
442
- window.location.href = socialUrl(
443
- provider === "Facebook" ? "facebook" : "google-oauth2",
444
- shop.domain
445
- );
446
- return {
447
- _tag: "Success",
448
- data: void 0
449
- };
533
+ state.value = nonce();
534
+ const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
535
+ window.location.href = adapter.getAuthorizeUrl({
536
+ connection,
537
+ state: state.value ?? "",
538
+ shop: shop.domain,
539
+ appUrl: (
540
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
541
+ (typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io"
542
+ )
543
+ });
544
+ return { _tag: "Success", data: void 0 };
450
545
  },
451
546
  async verify(code) {
452
547
  try {
453
- const result = await auth.authenticate({ code });
548
+ const result = await adapter.authenticate({ code });
549
+ if (result._tag === "Failure") {
550
+ return { _tag: "Failure", error: formatError(result.error) };
551
+ }
552
+ storeTokens(result.data);
454
553
  await onSuccess();
455
- return { _tag: "Success", data: result };
554
+ return { _tag: "Success", data: void 0 };
456
555
  } catch (error) {
457
- return {
458
- _tag: "Failure",
459
- error: formatError(error)
460
- };
556
+ return { _tag: "Failure", error: formatError(error) };
461
557
  }
462
558
  }
463
559
  },
464
560
  async logout() {
465
- await auth.logout();
561
+ accessToken.value = null;
562
+ delegatedToken.value = null;
563
+ refreshToken.value = null;
564
+ expires.value = null;
466
565
  return { _tag: "Success", data: void 0 };
467
566
  },
468
567
  async handleQueryParams(query) {
469
568
  if (query.has(PARAMS.DELETGATED_TOKEN)) {
470
- auth.setDelegatedToken(query.get(PARAMS.DELETGATED_TOKEN));
569
+ delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
471
570
  await onSuccess(query);
472
571
  return { _tag: "Success", data: void 0 };
473
572
  }
@@ -479,16 +578,13 @@ function createLoginService(init) {
479
578
  }
480
579
  const code = query.get(PARAMS.CODE);
481
580
  if (code != null) {
482
- if (query.get(PARAMS.STATE) !== auth.state.value) {
483
- return {
484
- _tag: "Failure",
485
- error: formatError("Invalid state")
486
- };
581
+ if (query.get(PARAMS.STATE) !== state.value) {
582
+ return { _tag: "Failure", error: formatError("Invalid state") };
487
583
  } else {
488
584
  return service.social.verify(code);
489
585
  }
490
586
  }
491
- if (query.get(PARAMS.REFRESH) === "1" && await auth.getToken() != null) {
587
+ if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
492
588
  await onSuccess();
493
589
  return { _tag: "Success", data: void 0 };
494
590
  }
@@ -505,17 +601,75 @@ function createLoginService(init) {
505
601
  }
506
602
  return { _tag: "Success", data: void 0 };
507
603
  },
508
- isAuthenticated: auth.isAuthenticated,
509
- getToken: () => auth.getToken()
604
+ isAuthenticated: w(
605
+ () => delegatedToken.value != null || refreshToken.value != null
606
+ ),
607
+ getToken,
608
+ async getAuthHeaders() {
609
+ if (delegatedToken.value) {
610
+ return { "X-Delegated-Token": delegatedToken.value };
611
+ }
612
+ const token = await getToken();
613
+ if (token) {
614
+ return { Authorization: `Bearer ${token}` };
615
+ }
616
+ return {};
617
+ }
510
618
  };
511
619
  return service;
512
620
  }
513
621
  const LoginServiceContext = createContext("services/login");
514
622
  function createMockOrdersAdapter() {
623
+ const mockOrders = [
624
+ {
625
+ id: "10001",
626
+ createdAt: "2024-10-15T09:30:00Z",
627
+ placedAt: "2024-10-15T09:30:00Z",
628
+ currencyCode: "USD",
629
+ totalPrice: 89.97,
630
+ financialStatus: "PAID",
631
+ fulfillmentStatus: "FULFILLED",
632
+ lines: [
633
+ {
634
+ id: "line_1",
635
+ title: "Monthly Coffee Subscription",
636
+ quantity: 1,
637
+ variantImage: null,
638
+ customAttributes: [
639
+ { key: "roast", value: "dark" },
640
+ { key: "size", value: "1lb" }
641
+ ]
642
+ }
643
+ ],
644
+ customAttributes: [
645
+ { key: "order_type", value: "subscription_renewal" },
646
+ { key: "gift_wrap", value: "false" }
647
+ ]
648
+ },
649
+ {
650
+ id: "10002",
651
+ createdAt: "2024-09-20T14:15:00Z",
652
+ placedAt: "2024-09-20T14:15:00Z",
653
+ currencyCode: "USD",
654
+ totalPrice: 45.99,
655
+ financialStatus: "PAID",
656
+ fulfillmentStatus: "PARTIALLY_FULFILLED",
657
+ lines: [
658
+ {
659
+ id: "line_2",
660
+ title: "Premium Tea Set",
661
+ quantity: 2,
662
+ variantImage: null,
663
+ customAttributes: []
664
+ }
665
+ ],
666
+ customAttributes: []
667
+ }
668
+ ];
515
669
  return {
516
670
  async getOrders() {
517
671
  return {
518
- items: [],
672
+ items: mockOrders,
519
673
  pageInfo: {
520
674
  hasNextPage: false,
521
675
  hasPrevPage: false,
@@ -679,6 +833,18 @@ function createMockProductAdapter() {
679
833
  );
680
834
  return product == null ? null : processProduct(product);
681
835
  },
836
+ async getProductsByIds() {
837
+ return [];
838
+ },
839
+ async getBoxVariants() {
840
+ return [];
841
+ },
842
+ async getAvailableProducts() {
843
+ return [];
844
+ },
845
+ async getBox() {
846
+ return null;
847
+ },
682
848
  async search(opts) {
683
849
  await new Promise((resolve) => setTimeout(resolve, 100));
684
850
  let filteredProducts = [...products];
@@ -719,6 +885,185 @@ function createMockProductAdapter() {
719
885
  }
720
886
  };
721
887
  }
888
+ function mapVariant(variant, currencyCode) {
889
+ var _a;
890
+ return {
891
+ id: variant.id,
892
+ title: variant.title,
893
+ image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
894
+ price: { amount: variant.price.toFixed(2), currencyCode },
895
+ ...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
896
+ purchaseOptions: variant.purchaseOptions.map((opt) => ({
897
+ name: opt.name,
898
+ price: { amount: opt.price.toFixed(2), currencyCode },
899
+ interval: opt.interval
900
+ }))
901
+ } : {}
902
+ };
903
+ }
904
+ function mapProduct(product, currencyCode) {
905
+ return {
906
+ id: product.id,
907
+ title: product.title,
908
+ featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
909
+ hasOnlyDefaultVariant: product.variants.length <= 1,
910
+ variants: {
911
+ items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
912
+ pageInfo: {
913
+ hasNextPage: false,
914
+ hasPrevPage: false,
915
+ startCursor: null,
916
+ endCursor: null
917
+ }
918
+ },
919
+ collections: product.collections
920
+ };
921
+ }
922
+ function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
923
+ const { currencyCode } = opts;
924
+ async function fetchProducts(params) {
925
+ const query = params.toString() ? `?${params.toString()}` : "";
926
+ const res = await fetcher(`/customer/v1/products${query}`);
927
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
928
+ return res.json();
929
+ }
930
+ return {
931
+ async getById(id) {
932
+ try {
933
+ const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
934
+ const body = await fetchProducts(params);
935
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
936
+ } catch {
937
+ return null;
938
+ }
939
+ },
940
+ async getByVariantId(variantId) {
941
+ try {
942
+ const params = new URLSearchParams({
943
+ query: `variants.id:"${variantId}"`,
944
+ limit: "1"
945
+ });
946
+ const body = await fetchProducts(params);
947
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
948
+ } catch {
949
+ return null;
950
+ }
951
+ },
952
+ async search(opts2) {
953
+ try {
954
+ const params = new URLSearchParams();
955
+ if (opts2 == null ? void 0 : opts2.query) {
956
+ params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
957
+ }
958
+ if (opts2 == null ? void 0 : opts2.pagination) {
959
+ if ("after" in opts2.pagination)
960
+ params.set("after", opts2.pagination.after);
961
+ else if ("before" in opts2.pagination)
962
+ params.set("before", opts2.pagination.before);
963
+ }
964
+ const body = await fetchProducts(params);
965
+ return {
966
+ items: body.data.map((p2) => mapProduct(p2, currencyCode)),
967
+ pageInfo: {
968
+ hasNextPage: body.hasNextPage,
969
+ hasPrevPage: body.hasPrevPage,
970
+ startCursor: body.startCursor,
971
+ endCursor: body.endCursor
972
+ }
973
+ };
974
+ } catch {
975
+ return {
976
+ items: [],
977
+ pageInfo: {
978
+ hasNextPage: false,
979
+ hasPrevPage: false,
980
+ startCursor: null,
981
+ endCursor: null
982
+ }
983
+ };
984
+ }
985
+ },
986
+ async getProductsByIds({ ids }) {
987
+ const params = new URLSearchParams();
988
+ for (const id of ids) params.append("ids", id);
989
+ const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
990
+ if (!res.ok) return [];
991
+ const data = await res.json();
992
+ return (data ?? []).map((p2) => ({
993
+ id: p2.id,
994
+ title: p2.title ?? "",
995
+ featuredImage: p2.featuredImage ? { url: p2.featuredImage.url, altText: p2.featuredImage.altText } : null,
996
+ hasOnlyDefaultVariant: true,
997
+ collections: []
998
+ }));
999
+ },
1000
+ async getBoxVariants({ ids, subscriptionId }) {
1001
+ const params = new URLSearchParams();
1002
+ for (const id of ids) params.append("ids", id);
1003
+ params.set("subscriptionId", subscriptionId);
1004
+ const res = await fetcher(`/customer/products/variants?${params.toString()}`);
1005
+ if (!res.ok) return [];
1006
+ const data = await res.json();
1007
+ return (data ?? []).map((p2) => {
1008
+ var _a;
1009
+ return {
1010
+ id: p2.id,
1011
+ title: p2.title,
1012
+ description: p2.description ?? "",
1013
+ featuredImage: p2.featuredImage,
1014
+ hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
1015
+ variants: (p2.variants ?? []).map((v2) => ({
1016
+ id: v2.id,
1017
+ availableForSale: v2.availableForSale,
1018
+ title: v2.title,
1019
+ currentPrice: v2.currentPrice,
1020
+ basePrice: v2.basePrice,
1021
+ image: v2.image,
1022
+ recurring: v2.recurring
1023
+ }))
1024
+ };
1025
+ });
1026
+ },
1027
+ async getAvailableProducts({ subscriptionId, lineId, q: q2, mode, action, pagination }) {
1028
+ const params = new URLSearchParams();
1029
+ params.set("action", action);
1030
+ if (q2) params.set("q", q2);
1031
+ if (mode) params.set("mode", mode);
1032
+ if (pagination && "after" in pagination) params.set("after", pagination.after);
1033
+ if (pagination && "before" in pagination) params.set("before", pagination.before);
1034
+ const res = await fetcher(
1035
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
1036
+ );
1037
+ if (!res.ok) return [];
1038
+ const data = await res.json();
1039
+ return (data ?? []).map((p2) => {
1040
+ var _a;
1041
+ return {
1042
+ id: p2.id,
1043
+ title: p2.title,
1044
+ description: p2.description ?? "",
1045
+ featuredImage: p2.featuredImage,
1046
+ hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
1047
+ variants: (p2.variants ?? []).map((v2) => ({
1048
+ id: v2.id,
1049
+ availableForSale: v2.availableForSale,
1050
+ title: v2.title,
1051
+ currentPrice: v2.currentPrice,
1052
+ basePrice: v2.basePrice,
1053
+ image: v2.image,
1054
+ recurring: v2.recurring
1055
+ }))
1056
+ };
1057
+ });
1058
+ },
1059
+ async getBox({ id }) {
1060
+ const res = await fetcher(`/customer/boxes/${id}`);
1061
+ if (!res.ok) return null;
1062
+ const data = await res.json();
1063
+ return { id, ...data };
1064
+ }
1065
+ };
1066
+ }
722
1067
  const ProductServiceContext = createContext("services/product");
723
1068
  function createProductService(adapter) {
724
1069
  return {
@@ -730,7 +1075,11 @@ function createProductService(adapter) {
730
1075
  },
731
1076
  async search(opts) {
732
1077
  return adapter.search(opts);
733
- }
1078
+ },
1079
+ getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
1080
+ getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
1081
+ getAvailableProducts: (params) => adapter.getAvailableProducts(params),
1082
+ getBox: (id) => adapter.getBox({ id })
734
1083
  };
735
1084
  }
736
1085
  function routeNameTableFromPages(pages) {
@@ -784,19 +1133,428 @@ function createMockRouterService() {
784
1133
  }
785
1134
  }).routerService;
786
1135
  }
787
- function createMockSubscriptionAdapter(options = {}) {
788
- const mockSubscriptions = [
1136
+ function futureDateStr(daysFromNow) {
1137
+ const d3 = /* @__PURE__ */ new Date();
1138
+ d3.setDate(d3.getDate() + daysFromNow);
1139
+ return d3.toISOString().split("T")[0] ?? "";
1140
+ }
1141
+ function createMockSchedulesAdapter() {
1142
+ let orders = [
789
1143
  {
790
- id: "sub_1",
1144
+ id: "sched_1",
791
1145
  customerId: "customer_1",
1146
+ subscriptionId: "sub_1",
1147
+ date: "2026-05-22T12:00:00Z",
1148
+ skipped: false,
1149
+ currencyCode: "USD",
1150
+ subtotal: 2999,
1151
+ deliveryPrice: 500,
1152
+ cycleIndex: 4,
1153
+ deliveryDate: futureDateStr(7),
1154
+ status: "scheduled",
792
1155
  items: [
793
1156
  {
794
- id: "item_1",
795
- productId: "product_1",
1157
+ id: "sched_item_1",
1158
+ title: "Rose Face Serum",
1159
+ subtitle: null,
1160
+ imageUrl: "https://images.unsplash.com/photo-1611930022073-b7a4ba5fcccd?w=200",
796
1161
  variantId: "variant_1",
797
- quantity: 2,
798
- price: { amount: "29.99", currencyCode: "USD" },
799
- nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15")
1162
+ quantity: 1,
1163
+ price: 2999,
1164
+ totalPrice: 2999,
1165
+ variantImage: null,
1166
+ variantAvailableForSale: true,
1167
+ subscriptionId: "sub_1",
1168
+ subscriptionLineId: "item_1",
1169
+ productId: "product_1",
1170
+ parentProductId: null,
1171
+ parentLineId: null,
1172
+ isVirtual: false,
1173
+ isUpsell: false,
1174
+ isOneTime: false,
1175
+ customAttributes: []
1176
+ },
1177
+ {
1178
+ id: "sched_item_2",
1179
+ title: "Vitamin C Moisturizer",
1180
+ subtitle: null,
1181
+ imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
1182
+ variantId: "variant_2",
1183
+ quantity: 1,
1184
+ price: 2999,
1185
+ totalPrice: 2999,
1186
+ variantImage: null,
1187
+ variantAvailableForSale: true,
1188
+ subscriptionId: "sub_1",
1189
+ subscriptionLineId: "item_2",
1190
+ productId: "product_2",
1191
+ parentProductId: null,
1192
+ parentLineId: null,
1193
+ isVirtual: false,
1194
+ isUpsell: false,
1195
+ isOneTime: false,
1196
+ customAttributes: []
1197
+ }
1198
+ ],
1199
+ discounts: []
1200
+ },
1201
+ {
1202
+ id: "sched_2",
1203
+ customerId: "customer_1",
1204
+ subscriptionId: "sub_1",
1205
+ date: "2026-05-22T12:00:00Z",
1206
+ skipped: false,
1207
+ currencyCode: "USD",
1208
+ subtotal: 2999,
1209
+ deliveryPrice: 500,
1210
+ cycleIndex: 4,
1211
+ deliveryDate: futureDateStr(7),
1212
+ status: "scheduled",
1213
+ items: [
1214
+ {
1215
+ id: "sched_item_3",
1216
+ title: "Rose Face Serum",
1217
+ subtitle: null,
1218
+ imageUrl: "https://images.unsplash.com/photo-1611930022073-b7a4ba5fcccd?w=200",
1219
+ variantId: "variant_1",
1220
+ quantity: 1,
1221
+ price: 2999,
1222
+ totalPrice: 2999,
1223
+ variantImage: null,
1224
+ variantAvailableForSale: true,
1225
+ subscriptionId: "sub_1",
1226
+ subscriptionLineId: "item_1",
1227
+ productId: "product_1",
1228
+ parentProductId: null,
1229
+ parentLineId: null,
1230
+ isVirtual: false,
1231
+ isUpsell: false,
1232
+ isOneTime: false,
1233
+ customAttributes: []
1234
+ },
1235
+ {
1236
+ id: "sched_item_4",
1237
+ title: "Hyaluronic Acid Serum",
1238
+ subtitle: null,
1239
+ imageUrl: "https://images.unsplash.com/photo-1621232201696-ba6c9db10a97?w=200",
1240
+ variantId: "variant_3",
1241
+ quantity: 1,
1242
+ price: 2999,
1243
+ totalPrice: 2999,
1244
+ variantImage: null,
1245
+ variantAvailableForSale: true,
1246
+ subscriptionId: "sub_1",
1247
+ subscriptionLineId: "item_3",
1248
+ productId: "product_3",
1249
+ parentProductId: null,
1250
+ parentLineId: null,
1251
+ isVirtual: false,
1252
+ isUpsell: false,
1253
+ isOneTime: false,
1254
+ customAttributes: []
1255
+ }
1256
+ ],
1257
+ discounts: []
1258
+ }
1259
+ ];
1260
+ let adjustments = [];
1261
+ return {
1262
+ async getUpcomingOrders(params) {
1263
+ await new Promise((r) => setTimeout(r, 200));
1264
+ const filtered = params.subscriptionId ? orders.filter((o2) => o2.subscriptionId === params.subscriptionId) : orders;
1265
+ return { _tag: "Success", data: filtered.slice(0, params.count) };
1266
+ },
1267
+ async getAdjustments() {
1268
+ await new Promise((r) => setTimeout(r, 200));
1269
+ return { _tag: "Success", data: [...adjustments] };
1270
+ },
1271
+ async skipOrder({ subscriptionId }) {
1272
+ await new Promise((r) => setTimeout(r, 300));
1273
+ const order = orders.find(
1274
+ (o2) => o2.subscriptionId === subscriptionId && o2.status === "scheduled"
1275
+ );
1276
+ if (!order) return { _tag: "Failure", error: "No scheduled order found" };
1277
+ orders = orders.map(
1278
+ (o2) => o2.id === order.id ? { ...o2, status: "skipped" } : o2
1279
+ );
1280
+ const adj = {
1281
+ id: `adj-${Date.now()}`,
1282
+ subscriptionId,
1283
+ type: "skip"
1284
+ };
1285
+ adjustments = [...adjustments, adj];
1286
+ return { _tag: "Success", data: void 0 };
1287
+ },
1288
+ async changeDateAdjustment({ subscriptionId, newDate }) {
1289
+ await new Promise((r) => setTimeout(r, 300));
1290
+ const order = orders.find(
1291
+ (o2) => o2.subscriptionId === subscriptionId && o2.status === "scheduled"
1292
+ );
1293
+ if (!order) return { _tag: "Failure", error: "No scheduled order found" };
1294
+ orders = orders.map(
1295
+ (o2) => o2.id === order.id ? { ...o2, deliveryDate: newDate } : o2
1296
+ );
1297
+ const adj = {
1298
+ id: `adj-${Date.now()}`,
1299
+ subscriptionId,
1300
+ type: "date_change",
1301
+ newDate
1302
+ };
1303
+ adjustments = [...adjustments, adj];
1304
+ return { _tag: "Success", data: void 0 };
1305
+ },
1306
+ async removeAdjustment({ adjustmentId }) {
1307
+ await new Promise((r) => setTimeout(r, 200));
1308
+ adjustments = adjustments.filter((a2) => a2.id !== adjustmentId);
1309
+ return { _tag: "Success", data: void 0 };
1310
+ }
1311
+ };
1312
+ }
1313
+ function createApiSchedulesAdapter(fetcher) {
1314
+ return {
1315
+ async getUpcomingOrders({ count, subscriptionId }) {
1316
+ try {
1317
+ const params = new URLSearchParams({ count: String(count) });
1318
+ if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
1319
+ const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
1320
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1321
+ const data = await res.json();
1322
+ return { _tag: "Success", data: data.orders };
1323
+ } catch (e3) {
1324
+ return { _tag: "Failure", error: e3 };
1325
+ }
1326
+ },
1327
+ async getAdjustments() {
1328
+ try {
1329
+ const res = await fetcher(`/customer/v1/schedules/adjustments`);
1330
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1331
+ const data = await res.json();
1332
+ const adjustments = data.data.map((a2) => ({
1333
+ id: a2.id,
1334
+ subscriptionId: a2.subscriptionId ?? "",
1335
+ type: a2.type === "CHANGE_DATE" ? "date_change" : "skip",
1336
+ newDate: a2.newDate
1337
+ }));
1338
+ return { _tag: "Success", data: adjustments };
1339
+ } catch (e3) {
1340
+ return { _tag: "Failure", error: e3 };
1341
+ }
1342
+ },
1343
+ async skipOrder({ cycle, date }) {
1344
+ if (cycle == null && date == null) {
1345
+ return { _tag: "Failure", error: "Either cycle or date is required for skipOrder" };
1346
+ }
1347
+ try {
1348
+ const matcher = cycle != null ? { type: "CYCLE", input: { cycle } } : { type: "DATE", input: { date: new Date(date).toISOString() } };
1349
+ const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1350
+ method: "POST",
1351
+ headers: { "Content-Type": "application/json" },
1352
+ body: JSON.stringify({
1353
+ matcher,
1354
+ action: { type: "SKIP_ORDER", input: { reason: "" } }
1355
+ })
1356
+ });
1357
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1358
+ return { _tag: "Success", data: void 0 };
1359
+ } catch (e3) {
1360
+ return { _tag: "Failure", error: e3 };
1361
+ }
1362
+ },
1363
+ async changeDateAdjustment({ newDate, currentDate }) {
1364
+ if (currentDate == null) {
1365
+ return {
1366
+ _tag: "Failure",
1367
+ error: "currentDate is required for changeDateAdjustment"
1368
+ };
1369
+ }
1370
+ try {
1371
+ const matcher = { type: "DATE", input: { date: new Date(currentDate).toISOString() } };
1372
+ const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1373
+ method: "POST",
1374
+ headers: { "Content-Type": "application/json" },
1375
+ body: JSON.stringify({
1376
+ matcher,
1377
+ action: {
1378
+ type: "CHANGE_DATE",
1379
+ input: { newDate: new Date(newDate).toISOString() }
1380
+ }
1381
+ })
1382
+ });
1383
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1384
+ return { _tag: "Success", data: void 0 };
1385
+ } catch (e3) {
1386
+ return { _tag: "Failure", error: e3 };
1387
+ }
1388
+ },
1389
+ async removeAdjustment({ adjustmentId }) {
1390
+ try {
1391
+ const res = await fetcher(
1392
+ `/customer/v1/schedules/adjustments/${adjustmentId}`,
1393
+ { method: "DELETE" }
1394
+ );
1395
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1396
+ return { _tag: "Success", data: void 0 };
1397
+ } catch (e3) {
1398
+ return { _tag: "Failure", error: e3 };
1399
+ }
1400
+ }
1401
+ };
1402
+ }
1403
+ const SchedulesServiceContext = createContext("services/schedules");
1404
+ function createSchedulesService(adapter, opts) {
1405
+ const current = d(null);
1406
+ const currentLoading = d(false);
1407
+ const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
1408
+ let reloadToken = 0;
1409
+ async function getUpcomingOrder() {
1410
+ const result = await adapter.getUpcomingOrders({ count: 1 });
1411
+ if (result._tag === "Failure") return result;
1412
+ return { _tag: "Success", data: result.data[0] ?? null };
1413
+ }
1414
+ async function getUpcomingOrders(count) {
1415
+ return adapter.getUpcomingOrders({ count });
1416
+ }
1417
+ async function reloadCurrent(subscriptionId) {
1418
+ var _a;
1419
+ const token = ++reloadToken;
1420
+ currentLoading.value = true;
1421
+ const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
1422
+ const result = await adapter.getUpcomingOrders({
1423
+ count,
1424
+ subscriptionId
1425
+ });
1426
+ if (token !== reloadToken) return;
1427
+ currentLoading.value = false;
1428
+ if (result._tag === "Success") {
1429
+ current.value = result.data;
1430
+ }
1431
+ }
1432
+ return {
1433
+ current,
1434
+ currentLoading,
1435
+ hasPendingBilling,
1436
+ getUpcomingOrder,
1437
+ getUpcomingOrders,
1438
+ async getAdjustments() {
1439
+ return adapter.getAdjustments();
1440
+ },
1441
+ async skipOrder(params) {
1442
+ const result = await adapter.skipOrder(params);
1443
+ if (result._tag === "Success") {
1444
+ await reloadCurrent(params.subscriptionId);
1445
+ }
1446
+ return result;
1447
+ },
1448
+ async changeDateAdjustment(params) {
1449
+ const result = await adapter.changeDateAdjustment(params);
1450
+ if (result._tag === "Success") {
1451
+ await reloadCurrent(params.subscriptionId);
1452
+ }
1453
+ return result;
1454
+ },
1455
+ async removeAdjustment(params) {
1456
+ return adapter.removeAdjustment(params);
1457
+ },
1458
+ reloadCurrent
1459
+ };
1460
+ }
1461
+ const SettingsServiceContext = createContext("services/settings");
1462
+ function createSettingsService(initial = {}) {
1463
+ const current = d(initial);
1464
+ return {
1465
+ current,
1466
+ get(key) {
1467
+ return current.value[key];
1468
+ }
1469
+ };
1470
+ }
1471
+ function createShopService(options) {
1472
+ const contactEmail = d(options.getContactEmail());
1473
+ const service = { contactEmail };
1474
+ return service;
1475
+ }
1476
+ const ShopServiceContext = createContext("services/shop");
1477
+ function createMockSubscriptionAdapter(options = {}) {
1478
+ const richBase = (id, itemId, renewalId) => ({
1479
+ id,
1480
+ customerId: "customer_1",
1481
+ items: [
1482
+ {
1483
+ id: itemId,
1484
+ productId: "product_1",
1485
+ variantId: "variant_1",
1486
+ quantity: 2,
1487
+ price: { amount: "29.99", currencyCode: "USD" },
1488
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1489
+ isUpsell: false,
1490
+ isVirtual: false,
1491
+ nextBillingDate: "2025-11-15T00:00:00Z",
1492
+ currentPrice: { amount: "29.99", currencyCode: "USD" },
1493
+ lineDiscountedPrice: { amount: "26.99", currencyCode: "USD" },
1494
+ variantAvailableForSale: true
1495
+ }
1496
+ ],
1497
+ discounts: [
1498
+ {
1499
+ id: "discount_1",
1500
+ type: "percentage",
1501
+ value: 10,
1502
+ code: "SAVE10",
1503
+ description: "10% off subscription"
1504
+ }
1505
+ ],
1506
+ status: "active",
1507
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1508
+ frequency: "1 MONTH",
1509
+ createdAt: "2024-10-01T00:00:00Z",
1510
+ updatedAt: "2024-10-14T00:00:00Z",
1511
+ billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
1512
+ deliveryPolicy: {
1513
+ intervalCount: 1,
1514
+ interval: "MONTH",
1515
+ anchors: [{ day: 15, type: "MONTHDAY" }]
1516
+ },
1517
+ nextBillingDate: "2025-11-15T00:00:00Z",
1518
+ nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
1519
+ lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
1520
+ nextFulfillment: "2025-11-17T00:00:00Z",
1521
+ estimatedOrderDate: "2025-11-13T00:00:00Z",
1522
+ customerPaymentMethod: {
1523
+ id: "pm_1",
1524
+ instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
1525
+ active: true,
1526
+ isRecurring: true
1527
+ },
1528
+ requiredBillingCyclesLeft: 3,
1529
+ renewal: { id: renewalId, isInActiveProcessState: false },
1530
+ lastPaymentStatus: "succeeded",
1531
+ pausePeriod: null,
1532
+ isEditable: true
1533
+ });
1534
+ const mockSubscriptions = [
1535
+ {
1536
+ id: "sub_1",
1537
+ customerId: "customer_1",
1538
+ items: [
1539
+ {
1540
+ id: "item_1",
1541
+ productId: "product_1",
1542
+ variantId: "variant_1",
1543
+ quantity: 2,
1544
+ price: { amount: "29.99", currencyCode: "USD" },
1545
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1546
+ isUpsell: false,
1547
+ isVirtual: false,
1548
+ nextBillingDate: "2025-11-15T00:00:00Z",
1549
+ currentPrice: { amount: "29.99", currencyCode: "USD" },
1550
+ lineDiscountedPrice: { amount: "26.99", currencyCode: "USD" },
1551
+ variantAvailableForSale: true,
1552
+ billingPolicy: { intervalCount: 1, interval: "MONTH" },
1553
+ deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
1554
+ customAttributes: [
1555
+ { key: "roast_level", value: "medium" },
1556
+ { key: "grind_type", value: "whole_bean" }
1557
+ ]
800
1558
  }
801
1559
  ],
802
1560
  discounts: [
@@ -812,7 +1570,33 @@ function createMockSubscriptionAdapter(options = {}) {
812
1570
  nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
813
1571
  frequency: "1 MONTH",
814
1572
  createdAt: "2024-10-01T00:00:00Z",
815
- updatedAt: "2024-10-14T00:00:00Z"
1573
+ updatedAt: "2024-10-14T00:00:00Z",
1574
+ billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
1575
+ deliveryPolicy: {
1576
+ intervalCount: 1,
1577
+ interval: "MONTH",
1578
+ anchors: [{ day: 15, type: "MONTHDAY" }]
1579
+ },
1580
+ nextBillingDate: "2025-11-15T00:00:00Z",
1581
+ nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
1582
+ lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
1583
+ nextFulfillment: "2025-11-17T00:00:00Z",
1584
+ estimatedOrderDate: "2025-11-13T00:00:00Z",
1585
+ customerPaymentMethod: {
1586
+ id: "pm_1",
1587
+ instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
1588
+ active: true,
1589
+ isRecurring: true
1590
+ },
1591
+ requiredBillingCyclesLeft: 3,
1592
+ renewal: { id: "ren_1", isInActiveProcessState: false },
1593
+ lastPaymentStatus: "succeeded",
1594
+ pausePeriod: null,
1595
+ isEditable: true,
1596
+ customAttributes: [
1597
+ { key: "subscription_tier", value: "premium" },
1598
+ { key: "gift_message", value: "Enjoy your coffee!" }
1599
+ ]
816
1600
  },
817
1601
  {
818
1602
  id: "sub_2",
@@ -824,7 +1608,10 @@ function createMockSubscriptionAdapter(options = {}) {
824
1608
  variantId: "variant_2",
825
1609
  quantity: 1,
826
1610
  price: { amount: "49.99", currencyCode: "USD" },
827
- nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20")
1611
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
1612
+ billingPolicy: { intervalCount: 2, interval: "WEEK" },
1613
+ deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
1614
+ customAttributes: []
828
1615
  }
829
1616
  ],
830
1617
  discounts: [],
@@ -832,7 +1619,50 @@ function createMockSubscriptionAdapter(options = {}) {
832
1619
  nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
833
1620
  frequency: "2 WEEK",
834
1621
  createdAt: "2024-09-15T00:00:00Z",
835
- updatedAt: "2024-10-10T00:00:00Z"
1622
+ updatedAt: "2024-10-10T00:00:00Z",
1623
+ customAttributes: []
1624
+ },
1625
+ // Per-status fixtures: groundwork for the deferred editor preview picker
1626
+ // (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
1627
+ // Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
1628
+ {
1629
+ ...richBase("sub_paused", "item_paused", "ren_paused"),
1630
+ status: "paused",
1631
+ pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
1632
+ },
1633
+ {
1634
+ ...richBase("sub_canceled", "item_canceled", "ren_canceled"),
1635
+ status: "canceled",
1636
+ pausePeriod: null
1637
+ },
1638
+ {
1639
+ ...richBase("sub_failed", "item_failed", "ren_failed"),
1640
+ status: "failed",
1641
+ lastPaymentStatus: "failed",
1642
+ pausePeriod: null
1643
+ },
1644
+ {
1645
+ ...richBase("sub_expired", "item_expired", "ren_expired"),
1646
+ status: "expired",
1647
+ pausePeriod: null
1648
+ }
1649
+ ];
1650
+ const mockUpsellProducts = [
1651
+ {
1652
+ id: "upsell-1",
1653
+ variantId: "upsell-v-1",
1654
+ title: "Retinol Night Cream",
1655
+ imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
1656
+ price: { amount: "38.00", currencyCode: "USD" },
1657
+ description: "Anti-aging night treatment"
1658
+ },
1659
+ {
1660
+ id: "upsell-2",
1661
+ variantId: "upsell-v-2",
1662
+ title: "SPF 50 Day Shield",
1663
+ imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
1664
+ price: { amount: "24.00", currencyCode: "USD" },
1665
+ description: "Lightweight daily sun protection"
836
1666
  }
837
1667
  ];
838
1668
  return {
@@ -843,7 +1673,7 @@ function createMockSubscriptionAdapter(options = {}) {
843
1673
  },
844
1674
  async search(options2 = {}) {
845
1675
  await new Promise((resolve) => setTimeout(resolve, 300));
846
- const pageSize = 1;
1676
+ const pageSize = 2;
847
1677
  const allItems = mockSubscriptions;
848
1678
  let startIndex = 0;
849
1679
  let endIndex = pageSize;
@@ -859,20 +1689,22 @@ function createMockSubscriptionAdapter(options = {}) {
859
1689
  }
860
1690
  }
861
1691
  const pageItems = allItems.slice(startIndex, endIndex);
862
- const paginatedList = {
863
- items: pageItems.map((sub) => ({
864
- ...sub,
865
- items: sub.items.map((item) => ({ ...item })),
866
- discounts: sub.discounts.map((discount) => ({ ...discount }))
867
- })),
868
- pageInfo: {
869
- hasNextPage: endIndex < allItems.length,
870
- hasPrevPage: startIndex > 0,
871
- startCursor: pageItems.length > 0 ? startIndex.toString() : null,
872
- endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
1692
+ return {
1693
+ _tag: "Success",
1694
+ data: {
1695
+ items: pageItems.map((sub) => ({
1696
+ ...sub,
1697
+ items: sub.items.map((item) => ({ ...item })),
1698
+ discounts: sub.discounts.map((discount) => ({ ...discount }))
1699
+ })),
1700
+ pageInfo: {
1701
+ hasNextPage: endIndex < allItems.length,
1702
+ hasPrevPage: startIndex > 0,
1703
+ startCursor: pageItems.length > 0 ? startIndex.toString() : null,
1704
+ endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
1705
+ }
873
1706
  }
874
1707
  };
875
- return { _tag: "Success", data: paginatedList };
876
1708
  },
877
1709
  async addItem() {
878
1710
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -882,6 +1714,17 @@ function createMockSubscriptionAdapter(options = {}) {
882
1714
  await new Promise((resolve) => setTimeout(resolve, 300));
883
1715
  return { _tag: "Success", data: void 0 };
884
1716
  },
1717
+ async updateItem() {
1718
+ await new Promise((resolve) => setTimeout(resolve, 300));
1719
+ return { _tag: "Success", data: void 0 };
1720
+ },
1721
+ async skipLineItem() {
1722
+ await new Promise((resolve) => setTimeout(resolve, 300));
1723
+ return { _tag: "Success", data: void 0 };
1724
+ },
1725
+ async updateBoxItems() {
1726
+ return { _tag: "Success", data: void 0 };
1727
+ },
885
1728
  async addDiscount() {
886
1729
  await new Promise((resolve) => setTimeout(resolve, 300));
887
1730
  const behavior = options.addDiscountBehavior ?? { kind: "success" };
@@ -910,24 +1753,11 @@ function createMockSubscriptionAdapter(options = {}) {
910
1753
  (sub) => sub.id === params.subscriptionId
911
1754
  );
912
1755
  if (!subscription) {
913
- return {
914
- _tag: "Failure",
915
- error: "Subscription not found"
916
- };
1756
+ return { _tag: "Failure", error: "Subscription not found" };
917
1757
  }
918
1758
  subscription.nextDeliveryDate = new Date(params.newDate);
919
1759
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
920
- subscription.items.forEach((item) => {
921
- item.nextDeliveryDate = new Date(params.newDate);
922
- });
923
- return {
924
- _tag: "Success",
925
- data: {
926
- ...subscription,
927
- items: subscription.items.map((item) => ({ ...item })),
928
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
929
- }
930
- };
1760
+ return { _tag: "Success", data: { ...subscription } };
931
1761
  },
932
1762
  async resume(params) {
933
1763
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -935,21 +1765,11 @@ function createMockSubscriptionAdapter(options = {}) {
935
1765
  (sub) => sub.id === params.subscriptionId
936
1766
  );
937
1767
  if (!subscription) {
938
- return {
939
- _tag: "Failure",
940
- error: "Subscription not found"
941
- };
1768
+ return { _tag: "Failure", error: "Subscription not found" };
942
1769
  }
943
1770
  subscription.status = "active";
944
1771
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
945
- return {
946
- _tag: "Success",
947
- data: {
948
- ...subscription,
949
- items: subscription.items.map((item) => ({ ...item })),
950
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
951
- }
952
- };
1772
+ return { _tag: "Success", data: { ...subscription } };
953
1773
  },
954
1774
  async pause(params) {
955
1775
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -957,21 +1777,11 @@ function createMockSubscriptionAdapter(options = {}) {
957
1777
  (sub) => sub.id === params.subscriptionId
958
1778
  );
959
1779
  if (!subscription) {
960
- return {
961
- _tag: "Failure",
962
- error: "Subscription not found"
963
- };
1780
+ return { _tag: "Failure", error: "Subscription not found" };
964
1781
  }
965
1782
  subscription.status = "paused";
966
1783
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
967
- return {
968
- _tag: "Success",
969
- data: {
970
- ...subscription,
971
- items: subscription.items.map((item) => ({ ...item })),
972
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
973
- }
974
- };
1784
+ return { _tag: "Success", data: { ...subscription } };
975
1785
  },
976
1786
  async reactivate(params) {
977
1787
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -979,21 +1789,11 @@ function createMockSubscriptionAdapter(options = {}) {
979
1789
  (sub) => sub.id === params.subscriptionId
980
1790
  );
981
1791
  if (!subscription) {
982
- return {
983
- _tag: "Failure",
984
- error: "Subscription not found"
985
- };
1792
+ return { _tag: "Failure", error: "Subscription not found" };
986
1793
  }
987
1794
  subscription.status = "active";
988
1795
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
989
- return {
990
- _tag: "Success",
991
- data: {
992
- ...subscription,
993
- items: subscription.items.map((item) => ({ ...item })),
994
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
995
- }
996
- };
1796
+ return { _tag: "Success", data: { ...subscription } };
997
1797
  },
998
1798
  async cancel(params) {
999
1799
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1001,21 +1801,11 @@ function createMockSubscriptionAdapter(options = {}) {
1001
1801
  (sub) => sub.id === params.subscriptionId
1002
1802
  );
1003
1803
  if (!subscription) {
1004
- return {
1005
- _tag: "Failure",
1006
- error: "Subscription not found"
1007
- };
1804
+ return { _tag: "Failure", error: "Subscription not found" };
1008
1805
  }
1009
1806
  subscription.status = "canceled";
1010
1807
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1011
- return {
1012
- _tag: "Success",
1013
- data: {
1014
- ...subscription,
1015
- items: subscription.items.map((item) => ({ ...item })),
1016
- discounts: subscription.discounts.map((discount) => ({ ...discount }))
1017
- }
1018
- };
1808
+ return { _tag: "Success", data: { ...subscription } };
1019
1809
  },
1020
1810
  async getAvailablePlans() {
1021
1811
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1043,13 +1833,514 @@ function createMockSubscriptionAdapter(options = {}) {
1043
1833
  ]
1044
1834
  };
1045
1835
  },
1836
+ async getLastOrder(params) {
1837
+ await new Promise((resolve) => setTimeout(resolve, 200));
1838
+ const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
1839
+ if (!subscription) return { _tag: "Success", data: null };
1840
+ return {
1841
+ _tag: "Success",
1842
+ data: {
1843
+ id: `mock_last_order_${params.subscriptionId}`,
1844
+ createdAt: "2026-04-22T12:00:00Z"
1845
+ }
1846
+ };
1847
+ },
1848
+ async renewNow(_params) {
1849
+ await new Promise((resolve) => setTimeout(resolve, 300));
1850
+ return { _tag: "Success", data: { result: "success" } };
1851
+ },
1852
+ async changeFrequency(params) {
1853
+ await new Promise((resolve) => setTimeout(resolve, 300));
1854
+ const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
1855
+ if (!subscription) {
1856
+ return { _tag: "Failure", error: "Subscription not found" };
1857
+ }
1858
+ if (typeof params.plan === "string") {
1859
+ subscription.frequency = `1 ${params.plan}`;
1860
+ } else {
1861
+ subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
1862
+ if (subscription.billingPolicy) {
1863
+ subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
1864
+ }
1865
+ if (subscription.deliveryPolicy) {
1866
+ subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
1867
+ }
1868
+ }
1869
+ subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1870
+ return {
1871
+ _tag: "Success",
1872
+ data: {
1873
+ ...subscription,
1874
+ items: subscription.items.map((item) => ({ ...item })),
1875
+ discounts: subscription.discounts.map((d3) => ({ ...d3 }))
1876
+ }
1877
+ };
1878
+ },
1046
1879
  async getDefaultSubscriptionId() {
1047
1880
  var _a;
1048
1881
  await new Promise((resolve) => setTimeout(resolve, 300));
1049
1882
  return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
1883
+ },
1884
+ async getUpsellProducts() {
1885
+ await new Promise((resolve) => setTimeout(resolve, 300));
1886
+ return { _tag: "Success", data: [...mockUpsellProducts] };
1887
+ },
1888
+ async addUpsellProduct() {
1889
+ await new Promise((resolve) => setTimeout(resolve, 400));
1890
+ return { _tag: "Success", data: void 0 };
1050
1891
  }
1051
1892
  };
1052
1893
  }
1894
+ function normalizeInstrumentType(type) {
1895
+ return type.replace(/-/g, "_");
1896
+ }
1897
+ function mapPaymentMethod(rawPm) {
1898
+ if (!rawPm) return null;
1899
+ if (typeof rawPm === "string") {
1900
+ return { id: rawPm };
1901
+ }
1902
+ return {
1903
+ id: rawPm.id ?? "",
1904
+ ...rawPm.instrument != null ? {
1905
+ instrument: {
1906
+ ...rawPm.instrument,
1907
+ type: normalizeInstrumentType(rawPm.instrument.type)
1908
+ }
1909
+ } : {},
1910
+ ...rawPm.active != null ? { active: rawPm.active } : {},
1911
+ ...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
1912
+ };
1913
+ }
1914
+ function mapSubscription(raw) {
1915
+ var _a, _b;
1916
+ const status = raw.status === "merged" ? "canceled" : raw.status;
1917
+ const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
1918
+ const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
1919
+ return {
1920
+ id: raw.id,
1921
+ customerId: "",
1922
+ serial: raw.serial,
1923
+ items: (raw.items ?? []).map(
1924
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1925
+ (item) => ({
1926
+ id: item.id,
1927
+ productId: item.productId ?? item.product ?? "",
1928
+ variantId: item.variantId ?? item.variant ?? "",
1929
+ quantity: item.quantity,
1930
+ price: item.price ?? {
1931
+ amount: String(item.totalPrice ?? 0),
1932
+ currencyCode: raw.currencyCode ?? ""
1933
+ },
1934
+ nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
1935
+ billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
1936
+ deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
1937
+ // Map human-readable titles from the v1 SubscriptionItemResource
1938
+ title: item.title,
1939
+ variantTitle: item.subtitle ?? void 0,
1940
+ // recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
1941
+ isOneTime: item.recurringCycleLimit === 1
1942
+ })
1943
+ ),
1944
+ discounts: (raw.discounts ?? []).map(
1945
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1946
+ (d3) => {
1947
+ var _a2, _b2, _c;
1948
+ return {
1949
+ id: d3.id,
1950
+ type: ((_a2 = d3.value) == null ? void 0 : _a2.type) === "fixed-amount" || d3.type === "fixed_amount" ? "fixed_amount" : "percentage",
1951
+ value: ((_b2 = d3.value) == null ? void 0 : _b2.amount) ?? ((_c = d3.value) == null ? void 0 : _c.percentage) ?? d3.value ?? 0,
1952
+ code: d3.title ?? d3.code ?? "",
1953
+ description: d3.description ?? ""
1954
+ };
1955
+ }
1956
+ ),
1957
+ status,
1958
+ nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
1959
+ // Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
1960
+ nextBillingDate: raw.nextBillingDate ?? null,
1961
+ // Map customerPaymentMethod — note: arrives as bare id (not expanded) since the
1962
+ // subscriptions endpoint does not support ?expand=paymentMethod; instrument digits
1963
+ // remain a data gap pending backend API extension.
1964
+ customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
1965
+ frequency,
1966
+ createdAt: raw.createdAt,
1967
+ updatedAt: raw.updatedAt,
1968
+ billingPolicy: raw.billingPolicy ?? null,
1969
+ deliveryPolicy: raw.deliveryPolicy ?? null
1970
+ };
1971
+ }
1972
+ function createApiSubscriptionAdapter(fetcher) {
1973
+ const adapter = {
1974
+ async search(options) {
1975
+ var _a;
1976
+ try {
1977
+ const params = new URLSearchParams();
1978
+ if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
1979
+ const q2 = options.filters.filter((f2) => Object.keys(f2).length > 0).flatMap((f2) => Object.entries(f2).filter(([, v2]) => Boolean(v2))).map(([k2, v2]) => `${k2}:'${v2}'`).join(" AND ");
1980
+ if (q2) params.set("query", q2);
1981
+ }
1982
+ if (options == null ? void 0 : options.pagination) {
1983
+ if ("after" in options.pagination)
1984
+ params.set("after", options.pagination.after);
1985
+ else if ("before" in options.pagination)
1986
+ params.set("before", options.pagination.before);
1987
+ }
1988
+ const query = params.toString() ? `?${params.toString()}` : "";
1989
+ const res = await fetcher(`/customer/v1/subscriptions${query}`);
1990
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1991
+ const body = await res.json();
1992
+ return {
1993
+ _tag: "Success",
1994
+ data: {
1995
+ items: body.data.map(mapSubscription),
1996
+ pageInfo: {
1997
+ hasNextPage: body.hasNextPage,
1998
+ hasPrevPage: body.hasPrevPage,
1999
+ startCursor: body.startCursor,
2000
+ endCursor: body.endCursor
2001
+ }
2002
+ }
2003
+ };
2004
+ } catch (e3) {
2005
+ return { _tag: "Failure", error: e3 };
2006
+ }
2007
+ },
2008
+ async getById(id) {
2009
+ try {
2010
+ const res = await fetcher(
2011
+ `/customer/v1/subscriptions?query=id:"${id}"&limit=1`
2012
+ );
2013
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2014
+ const body = await res.json();
2015
+ const raw = body.data[0];
2016
+ return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
2017
+ } catch (e3) {
2018
+ return { _tag: "Failure", error: e3 };
2019
+ }
2020
+ },
2021
+ async addItem({ subscriptionId, item }) {
2022
+ try {
2023
+ const res = await fetcher(
2024
+ `/customer/v1/subscriptions/${subscriptionId}/items`,
2025
+ {
2026
+ method: "POST",
2027
+ headers: { "Content-Type": "application/json" },
2028
+ body: JSON.stringify(item)
2029
+ }
2030
+ );
2031
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2032
+ return { _tag: "Success", data: void 0 };
2033
+ } catch (e3) {
2034
+ return { _tag: "Failure", error: e3 };
2035
+ }
2036
+ },
2037
+ async removeItem({ subscriptionId, itemId }) {
2038
+ try {
2039
+ const res = await fetcher(
2040
+ `/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
2041
+ { method: "DELETE" }
2042
+ );
2043
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2044
+ return { _tag: "Success", data: void 0 };
2045
+ } catch (e3) {
2046
+ return { _tag: "Failure", error: e3 };
2047
+ }
2048
+ },
2049
+ async updateItem({ subscriptionId, lineId, update }) {
2050
+ try {
2051
+ const body = {
2052
+ ...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
2053
+ ...update.quantity !== void 0 ? { quantity: update.quantity } : {},
2054
+ ...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
2055
+ ...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
2056
+ };
2057
+ const res = await fetcher(
2058
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
2059
+ {
2060
+ method: "PUT",
2061
+ headers: { "Content-Type": "application/json" },
2062
+ body: JSON.stringify(body)
2063
+ }
2064
+ );
2065
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2066
+ return { _tag: "Success", data: void 0 };
2067
+ } catch (e3) {
2068
+ return { _tag: "Failure", error: e3 };
2069
+ }
2070
+ },
2071
+ async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
2072
+ try {
2073
+ const res = await fetcher(
2074
+ `/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
2075
+ {
2076
+ method: "PUT",
2077
+ headers: { "Content-Type": "application/json" },
2078
+ body: JSON.stringify({ nextBillingDate })
2079
+ }
2080
+ );
2081
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2082
+ return { _tag: "Success", data: void 0 };
2083
+ } catch (e3) {
2084
+ return { _tag: "Failure", error: e3 };
2085
+ }
2086
+ },
2087
+ async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
2088
+ try {
2089
+ const res = await fetcher(
2090
+ `/customer/subscriptions/${subscriptionId}/update-box-products`,
2091
+ {
2092
+ method: "POST",
2093
+ headers: { "Content-Type": "application/json" },
2094
+ body: JSON.stringify({ parentId, items, shippingMethodHandle })
2095
+ }
2096
+ );
2097
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2098
+ return { _tag: "Success", data: void 0 };
2099
+ } catch (e3) {
2100
+ return { _tag: "Failure", error: e3 };
2101
+ }
2102
+ },
2103
+ async addDiscount({ subscriptionId, code }) {
2104
+ try {
2105
+ const res = await fetcher(
2106
+ `/customer/v1/subscriptions/${subscriptionId}/discounts`,
2107
+ {
2108
+ method: "POST",
2109
+ headers: { "Content-Type": "application/json" },
2110
+ body: JSON.stringify({ code })
2111
+ }
2112
+ );
2113
+ if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
2114
+ return { _tag: "Success", data: void 0 };
2115
+ } catch (e3) {
2116
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: e3 instanceof Error ? e3.message : void 0 } };
2117
+ }
2118
+ },
2119
+ async removeDiscount({ subscriptionId, discountId }) {
2120
+ try {
2121
+ const res = await fetcher(
2122
+ `/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
2123
+ { method: "DELETE" }
2124
+ );
2125
+ if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
2126
+ return { _tag: "Success", data: void 0 };
2127
+ } catch (e3) {
2128
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: e3 instanceof Error ? e3.message : void 0 } };
2129
+ }
2130
+ },
2131
+ // Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
2132
+ async reschedule({ subscriptionId, newDate }) {
2133
+ try {
2134
+ const res = await fetcher(
2135
+ `/customer/v1/subscriptions/${subscriptionId}`,
2136
+ {
2137
+ method: "PATCH",
2138
+ headers: { "Content-Type": "application/json" },
2139
+ body: JSON.stringify({ nextBillingDate: newDate })
2140
+ }
2141
+ );
2142
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2143
+ const data = await res.json();
2144
+ return { _tag: "Success", data: mapSubscription(data) };
2145
+ } catch (e3) {
2146
+ return { _tag: "Failure", error: e3 };
2147
+ }
2148
+ },
2149
+ async resume({ subscriptionId }) {
2150
+ try {
2151
+ const res = await fetcher(
2152
+ `/customer/v1/subscriptions/${subscriptionId}/resume`,
2153
+ { method: "POST" }
2154
+ );
2155
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2156
+ const data = await res.json();
2157
+ return { _tag: "Success", data: mapSubscription(data) };
2158
+ } catch (e3) {
2159
+ return { _tag: "Failure", error: e3 };
2160
+ }
2161
+ },
2162
+ async pause({ subscriptionId }) {
2163
+ try {
2164
+ const res = await fetcher(
2165
+ `/customer/v1/subscriptions/${subscriptionId}/pause`,
2166
+ { method: "POST" }
2167
+ );
2168
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2169
+ const data = await res.json();
2170
+ return { _tag: "Success", data: mapSubscription(data) };
2171
+ } catch (e3) {
2172
+ return { _tag: "Failure", error: e3 };
2173
+ }
2174
+ },
2175
+ async reactivate({ subscriptionId }) {
2176
+ try {
2177
+ const res = await fetcher(
2178
+ `/customer/v1/subscriptions/${subscriptionId}/reactivate`,
2179
+ { method: "POST" }
2180
+ );
2181
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2182
+ const data = await res.json();
2183
+ return { _tag: "Success", data: mapSubscription(data) };
2184
+ } catch (e3) {
2185
+ return { _tag: "Failure", error: e3 };
2186
+ }
2187
+ },
2188
+ async cancel({ subscriptionId, reason }) {
2189
+ try {
2190
+ const res = await fetcher(
2191
+ `/customer/v1/subscriptions/${subscriptionId}/cancel`,
2192
+ {
2193
+ method: "POST",
2194
+ headers: { "Content-Type": "application/json" },
2195
+ body: JSON.stringify({ cancellationReason: reason })
2196
+ }
2197
+ );
2198
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2199
+ const data = await res.json();
2200
+ return { _tag: "Success", data: mapSubscription(data) };
2201
+ } catch (e3) {
2202
+ return { _tag: "Failure", error: e3 };
2203
+ }
2204
+ },
2205
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
2206
+ async getAvailablePlans({ subscriptionId }) {
2207
+ try {
2208
+ const res = await fetcher(
2209
+ `/customer/subscriptions/${subscriptionId}/plans`
2210
+ );
2211
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2212
+ const data = await res.json();
2213
+ return { _tag: "Success", data };
2214
+ } catch (e3) {
2215
+ return { _tag: "Failure", error: e3 };
2216
+ }
2217
+ },
2218
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
2219
+ async getDefaultSubscriptionId() {
2220
+ try {
2221
+ const res = await fetcher(
2222
+ `/customer/subscriptions/one-by-next-billing-date`
2223
+ );
2224
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2225
+ const data = await res.json();
2226
+ return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
2227
+ } catch (e3) {
2228
+ return { _tag: "Failure", error: e3 };
2229
+ }
2230
+ },
2231
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
2232
+ async getUpsellProducts({ subscriptionId }) {
2233
+ try {
2234
+ const res = await fetcher(
2235
+ `/customer/subscriptions/${subscriptionId}/upsell-products`
2236
+ );
2237
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2238
+ const data = await res.json();
2239
+ const products = data.map((p2) => {
2240
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
2241
+ return {
2242
+ id: p2.id,
2243
+ variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
2244
+ title: p2.title ?? "",
2245
+ imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
2246
+ price: {
2247
+ amount: String(((_f = (_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
2248
+ currencyCode: ((_i = (_h = (_g = p2.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
2249
+ },
2250
+ // recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
2251
+ recurring: (_k = (_j = p2.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
2252
+ };
2253
+ });
2254
+ return { _tag: "Success", data: products };
2255
+ } catch (e3) {
2256
+ return { _tag: "Failure", error: e3 };
2257
+ }
2258
+ },
2259
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
2260
+ async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
2261
+ try {
2262
+ const res = await fetcher(
2263
+ `/customer/subscriptions/${subscriptionId}/upsell`,
2264
+ {
2265
+ method: "POST",
2266
+ headers: { "Content-Type": "application/json" },
2267
+ body: JSON.stringify({
2268
+ productVariantId: variantId,
2269
+ quantity,
2270
+ ...oneTime ? { renewalLimit: 1 } : {}
2271
+ })
2272
+ }
2273
+ );
2274
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2275
+ return { _tag: "Success", data: void 0 };
2276
+ } catch (e3) {
2277
+ return { _tag: "Failure", error: e3 };
2278
+ }
2279
+ },
2280
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
2281
+ async getLastOrder({ subscriptionId }) {
2282
+ try {
2283
+ const res = await fetcher(
2284
+ `/customer/subscriptions/${subscriptionId}/last-order`
2285
+ );
2286
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2287
+ const data = await res.json();
2288
+ if (!data) return { _tag: "Success", data: null };
2289
+ return {
2290
+ _tag: "Success",
2291
+ data: { id: data.id, createdAt: data.createdAt }
2292
+ };
2293
+ } catch (e3) {
2294
+ return { _tag: "Failure", error: e3 };
2295
+ }
2296
+ },
2297
+ // Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
2298
+ // retry → POST /customer/subscriptions/{id}/retry
2299
+ // renewalId → POST /customer/renewals/{renewalId}/renew
2300
+ // default → POST /customer/subscriptions/{id}/order-now
2301
+ async renewNow({ subscriptionId, renewalId, retry }) {
2302
+ try {
2303
+ const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
2304
+ const res = await fetcher(url, { method: "POST" });
2305
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2306
+ const body = res.status === 204 ? null : await res.json();
2307
+ return {
2308
+ _tag: "Success",
2309
+ data: {
2310
+ nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
2311
+ result: (body == null ? void 0 : body.result) ?? "success"
2312
+ }
2313
+ };
2314
+ } catch (e3) {
2315
+ return { _tag: "Failure", error: e3 };
2316
+ }
2317
+ },
2318
+ // Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
2319
+ async changeFrequency({ subscriptionId, plan }) {
2320
+ try {
2321
+ const res = await fetcher(
2322
+ `/customer/subscriptions/${subscriptionId}/plan`,
2323
+ {
2324
+ method: "POST",
2325
+ headers: { "Content-Type": "application/json" },
2326
+ body: JSON.stringify({ plan })
2327
+ }
2328
+ );
2329
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
2330
+ const refreshed = await adapter.getById(subscriptionId);
2331
+ if (refreshed._tag === "Failure") return refreshed;
2332
+ if (refreshed.data) return { _tag: "Success", data: refreshed.data };
2333
+ return {
2334
+ _tag: "Failure",
2335
+ error: "Subscription not found after frequency change"
2336
+ };
2337
+ } catch (e3) {
2338
+ return { _tag: "Failure", error: e3 };
2339
+ }
2340
+ }
2341
+ };
2342
+ return adapter;
2343
+ }
1053
2344
  const SubscriptionServiceContext = createContext("services/subscription");
1054
2345
  function getCurrentSubscriptionId() {
1055
2346
  if (typeof window === "undefined") {
@@ -1072,41 +2363,75 @@ function getCurrentSubscriptionId() {
1072
2363
  }
1073
2364
  function createSubscriptionService(adapter) {
1074
2365
  const current = d(null);
1075
- const discountsChangedAtSignal = d(0);
1076
- const itemsChangedAtSignal = d(0);
1077
- async function refreshCurrent(subscriptionId) {
1078
- const result = await adapter.getById(subscriptionId);
1079
- if (result._tag === "Success" && result.data != null) {
1080
- current.value = result.data;
1081
- }
1082
- }
1083
- async function refreshAfterDiscountChange(subscriptionId) {
1084
- await refreshCurrent(subscriptionId);
1085
- discountsChangedAtSignal.value = discountsChangedAtSignal.value + 1;
2366
+ const hasPendingBilling = d(false);
2367
+ const discountsChangedAt = d(0);
2368
+ const itemsChangedAt = d(0);
2369
+ const lastOrder = d(null);
2370
+ async function refreshAfterDiscountChange() {
2371
+ await reload();
2372
+ discountsChangedAt.value = discountsChangedAt.value + 1;
1086
2373
  }
1087
- async function refreshAfterItemChange(subscriptionId) {
1088
- await refreshCurrent(subscriptionId);
1089
- itemsChangedAtSignal.value = itemsChangedAtSignal.value + 1;
2374
+ async function refreshAfterItemChange() {
2375
+ await reload();
2376
+ itemsChangedAt.value = itemsChangedAt.value + 1;
1090
2377
  }
1091
2378
  function subscriptionIdFromCurrent() {
1092
2379
  var _a;
1093
2380
  return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
1094
2381
  }
2382
+ async function reload() {
2383
+ var _a, _b;
2384
+ const id = (_a = current.value) == null ? void 0 : _a.id;
2385
+ if (!id) return;
2386
+ const [subResult, lastOrderResult] = await Promise.all([
2387
+ adapter.getById(id),
2388
+ adapter.getLastOrder({ subscriptionId: id })
2389
+ ]);
2390
+ if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
2391
+ if (subResult._tag === "Success") current.value = subResult.data;
2392
+ if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
2393
+ }
1095
2394
  const service = {
2395
+ current,
2396
+ hasPendingBilling,
2397
+ discountsChangedAt,
2398
+ itemsChangedAt,
2399
+ lastOrder,
1096
2400
  search: adapter.search,
1097
2401
  getById: adapter.getById,
1098
2402
  getCurrentSubscriptionId,
1099
2403
  addItem: async (subscriptionId, item) => {
1100
2404
  const result = await adapter.addItem({ subscriptionId, item });
1101
2405
  if (result._tag === "Success") {
1102
- await refreshAfterItemChange(subscriptionId);
2406
+ await refreshAfterItemChange();
1103
2407
  }
1104
2408
  return result;
1105
2409
  },
1106
2410
  removeItem: async (subscriptionId, itemId) => {
1107
2411
  const result = await adapter.removeItem({ subscriptionId, itemId });
1108
2412
  if (result._tag === "Success") {
1109
- await refreshAfterItemChange(subscriptionId);
2413
+ await refreshAfterItemChange();
2414
+ }
2415
+ return result;
2416
+ },
2417
+ updateItem: async (subscriptionId, lineId, update) => {
2418
+ const result = await adapter.updateItem({ subscriptionId, lineId, update });
2419
+ if (result._tag === "Success") {
2420
+ await refreshAfterItemChange();
2421
+ }
2422
+ return result;
2423
+ },
2424
+ skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
2425
+ const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
2426
+ if (result._tag === "Success") {
2427
+ await refreshAfterItemChange();
2428
+ }
2429
+ return result;
2430
+ },
2431
+ updateBoxItems: async (subscriptionId, params) => {
2432
+ const result = await adapter.updateBoxItems({ subscriptionId, ...params });
2433
+ if (result._tag === "Success") {
2434
+ await refreshAfterItemChange();
1110
2435
  }
1111
2436
  return result;
1112
2437
  },
@@ -1120,7 +2445,7 @@ function createSubscriptionService(adapter) {
1120
2445
  }
1121
2446
  const result = await adapter.addDiscount({ subscriptionId: id, code });
1122
2447
  if (result._tag === "Success") {
1123
- await refreshAfterDiscountChange(id);
2448
+ await refreshAfterDiscountChange();
1124
2449
  }
1125
2450
  return result;
1126
2451
  },
@@ -1137,7 +2462,7 @@ function createSubscriptionService(adapter) {
1137
2462
  discountId
1138
2463
  });
1139
2464
  if (result._tag === "Success") {
1140
- await refreshAfterDiscountChange(id);
2465
+ await refreshAfterDiscountChange();
1141
2466
  }
1142
2467
  return result;
1143
2468
  },
@@ -1146,11 +2471,13 @@ function createSubscriptionService(adapter) {
1146
2471
  pause: (subscriptionId) => adapter.pause({ subscriptionId }),
1147
2472
  reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
1148
2473
  cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
2474
+ renewNow: (params) => adapter.renewNow(params),
2475
+ changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
2476
+ reload,
1149
2477
  getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
1150
2478
  getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
1151
- current,
1152
- discountsChangedAt: discountsChangedAtSignal,
1153
- itemsChangedAt: itemsChangedAtSignal
2479
+ getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
2480
+ addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
1154
2481
  };
1155
2482
  return service;
1156
2483
  }
@@ -1420,65 +2747,85 @@ export {
1420
2747
  AuthenticationErrorReason,
1421
2748
  B as BlockWrapperEvent,
1422
2749
  C as ContextProvideEvent,
1423
- a as ContextRequestEvent,
2750
+ e as ContextRequestEvent,
1424
2751
  CustomerServiceContext,
1425
- D as DEFAULT_GLOBAL_STYLES,
2752
+ D2 as DEFAULT_GLOBAL_STYLES,
1426
2753
  DISCOUNT_REJECTION_REASONS,
1427
2754
  LoginServiceContext,
1428
2755
  OrdersServiceContext,
1429
2756
  ProductServiceContext,
1430
2757
  RouterServiceContext,
2758
+ SchedulesServiceContext,
1431
2759
  S as SelectedBlockContext,
2760
+ SettingsServiceContext,
2761
+ ShopServiceContext,
1432
2762
  u as Signal,
1433
2763
  StateContext,
1434
2764
  SubscriptionServiceContext,
1435
2765
  T as ThemeStateContext,
1436
- b as TranslationContext,
2766
+ a as TranslationContext,
1437
2767
  W as WorkflowServiceContext,
1438
- e as attachEmptyTracking,
1439
- f as backfillInstanceDefaults,
2768
+ _ as __recordDevtoolsInstance,
2769
+ f as __recordDevtoolsPropSetter,
2770
+ g as attachEmptyTracking,
2771
+ h as backfillInstanceDefaults,
1440
2772
  w as computed,
1441
- g as createBlockInstance,
1442
- h as createBlockInstanceFromJSON,
1443
- i as createBlockInstanceFromObject,
1444
- j as createCeRenderer,
2773
+ createApiCustomerAdapter,
2774
+ createApiLoginAdapter,
2775
+ createApiProductAdapter,
2776
+ createApiSchedulesAdapter,
2777
+ createApiSubscriptionAdapter,
2778
+ createBlockInstance,
2779
+ i as createBlockInstanceFromJSON,
2780
+ j as createBlockInstanceFromObject,
2781
+ k as createCeRenderer,
1445
2782
  createContext,
1446
2783
  createCustomerService,
2784
+ createLoginService,
2785
+ createMockCustomerAdapter,
2786
+ createMockLoginAdapter,
1447
2787
  createMockOrdersAdapter,
1448
2788
  createMockProductAdapter,
1449
2789
  createMockRouterService,
2790
+ createMockSchedulesAdapter,
1450
2791
  createMockSubscriptionAdapter,
1451
2792
  createMockWorkflowApiAdapter,
1452
2793
  createOrdersService,
1453
2794
  createProductService,
1454
2795
  createRouterService,
1455
- k as createSelectedBlock,
2796
+ createSchedulesService,
2797
+ l as createSelectedBlock,
2798
+ createSettingsService,
2799
+ createShopService,
1456
2800
  createState,
1457
2801
  createSubscriptionService,
1458
- l as createThemeState,
1459
- m as createTranslationService,
1460
- n as createWorkflowApiAdapter,
1461
- o as createWorkflowService,
1462
- p as defineBlock,
1463
- q as defineBlockRuntime,
2802
+ c as createThemeState,
2803
+ b as createTranslationService,
2804
+ d2 as createWorkflowApiAdapter,
2805
+ e2 as createWorkflowService,
2806
+ defineBlock,
2807
+ m as defineBlockRuntime,
2808
+ defineOverlayWrapper,
1464
2809
  E as effect,
1465
- s as getDefaultBlockForAction,
1466
- t as getDefinedBlocks,
1467
- v as hasDefaultBlockForAction,
1468
- x as hasRenderedContent,
1469
- y as indexOverrides,
1470
- z as injectContext,
1471
- A as isEditorPreview,
1472
- F as isStubDefinition,
2810
+ g2 as getDefaultBlockForAction,
2811
+ n as getDefinedBlocks,
2812
+ o as getDevtoolsBlockState,
2813
+ h2 as hasDefaultBlockForAction,
2814
+ p as hasRenderedContent,
2815
+ i2 as indexOverrides,
2816
+ q as injectContext,
2817
+ s as isEditorPreview,
2818
+ t as isStubDefinition,
1473
2819
  loadBlockRegistry,
1474
2820
  loginService,
1475
- G as onRegisterBlock,
1476
- H as provideContext,
2821
+ v as onRegisterBlock,
2822
+ x as provideContext,
1477
2823
  registerBlock,
1478
- I as serializeBlocks,
2824
+ y as serializeBlocks,
2825
+ z as setDevtoolsBlockProp,
1479
2826
  d as signal,
1480
- J as subscribeContext,
1481
- K as untracked,
2827
+ A as subscribeContext,
2828
+ D as untracked,
1482
2829
  waitForDevBlocksUrl,
1483
- L as withWrapper
2830
+ F as withWrapper
1484
2831
  };