@juo/blocks 1.1.0-alpha.4 → 1.1.0-alpha.6

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 (35) hide show
  1. package/dist/core.js +546 -504
  2. package/dist/services/customer/adapter.d.ts +1 -6
  3. package/dist/services/customer/adapter.d.ts.map +1 -1
  4. package/dist/services/customer/customerService.d.ts +3 -3
  5. package/dist/services/customer/customerService.d.ts.map +1 -1
  6. package/dist/services/customer/index.d.ts +2 -1
  7. package/dist/services/customer/index.d.ts.map +1 -1
  8. package/dist/services/customer/types.d.ts +2 -1
  9. package/dist/services/customer/types.d.ts.map +1 -1
  10. package/dist/services/login/adapter.d.ts +32 -0
  11. package/dist/services/login/adapter.d.ts.map +1 -0
  12. package/dist/services/login/index.d.ts +2 -1
  13. package/dist/services/login/index.d.ts.map +1 -1
  14. package/dist/services/login/loginService.d.ts +4 -2
  15. package/dist/services/login/loginService.d.ts.map +1 -1
  16. package/dist/services/order/adapter.d.ts +1 -4
  17. package/dist/services/order/adapter.d.ts.map +1 -1
  18. package/dist/services/order/orderService.d.ts.map +1 -1
  19. package/dist/services/order/types.d.ts +0 -4
  20. package/dist/services/order/types.d.ts.map +1 -1
  21. package/dist/services/product/adapter.d.ts +4 -1
  22. package/dist/services/product/adapter.d.ts.map +1 -1
  23. package/dist/services/product/index.d.ts +1 -1
  24. package/dist/services/product/index.d.ts.map +1 -1
  25. package/dist/services/schedules/adapter.d.ts +2 -0
  26. package/dist/services/schedules/adapter.d.ts.map +1 -1
  27. package/dist/services/schedules/types.d.ts +2 -0
  28. package/dist/services/schedules/types.d.ts.map +1 -1
  29. package/dist/services/subscription/adapter.d.ts +0 -7
  30. package/dist/services/subscription/adapter.d.ts.map +1 -1
  31. package/dist/services/subscription/subscriptionService.d.ts +0 -4
  32. package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
  33. package/dist/services/subscription/types.d.ts +8 -15
  34. package/dist/services/subscription/types.d.ts.map +1 -1
  35. package/package.json +1 -1
package/dist/core.js CHANGED
@@ -155,6 +155,122 @@ function uuid() {
155
155
  (c2) => (c2 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c2 / 4).toString(16)
156
156
  );
157
157
  }
158
+ function createMockLoginAdapter() {
159
+ return {
160
+ async passwordlessStart() {
161
+ return { _tag: "Success", data: void 0 };
162
+ },
163
+ async authenticate() {
164
+ return {
165
+ _tag: "Success",
166
+ data: {
167
+ access_token: "mock-access-token",
168
+ refresh_token: "mock-refresh-token",
169
+ expires_in: 3600
170
+ }
171
+ };
172
+ },
173
+ getAuthorizeUrl({ connection, state, shop, appUrl }) {
174
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
175
+ url.searchParams.set("connection", connection);
176
+ url.searchParams.set("state", state);
177
+ url.searchParams.set("shop", shop);
178
+ return url.toString();
179
+ }
180
+ };
181
+ }
182
+ function createApiLoginAdapter(fetcher, opts) {
183
+ const { shopDomain, appUrl } = opts;
184
+ async function callAuthEndpoint(path, body, lang) {
185
+ const url = lang ? `${path}?lang=${lang}` : path;
186
+ const res = await fetcher(url, {
187
+ method: "POST",
188
+ headers: {
189
+ Accept: "application/json",
190
+ "Content-Type": "application/json",
191
+ "X-Shopify-Shop-Domain": shopDomain
192
+ },
193
+ body: JSON.stringify(body)
194
+ });
195
+ if (res.status === 403) {
196
+ const data = await res.json();
197
+ if ("error_description" in data) {
198
+ if (data.error_description === "Wrong email or verification code.") {
199
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
200
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
201
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
202
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
203
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
204
+ }
205
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
206
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
207
+ connection: data.connection
208
+ });
209
+ }
210
+ }
211
+ if (!res.ok) {
212
+ throw new Error(`Unexpected response: ${res.statusText}`);
213
+ }
214
+ return { _tag: "Success", data: void 0 };
215
+ }
216
+ return {
217
+ async passwordlessStart({ email, lang }) {
218
+ try {
219
+ await callAuthEndpoint(
220
+ "/auth/customer/passwordless/start",
221
+ { email },
222
+ lang
223
+ );
224
+ return { _tag: "Success", data: void 0 };
225
+ } catch (e2) {
226
+ return { _tag: "Failure", error: e2 };
227
+ }
228
+ },
229
+ async authenticate(params) {
230
+ try {
231
+ const res = await fetcher("/auth/customer/token", {
232
+ method: "POST",
233
+ headers: {
234
+ Accept: "application/json",
235
+ "Content-Type": "application/json",
236
+ "X-Shopify-Shop-Domain": shopDomain
237
+ },
238
+ body: JSON.stringify(params)
239
+ });
240
+ if (res.status === 403) {
241
+ const data = await res.json();
242
+ if ("error_description" in data) {
243
+ if (data.error_description === "Wrong email or verification code.") {
244
+ throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
245
+ } else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
246
+ throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
247
+ } else if (data.error_description === "The verification code has expired. Please try to login again.") {
248
+ throw new AuthenticationError(AuthenticationErrorReason.Expired);
249
+ }
250
+ } else if ("code" in data && data.code === "AUTH_ERROR") {
251
+ throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
252
+ connection: data.connection
253
+ });
254
+ }
255
+ }
256
+ if (!res.ok) {
257
+ throw new Error(`Unexpected response: ${res.statusText}`);
258
+ }
259
+ return { _tag: "Success", data: await res.json() };
260
+ } catch (e2) {
261
+ return { _tag: "Failure", error: e2 };
262
+ }
263
+ },
264
+ getAuthorizeUrl({ connection, state, shop }) {
265
+ const url = new URL(`${appUrl}/auth/customer/authorize`);
266
+ url.searchParams.set("connection", connection);
267
+ url.searchParams.set("state", state);
268
+ url.searchParams.set("shop", shop);
269
+ return url.toString();
270
+ }
271
+ };
272
+ }
273
+ const __vite_import_meta_env__ = {};
158
274
  var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
159
275
  AuthenticationErrorReason2["Expired"] = "expired";
160
276
  AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
@@ -173,6 +289,9 @@ class AuthenticationError extends Error {
173
289
  this.data = data ?? {};
174
290
  }
175
291
  }
292
+ const STORAGE_DELEGATED_TOKEN = "delegated-token";
293
+ const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
294
+ const STORAGE_STATE_KEY = "state";
176
295
  function storage(key, value) {
177
296
  const storageKey = `auth:${key}`;
178
297
  try {
@@ -188,83 +307,33 @@ function storage(key, value) {
188
307
  }
189
308
  return null;
190
309
  }
191
- const STORAGE_DELEGATED_TOKEN = "delegated-token";
192
- const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
193
- const STORAGE_STATE_KEY = "state";
194
- function createAuthFunctions(basePath, shop) {
195
- async function start(email, lang) {
196
- const response = await fetch(
197
- `${basePath}/auth/customer/passwordless/start?lang=${lang}`,
198
- {
199
- method: "POST",
200
- headers: {
201
- Accept: "application/json",
202
- "Content-Type": "application/json",
203
- "X-Shopify-Shop-Domain": shop
204
- },
205
- body: JSON.stringify({ email })
206
- }
207
- );
208
- if (response.status !== 200) {
209
- throw new Error(`Unexpected response: ${response.statusText}`);
210
- }
211
- }
212
- async function authenticate(data) {
213
- const response = await fetch(`${basePath}/auth/customer/token`, {
214
- method: "POST",
215
- headers: {
216
- Accept: "application/json",
217
- "Content-Type": "application/json",
218
- "X-Shopify-Shop-Domain": shop
219
- },
220
- body: JSON.stringify(data)
221
- });
222
- if (response.status === 403) {
223
- const data2 = await response.json();
224
- if ("error_description" in data2) {
225
- if (data2.error_description === "Wrong email or verification code.") {
226
- throw new AuthenticationError(
227
- "wrong-code"
228
- /* IncorrectCode */
229
- );
230
- } else if (data2.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
231
- throw new AuthenticationError(
232
- "too-many-tries"
233
- /* TooManyTries */
234
- );
235
- } else if (data2.error_description === "The verification code has expired. Please try to login again.") {
236
- throw new AuthenticationError(
237
- "expired"
238
- /* Expired */
239
- );
240
- }
241
- } else if ("code" in data2) {
242
- if (data2.code === "AUTH_ERROR") {
243
- throw new AuthenticationError("no-email", {
244
- connection: data2.connection
245
- });
246
- }
247
- }
248
- }
249
- if (response.status !== 200) {
250
- throw new Error(`Unexpected response: ${response.statusText}`);
251
- }
252
- return await response.json();
253
- }
254
- return { start, authenticate };
255
- }
256
310
  function nonce(len = 10) {
257
311
  return uuid().replace(/-/g, "").slice(0, len);
258
312
  }
259
- function createAuthService(apiUrl, shopDomain) {
260
- const { start: authStart, authenticate: authAuthenticate } = createAuthFunctions(apiUrl, shopDomain);
313
+ function loginService(deps) {
314
+ const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
315
+ 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";
316
+ const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
317
+ const adapter = createApiLoginAdapter(fetcher, {
318
+ shopDomain: deps.shop.domain,
319
+ appUrl
320
+ });
321
+ return createLoginService({
322
+ adapter,
323
+ router: deps.router,
324
+ formatError: deps.formatError,
325
+ shop: {
326
+ domain: deps.shop.domain,
327
+ locale: w(() => deps.getLocale())
328
+ },
329
+ onLoginSuccess: deps.onLoginSuccess
330
+ });
331
+ }
332
+ function createLoginService(init) {
333
+ const { adapter, formatError, router, shop, onLoginSuccess } = init;
261
334
  const accessToken = d(null);
262
- const delegatedToken = d(
263
- storage(STORAGE_DELEGATED_TOKEN)
264
- );
265
- const refreshToken = d(
266
- storage(STORAGE_REFRESH_TOKEN_KEY)
267
- );
335
+ const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
336
+ const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
268
337
  const expires = d(null);
269
338
  const state = d(storage(STORAGE_STATE_KEY));
270
339
  E(() => {
@@ -276,67 +345,28 @@ function createAuthService(apiUrl, shopDomain) {
276
345
  E(() => {
277
346
  storage(STORAGE_STATE_KEY, state.value);
278
347
  });
279
- function store(data) {
348
+ function storeTokens(data) {
280
349
  const { access_token, refresh_token, expires_in } = data;
281
350
  accessToken.value = access_token;
282
351
  refreshToken.value = refresh_token;
283
352
  expires.value = new Date(Date.now() + expires_in);
284
353
  }
285
- return {
286
- state,
287
- async start(email, lang) {
288
- await authStart(email, lang);
289
- },
290
- async authenticate(data) {
291
- store(await authAuthenticate(data));
292
- },
293
- getDelegatedToken() {
294
- return delegatedToken.value;
295
- },
296
- setDelegatedToken(token) {
297
- delegatedToken.value = token;
298
- },
299
- async getToken() {
300
- if (refreshToken.value == null) {
301
- return null;
302
- }
303
- if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
304
- accessToken.value = new Promise(
305
- (resolve, reject) => authAuthenticate({ refreshToken: refreshToken.value }).then((data) => data).then((data) => {
306
- store(data);
307
- resolve(data.access_token);
354
+ async function getToken() {
355
+ if (refreshToken.value == null) return null;
356
+ if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
357
+ accessToken.value = new Promise(
358
+ (resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
359
+ if (result._tag === "Failure") {
360
+ reject(result.error);
308
361
  return;
309
- }).catch(reject)
310
- );
311
- }
312
- return Promise.resolve(accessToken.value);
313
- },
314
- logout() {
315
- accessToken.value = null;
316
- delegatedToken.value = null;
317
- refreshToken.value = null;
318
- expires.value = null;
319
- },
320
- isAuthenticated: w(
321
- () => delegatedToken.value != null || refreshToken.value != null
322
- )
323
- };
324
- }
325
- function loginService(deps) {
326
- return createLoginService({
327
- apiUrl: `${deps.shop.rootUrl.replace(/\/$/, "")}/api`,
328
- router: deps.router,
329
- formatError: deps.formatError,
330
- shop: {
331
- domain: deps.shop.domain,
332
- locale: w(() => deps.getLocale())
333
- },
334
- onLoginSuccess: deps.onLoginSuccess
335
- });
336
- }
337
- function createLoginService(init) {
338
- const { formatError, router, shop, onLoginSuccess } = init;
339
- const auth = createAuthService(init.apiUrl.replace(/\/$/, ""), shop.domain);
362
+ }
363
+ storeTokens(result.data);
364
+ resolve(result.data.access_token);
365
+ }).catch(reject)
366
+ );
367
+ }
368
+ return Promise.resolve(accessToken.value);
369
+ }
340
370
  const userEmail = d(null);
341
371
  async function onSuccess(query) {
342
372
  await (onLoginSuccess == null ? void 0 : onLoginSuccess());
@@ -349,13 +379,9 @@ function createLoginService(init) {
349
379
  queryParams[key] = value;
350
380
  }
351
381
  });
352
- router.push({
353
- path: decodeURIComponent(redirect),
354
- query: queryParams
355
- });
382
+ router.push({ path: decodeURIComponent(redirect), query: queryParams });
356
383
  } else {
357
- const redirect = query.get(PARAMS.REDIRECT_TO);
358
- router.replace(decodeURIComponent(redirect));
384
+ router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
359
385
  }
360
386
  } else {
361
387
  router.replace("/");
@@ -367,16 +393,17 @@ function createLoginService(init) {
367
393
  async start(email) {
368
394
  try {
369
395
  userEmail.value = email;
370
- await auth.start(
371
- userEmail.value,
372
- shop.locale instanceof u ? shop.locale.value : shop.locale
373
- );
396
+ const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
397
+ const result = await adapter.passwordlessStart({
398
+ email: userEmail.value,
399
+ lang
400
+ });
401
+ if (result._tag === "Failure") {
402
+ return { _tag: "Failure", error: formatError(result.error) };
403
+ }
374
404
  return { _tag: "Success", data: void 0 };
375
405
  } catch (error) {
376
- return {
377
- _tag: "Failure",
378
- error: formatError(error)
379
- };
406
+ return { _tag: "Failure", error: formatError(error) };
380
407
  }
381
408
  },
382
409
  async verify(email, code) {
@@ -387,63 +414,61 @@ function createLoginService(init) {
387
414
  };
388
415
  }
389
416
  try {
390
- const result = await auth.authenticate({
417
+ const result = await adapter.authenticate({
391
418
  email: userEmail.value,
392
419
  code
393
420
  });
421
+ if (result._tag === "Failure") {
422
+ return { _tag: "Failure", error: formatError(result.error) };
423
+ }
424
+ storeTokens(result.data);
394
425
  await onSuccess();
395
- return { _tag: "Success", data: result };
426
+ return { _tag: "Success", data: void 0 };
396
427
  } catch (error) {
397
- return {
398
- _tag: "Failure",
399
- error: formatError(error)
400
- };
428
+ return { _tag: "Failure", error: formatError(error) };
401
429
  }
402
430
  }
403
431
  }
404
432
  },
405
433
  social: {
406
434
  async start(provider) {
407
- function socialUrl(key, shop2) {
408
- auth.state.value = nonce();
409
- const url = new URL(
410
- // TODO: double check admin url
411
- `${"https://admin.juo.io"}/auth/customer/authorize`
412
- );
413
- url.searchParams.append("connection", key);
414
- url.searchParams.append("state", auth.state.value ?? "");
415
- url.searchParams.append("shop", shop2);
416
- return url.toString();
417
- }
418
- window.location.href = socialUrl(
419
- provider === "Facebook" ? "facebook" : "google-oauth2",
420
- shop.domain
421
- );
422
- return {
423
- _tag: "Success",
424
- data: void 0
425
- };
435
+ state.value = nonce();
436
+ const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
437
+ window.location.href = adapter.getAuthorizeUrl({
438
+ connection,
439
+ state: state.value ?? "",
440
+ shop: shop.domain,
441
+ appUrl: (
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ (typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io"
444
+ )
445
+ });
446
+ return { _tag: "Success", data: void 0 };
426
447
  },
427
448
  async verify(code) {
428
449
  try {
429
- const result = await auth.authenticate({ code });
450
+ const result = await adapter.authenticate({ code });
451
+ if (result._tag === "Failure") {
452
+ return { _tag: "Failure", error: formatError(result.error) };
453
+ }
454
+ storeTokens(result.data);
430
455
  await onSuccess();
431
- return { _tag: "Success", data: result };
456
+ return { _tag: "Success", data: void 0 };
432
457
  } catch (error) {
433
- return {
434
- _tag: "Failure",
435
- error: formatError(error)
436
- };
458
+ return { _tag: "Failure", error: formatError(error) };
437
459
  }
438
460
  }
439
461
  },
440
462
  async logout() {
441
- await auth.logout();
463
+ accessToken.value = null;
464
+ delegatedToken.value = null;
465
+ refreshToken.value = null;
466
+ expires.value = null;
442
467
  return { _tag: "Success", data: void 0 };
443
468
  },
444
469
  async handleQueryParams(query) {
445
470
  if (query.has(PARAMS.DELETGATED_TOKEN)) {
446
- auth.setDelegatedToken(query.get(PARAMS.DELETGATED_TOKEN));
471
+ delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
447
472
  await onSuccess(query);
448
473
  return { _tag: "Success", data: void 0 };
449
474
  }
@@ -455,16 +480,13 @@ function createLoginService(init) {
455
480
  }
456
481
  const code = query.get(PARAMS.CODE);
457
482
  if (code != null) {
458
- if (query.get(PARAMS.STATE) !== auth.state.value) {
459
- return {
460
- _tag: "Failure",
461
- error: formatError("Invalid state")
462
- };
483
+ if (query.get(PARAMS.STATE) !== state.value) {
484
+ return { _tag: "Failure", error: formatError("Invalid state") };
463
485
  } else {
464
486
  return service.social.verify(code);
465
487
  }
466
488
  }
467
- if (query.get(PARAMS.REFRESH) === "1" && await auth.getToken() != null) {
489
+ if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
468
490
  await onSuccess();
469
491
  return { _tag: "Success", data: void 0 };
470
492
  }
@@ -481,14 +503,15 @@ function createLoginService(init) {
481
503
  }
482
504
  return { _tag: "Success", data: void 0 };
483
505
  },
484
- isAuthenticated: auth.isAuthenticated,
485
- getToken: () => auth.getToken(),
506
+ isAuthenticated: w(
507
+ () => delegatedToken.value != null || refreshToken.value != null
508
+ ),
509
+ getToken,
486
510
  async getAuthHeaders() {
487
- const delegated = auth.getDelegatedToken();
488
- if (delegated) {
489
- return { "X-Delegated-Token": delegated };
511
+ if (delegatedToken.value) {
512
+ return { "X-Delegated-Token": delegatedToken.value };
490
513
  }
491
- const token = await auth.getToken();
514
+ const token = await getToken();
492
515
  if (token) {
493
516
  return { Authorization: `Bearer ${token}` };
494
517
  }
@@ -678,6 +701,106 @@ function createMockProductAdapter() {
678
701
  }
679
702
  };
680
703
  }
704
+ function mapVariant(variant, currencyCode) {
705
+ var _a;
706
+ return {
707
+ id: variant.id,
708
+ title: variant.title,
709
+ image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
710
+ price: { amount: variant.price.toFixed(2), currencyCode },
711
+ ...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
712
+ purchaseOptions: variant.purchaseOptions.map((opt) => ({
713
+ name: opt.name,
714
+ price: { amount: opt.price.toFixed(2), currencyCode },
715
+ interval: opt.interval
716
+ }))
717
+ } : {}
718
+ };
719
+ }
720
+ function mapProduct(product, currencyCode) {
721
+ return {
722
+ id: product.id,
723
+ title: product.title,
724
+ featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
725
+ hasOnlyDefaultVariant: product.variants.length <= 1,
726
+ variants: {
727
+ items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
728
+ pageInfo: {
729
+ hasNextPage: false,
730
+ hasPrevPage: false,
731
+ startCursor: null,
732
+ endCursor: null
733
+ }
734
+ },
735
+ collections: product.collections
736
+ };
737
+ }
738
+ function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
739
+ const { currencyCode } = opts;
740
+ async function fetchProducts(params) {
741
+ const query = params.toString() ? `?${params.toString()}` : "";
742
+ const res = await fetcher(`/customer/v1/products${query}`);
743
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
744
+ return res.json();
745
+ }
746
+ return {
747
+ async getById(id) {
748
+ try {
749
+ const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
750
+ const body = await fetchProducts(params);
751
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
752
+ } catch {
753
+ return null;
754
+ }
755
+ },
756
+ async getByVariantId(variantId) {
757
+ try {
758
+ const params = new URLSearchParams({
759
+ query: `variants.id:"${variantId}"`,
760
+ limit: "1"
761
+ });
762
+ const body = await fetchProducts(params);
763
+ return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
764
+ } catch {
765
+ return null;
766
+ }
767
+ },
768
+ async search(opts2) {
769
+ try {
770
+ const params = new URLSearchParams();
771
+ if (opts2 == null ? void 0 : opts2.query) {
772
+ params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
773
+ }
774
+ if (opts2 == null ? void 0 : opts2.pagination) {
775
+ if ("after" in opts2.pagination)
776
+ params.set("after", opts2.pagination.after);
777
+ else if ("before" in opts2.pagination)
778
+ params.set("before", opts2.pagination.before);
779
+ }
780
+ const body = await fetchProducts(params);
781
+ return {
782
+ items: body.data.map((p2) => mapProduct(p2, currencyCode)),
783
+ pageInfo: {
784
+ hasNextPage: body.hasNextPage,
785
+ hasPrevPage: body.hasPrevPage,
786
+ startCursor: body.startCursor,
787
+ endCursor: body.endCursor
788
+ }
789
+ };
790
+ } catch {
791
+ return {
792
+ items: [],
793
+ pageInfo: {
794
+ hasNextPage: false,
795
+ hasPrevPage: false,
796
+ startCursor: null,
797
+ endCursor: null
798
+ }
799
+ };
800
+ }
801
+ }
802
+ };
803
+ }
681
804
  const ProductServiceContext = createContext("services/product");
682
805
  function createProductService(adapter) {
683
806
  return {
@@ -759,14 +882,6 @@ function createMockSubscriptionAdapter() {
759
882
  imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
760
883
  price: { amount: "24.00", currencyCode: "USD" },
761
884
  description: "Lightweight daily sun protection"
762
- },
763
- {
764
- id: "upsell-3",
765
- variantId: "upsell-v-3",
766
- title: "Collagen Eye Patches",
767
- imageUrl: "https://images.unsplash.com/photo-1512290923902-8a9f81dc236c?w=200",
768
- price: { amount: "18.00", currencyCode: "USD" },
769
- description: "Depuffing under-eye treatment"
770
885
  }
771
886
  ];
772
887
  return {
@@ -777,7 +892,7 @@ function createMockSubscriptionAdapter() {
777
892
  },
778
893
  async search(options = {}) {
779
894
  await new Promise((resolve) => setTimeout(resolve, 300));
780
- const pageSize = 1;
895
+ const pageSize = 2;
781
896
  const allItems = mockSubscriptions;
782
897
  let startIndex = 0;
783
898
  let endIndex = pageSize;
@@ -793,20 +908,22 @@ function createMockSubscriptionAdapter() {
793
908
  }
794
909
  }
795
910
  const pageItems = allItems.slice(startIndex, endIndex);
796
- const paginatedList = {
797
- items: pageItems.map((sub) => ({
798
- ...sub,
799
- items: sub.items.map((item) => ({ ...item })),
800
- discounts: sub.discounts.map((discount) => ({ ...discount }))
801
- })),
802
- pageInfo: {
803
- hasNextPage: endIndex < allItems.length,
804
- hasPrevPage: startIndex > 0,
805
- startCursor: pageItems.length > 0 ? startIndex.toString() : null,
806
- endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
911
+ return {
912
+ _tag: "Success",
913
+ data: {
914
+ items: pageItems.map((sub) => ({
915
+ ...sub,
916
+ items: sub.items.map((item) => ({ ...item })),
917
+ discounts: sub.discounts.map((discount) => ({ ...discount }))
918
+ })),
919
+ pageInfo: {
920
+ hasNextPage: endIndex < allItems.length,
921
+ hasPrevPage: startIndex > 0,
922
+ startCursor: pageItems.length > 0 ? startIndex.toString() : null,
923
+ endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
924
+ }
807
925
  }
808
926
  };
809
- return { _tag: "Success", data: paginatedList };
810
927
  },
811
928
  async addItem() {
812
929
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -830,26 +947,11 @@ function createMockSubscriptionAdapter() {
830
947
  (sub) => sub.id === params.subscriptionId
831
948
  );
832
949
  if (!subscription) {
833
- return {
834
- _tag: "Failure",
835
- error: "Subscription not found"
836
- };
950
+ return { _tag: "Failure", error: "Subscription not found" };
837
951
  }
838
952
  subscription.nextDeliveryDate = new Date(params.newDate);
839
953
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
840
- subscription.items.forEach((item) => {
841
- item.nextDeliveryDate = new Date(params.newDate);
842
- });
843
- return {
844
- _tag: "Success",
845
- data: {
846
- ...subscription,
847
- items: subscription.items.map((item) => ({ ...item })),
848
- discounts: subscription.discounts.map((discount) => ({
849
- ...discount
850
- }))
851
- }
852
- };
954
+ return { _tag: "Success", data: { ...subscription } };
853
955
  },
854
956
  async resume(params) {
855
957
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -857,23 +959,11 @@ function createMockSubscriptionAdapter() {
857
959
  (sub) => sub.id === params.subscriptionId
858
960
  );
859
961
  if (!subscription) {
860
- return {
861
- _tag: "Failure",
862
- error: "Subscription not found"
863
- };
962
+ return { _tag: "Failure", error: "Subscription not found" };
864
963
  }
865
964
  subscription.status = "active";
866
965
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
867
- return {
868
- _tag: "Success",
869
- data: {
870
- ...subscription,
871
- items: subscription.items.map((item) => ({ ...item })),
872
- discounts: subscription.discounts.map((discount) => ({
873
- ...discount
874
- }))
875
- }
876
- };
966
+ return { _tag: "Success", data: { ...subscription } };
877
967
  },
878
968
  async pause(params) {
879
969
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -881,23 +971,11 @@ function createMockSubscriptionAdapter() {
881
971
  (sub) => sub.id === params.subscriptionId
882
972
  );
883
973
  if (!subscription) {
884
- return {
885
- _tag: "Failure",
886
- error: "Subscription not found"
887
- };
974
+ return { _tag: "Failure", error: "Subscription not found" };
888
975
  }
889
976
  subscription.status = "paused";
890
977
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
891
- return {
892
- _tag: "Success",
893
- data: {
894
- ...subscription,
895
- items: subscription.items.map((item) => ({ ...item })),
896
- discounts: subscription.discounts.map((discount) => ({
897
- ...discount
898
- }))
899
- }
900
- };
978
+ return { _tag: "Success", data: { ...subscription } };
901
979
  },
902
980
  async reactivate(params) {
903
981
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -905,23 +983,11 @@ function createMockSubscriptionAdapter() {
905
983
  (sub) => sub.id === params.subscriptionId
906
984
  );
907
985
  if (!subscription) {
908
- return {
909
- _tag: "Failure",
910
- error: "Subscription not found"
911
- };
986
+ return { _tag: "Failure", error: "Subscription not found" };
912
987
  }
913
988
  subscription.status = "active";
914
989
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
915
- return {
916
- _tag: "Success",
917
- data: {
918
- ...subscription,
919
- items: subscription.items.map((item) => ({ ...item })),
920
- discounts: subscription.discounts.map((discount) => ({
921
- ...discount
922
- }))
923
- }
924
- };
990
+ return { _tag: "Success", data: { ...subscription } };
925
991
  },
926
992
  async cancel(params) {
927
993
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -929,23 +995,11 @@ function createMockSubscriptionAdapter() {
929
995
  (sub) => sub.id === params.subscriptionId
930
996
  );
931
997
  if (!subscription) {
932
- return {
933
- _tag: "Failure",
934
- error: "Subscription not found"
935
- };
998
+ return { _tag: "Failure", error: "Subscription not found" };
936
999
  }
937
1000
  subscription.status = "canceled";
938
1001
  subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
939
- return {
940
- _tag: "Success",
941
- data: {
942
- ...subscription,
943
- items: subscription.items.map((item) => ({ ...item })),
944
- discounts: subscription.discounts.map((discount) => ({
945
- ...discount
946
- }))
947
- }
948
- };
1002
+ return { _tag: "Success", data: { ...subscription } };
949
1003
  },
950
1004
  async getAvailablePlans() {
951
1005
  await new Promise((resolve) => setTimeout(resolve, 300));
@@ -978,26 +1032,6 @@ function createMockSubscriptionAdapter() {
978
1032
  await new Promise((resolve) => setTimeout(resolve, 300));
979
1033
  return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
980
1034
  },
981
- async getDeliveryFrequencies() {
982
- await new Promise((resolve) => setTimeout(resolve, 200));
983
- return {
984
- _tag: "Success",
985
- data: [
986
- {
987
- id: "plan_1",
988
- name: "Monthly",
989
- billingPolicy: { intervalCount: 1, interval: "MONTH" },
990
- deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
991
- },
992
- {
993
- id: "plan_2",
994
- name: "Every 2 months",
995
- billingPolicy: { intervalCount: 2, interval: "MONTH" },
996
- deliveryPolicy: { intervalCount: 2, interval: "MONTH" }
997
- }
998
- ]
999
- };
1000
- },
1001
1035
  async getUpsellProducts() {
1002
1036
  await new Promise((resolve) => setTimeout(resolve, 300));
1003
1037
  return { _tag: "Success", data: [...mockUpsellProducts] };
@@ -1005,74 +1039,96 @@ function createMockSubscriptionAdapter() {
1005
1039
  async addUpsellProduct() {
1006
1040
  await new Promise((resolve) => setTimeout(resolve, 400));
1007
1041
  return { _tag: "Success", data: void 0 };
1008
- },
1009
- async changeFrequency(params) {
1010
- await new Promise((resolve) => setTimeout(resolve, 300));
1011
- const subscription = mockSubscriptions.find(
1012
- (sub) => sub.id === params.subscriptionId
1013
- );
1014
- if (!subscription) {
1015
- return { _tag: "Failure", error: "Subscription not found" };
1016
- }
1017
- subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1018
- return {
1019
- _tag: "Success",
1020
- data: { ...subscription }
1021
- };
1022
1042
  }
1023
1043
  };
1024
1044
  }
1045
+ function mapSubscription(raw) {
1046
+ var _a, _b;
1047
+ const status = raw.status === "merged" ? "canceled" : raw.status;
1048
+ const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
1049
+ const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
1050
+ return {
1051
+ id: raw.id,
1052
+ customerId: "",
1053
+ items: (raw.items ?? []).map(
1054
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1055
+ (item) => ({
1056
+ id: item.id,
1057
+ productId: item.productId ?? item.product ?? "",
1058
+ variantId: item.variantId ?? item.variant ?? "",
1059
+ quantity: item.quantity,
1060
+ price: item.price ?? {
1061
+ amount: String(item.totalPrice ?? 0),
1062
+ currencyCode: raw.currencyCode ?? ""
1063
+ },
1064
+ nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null
1065
+ })
1066
+ ),
1067
+ discounts: (raw.discounts ?? []).map(
1068
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1069
+ (d2) => {
1070
+ var _a2, _b2, _c;
1071
+ return {
1072
+ id: d2.id,
1073
+ type: ((_a2 = d2.value) == null ? void 0 : _a2.type) === "fixed-amount" || d2.type === "fixed_amount" ? "fixed_amount" : "percentage",
1074
+ value: ((_b2 = d2.value) == null ? void 0 : _b2.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? d2.value ?? 0,
1075
+ code: d2.title ?? d2.code ?? "",
1076
+ description: d2.description ?? ""
1077
+ };
1078
+ }
1079
+ ),
1080
+ status,
1081
+ nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
1082
+ frequency,
1083
+ createdAt: raw.createdAt,
1084
+ updatedAt: raw.updatedAt
1085
+ };
1086
+ }
1025
1087
  function createApiSubscriptionAdapter(fetcher) {
1026
1088
  return {
1027
1089
  async search(options) {
1090
+ var _a;
1028
1091
  try {
1029
1092
  const params = new URLSearchParams();
1030
- if (options == null ? void 0 : options.filters) {
1031
- params.set("filters", JSON.stringify(options.filters));
1093
+ if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
1094
+ 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 ");
1095
+ if (q2) params.set("query", q2);
1032
1096
  }
1033
1097
  if (options == null ? void 0 : options.pagination) {
1034
- if ("after" in options.pagination) {
1098
+ if ("after" in options.pagination)
1035
1099
  params.set("after", options.pagination.after);
1036
- } else if ("before" in options.pagination) {
1100
+ else if ("before" in options.pagination)
1037
1101
  params.set("before", options.pagination.before);
1038
- }
1039
1102
  }
1040
1103
  const query = params.toString() ? `?${params.toString()}` : "";
1041
1104
  const res = await fetcher(`/customer/v1/subscriptions${query}`);
1042
1105
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1043
1106
  const body = await res.json();
1044
- body.data.forEach((sub) => {
1045
- if (sub.nextDeliveryDate) {
1046
- sub.nextDeliveryDate = new Date(sub.nextDeliveryDate);
1047
- }
1048
- sub.items.forEach((item) => {
1049
- if (item.nextDeliveryDate) {
1050
- item.nextDeliveryDate = new Date(item.nextDeliveryDate);
1107
+ return {
1108
+ _tag: "Success",
1109
+ data: {
1110
+ items: body.data.map(mapSubscription),
1111
+ pageInfo: {
1112
+ hasNextPage: body.hasNextPage,
1113
+ hasPrevPage: body.hasPrevPage,
1114
+ startCursor: body.startCursor,
1115
+ endCursor: body.endCursor
1051
1116
  }
1052
- });
1053
- });
1054
- const data = {
1055
- items: body.data,
1056
- pageInfo: {
1057
- hasNextPage: body.hasNextPage,
1058
- hasPrevPage: body.hasPrevPage,
1059
- startCursor: body.startCursor,
1060
- endCursor: body.endCursor
1061
1117
  }
1062
1118
  };
1063
- return { _tag: "Success", data };
1064
1119
  } catch (e2) {
1065
1120
  return { _tag: "Failure", error: e2 };
1066
1121
  }
1067
1122
  },
1068
1123
  async getById(id) {
1069
1124
  try {
1070
- const res = await fetcher(`/customer/v1/subscriptions/${id}`);
1125
+ const res = await fetcher(
1126
+ `/customer/v1/subscriptions?query=id:"${id}"&limit=1`
1127
+ );
1071
1128
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1072
- const data = await res.json();
1073
- if (data.nextDeliveryDate)
1074
- data.nextDeliveryDate = new Date(data.nextDeliveryDate);
1075
- return { _tag: "Success", data };
1129
+ const body = await res.json();
1130
+ const raw = body.data[0];
1131
+ return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
1076
1132
  } catch (e2) {
1077
1133
  return { _tag: "Failure", error: e2 };
1078
1134
  }
@@ -1133,21 +1189,20 @@ function createApiSubscriptionAdapter(fetcher) {
1133
1189
  return { _tag: "Failure", error: e2 };
1134
1190
  }
1135
1191
  },
1192
+ // Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
1136
1193
  async reschedule({ subscriptionId, newDate }) {
1137
1194
  try {
1138
1195
  const res = await fetcher(
1139
- `/customer/v1/subscriptions/${subscriptionId}/reschedule`,
1196
+ `/customer/v1/subscriptions/${subscriptionId}`,
1140
1197
  {
1141
- method: "POST",
1198
+ method: "PATCH",
1142
1199
  headers: { "Content-Type": "application/json" },
1143
- body: JSON.stringify({ newDate })
1200
+ body: JSON.stringify({ nextBillingDate: newDate })
1144
1201
  }
1145
1202
  );
1146
1203
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1147
1204
  const data = await res.json();
1148
- if (data.nextDeliveryDate)
1149
- data.nextDeliveryDate = new Date(data.nextDeliveryDate);
1150
- return { _tag: "Success", data };
1205
+ return { _tag: "Success", data: mapSubscription(data) };
1151
1206
  } catch (e2) {
1152
1207
  return { _tag: "Failure", error: e2 };
1153
1208
  }
@@ -1160,7 +1215,7 @@ function createApiSubscriptionAdapter(fetcher) {
1160
1215
  );
1161
1216
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1162
1217
  const data = await res.json();
1163
- return { _tag: "Success", data };
1218
+ return { _tag: "Success", data: mapSubscription(data) };
1164
1219
  } catch (e2) {
1165
1220
  return { _tag: "Failure", error: e2 };
1166
1221
  }
@@ -1173,7 +1228,7 @@ function createApiSubscriptionAdapter(fetcher) {
1173
1228
  );
1174
1229
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1175
1230
  const data = await res.json();
1176
- return { _tag: "Success", data };
1231
+ return { _tag: "Success", data: mapSubscription(data) };
1177
1232
  } catch (e2) {
1178
1233
  return { _tag: "Failure", error: e2 };
1179
1234
  }
@@ -1186,7 +1241,7 @@ function createApiSubscriptionAdapter(fetcher) {
1186
1241
  );
1187
1242
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1188
1243
  const data = await res.json();
1189
- return { _tag: "Success", data };
1244
+ return { _tag: "Success", data: mapSubscription(data) };
1190
1245
  } catch (e2) {
1191
1246
  return { _tag: "Failure", error: e2 };
1192
1247
  }
@@ -1198,71 +1253,81 @@ function createApiSubscriptionAdapter(fetcher) {
1198
1253
  {
1199
1254
  method: "POST",
1200
1255
  headers: { "Content-Type": "application/json" },
1201
- body: JSON.stringify({ reason })
1256
+ body: JSON.stringify({ cancellationReason: reason })
1202
1257
  }
1203
1258
  );
1204
1259
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1205
1260
  const data = await res.json();
1206
- return { _tag: "Success", data };
1261
+ return { _tag: "Success", data: mapSubscription(data) };
1207
1262
  } catch (e2) {
1208
1263
  return { _tag: "Failure", error: e2 };
1209
1264
  }
1210
1265
  },
1266
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
1211
1267
  async getAvailablePlans({ subscriptionId }) {
1212
1268
  try {
1213
1269
  const res = await fetcher(
1214
- `/customer/v1/subscriptions/${subscriptionId}/plans`
1270
+ `/customer/subscriptions/${subscriptionId}/plans`
1215
1271
  );
1216
1272
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1217
1273
  const data = await res.json();
1218
- return { _tag: "Success", data: data.items };
1274
+ return { _tag: "Success", data };
1219
1275
  } catch (e2) {
1220
1276
  return { _tag: "Failure", error: e2 };
1221
1277
  }
1222
1278
  },
1279
+ // Legacy endpoint — no /customer/v1 equivalent exists yet.
1223
1280
  async getDefaultSubscriptionId() {
1224
- var _a;
1225
- try {
1226
- const res = await fetcher(`/customer/v1/subscriptions?limit=1`);
1227
- if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1228
- const body = await res.json();
1229
- return { _tag: "Success", data: ((_a = body.data[0]) == null ? void 0 : _a.id) ?? null };
1230
- } catch (e2) {
1231
- return { _tag: "Failure", error: e2 };
1232
- }
1233
- },
1234
- async getDeliveryFrequencies({ subscriptionId }) {
1235
1281
  try {
1236
1282
  const res = await fetcher(
1237
- `/customer/v1/subscriptions/${subscriptionId}/frequencies`
1283
+ `/customer/subscriptions/one-by-next-billing-date`
1238
1284
  );
1239
1285
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1240
1286
  const data = await res.json();
1241
- return { _tag: "Success", data: data.items };
1287
+ return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
1242
1288
  } catch (e2) {
1243
1289
  return { _tag: "Failure", error: e2 };
1244
1290
  }
1245
1291
  },
1292
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
1246
1293
  async getUpsellProducts({ subscriptionId }) {
1247
1294
  try {
1248
1295
  const res = await fetcher(
1249
- `/customer/v1/subscriptions/${subscriptionId}/upsells`
1296
+ `/customer/subscriptions/${subscriptionId}/upsell-products`
1250
1297
  );
1251
1298
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1252
1299
  const data = await res.json();
1253
- return { _tag: "Success", data: data.items };
1300
+ const products = data.map((p2) => {
1301
+ var _a, _b, _c, _d, _e;
1302
+ return {
1303
+ id: p2.id,
1304
+ variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
1305
+ title: p2.title ?? "",
1306
+ imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
1307
+ price: {
1308
+ amount: String(((_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.price) ?? 0),
1309
+ currencyCode: p2.currencyCode ?? ""
1310
+ }
1311
+ };
1312
+ });
1313
+ return { _tag: "Success", data: products };
1254
1314
  } catch (e2) {
1255
1315
  return { _tag: "Failure", error: e2 };
1256
1316
  }
1257
1317
  },
1318
+ // Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
1258
1319
  async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
1259
1320
  try {
1260
1321
  const res = await fetcher(
1261
- `/customer/v1/subscriptions/${subscriptionId}/upsells`,
1322
+ `/customer/subscriptions/${subscriptionId}/upsell`,
1262
1323
  {
1263
1324
  method: "POST",
1264
1325
  headers: { "Content-Type": "application/json" },
1265
- body: JSON.stringify({ variantId, quantity, oneTime })
1326
+ body: JSON.stringify({
1327
+ productVariantId: variantId,
1328
+ quantity,
1329
+ ...oneTime ? { renewalLimit: 1 } : {}
1330
+ })
1266
1331
  }
1267
1332
  );
1268
1333
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
@@ -1270,23 +1335,6 @@ function createApiSubscriptionAdapter(fetcher) {
1270
1335
  } catch (e2) {
1271
1336
  return { _tag: "Failure", error: e2 };
1272
1337
  }
1273
- },
1274
- async changeFrequency({ subscriptionId, sellingPlanId }) {
1275
- try {
1276
- const res = await fetcher(
1277
- `/customer/v1/subscriptions/${subscriptionId}/frequency`,
1278
- {
1279
- method: "POST",
1280
- headers: { "Content-Type": "application/json" },
1281
- body: JSON.stringify({ sellingPlanId })
1282
- }
1283
- );
1284
- if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1285
- const data = await res.json();
1286
- return { _tag: "Success", data };
1287
- } catch (e2) {
1288
- return { _tag: "Failure", error: e2 };
1289
- }
1290
1338
  }
1291
1339
  };
1292
1340
  }
@@ -1342,27 +1390,68 @@ function createSubscriptionService(adapter) {
1342
1390
  cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
1343
1391
  getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
1344
1392
  getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
1345
- getDeliveryFrequencies: (params) => adapter.getDeliveryFrequencies ? adapter.getDeliveryFrequencies(params) : Promise.resolve({ _tag: "Success", data: [] }),
1346
1393
  getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
1347
- addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 }),
1348
- changeFrequency: (params) => adapter.changeFrequency ? adapter.changeFrequency(params) : Promise.resolve({ _tag: "Failure", error: "Not implemented" })
1394
+ addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
1349
1395
  };
1350
1396
  }
1351
1397
  const CustomerServiceContext = createContext("services/customer");
1352
- function createCustomerService() {
1398
+ function createCustomerService(adapter) {
1353
1399
  const current = d(null);
1354
- const service = { current };
1355
- return service;
1400
+ return {
1401
+ current,
1402
+ async getCurrent() {
1403
+ const result = await adapter.getCurrent();
1404
+ if (result._tag === "Success") current.value = result.data;
1405
+ return result;
1406
+ }
1407
+ };
1408
+ }
1409
+ function createMockCustomerAdapter() {
1410
+ const customer = { id: "customer-1", name: "Customer 1" };
1411
+ return {
1412
+ async getCurrent() {
1413
+ return { _tag: "Success", data: customer };
1414
+ }
1415
+ };
1416
+ }
1417
+ function createApiCustomerAdapter(fetcher) {
1418
+ return {
1419
+ async getCurrent() {
1420
+ try {
1421
+ const res = await fetcher("/customer/info");
1422
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1423
+ const data = await res.json();
1424
+ const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
1425
+ return {
1426
+ _tag: "Success",
1427
+ data: { id: data.id, name, email: data.email }
1428
+ };
1429
+ } catch (e2) {
1430
+ return { _tag: "Failure", error: e2 };
1431
+ }
1432
+ }
1433
+ };
1434
+ }
1435
+ function mapOrder(order) {
1436
+ const items = order.items.map((item) => ({
1437
+ id: item.id,
1438
+ productId: item.productId ?? "",
1439
+ variantId: item.variantId ?? "",
1440
+ quantity: item.quantity
1441
+ }));
1442
+ return {
1443
+ id: order.id,
1444
+ customerId: "",
1445
+ items,
1446
+ deliveryDate: order.placedAt ?? null,
1447
+ status: "delivered"
1448
+ };
1356
1449
  }
1357
1450
  function createMockOrderAdapter() {
1358
- let orders = [
1359
- {
1360
- id: "order-1",
1361
- customerId: "customer-1",
1362
- items: [],
1363
- deliveryDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null,
1364
- status: "pending"
1365
- },
1451
+ const now = Date.now();
1452
+ const past1 = new Date(now - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null;
1453
+ const past2 = new Date(now - 60 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null;
1454
+ const orders = [
1366
1455
  {
1367
1456
  id: "order-2",
1368
1457
  customerId: "customer-1",
@@ -1370,121 +1459,47 @@ function createMockOrderAdapter() {
1370
1459
  { id: "item-1", productId: "p1", variantId: "v1", quantity: 1 },
1371
1460
  { id: "item-2", productId: "p2", variantId: "v2", quantity: 2 }
1372
1461
  ],
1373
- deliveryDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null,
1462
+ deliveryDate: past1,
1374
1463
  status: "delivered"
1375
1464
  },
1376
1465
  {
1377
1466
  id: "order-3",
1378
1467
  customerId: "customer-1",
1379
- items: [
1380
- { id: "item-3", productId: "p1", variantId: "v1", quantity: 1 }
1381
- ],
1382
- deliveryDate: new Date(Date.now() - 60 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null,
1468
+ items: [{ id: "item-3", productId: "p1", variantId: "v1", quantity: 1 }],
1469
+ deliveryDate: past2,
1383
1470
  status: "delivered"
1384
- },
1385
- {
1386
- id: "order-4",
1387
- customerId: "customer-1",
1388
- items: [],
1389
- deliveryDate: new Date(Date.now() - 90 * 24 * 60 * 60 * 1e3).toISOString().split("T")[0] ?? null,
1390
- status: "skipped"
1391
1471
  }
1392
1472
  ];
1393
1473
  return {
1394
- async getUpcoming() {
1395
- return {
1396
- _tag: "Success",
1397
- data: orders.filter((o2) => o2.status === "pending")
1398
- };
1399
- },
1400
1474
  async getHistory() {
1401
- return {
1402
- _tag: "Success",
1403
- data: orders.filter((o2) => o2.status === "delivered" || o2.status === "skipped").sort((a3, b3) => (b3.deliveryDate ?? "").localeCompare(a3.deliveryDate ?? ""))
1404
- };
1475
+ return { _tag: "Success", data: orders };
1405
1476
  },
1406
1477
  async getById(id) {
1407
- return {
1408
- _tag: "Success",
1409
- data: orders.find((o2) => o2.id === id) ?? null
1410
- };
1411
- },
1412
- async updateItems(orderId, items) {
1413
- const order = orders.find((o2) => o2.id === orderId);
1414
- if (!order) return { _tag: "Failure", error: "Order not found" };
1415
- const updated = {
1416
- ...order,
1417
- items: items.map((item, i2) => ({
1418
- id: `item-${Date.now()}-${i2}`,
1419
- ...item
1420
- }))
1421
- };
1422
- orders = orders.map((o2) => o2.id === orderId ? updated : o2);
1423
- return { _tag: "Success", data: updated };
1424
- },
1425
- async skip(orderId) {
1426
- const order = orders.find((o2) => o2.id === orderId);
1427
- if (!order) return { _tag: "Failure", error: "Order not found" };
1428
- const updated = { ...order, status: "skipped" };
1429
- orders = orders.map((o2) => o2.id === orderId ? updated : o2);
1430
- return { _tag: "Success", data: updated };
1478
+ return { _tag: "Success", data: orders.find((o2) => o2.id === id) ?? null };
1431
1479
  }
1432
1480
  };
1433
1481
  }
1434
1482
  function createApiOrderAdapter(fetcher) {
1435
1483
  return {
1436
- async getUpcoming() {
1437
- try {
1438
- const res = await fetcher("/customer/v1/orders?status=pending");
1439
- if (!res.ok) return { _tag: "Failure", error: await res.text() };
1440
- const body = await res.json();
1441
- return { _tag: "Success", data: body.orders };
1442
- } catch (e2) {
1443
- return { _tag: "Failure", error: e2 };
1444
- }
1445
- },
1446
1484
  async getHistory() {
1447
1485
  try {
1448
- const res = await fetcher("/customer/v1/orders?status=delivered,skipped");
1449
- if (!res.ok) return { _tag: "Failure", error: await res.text() };
1486
+ const res = await fetcher("/customer/v1/orders");
1487
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1450
1488
  const body = await res.json();
1451
- return { _tag: "Success", data: body.orders };
1489
+ return { _tag: "Success", data: body.orders.map(mapOrder) };
1452
1490
  } catch (e2) {
1453
1491
  return { _tag: "Failure", error: e2 };
1454
1492
  }
1455
1493
  },
1456
1494
  async getById(id) {
1457
1495
  try {
1458
- const res = await fetcher(`/customer/v1/orders/${id}`);
1459
- if (!res.ok) return { _tag: "Failure", error: await res.text() };
1460
- const data = await res.json();
1461
- return { _tag: "Success", data };
1462
- } catch (e2) {
1463
- return { _tag: "Failure", error: e2 };
1464
- }
1465
- },
1466
- async updateItems(orderId, items) {
1467
- try {
1468
- const res = await fetcher(`/customer/v1/orders/${orderId}/items`, {
1469
- method: "PUT",
1470
- headers: { "Content-Type": "application/json" },
1471
- body: JSON.stringify({ items })
1472
- });
1473
- if (!res.ok) return { _tag: "Failure", error: await res.text() };
1474
- const data = await res.json();
1475
- return { _tag: "Success", data };
1476
- } catch (e2) {
1477
- return { _tag: "Failure", error: e2 };
1478
- }
1479
- },
1480
- async skip(orderId) {
1481
- try {
1482
- const res = await fetcher(`/customer/v1/orders/${orderId}/skip`, {
1483
- method: "POST"
1484
- });
1485
- if (!res.ok) return { _tag: "Failure", error: await res.text() };
1486
- const data = await res.json();
1487
- return { _tag: "Success", data };
1496
+ const res = await fetcher(
1497
+ `/customer/v1/orders?query=id:"${id}"&limit=1`
1498
+ );
1499
+ if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1500
+ const body = await res.json();
1501
+ const raw = body.orders[0];
1502
+ return { _tag: "Success", data: raw ? mapOrder(raw) : null };
1488
1503
  } catch (e2) {
1489
1504
  return { _tag: "Failure", error: e2 };
1490
1505
  }
@@ -1494,11 +1509,8 @@ function createApiOrderAdapter(fetcher) {
1494
1509
  const OrderServiceContext = createContext("services/order");
1495
1510
  function createOrderService(adapter) {
1496
1511
  return {
1497
- getUpcoming: () => adapter.getUpcoming(),
1498
1512
  getHistory: () => adapter.getHistory(),
1499
- getById: (id) => adapter.getById(id),
1500
- updateItems: (orderId, items) => adapter.updateItems(orderId, items),
1501
- skip: (orderId) => adapter.skip(orderId)
1513
+ getById: (id) => adapter.getById(id)
1502
1514
  };
1503
1515
  }
1504
1516
  function futureDateStr(daysFromNow) {
@@ -1613,9 +1625,7 @@ function createApiSchedulesAdapter(fetcher) {
1613
1625
  return {
1614
1626
  async getUpcomingOrders(count) {
1615
1627
  try {
1616
- const res = await fetcher(
1617
- `/customer/v1/schedules?count=${count}`
1618
- );
1628
+ const res = await fetcher(`/customer/v1/schedules/?count=${count}`);
1619
1629
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1620
1630
  const data = await res.json();
1621
1631
  return { _tag: "Success", data: data.orders };
@@ -1628,17 +1638,30 @@ function createApiSchedulesAdapter(fetcher) {
1628
1638
  const res = await fetcher(`/customer/v1/schedules/adjustments`);
1629
1639
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1630
1640
  const data = await res.json();
1631
- return { _tag: "Success", data: data.items };
1641
+ const adjustments = data.data.map((a3) => ({
1642
+ id: a3.id,
1643
+ subscriptionId: a3.subscriptionId ?? "",
1644
+ type: a3.type === "CHANGE_DATE" ? "date_change" : "skip",
1645
+ newDate: a3.newDate
1646
+ }));
1647
+ return { _tag: "Success", data: adjustments };
1632
1648
  } catch (e2) {
1633
1649
  return { _tag: "Failure", error: e2 };
1634
1650
  }
1635
1651
  },
1636
- async skipOrder(params) {
1652
+ async skipOrder({ cycle, date }) {
1653
+ if (cycle == null && date == null) {
1654
+ return { _tag: "Failure", error: "Either cycle or date is required for skipOrder" };
1655
+ }
1637
1656
  try {
1657
+ const matcher = cycle != null ? { type: "CYCLE", input: { cycle } } : { type: "DATE", input: { date: new Date(date).toISOString() } };
1638
1658
  const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1639
1659
  method: "POST",
1640
1660
  headers: { "Content-Type": "application/json" },
1641
- body: JSON.stringify({ action: "skip", ...params })
1661
+ body: JSON.stringify({
1662
+ matcher,
1663
+ action: { type: "SKIP_ORDER", input: { reason: "" } }
1664
+ })
1642
1665
  });
1643
1666
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1644
1667
  return { _tag: "Success", data: void 0 };
@@ -1646,12 +1669,25 @@ function createApiSchedulesAdapter(fetcher) {
1646
1669
  return { _tag: "Failure", error: e2 };
1647
1670
  }
1648
1671
  },
1649
- async changeDateAdjustment(params) {
1672
+ async changeDateAdjustment({ newDate, currentDate }) {
1673
+ if (currentDate == null) {
1674
+ return {
1675
+ _tag: "Failure",
1676
+ error: "currentDate is required for changeDateAdjustment"
1677
+ };
1678
+ }
1650
1679
  try {
1680
+ const matcher = { type: "DATE", input: { date: new Date(currentDate).toISOString() } };
1651
1681
  const res = await fetcher(`/customer/v1/schedules/adjustments`, {
1652
1682
  method: "POST",
1653
1683
  headers: { "Content-Type": "application/json" },
1654
- body: JSON.stringify({ action: "date_change", ...params })
1684
+ body: JSON.stringify({
1685
+ matcher,
1686
+ action: {
1687
+ type: "CHANGE_DATE",
1688
+ input: { newDate: new Date(newDate).toISOString() }
1689
+ }
1690
+ })
1655
1691
  });
1656
1692
  if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
1657
1693
  return { _tag: "Success", data: void 0 };
@@ -1993,7 +2029,10 @@ export {
1993
2029
  T2 as TranslationContext,
1994
2030
  W as WorkflowServiceContext,
1995
2031
  w as computed,
2032
+ createApiCustomerAdapter,
2033
+ createApiLoginAdapter,
1996
2034
  createApiOrderAdapter,
2035
+ createApiProductAdapter,
1997
2036
  createApiSchedulesAdapter,
1998
2037
  createApiSubscriptionAdapter,
1999
2038
  a as createBlockInstance,
@@ -2002,6 +2041,9 @@ export {
2002
2041
  f as createCeRenderer,
2003
2042
  createContext,
2004
2043
  createCustomerService,
2044
+ createLoginService,
2045
+ createMockCustomerAdapter,
2046
+ createMockLoginAdapter,
2005
2047
  createMockOrderAdapter,
2006
2048
  createMockProductAdapter,
2007
2049
  createMockSchedulesAdapter,