@juo/blocks 1.11.0 → 1.12.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/core.js +982 -271
  2. package/dist/services/customer/adapter.d.ts +8 -0
  3. package/dist/services/customer/adapter.d.ts.map +1 -0
  4. package/dist/services/customer/customerService.d.ts +3 -2
  5. package/dist/services/customer/customerService.d.ts.map +1 -1
  6. package/dist/services/customer/index.d.ts +1 -0
  7. package/dist/services/customer/index.d.ts.map +1 -1
  8. package/dist/services/customer/types.d.ts +4 -1
  9. package/dist/services/customer/types.d.ts.map +1 -1
  10. package/dist/services/index.d.ts +1 -0
  11. package/dist/services/index.d.ts.map +1 -1
  12. package/dist/services/login/adapter.d.ts +32 -0
  13. package/dist/services/login/adapter.d.ts.map +1 -0
  14. package/dist/services/login/index.d.ts +3 -1
  15. package/dist/services/login/index.d.ts.map +1 -1
  16. package/dist/services/login/loginService.d.ts +6 -2
  17. package/dist/services/login/loginService.d.ts.map +1 -1
  18. package/dist/services/order/adapter.d.ts +9 -0
  19. package/dist/services/order/adapter.d.ts.map +1 -0
  20. package/dist/services/order/index.d.ts +5 -0
  21. package/dist/services/order/index.d.ts.map +1 -0
  22. package/dist/services/order/orderService.d.ts +7 -0
  23. package/dist/services/order/orderService.d.ts.map +1 -0
  24. package/dist/services/order/types.d.ts +21 -0
  25. package/dist/services/order/types.d.ts.map +1 -0
  26. package/dist/services/product/adapter.d.ts +4 -1
  27. package/dist/services/product/adapter.d.ts.map +1 -1
  28. package/dist/services/product/index.d.ts +1 -1
  29. package/dist/services/product/index.d.ts.map +1 -1
  30. package/dist/services/product/types.d.ts +2 -0
  31. package/dist/services/product/types.d.ts.map +1 -1
  32. package/dist/services/schedules/adapter.d.ts +22 -0
  33. package/dist/services/schedules/adapter.d.ts.map +1 -0
  34. package/dist/services/schedules/index.d.ts +5 -0
  35. package/dist/services/schedules/index.d.ts.map +1 -0
  36. package/dist/services/schedules/schedulesService.d.ts +10 -0
  37. package/dist/services/schedules/schedulesService.d.ts.map +1 -0
  38. package/dist/services/schedules/types.d.ts +47 -0
  39. package/dist/services/schedules/types.d.ts.map +1 -0
  40. package/dist/services/subscription/adapter.d.ts +12 -2
  41. package/dist/services/subscription/adapter.d.ts.map +1 -1
  42. package/dist/services/subscription/index.d.ts +1 -1
  43. package/dist/services/subscription/index.d.ts.map +1 -1
  44. package/dist/services/subscription/subscriptionService.d.ts +1 -6
  45. package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
  46. package/dist/services/subscription/types.d.ts +25 -9
  47. package/dist/services/subscription/types.d.ts.map +1 -1
  48. package/dist/utils/index.d.ts +6 -0
  49. package/dist/utils/index.d.ts.map +1 -1
  50. package/package.json +1 -1
package/dist/core.js CHANGED
@@ -156,13 +156,43 @@ async function loadBlockRegistry(options = {}) {
156
156
  function createCustomerService(options) {
157
157
  const current = d(null);
158
158
  const accountOrdersUrl = d(options.getAccountOrdersUrl());
159
- const service = {
159
+ return {
160
160
  current,
161
- accountOrdersUrl
161
+ accountOrdersUrl,
162
+ async getCurrent() {
163
+ const result = await options.adapter.getCurrent();
164
+ if (result._tag === "Success") current.value = result.data;
165
+ return result;
166
+ }
162
167
  };
163
- return service;
164
168
  }
165
169
  const CustomerServiceContext = createContext("services/customer");
170
+ function createMockCustomerAdapter() {
171
+ const customer = { id: "customer-1", name: "Customer 1" };
172
+ return {
173
+ async getCurrent() {
174
+ return { _tag: "Success", data: customer };
175
+ }
176
+ };
177
+ }
178
+ function createApiCustomerAdapter(fetcher) {
179
+ return {
180
+ async getCurrent() {
181
+ try {
182
+ const res = await fetcher("/customer/info");
183
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
184
+ const data = await res.json();
185
+ const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
186
+ return {
187
+ _tag: "Success",
188
+ data: { id: data.id, name, email: data.email }
189
+ };
190
+ } catch (e2) {
191
+ return { _tag: "Failure", error: e2 };
192
+ }
193
+ }
194
+ };
195
+ }
166
196
  const PARAMS = {
167
197
  DELETGATED_TOKEN: "delegated-token",
168
198
  REDIRECT_TO: "redirect-to",
@@ -179,6 +209,122 @@ function uuid() {
179
209
  (c) => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
180
210
  );
181
211
  }
212
+ function createMockLoginAdapter() {
213
+ return {
214
+ async passwordlessStart() {
215
+ return { _tag: "Success", data: void 0 };
216
+ },
217
+ async authenticate() {
218
+ return {
219
+ _tag: "Success",
220
+ data: {
221
+ access_token: "mock-access-token",
222
+ refresh_token: "mock-refresh-token",
223
+ expires_in: 3600
224
+ }
225
+ };
226
+ },
227
+ getAuthorizeUrl({ connection, state, shop, appUrl }) {
228
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
229
+ url.searchParams.set("connection", connection);
230
+ url.searchParams.set("state", state);
231
+ url.searchParams.set("shop", shop);
232
+ return url.toString();
233
+ }
234
+ };
235
+ }
236
+ function createApiLoginAdapter(fetcher, opts) {
237
+ const { shopDomain, appUrl } = opts;
238
+ async function callAuthEndpoint(path, body, lang) {
239
+ const url = lang ? `${path}?lang=${lang}` : path;
240
+ const res = await fetcher(url, {
241
+ method: "POST",
242
+ headers: {
243
+ Accept: "application/json",
244
+ "Content-Type": "application/json",
245
+ "X-Shopify-Shop-Domain": shopDomain
246
+ },
247
+ body: JSON.stringify(body)
248
+ });
249
+ if (res.status === 403) {
250
+ const data = await res.json();
251
+ if ("error_description" in data) {
252
+ if (data.error_description === "Wrong email or verification code.") {
253
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
254
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
255
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
256
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
257
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
258
+ }
259
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
260
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
261
+ connection: data.connection
262
+ });
263
+ }
264
+ }
265
+ if (!res.ok) {
266
+ throw new Error(`Unexpected response: ${res.statusText}`);
267
+ }
268
+ return { _tag: "Success", data: void 0 };
269
+ }
270
+ return {
271
+ async passwordlessStart({ email, lang }) {
272
+ try {
273
+ await callAuthEndpoint(
274
+ "/auth/customer/passwordless/start",
275
+ { email },
276
+ lang
277
+ );
278
+ return { _tag: "Success", data: void 0 };
279
+ } catch (e2) {
280
+ return { _tag: "Failure", error: e2 };
281
+ }
282
+ },
283
+ async authenticate(params) {
284
+ try {
285
+ const res = await fetcher("/auth/customer/token", {
286
+ method: "POST",
287
+ headers: {
288
+ Accept: "application/json",
289
+ "Content-Type": "application/json",
290
+ "X-Shopify-Shop-Domain": shopDomain
291
+ },
292
+ body: JSON.stringify(params)
293
+ });
294
+ if (res.status === 403) {
295
+ const data = await res.json();
296
+ if ("error_description" in data) {
297
+ if (data.error_description === "Wrong email or verification code.") {
298
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
299
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
300
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
301
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
302
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
303
+ }
304
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
305
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
306
+ connection: data.connection
307
+ });
308
+ }
309
+ }
310
+ if (!res.ok) {
311
+ throw new Error(`Unexpected response: ${res.statusText}`);
312
+ }
313
+ return { _tag: "Success", data: await res.json() };
314
+ } catch (e2) {
315
+ return { _tag: "Failure", error: e2 };
316
+ }
317
+ },
318
+ getAuthorizeUrl({ connection, state, shop }) {
319
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
320
+ url.searchParams.set("connection", connection);
321
+ url.searchParams.set("state", state);
322
+ url.searchParams.set("shop", shop);
323
+ return url.toString();
324
+ }
325
+ };
326
+ }
327
+ const __vite_import_meta_env__ = {};
182
328
  var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
183
329
  AuthenticationErrorReason2["Expired"] = "expired";
184
330
  AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
@@ -197,6 +343,9 @@ class AuthenticationError extends Error {
197
343
  this.data = data ?? {};
198
344
  }
199
345
  }
346
+ const STORAGE_DELEGATED_TOKEN = "delegated-token";
347
+ const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
348
+ const STORAGE_STATE_KEY = "state";
200
349
  function storage(key, value) {
201
350
  const storageKey = `auth:${key}`;
202
351
  try {
@@ -212,83 +361,33 @@ function storage(key, value) {
212
361
  }
213
362
  return null;
214
363
  }
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
364
  function nonce(len = 10) {
281
365
  return uuid().replace(/-/g, "").slice(0, len);
282
366
  }
283
- function createAuthService(apiUrl, shopDomain) {
284
- const { start: authStart, authenticate: authAuthenticate } = createAuthFunctions(apiUrl, shopDomain);
367
+ function loginService(deps) {
368
+ const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
369
+ 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";
370
+ const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
371
+ const adapter = createApiLoginAdapter(fetcher, {
372
+ shopDomain: deps.shop.domain,
373
+ appUrl
374
+ });
375
+ return createLoginService({
376
+ adapter,
377
+ router: deps.router,
378
+ formatError: deps.formatError,
379
+ shop: {
380
+ domain: deps.shop.domain,
381
+ locale: w(() => deps.getLocale())
382
+ },
383
+ onLoginSuccess: deps.onLoginSuccess
384
+ });
385
+ }
386
+ function createLoginService(init) {
387
+ const { adapter, formatError, router, shop, onLoginSuccess } = init;
285
388
  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
- );
389
+ const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
390
+ const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
292
391
  const expires = d(null);
293
392
  const state = d(storage(STORAGE_STATE_KEY));
294
393
  E(() => {
@@ -300,67 +399,28 @@ function createAuthService(apiUrl, shopDomain) {
300
399
  E(() => {
301
400
  storage(STORAGE_STATE_KEY, state.value);
302
401
  });
303
- function store(data) {
402
+ function storeTokens(data) {
304
403
  const { access_token, refresh_token, expires_in } = data;
305
404
  accessToken.value = access_token;
306
405
  refreshToken.value = refresh_token;
307
406
  expires.value = new Date(Date.now() + expires_in);
308
407
  }
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);
408
+ async function getToken() {
409
+ if (refreshToken.value == null) return null;
410
+ if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
411
+ accessToken.value = new Promise(
412
+ (resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
413
+ if (result._tag === "Failure") {
414
+ reject(result.error);
332
415
  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);
416
+ }
417
+ storeTokens(result.data);
418
+ resolve(result.data.access_token);
419
+ }).catch(reject)
420
+ );
421
+ }
422
+ return Promise.resolve(accessToken.value);
423
+ }
364
424
  const userEmail = d(null);
365
425
  async function onSuccess(query) {
366
426
  await (onLoginSuccess == null ? void 0 : onLoginSuccess());
@@ -373,13 +433,9 @@ function createLoginService(init) {
373
433
  queryParams[key] = value;
374
434
  }
375
435
  });
376
- router.push({
377
- path: decodeURIComponent(redirect),
378
- query: queryParams
379
- });
436
+ router.push({ path: decodeURIComponent(redirect), query: queryParams });
380
437
  } else {
381
- const redirect = query.get(PARAMS.REDIRECT_TO);
382
- router.replace(decodeURIComponent(redirect));
438
+ router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
383
439
  }
384
440
  } else {
385
441
  router.replace("/");
@@ -391,16 +447,17 @@ function createLoginService(init) {
391
447
  async start(email) {
392
448
  try {
393
449
  userEmail.value = email;
394
- await auth.start(
395
- userEmail.value,
396
- shop.locale instanceof u ? shop.locale.value : shop.locale
397
- );
450
+ const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
451
+ const result = await adapter.passwordlessStart({
452
+ email: userEmail.value,
453
+ lang
454
+ });
455
+ if (result._tag === "Failure") {
456
+ return { _tag: "Failure", error: formatError(result.error) };
457
+ }
398
458
  return { _tag: "Success", data: void 0 };
399
459
  } catch (error) {
400
- return {
401
- _tag: "Failure",
402
- error: formatError(error)
403
- };
460
+ return { _tag: "Failure", error: formatError(error) };
404
461
  }
405
462
  },
406
463
  async verify(email, code) {
@@ -411,63 +468,61 @@ function createLoginService(init) {
411
468
  };
412
469
  }
413
470
  try {
414
- const result = await auth.authenticate({
471
+ const result = await adapter.authenticate({
415
472
  email: userEmail.value,
416
473
  code
417
474
  });
475
+ if (result._tag === "Failure") {
476
+ return { _tag: "Failure", error: formatError(result.error) };
477
+ }
478
+ storeTokens(result.data);
418
479
  await onSuccess();
419
- return { _tag: "Success", data: result };
480
+ return { _tag: "Success", data: void 0 };
420
481
  } catch (error) {
421
- return {
422
- _tag: "Failure",
423
- error: formatError(error)
424
- };
482
+ return { _tag: "Failure", error: formatError(error) };
425
483
  }
426
484
  }
427
485
  }
428
486
  },
429
487
  social: {
430
488
  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
- };
489
+ state.value = nonce();
490
+ const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
491
+ window.location.href = adapter.getAuthorizeUrl({
492
+ connection,
493
+ state: state.value ?? "",
494
+ shop: shop.domain,
495
+ appUrl: (
496
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
497
+ (typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io"
498
+ )
499
+ });
500
+ return { _tag: "Success", data: void 0 };
450
501
  },
451
502
  async verify(code) {
452
503
  try {
453
- const result = await auth.authenticate({ code });
504
+ const result = await adapter.authenticate({ code });
505
+ if (result._tag === "Failure") {
506
+ return { _tag: "Failure", error: formatError(result.error) };
507
+ }
508
+ storeTokens(result.data);
454
509
  await onSuccess();
455
- return { _tag: "Success", data: result };
510
+ return { _tag: "Success", data: void 0 };
456
511
  } catch (error) {
457
- return {
458
- _tag: "Failure",
459
- error: formatError(error)
460
- };
512
+ return { _tag: "Failure", error: formatError(error) };
461
513
  }
462
514
  }
463
515
  },
464
516
  async logout() {
465
- await auth.logout();
517
+ accessToken.value = null;
518
+ delegatedToken.value = null;
519
+ refreshToken.value = null;
520
+ expires.value = null;
466
521
  return { _tag: "Success", data: void 0 };
467
522
  },
468
523
  async handleQueryParams(query) {
469
524
  if (query.has(PARAMS.DELETGATED_TOKEN)) {
470
- auth.setDelegatedToken(query.get(PARAMS.DELETGATED_TOKEN));
525
+ delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
471
526
  await onSuccess(query);
472
527
  return { _tag: "Success", data: void 0 };
473
528
  }
@@ -479,16 +534,13 @@ function createLoginService(init) {
479
534
  }
480
535
  const code = query.get(PARAMS.CODE);
481
536
  if (code != null) {
482
- if (query.get(PARAMS.STATE) !== auth.state.value) {
483
- return {
484
- _tag: "Failure",
485
- error: formatError("Invalid state")
486
- };
537
+ if (query.get(PARAMS.STATE) !== state.value) {
538
+ return { _tag: "Failure", error: formatError("Invalid state") };
487
539
  } else {
488
540
  return service.social.verify(code);
489
541
  }
490
542
  }
491
- if (query.get(PARAMS.REFRESH) === "1" && await auth.getToken() != null) {
543
+ if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
492
544
  await onSuccess();
493
545
  return { _tag: "Success", data: void 0 };
494
546
  }
@@ -505,8 +557,20 @@ function createLoginService(init) {
505
557
  }
506
558
  return { _tag: "Success", data: void 0 };
507
559
  },
508
- isAuthenticated: auth.isAuthenticated,
509
- getToken: () => auth.getToken()
560
+ isAuthenticated: w(
561
+ () => delegatedToken.value != null || refreshToken.value != null
562
+ ),
563
+ getToken,
564
+ async getAuthHeaders() {
565
+ if (delegatedToken.value) {
566
+ return { "X-Delegated-Token": delegatedToken.value };
567
+ }
568
+ const token = await getToken();
569
+ if (token) {
570
+ return { Authorization: `Bearer ${token}` };
571
+ }
572
+ return {};
573
+ }
510
574
  };
511
575
  return service;
512
576
  }
@@ -719,6 +783,106 @@ function createMockProductAdapter() {
719
783
  }
720
784
  };
721
785
  }
786
+ function mapVariant(variant, currencyCode) {
787
+ var _a;
788
+ return {
789
+ id: variant.id,
790
+ title: variant.title,
791
+ image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
792
+ price: { amount: variant.price.toFixed(2), currencyCode },
793
+ ...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
794
+ purchaseOptions: variant.purchaseOptions.map((opt) => ({
795
+ name: opt.name,
796
+ price: { amount: opt.price.toFixed(2), currencyCode },
797
+ interval: opt.interval
798
+ }))
799
+ } : {}
800
+ };
801
+ }
802
+ function mapProduct(product, currencyCode) {
803
+ return {
804
+ id: product.id,
805
+ title: product.title,
806
+ featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
807
+ hasOnlyDefaultVariant: product.variants.length <= 1,
808
+ variants: {
809
+ items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
810
+ pageInfo: {
811
+ hasNextPage: false,
812
+ hasPrevPage: false,
813
+ startCursor: null,
814
+ endCursor: null
815
+ }
816
+ },
817
+ collections: product.collections
818
+ };
819
+ }
820
+ function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
821
+ const { currencyCode } = opts;
822
+ async function fetchProducts(params) {
823
+ const query = params.toString() ? `?${params.toString()}` : "";
824
+ const res = await fetcher(`/customer/v1/products${query}`);
825
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
826
+ return res.json();
827
+ }
828
+ return {
829
+ async getById(id) {
830
+ try {
831
+ const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
832
+ const body = await fetchProducts(params);
833
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
834
+ } catch {
835
+ return null;
836
+ }
837
+ },
838
+ async getByVariantId(variantId) {
839
+ try {
840
+ const params = new URLSearchParams({
841
+ query: `variants.id:"${variantId}"`,
842
+ limit: "1"
843
+ });
844
+ const body = await fetchProducts(params);
845
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
846
+ } catch {
847
+ return null;
848
+ }
849
+ },
850
+ async search(opts2) {
851
+ try {
852
+ const params = new URLSearchParams();
853
+ if (opts2 == null ? void 0 : opts2.query) {
854
+ params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
855
+ }
856
+ if (opts2 == null ? void 0 : opts2.pagination) {
857
+ if ("after" in opts2.pagination)
858
+ params.set("after", opts2.pagination.after);
859
+ else if ("before" in opts2.pagination)
860
+ params.set("before", opts2.pagination.before);
861
+ }
862
+ const body = await fetchProducts(params);
863
+ return {
864
+ items: body.data.map((p2) => mapProduct(p2, currencyCode)),
865
+ pageInfo: {
866
+ hasNextPage: body.hasNextPage,
867
+ hasPrevPage: body.hasPrevPage,
868
+ startCursor: body.startCursor,
869
+ endCursor: body.endCursor
870
+ }
871
+ };
872
+ } catch {
873
+ return {
874
+ items: [],
875
+ pageInfo: {
876
+ hasNextPage: false,
877
+ hasPrevPage: false,
878
+ startCursor: null,
879
+ endCursor: null
880
+ }
881
+ };
882
+ }
883
+ }
884
+ };
885
+ }
722
886
  const ProductServiceContext = createContext("services/product");
723
887
  function createProductService(adapter) {
724
888
  return {
@@ -784,6 +948,250 @@ function createMockRouterService() {
784
948
  }
785
949
  }).routerService;
786
950
  }
951
+ function futureDateStr(daysFromNow) {
952
+ const d2 = /* @__PURE__ */ new Date();
953
+ d2.setDate(d2.getDate() + daysFromNow);
954
+ return d2.toISOString().split("T")[0] ?? "";
955
+ }
956
+ function createMockSchedulesAdapter() {
957
+ let orders = [
958
+ {
959
+ id: "schedule-order-1",
960
+ subscriptionId: "sub_1",
961
+ deliveryDate: futureDateStr(7),
962
+ items: [
963
+ {
964
+ id: "si-1",
965
+ productId: "p-beauty-1",
966
+ variantId: "v-beauty-1",
967
+ quantity: 1,
968
+ title: "Rose Face Serum",
969
+ imageUrl: "https://images.unsplash.com/photo-1611930022073-b7a4ba5fcccd?w=200"
970
+ },
971
+ {
972
+ id: "si-2",
973
+ productId: "p-beauty-2",
974
+ variantId: "v-beauty-2",
975
+ quantity: 1,
976
+ title: "Vitamin C Moisturizer",
977
+ imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200"
978
+ }
979
+ ],
980
+ status: "scheduled"
981
+ },
982
+ {
983
+ id: "schedule-order-2",
984
+ subscriptionId: "sub_1",
985
+ deliveryDate: futureDateStr(35),
986
+ items: [
987
+ {
988
+ id: "si-3",
989
+ productId: "p-beauty-1",
990
+ variantId: "v-beauty-1",
991
+ quantity: 1,
992
+ title: "Rose Face Serum",
993
+ imageUrl: "https://images.unsplash.com/photo-1611930022073-b7a4ba5fcccd?w=200"
994
+ },
995
+ {
996
+ id: "si-4",
997
+ productId: "p-beauty-3",
998
+ variantId: "v-beauty-3",
999
+ quantity: 1,
1000
+ title: "Hyaluronic Acid Serum",
1001
+ imageUrl: "https://images.unsplash.com/photo-1621232201696-ba6c9db10a97?w=200"
1002
+ }
1003
+ ],
1004
+ status: "scheduled"
1005
+ }
1006
+ ];
1007
+ let adjustments = [];
1008
+ return {
1009
+ async getUpcomingOrders(count) {
1010
+ await new Promise((r) => setTimeout(r, 200));
1011
+ return { _tag: "Success", data: orders.slice(0, count) };
1012
+ },
1013
+ async getAdjustments() {
1014
+ await new Promise((r) => setTimeout(r, 200));
1015
+ return { _tag: "Success", data: [...adjustments] };
1016
+ },
1017
+ async skipOrder({ subscriptionId }) {
1018
+ await new Promise((r) => setTimeout(r, 300));
1019
+ const order = orders.find(
1020
+ (o2) => o2.subscriptionId === subscriptionId && o2.status === "scheduled"
1021
+ );
1022
+ if (!order) return { _tag: "Failure", error: "No scheduled order found" };
1023
+ orders = orders.map(
1024
+ (o2) => o2.id === order.id ? { ...o2, status: "skipped" } : o2
1025
+ );
1026
+ const adj = {
1027
+ id: `adj-${Date.now()}`,
1028
+ subscriptionId,
1029
+ type: "skip"
1030
+ };
1031
+ adjustments = [...adjustments, adj];
1032
+ return { _tag: "Success", data: void 0 };
1033
+ },
1034
+ async changeDateAdjustment({ subscriptionId, newDate }) {
1035
+ await new Promise((r) => setTimeout(r, 300));
1036
+ const order = orders.find(
1037
+ (o2) => o2.subscriptionId === subscriptionId && o2.status === "scheduled"
1038
+ );
1039
+ if (!order) return { _tag: "Failure", error: "No scheduled order found" };
1040
+ orders = orders.map(
1041
+ (o2) => o2.id === order.id ? { ...o2, deliveryDate: newDate } : o2
1042
+ );
1043
+ const adj = {
1044
+ id: `adj-${Date.now()}`,
1045
+ subscriptionId,
1046
+ type: "date_change",
1047
+ newDate
1048
+ };
1049
+ adjustments = [...adjustments, adj];
1050
+ return { _tag: "Success", data: void 0 };
1051
+ },
1052
+ async removeAdjustment({ adjustmentId }) {
1053
+ await new Promise((r) => setTimeout(r, 200));
1054
+ adjustments = adjustments.filter((a2) => a2.id !== adjustmentId);
1055
+ return { _tag: "Success", data: void 0 };
1056
+ }
1057
+ };
1058
+ }
1059
+ function createApiSchedulesAdapter(fetcher) {
1060
+ return {
1061
+ async getUpcomingOrders(count) {
1062
+ try {
1063
+ const res = await fetcher(`/customer/v1/schedules/?count=${count}`);
1064
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1065
+ const data = await res.json();
1066
+ return { _tag: "Success", data: data.orders };
1067
+ } catch (e2) {
1068
+ return { _tag: "Failure", error: e2 };
1069
+ }
1070
+ },
1071
+ async getAdjustments() {
1072
+ try {
1073
+ const res = await fetcher(`/customer/v1/schedules/adjustments`);
1074
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1075
+ const data = await res.json();
1076
+ const adjustments = data.data.map((a2) => ({
1077
+ id: a2.id,
1078
+ subscriptionId: a2.subscriptionId ?? "",
1079
+ type: a2.type === "CHANGE_DATE" ? "date_change" : "skip",
1080
+ newDate: a2.newDate
1081
+ }));
1082
+ return { _tag: "Success", data: adjustments };
1083
+ } catch (e2) {
1084
+ return { _tag: "Failure", error: e2 };
1085
+ }
1086
+ },
1087
+ async skipOrder({ cycle, date }) {
1088
+ if (cycle == null && date == null) {
1089
+ return { _tag: "Failure", error: "Either cycle or date is required for skipOrder" };
1090
+ }
1091
+ try {
1092
+ const matcher = cycle != null ? { type: "CYCLE", input: { cycle } } : { type: "DATE", input: { date: new Date(date).toISOString() } };
1093
+ const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1094
+ method: "POST",
1095
+ headers: { "Content-Type": "application/json" },
1096
+ body: JSON.stringify({
1097
+ matcher,
1098
+ action: { type: "SKIP_ORDER", input: { reason: "" } }
1099
+ })
1100
+ });
1101
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1102
+ return { _tag: "Success", data: void 0 };
1103
+ } catch (e2) {
1104
+ return { _tag: "Failure", error: e2 };
1105
+ }
1106
+ },
1107
+ async changeDateAdjustment({ newDate, currentDate }) {
1108
+ if (currentDate == null) {
1109
+ return {
1110
+ _tag: "Failure",
1111
+ error: "currentDate is required for changeDateAdjustment"
1112
+ };
1113
+ }
1114
+ try {
1115
+ const matcher = { type: "DATE", input: { date: new Date(currentDate).toISOString() } };
1116
+ const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1117
+ method: "POST",
1118
+ headers: { "Content-Type": "application/json" },
1119
+ body: JSON.stringify({
1120
+ matcher,
1121
+ action: {
1122
+ type: "CHANGE_DATE",
1123
+ input: { newDate: new Date(newDate).toISOString() }
1124
+ }
1125
+ })
1126
+ });
1127
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1128
+ return { _tag: "Success", data: void 0 };
1129
+ } catch (e2) {
1130
+ return { _tag: "Failure", error: e2 };
1131
+ }
1132
+ },
1133
+ async removeAdjustment({ adjustmentId }) {
1134
+ try {
1135
+ const res = await fetcher(
1136
+ `/customer/v1/schedules/adjustments/${adjustmentId}`,
1137
+ { method: "DELETE" }
1138
+ );
1139
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1140
+ return { _tag: "Success", data: void 0 };
1141
+ } catch (e2) {
1142
+ return { _tag: "Failure", error: e2 };
1143
+ }
1144
+ }
1145
+ };
1146
+ }
1147
+ const SchedulesServiceContext = createContext("services/schedules");
1148
+ function createSchedulesService(adapter, opts) {
1149
+ const upcoming = d([]);
1150
+ async function refreshUpcoming() {
1151
+ const count = Math.max(upcoming.value.length, 3);
1152
+ const result = await adapter.getUpcomingOrders(count);
1153
+ if (result._tag === "Success") {
1154
+ upcoming.value = result.data;
1155
+ }
1156
+ }
1157
+ return {
1158
+ upcoming,
1159
+ hasPendingBilling: opts.hasPendingBilling,
1160
+ async getUpcomingOrder() {
1161
+ const result = await adapter.getUpcomingOrders(1);
1162
+ if (result._tag === "Failure") return result;
1163
+ upcoming.value = result.data;
1164
+ return { _tag: "Success", data: result.data[0] ?? null };
1165
+ },
1166
+ async getUpcomingOrders(count) {
1167
+ const result = await adapter.getUpcomingOrders(count);
1168
+ if (result._tag === "Success") {
1169
+ upcoming.value = result.data;
1170
+ }
1171
+ return result;
1172
+ },
1173
+ async getAdjustments() {
1174
+ return adapter.getAdjustments();
1175
+ },
1176
+ async skipOrder(params) {
1177
+ const result = await adapter.skipOrder(params);
1178
+ if (result._tag === "Success") {
1179
+ await refreshUpcoming();
1180
+ }
1181
+ return result;
1182
+ },
1183
+ async changeDateAdjustment(params) {
1184
+ const result = await adapter.changeDateAdjustment(params);
1185
+ if (result._tag === "Success") {
1186
+ await refreshUpcoming();
1187
+ }
1188
+ return result;
1189
+ },
1190
+ async removeAdjustment(params) {
1191
+ return adapter.removeAdjustment(params);
1192
+ }
1193
+ };
1194
+ }
787
1195
  function createMockSubscriptionAdapter(options = {}) {
788
1196
  const mockSubscriptions = [
789
1197
  {
@@ -796,7 +1204,9 @@ function createMockSubscriptionAdapter(options = {}) {
796
1204
  variantId: "variant_1",
797
1205
  quantity: 2,
798
1206
  price: { amount: "29.99", currencyCode: "USD" },
799
- nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15")
1207
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
1208
+ billingPolicy: { intervalCount: 1, interval: "MONTH" },
1209
+ deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
800
1210
  }
801
1211
  ],
802
1212
  discounts: [
@@ -824,7 +1234,9 @@ function createMockSubscriptionAdapter(options = {}) {
824
1234
  variantId: "variant_2",
825
1235
  quantity: 1,
826
1236
  price: { amount: "49.99", currencyCode: "USD" },
827
- nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20")
1237
+ nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
1238
+ billingPolicy: { intervalCount: 2, interval: "WEEK" },
1239
+ deliveryPolicy: { intervalCount: 2, interval: "WEEK" }
828
1240
  }
829
1241
  ],
830
1242
  discounts: [],
@@ -835,6 +1247,24 @@ function createMockSubscriptionAdapter(options = {}) {
835
1247
  updatedAt: "2024-10-10T00:00:00Z"
836
1248
  }
837
1249
  ];
1250
+ const mockUpsellProducts = [
1251
+ {
1252
+ id: "upsell-1",
1253
+ variantId: "upsell-v-1",
1254
+ title: "Retinol Night Cream",
1255
+ imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
1256
+ price: { amount: "38.00", currencyCode: "USD" },
1257
+ description: "Anti-aging night treatment"
1258
+ },
1259
+ {
1260
+ id: "upsell-2",
1261
+ variantId: "upsell-v-2",
1262
+ title: "SPF 50 Day Shield",
1263
+ imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
1264
+ price: { amount: "24.00", currencyCode: "USD" },
1265
+ description: "Lightweight daily sun protection"
1266
+ }
1267
+ ];
838
1268
  return {
839
1269
  async getById(id) {
840
1270
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -843,7 +1273,7 @@ function createMockSubscriptionAdapter(options = {}) {
843
1273
  },
844
1274
  async search(options2 = {}) {
845
1275
  await new Promise((resolve) => setTimeout(resolve, 300));
846
- const pageSize = 1;
1276
+ const pageSize = 2;
847
1277
  const allItems = mockSubscriptions;
848
1278
  let startIndex = 0;
849
1279
  let endIndex = pageSize;
@@ -859,20 +1289,22 @@ function createMockSubscriptionAdapter(options = {}) {
859
1289
  }
860
1290
  }
861
1291
  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
1292
+ return {
1293
+ _tag: "Success",
1294
+ data: {
1295
+ items: pageItems.map((sub) => ({
1296
+ ...sub,
1297
+ items: sub.items.map((item) => ({ ...item })),
1298
+ discounts: sub.discounts.map((discount) => ({ ...discount }))
1299
+ })),
1300
+ pageInfo: {
1301
+ hasNextPage: endIndex < allItems.length,
1302
+ hasPrevPage: startIndex > 0,
1303
+ startCursor: pageItems.length > 0 ? startIndex.toString() : null,
1304
+ endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
1305
+ }
873
1306
  }
874
1307
  };
875
- return { _tag: "Success", data: paginatedList };
876
1308
  },
877
1309
  async addItem() {
878
1310
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -910,24 +1342,11 @@ function createMockSubscriptionAdapter(options = {}) {
910
1342
  (sub) => sub.id === params.subscriptionId
911
1343
  );
912
1344
  if (!subscription) {
913
- return {
914
- _tag: "Failure",
915
- error: "Subscription not found"
916
- };
1345
+ return { _tag: "Failure", error: "Subscription not found" };
917
1346
  }
918
1347
  subscription.nextDeliveryDate = new Date(params.newDate);
919
1348
  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
- };
1349
+ return { _tag: "Success", data: { ...subscription } };
931
1350
  },
932
1351
  async resume(params) {
933
1352
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -935,21 +1354,11 @@ function createMockSubscriptionAdapter(options = {}) {
935
1354
  (sub) => sub.id === params.subscriptionId
936
1355
  );
937
1356
  if (!subscription) {
938
- return {
939
- _tag: "Failure",
940
- error: "Subscription not found"
941
- };
1357
+ return { _tag: "Failure", error: "Subscription not found" };
942
1358
  }
943
1359
  subscription.status = "active";
944
1360
  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
- };
1361
+ return { _tag: "Success", data: { ...subscription } };
953
1362
  },
954
1363
  async pause(params) {
955
1364
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -957,21 +1366,11 @@ function createMockSubscriptionAdapter(options = {}) {
957
1366
  (sub) => sub.id === params.subscriptionId
958
1367
  );
959
1368
  if (!subscription) {
960
- return {
961
- _tag: "Failure",
962
- error: "Subscription not found"
963
- };
1369
+ return { _tag: "Failure", error: "Subscription not found" };
964
1370
  }
965
1371
  subscription.status = "paused";
966
1372
  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
- };
1373
+ return { _tag: "Success", data: { ...subscription } };
975
1374
  },
976
1375
  async reactivate(params) {
977
1376
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -979,21 +1378,11 @@ function createMockSubscriptionAdapter(options = {}) {
979
1378
  (sub) => sub.id === params.subscriptionId
980
1379
  );
981
1380
  if (!subscription) {
982
- return {
983
- _tag: "Failure",
984
- error: "Subscription not found"
985
- };
1381
+ return { _tag: "Failure", error: "Subscription not found" };
986
1382
  }
987
1383
  subscription.status = "active";
988
1384
  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
- };
1385
+ return { _tag: "Success", data: { ...subscription } };
997
1386
  },
998
1387
  async cancel(params) {
999
1388
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1001,21 +1390,11 @@ function createMockSubscriptionAdapter(options = {}) {
1001
1390
  (sub) => sub.id === params.subscriptionId
1002
1391
  );
1003
1392
  if (!subscription) {
1004
- return {
1005
- _tag: "Failure",
1006
- error: "Subscription not found"
1007
- };
1393
+ return { _tag: "Failure", error: "Subscription not found" };
1008
1394
  }
1009
1395
  subscription.status = "canceled";
1010
1396
  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
- };
1397
+ return { _tag: "Success", data: { ...subscription } };
1019
1398
  },
1020
1399
  async getAvailablePlans() {
1021
1400
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -1047,6 +1426,312 @@ function createMockSubscriptionAdapter(options = {}) {
1047
1426
  var _a;
1048
1427
  await new Promise((resolve) => setTimeout(resolve, 300));
1049
1428
  return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
1429
+ },
1430
+ async getUpsellProducts() {
1431
+ await new Promise((resolve) => setTimeout(resolve, 300));
1432
+ return { _tag: "Success", data: [...mockUpsellProducts] };
1433
+ },
1434
+ async addUpsellProduct() {
1435
+ await new Promise((resolve) => setTimeout(resolve, 400));
1436
+ return { _tag: "Success", data: void 0 };
1437
+ }
1438
+ };
1439
+ }
1440
+ function mapSubscription(raw) {
1441
+ var _a, _b;
1442
+ const status = raw.status === "merged" ? "canceled" : raw.status;
1443
+ const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
1444
+ const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
1445
+ return {
1446
+ id: raw.id,
1447
+ customerId: "",
1448
+ items: (raw.items ?? []).map(
1449
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1450
+ (item) => ({
1451
+ id: item.id,
1452
+ productId: item.productId ?? item.product ?? "",
1453
+ variantId: item.variantId ?? item.variant ?? "",
1454
+ quantity: item.quantity,
1455
+ price: item.price ?? {
1456
+ amount: String(item.totalPrice ?? 0),
1457
+ currencyCode: raw.currencyCode ?? ""
1458
+ },
1459
+ nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
1460
+ billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
1461
+ deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null
1462
+ })
1463
+ ),
1464
+ discounts: (raw.discounts ?? []).map(
1465
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1466
+ (d2) => {
1467
+ var _a2, _b2, _c;
1468
+ return {
1469
+ id: d2.id,
1470
+ type: ((_a2 = d2.value) == null ? void 0 : _a2.type) === "fixed-amount" || d2.type === "fixed_amount" ? "fixed_amount" : "percentage",
1471
+ value: ((_b2 = d2.value) == null ? void 0 : _b2.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? d2.value ?? 0,
1472
+ code: d2.title ?? d2.code ?? "",
1473
+ description: d2.description ?? ""
1474
+ };
1475
+ }
1476
+ ),
1477
+ status,
1478
+ nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
1479
+ frequency,
1480
+ createdAt: raw.createdAt,
1481
+ updatedAt: raw.updatedAt
1482
+ };
1483
+ }
1484
+ function createApiSubscriptionAdapter(fetcher) {
1485
+ return {
1486
+ async search(options) {
1487
+ var _a;
1488
+ try {
1489
+ const params = new URLSearchParams();
1490
+ if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
1491
+ 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 ");
1492
+ if (q2) params.set("query", q2);
1493
+ }
1494
+ if (options == null ? void 0 : options.pagination) {
1495
+ if ("after" in options.pagination)
1496
+ params.set("after", options.pagination.after);
1497
+ else if ("before" in options.pagination)
1498
+ params.set("before", options.pagination.before);
1499
+ }
1500
+ const query = params.toString() ? `?${params.toString()}` : "";
1501
+ const res = await fetcher(`/customer/v1/subscriptions${query}`);
1502
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1503
+ const body = await res.json();
1504
+ return {
1505
+ _tag: "Success",
1506
+ data: {
1507
+ items: body.data.map(mapSubscription),
1508
+ pageInfo: {
1509
+ hasNextPage: body.hasNextPage,
1510
+ hasPrevPage: body.hasPrevPage,
1511
+ startCursor: body.startCursor,
1512
+ endCursor: body.endCursor
1513
+ }
1514
+ }
1515
+ };
1516
+ } catch (e2) {
1517
+ return { _tag: "Failure", error: e2 };
1518
+ }
1519
+ },
1520
+ async getById(id) {
1521
+ try {
1522
+ const res = await fetcher(
1523
+ `/customer/v1/subscriptions?query=id:"${id}"&limit=1`
1524
+ );
1525
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1526
+ const body = await res.json();
1527
+ const raw = body.data[0];
1528
+ return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
1529
+ } catch (e2) {
1530
+ return { _tag: "Failure", error: e2 };
1531
+ }
1532
+ },
1533
+ async addItem({ subscriptionId, item }) {
1534
+ try {
1535
+ const res = await fetcher(
1536
+ `/customer/v1/subscriptions/${subscriptionId}/items`,
1537
+ {
1538
+ method: "POST",
1539
+ headers: { "Content-Type": "application/json" },
1540
+ body: JSON.stringify(item)
1541
+ }
1542
+ );
1543
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1544
+ return { _tag: "Success", data: void 0 };
1545
+ } catch (e2) {
1546
+ return { _tag: "Failure", error: e2 };
1547
+ }
1548
+ },
1549
+ async removeItem({ subscriptionId, itemId }) {
1550
+ try {
1551
+ const res = await fetcher(
1552
+ `/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
1553
+ { method: "DELETE" }
1554
+ );
1555
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1556
+ return { _tag: "Success", data: void 0 };
1557
+ } catch (e2) {
1558
+ return { _tag: "Failure", error: e2 };
1559
+ }
1560
+ },
1561
+ async addDiscount({ subscriptionId, code }) {
1562
+ try {
1563
+ const res = await fetcher(
1564
+ `/customer/v1/subscriptions/${subscriptionId}/discounts`,
1565
+ {
1566
+ method: "POST",
1567
+ headers: { "Content-Type": "application/json" },
1568
+ body: JSON.stringify({ code })
1569
+ }
1570
+ );
1571
+ if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
1572
+ return { _tag: "Success", data: void 0 };
1573
+ } catch (e2) {
1574
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
1575
+ }
1576
+ },
1577
+ async removeDiscount({ subscriptionId, discountId }) {
1578
+ try {
1579
+ const res = await fetcher(
1580
+ `/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
1581
+ { method: "DELETE" }
1582
+ );
1583
+ if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
1584
+ return { _tag: "Success", data: void 0 };
1585
+ } catch (e2) {
1586
+ return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
1587
+ }
1588
+ },
1589
+ // Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
1590
+ async reschedule({ subscriptionId, newDate }) {
1591
+ try {
1592
+ const res = await fetcher(
1593
+ `/customer/v1/subscriptions/${subscriptionId}`,
1594
+ {
1595
+ method: "PATCH",
1596
+ headers: { "Content-Type": "application/json" },
1597
+ body: JSON.stringify({ nextBillingDate: newDate })
1598
+ }
1599
+ );
1600
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1601
+ const data = await res.json();
1602
+ return { _tag: "Success", data: mapSubscription(data) };
1603
+ } catch (e2) {
1604
+ return { _tag: "Failure", error: e2 };
1605
+ }
1606
+ },
1607
+ async resume({ subscriptionId }) {
1608
+ try {
1609
+ const res = await fetcher(
1610
+ `/customer/v1/subscriptions/${subscriptionId}/resume`,
1611
+ { method: "POST" }
1612
+ );
1613
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1614
+ const data = await res.json();
1615
+ return { _tag: "Success", data: mapSubscription(data) };
1616
+ } catch (e2) {
1617
+ return { _tag: "Failure", error: e2 };
1618
+ }
1619
+ },
1620
+ async pause({ subscriptionId }) {
1621
+ try {
1622
+ const res = await fetcher(
1623
+ `/customer/v1/subscriptions/${subscriptionId}/pause`,
1624
+ { method: "POST" }
1625
+ );
1626
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1627
+ const data = await res.json();
1628
+ return { _tag: "Success", data: mapSubscription(data) };
1629
+ } catch (e2) {
1630
+ return { _tag: "Failure", error: e2 };
1631
+ }
1632
+ },
1633
+ async reactivate({ subscriptionId }) {
1634
+ try {
1635
+ const res = await fetcher(
1636
+ `/customer/v1/subscriptions/${subscriptionId}/reactivate`,
1637
+ { method: "POST" }
1638
+ );
1639
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1640
+ const data = await res.json();
1641
+ return { _tag: "Success", data: mapSubscription(data) };
1642
+ } catch (e2) {
1643
+ return { _tag: "Failure", error: e2 };
1644
+ }
1645
+ },
1646
+ async cancel({ subscriptionId, reason }) {
1647
+ try {
1648
+ const res = await fetcher(
1649
+ `/customer/v1/subscriptions/${subscriptionId}/cancel`,
1650
+ {
1651
+ method: "POST",
1652
+ headers: { "Content-Type": "application/json" },
1653
+ body: JSON.stringify({ cancellationReason: reason })
1654
+ }
1655
+ );
1656
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1657
+ const data = await res.json();
1658
+ return { _tag: "Success", data: mapSubscription(data) };
1659
+ } catch (e2) {
1660
+ return { _tag: "Failure", error: e2 };
1661
+ }
1662
+ },
1663
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
1664
+ async getAvailablePlans({ subscriptionId }) {
1665
+ try {
1666
+ const res = await fetcher(
1667
+ `/customer/subscriptions/${subscriptionId}/plans`
1668
+ );
1669
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1670
+ const data = await res.json();
1671
+ return { _tag: "Success", data };
1672
+ } catch (e2) {
1673
+ return { _tag: "Failure", error: e2 };
1674
+ }
1675
+ },
1676
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
1677
+ async getDefaultSubscriptionId() {
1678
+ try {
1679
+ const res = await fetcher(
1680
+ `/customer/subscriptions/one-by-next-billing-date`
1681
+ );
1682
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1683
+ const data = await res.json();
1684
+ return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
1685
+ } catch (e2) {
1686
+ return { _tag: "Failure", error: e2 };
1687
+ }
1688
+ },
1689
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
1690
+ async getUpsellProducts({ subscriptionId }) {
1691
+ try {
1692
+ const res = await fetcher(
1693
+ `/customer/subscriptions/${subscriptionId}/upsell-products`
1694
+ );
1695
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1696
+ const data = await res.json();
1697
+ const products = data.map((p2) => {
1698
+ var _a, _b, _c, _d, _e;
1699
+ return {
1700
+ id: p2.id,
1701
+ variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
1702
+ title: p2.title ?? "",
1703
+ imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
1704
+ price: {
1705
+ amount: String(((_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.price) ?? 0),
1706
+ currencyCode: p2.currencyCode ?? ""
1707
+ }
1708
+ };
1709
+ });
1710
+ return { _tag: "Success", data: products };
1711
+ } catch (e2) {
1712
+ return { _tag: "Failure", error: e2 };
1713
+ }
1714
+ },
1715
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
1716
+ async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
1717
+ try {
1718
+ const res = await fetcher(
1719
+ `/customer/subscriptions/${subscriptionId}/upsell`,
1720
+ {
1721
+ method: "POST",
1722
+ headers: { "Content-Type": "application/json" },
1723
+ body: JSON.stringify({
1724
+ productVariantId: variantId,
1725
+ quantity,
1726
+ ...oneTime ? { renewalLimit: 1 } : {}
1727
+ })
1728
+ }
1729
+ );
1730
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1731
+ return { _tag: "Success", data: void 0 };
1732
+ } catch (e2) {
1733
+ return { _tag: "Failure", error: e2 };
1734
+ }
1050
1735
  }
1051
1736
  };
1052
1737
  }
@@ -1072,6 +1757,7 @@ function getCurrentSubscriptionId() {
1072
1757
  }
1073
1758
  function createSubscriptionService(adapter) {
1074
1759
  const current = d(null);
1760
+ const hasPendingBilling = d(false);
1075
1761
  const discountsChangedAtSignal = d(0);
1076
1762
  const itemsChangedAtSignal = d(0);
1077
1763
  async function refreshCurrent(subscriptionId) {
@@ -1092,9 +1778,25 @@ function createSubscriptionService(adapter) {
1092
1778
  var _a;
1093
1779
  return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
1094
1780
  }
1095
- const service = {
1096
- search: adapter.search,
1097
- getById: adapter.getById,
1781
+ return {
1782
+ current,
1783
+ hasPendingBilling,
1784
+ discountsChangedAt: discountsChangedAtSignal,
1785
+ itemsChangedAt: itemsChangedAtSignal,
1786
+ async search(options) {
1787
+ const result = await adapter.search(options);
1788
+ if (result._tag === "Success") {
1789
+ current.value = result.data.items[0] ?? null;
1790
+ }
1791
+ return result;
1792
+ },
1793
+ async getById(id) {
1794
+ const result = await adapter.getById(id);
1795
+ if (result._tag === "Success" && result.data) {
1796
+ current.value = result.data;
1797
+ }
1798
+ return result;
1799
+ },
1098
1800
  getCurrentSubscriptionId,
1099
1801
  addItem: async (subscriptionId, item) => {
1100
1802
  const result = await adapter.addItem({ subscriptionId, item });
@@ -1148,11 +1850,9 @@ function createSubscriptionService(adapter) {
1148
1850
  cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
1149
1851
  getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
1150
1852
  getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
1151
- current,
1152
- discountsChangedAt: discountsChangedAtSignal,
1153
- itemsChangedAt: itemsChangedAtSignal
1853
+ getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
1854
+ addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
1154
1855
  };
1155
- return service;
1156
1856
  }
1157
1857
  const DISCOUNT_REJECTION_REASONS = [
1158
1858
  "INVALID_CODE",
@@ -1428,6 +2128,7 @@ export {
1428
2128
  OrdersServiceContext,
1429
2129
  ProductServiceContext,
1430
2130
  RouterServiceContext,
2131
+ SchedulesServiceContext,
1431
2132
  S as SelectedBlockContext,
1432
2133
  u as Signal,
1433
2134
  StateContext,
@@ -1438,20 +2139,30 @@ export {
1438
2139
  e as attachEmptyTracking,
1439
2140
  f as backfillInstanceDefaults,
1440
2141
  w as computed,
2142
+ createApiCustomerAdapter,
2143
+ createApiLoginAdapter,
2144
+ createApiProductAdapter,
2145
+ createApiSchedulesAdapter,
2146
+ createApiSubscriptionAdapter,
1441
2147
  g as createBlockInstance,
1442
2148
  h as createBlockInstanceFromJSON,
1443
2149
  i as createBlockInstanceFromObject,
1444
2150
  j as createCeRenderer,
1445
2151
  createContext,
1446
2152
  createCustomerService,
2153
+ createLoginService,
2154
+ createMockCustomerAdapter,
2155
+ createMockLoginAdapter,
1447
2156
  createMockOrdersAdapter,
1448
2157
  createMockProductAdapter,
1449
2158
  createMockRouterService,
2159
+ createMockSchedulesAdapter,
1450
2160
  createMockSubscriptionAdapter,
1451
2161
  createMockWorkflowApiAdapter,
1452
2162
  createOrdersService,
1453
2163
  createProductService,
1454
2164
  createRouterService,
2165
+ createSchedulesService,
1455
2166
  k as createSelectedBlock,
1456
2167
  createState,
1457
2168
  createSubscriptionService,