@juo/blocks 1.12.0 → 1.13.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.
- package/dist/{ExtensionRoot-9XfhG8YM.js → ExtensionRoot-peABLAZZ.js} +15 -3
- package/dist/block-B6s9jL7l.js +966 -0
- package/dist/block.d.ts +50 -0
- package/dist/block.d.ts.map +1 -1
- package/dist/block.devtools.test.d.ts +2 -0
- package/dist/block.devtools.test.d.ts.map +1 -0
- package/dist/{bridge-w5KQ-czl.js → bridge-r7Yk8fjY.js} +34 -25
- package/dist/context.d.ts.map +1 -1
- package/dist/context.devtools.test.d.ts +2 -0
- package/dist/context.devtools.test.d.ts.map +1 -0
- package/dist/core.js +1877 -377
- package/dist/devtools/env.d.ts +3 -0
- package/dist/devtools/env.d.ts.map +1 -0
- package/dist/devtools/hook.d.ts +35 -0
- package/dist/devtools/hook.d.ts.map +1 -0
- package/dist/devtools/index.d.ts +4 -0
- package/dist/devtools/index.d.ts.map +1 -0
- package/dist/devtools/protocol.d.ts +91 -0
- package/dist/devtools/protocol.d.ts.map +1 -0
- package/dist/devtools.js +44 -0
- package/dist/editor/bridge.d.ts +7 -0
- package/dist/editor/bridge.d.ts.map +1 -1
- package/dist/editor/components.d.ts.map +1 -1
- package/dist/editor/main.d.ts +2 -1
- package/dist/editor/main.d.ts.map +1 -1
- package/dist/editor/messages.d.ts +13 -2
- package/dist/editor/messages.d.ts.map +1 -1
- package/dist/editor/messages.test.d.ts +2 -0
- package/dist/editor/messages.test.d.ts.map +1 -0
- package/dist/editor/overlays.d.ts +14 -0
- package/dist/editor/overlays.d.ts.map +1 -0
- package/dist/editor/overlays.test.d.ts +2 -0
- package/dist/editor/overlays.test.d.ts.map +1 -0
- package/dist/editor.js +64 -45
- package/dist/{juo-workflow-CkyZRq60.js → juo-workflow-DY4rd256.js} +74 -873
- package/dist/main.d.ts +3 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/overlay-wrapper.d.ts +69 -0
- package/dist/overlay-wrapper.d.ts.map +1 -0
- package/dist/overlay-wrapper.test.d.ts +2 -0
- package/dist/overlay-wrapper.test.d.ts.map +1 -0
- package/dist/overlays/OverlayService.d.ts +34 -0
- package/dist/overlays/OverlayService.d.ts.map +1 -0
- package/dist/overlays/OverlayService.test.d.ts +2 -0
- package/dist/overlays/OverlayService.test.d.ts.map +1 -0
- package/dist/preact.js +1 -1
- package/dist/react.js +1 -1
- package/dist/registry.devtools.test.d.ts +2 -0
- package/dist/registry.devtools.test.d.ts.map +1 -0
- package/dist/services/customer/adapter.d.ts +8 -0
- package/dist/services/customer/adapter.d.ts.map +1 -0
- package/dist/services/customer/customerService.d.ts +3 -2
- package/dist/services/customer/customerService.d.ts.map +1 -1
- package/dist/services/customer/index.d.ts +1 -0
- package/dist/services/customer/index.d.ts.map +1 -1
- package/dist/services/customer/types.d.ts +4 -1
- package/dist/services/customer/types.d.ts.map +1 -1
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/login/adapter.d.ts +32 -0
- package/dist/services/login/adapter.d.ts.map +1 -0
- package/dist/services/login/index.d.ts +3 -1
- package/dist/services/login/index.d.ts.map +1 -1
- package/dist/services/login/loginService.d.ts +6 -2
- package/dist/services/login/loginService.d.ts.map +1 -1
- package/dist/services/order/adapter.d.ts +9 -0
- package/dist/services/order/adapter.d.ts.map +1 -0
- package/dist/services/order/index.d.ts +5 -0
- package/dist/services/order/index.d.ts.map +1 -0
- package/dist/services/order/orderService.d.ts +7 -0
- package/dist/services/order/orderService.d.ts.map +1 -0
- package/dist/services/order/types.d.ts +21 -0
- package/dist/services/order/types.d.ts.map +1 -0
- package/dist/services/product/adapter.d.ts +27 -2
- package/dist/services/product/adapter.d.ts.map +1 -1
- package/dist/services/product/adapter.test.d.ts +2 -0
- package/dist/services/product/adapter.test.d.ts.map +1 -0
- package/dist/services/product/index.d.ts +1 -1
- package/dist/services/product/index.d.ts.map +1 -1
- package/dist/services/product/productService.d.ts.map +1 -1
- package/dist/services/product/types.d.ts +68 -0
- package/dist/services/product/types.d.ts.map +1 -1
- package/dist/services/schedules/adapter.d.ts +47 -0
- package/dist/services/schedules/adapter.d.ts.map +1 -0
- package/dist/services/schedules/adapter.test.d.ts +2 -0
- package/dist/services/schedules/adapter.test.d.ts.map +1 -0
- package/dist/services/schedules/index.d.ts +5 -0
- package/dist/services/schedules/index.d.ts.map +1 -0
- package/dist/services/schedules/schedulesService.d.ts +11 -0
- package/dist/services/schedules/schedulesService.d.ts.map +1 -0
- package/dist/services/schedules/schedulesService.test.d.ts +2 -0
- package/dist/services/schedules/schedulesService.test.d.ts.map +1 -0
- package/dist/services/schedules/types.d.ts +101 -0
- package/dist/services/schedules/types.d.ts.map +1 -0
- package/dist/services/settings/index.d.ts +3 -0
- package/dist/services/settings/index.d.ts.map +1 -0
- package/dist/services/settings/settingsService.d.ts +7 -0
- package/dist/services/settings/settingsService.d.ts.map +1 -0
- package/dist/services/settings/settingsService.test.d.ts +2 -0
- package/dist/services/settings/settingsService.test.d.ts.map +1 -0
- package/dist/services/settings/types.d.ts +35 -0
- package/dist/services/settings/types.d.ts.map +1 -0
- package/dist/services/shop/index.d.ts +3 -0
- package/dist/services/shop/index.d.ts.map +1 -0
- package/dist/services/shop/shopService.d.ts +12 -0
- package/dist/services/shop/shopService.d.ts.map +1 -0
- package/dist/services/shop/shopService.test.d.ts +2 -0
- package/dist/services/shop/shopService.test.d.ts.map +1 -0
- package/dist/services/shop/types.d.ts +7 -0
- package/dist/services/shop/types.d.ts.map +1 -0
- package/dist/services/subscription/adapter.d.ts +58 -2
- package/dist/services/subscription/adapter.d.ts.map +1 -1
- package/dist/services/subscription/adapter.test.d.ts +2 -0
- package/dist/services/subscription/adapter.test.d.ts.map +1 -0
- package/dist/services/subscription/index.d.ts +1 -1
- package/dist/services/subscription/index.d.ts.map +1 -1
- package/dist/services/subscription/subscriptionService.d.ts +1 -6
- package/dist/services/subscription/subscriptionService.d.ts.map +1 -1
- package/dist/services/subscription/types.d.ts +216 -9
- package/dist/services/subscription/types.d.ts.map +1 -1
- package/dist/services/subscription/types.test.d.ts +2 -0
- package/dist/services/subscription/types.test.d.ts.map +1 -0
- package/dist/theme-state.d.ts +2 -1
- package/dist/theme-state.d.ts.map +1 -1
- package/dist/theme-state.params.test.d.ts +2 -0
- package/dist/theme-state.params.test.d.ts.map +1 -0
- package/dist/theme-state.translations.test.d.ts +2 -0
- package/dist/theme-state.translations.test.d.ts.map +1 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/vue/block.d.ts.map +1 -1
- package/dist/vue.js +25 -4
- package/dist/web-components/ExtensionRoot.d.ts +1 -0
- package/dist/web-components/ExtensionRoot.d.ts.map +1 -1
- package/dist/web-components-editor.js +4 -3
- package/dist/web-components-runtime.js +3 -2
- package/package.json +1 -1
package/dist/core.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import {
|
|
5
|
-
import { B,
|
|
4
|
+
import { e as createContext, d, k as w, l as defineBlock, g as createBlockInstance, h as getDefinedBlocks, r as registerBlock, E, u } from "./block-B6s9jL7l.js";
|
|
5
|
+
import { B, m, p, S, q, _, t, b, v, f, x, y, z, A, D, i, F, a, o, G, j, H, s, n, w as w2 } from "./block-B6s9jL7l.js";
|
|
6
|
+
import { D as D2, a as a2, T, W, c, b as b2, d as d2, e, g, h, i as i2 } from "./juo-workflow-DY4rd256.js";
|
|
6
7
|
import { reportBlockWarning, reportBlockError } from "./observability.js";
|
|
7
8
|
function createState() {
|
|
8
9
|
const discountCode = d(null);
|
|
@@ -72,6 +73,111 @@ function createState() {
|
|
|
72
73
|
};
|
|
73
74
|
}
|
|
74
75
|
const StateContext = createContext("state");
|
|
76
|
+
const OVERLAY_WRAPPER_SCHEMA = {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
props: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
layout: {
|
|
83
|
+
type: "string",
|
|
84
|
+
enum: ["modal", "sidebar"]
|
|
85
|
+
},
|
|
86
|
+
canAbort: {
|
|
87
|
+
type: "boolean"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
required: ["layout"],
|
|
91
|
+
additionalProperties: false
|
|
92
|
+
},
|
|
93
|
+
slots: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
content: {}
|
|
97
|
+
},
|
|
98
|
+
additionalProperties: false
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
required: ["props", "slots"],
|
|
102
|
+
additionalProperties: false
|
|
103
|
+
};
|
|
104
|
+
function defineOverlayWrapper(opts) {
|
|
105
|
+
const layout = opts.layout ?? "sidebar";
|
|
106
|
+
const canAbort = opts.canAbort ?? true;
|
|
107
|
+
return defineBlock(opts.name, {
|
|
108
|
+
displayName: opts.title,
|
|
109
|
+
group: "internal",
|
|
110
|
+
schema: OVERLAY_WRAPPER_SCHEMA,
|
|
111
|
+
initialValue: () => ({
|
|
112
|
+
props: { layout, canAbort },
|
|
113
|
+
slots: {
|
|
114
|
+
content: [createBlockInstance(opts.content)]
|
|
115
|
+
}
|
|
116
|
+
}),
|
|
117
|
+
renderer: () => null
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const OVERLAY_PREFIX$1 = "overlay:";
|
|
121
|
+
function callInitialValue(block) {
|
|
122
|
+
if (typeof block.initialValue !== "function") return block.initialValue;
|
|
123
|
+
try {
|
|
124
|
+
return block.initialValue();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.warn(
|
|
127
|
+
`[overlays] initialValue() threw for "${block.name}"; its content block will not be hidden from the palette.`,
|
|
128
|
+
error
|
|
129
|
+
);
|
|
130
|
+
return void 0;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function buildOverlayRegistry(blocks) {
|
|
134
|
+
var _a, _b;
|
|
135
|
+
const registry = {};
|
|
136
|
+
for (const block of blocks) {
|
|
137
|
+
if (!block.name.startsWith(OVERLAY_PREFIX$1)) continue;
|
|
138
|
+
const id = block.name.slice(OVERLAY_PREFIX$1.length);
|
|
139
|
+
const initial = callInitialValue(block);
|
|
140
|
+
const first = (_b = (_a = initial == null ? void 0 : initial.slots) == null ? void 0 : _a.content) == null ? void 0 : _b[0];
|
|
141
|
+
const contentBlock = typeof (first == null ? void 0 : first.type) === "string" ? first.type : "";
|
|
142
|
+
registry[id] = {
|
|
143
|
+
block: block.name,
|
|
144
|
+
title: block.displayName ?? id,
|
|
145
|
+
contentBlock
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return registry;
|
|
149
|
+
}
|
|
150
|
+
const OVERLAY_PREFIX = "overlay:";
|
|
151
|
+
const OverlayServiceContext = createContext("services/overlay");
|
|
152
|
+
function createOverlayService(themeState, options = {}) {
|
|
153
|
+
const getBlocks = options.getBlocks ?? getDefinedBlocks;
|
|
154
|
+
const current = w(() => {
|
|
155
|
+
const root = themeState.overlay.blocks.value[0];
|
|
156
|
+
const type = root == null ? void 0 : root.type;
|
|
157
|
+
if (typeof type !== "string" || !type.startsWith(OVERLAY_PREFIX)) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const props = root.props ?? {};
|
|
161
|
+
return {
|
|
162
|
+
name: type,
|
|
163
|
+
// Defaults mirror defineOverlayWrapper's seeding in overlay-wrapper.ts — keep in sync.
|
|
164
|
+
layout: props.layout ?? "sidebar",
|
|
165
|
+
canAbort: Boolean(props.canAbort ?? true)
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
return {
|
|
169
|
+
getEditableOverlays() {
|
|
170
|
+
return buildOverlayRegistry(getBlocks());
|
|
171
|
+
},
|
|
172
|
+
open(name, params) {
|
|
173
|
+
void themeState.resolve("overlay", name, params);
|
|
174
|
+
},
|
|
175
|
+
close() {
|
|
176
|
+
themeState.overlay.blocks.value = [];
|
|
177
|
+
},
|
|
178
|
+
current
|
|
179
|
+
};
|
|
180
|
+
}
|
|
75
181
|
function isLocalhostOrigin(origin) {
|
|
76
182
|
try {
|
|
77
183
|
const url = new URL(origin);
|
|
@@ -132,7 +238,7 @@ async function loadBlockRegistry(options = {}) {
|
|
|
132
238
|
if ("registerBlocks" in module && typeof module.registerBlocks === "function") {
|
|
133
239
|
module.registerBlocks();
|
|
134
240
|
} else {
|
|
135
|
-
Object.entries(module).forEach(([
|
|
241
|
+
Object.entries(module).forEach(([_2, item]) => {
|
|
136
242
|
if (typeof item === "object" && item != null) {
|
|
137
243
|
registerBlock(item);
|
|
138
244
|
}
|
|
@@ -156,13 +262,43 @@ async function loadBlockRegistry(options = {}) {
|
|
|
156
262
|
function createCustomerService(options) {
|
|
157
263
|
const current = d(null);
|
|
158
264
|
const accountOrdersUrl = d(options.getAccountOrdersUrl());
|
|
159
|
-
|
|
265
|
+
return {
|
|
160
266
|
current,
|
|
161
|
-
accountOrdersUrl
|
|
267
|
+
accountOrdersUrl,
|
|
268
|
+
async getCurrent() {
|
|
269
|
+
const result = await options.adapter.getCurrent();
|
|
270
|
+
if (result._tag === "Success") current.value = result.data;
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
162
273
|
};
|
|
163
|
-
return service;
|
|
164
274
|
}
|
|
165
275
|
const CustomerServiceContext = createContext("services/customer");
|
|
276
|
+
function createMockCustomerAdapter() {
|
|
277
|
+
const customer = { id: "customer-1", name: "Customer 1" };
|
|
278
|
+
return {
|
|
279
|
+
async getCurrent() {
|
|
280
|
+
return { _tag: "Success", data: customer };
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function createApiCustomerAdapter(fetcher) {
|
|
285
|
+
return {
|
|
286
|
+
async getCurrent() {
|
|
287
|
+
try {
|
|
288
|
+
const res = await fetcher("/customer/info");
|
|
289
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
290
|
+
const data = await res.json();
|
|
291
|
+
const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
|
|
292
|
+
return {
|
|
293
|
+
_tag: "Success",
|
|
294
|
+
data: { id: data.id, name, email: data.email }
|
|
295
|
+
};
|
|
296
|
+
} catch (e2) {
|
|
297
|
+
return { _tag: "Failure", error: e2 };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
166
302
|
const PARAMS = {
|
|
167
303
|
DELETGATED_TOKEN: "delegated-token",
|
|
168
304
|
REDIRECT_TO: "redirect-to",
|
|
@@ -176,9 +312,125 @@ const PARAMS = {
|
|
|
176
312
|
function uuid() {
|
|
177
313
|
return ("10000000-1000-4000-8000" + -1e11).replace(
|
|
178
314
|
/[018]/g,
|
|
179
|
-
(
|
|
315
|
+
(c2) => (c2 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c2 / 4).toString(16)
|
|
180
316
|
);
|
|
181
317
|
}
|
|
318
|
+
function createMockLoginAdapter() {
|
|
319
|
+
return {
|
|
320
|
+
async passwordlessStart() {
|
|
321
|
+
return { _tag: "Success", data: void 0 };
|
|
322
|
+
},
|
|
323
|
+
async authenticate() {
|
|
324
|
+
return {
|
|
325
|
+
_tag: "Success",
|
|
326
|
+
data: {
|
|
327
|
+
access_token: "mock-access-token",
|
|
328
|
+
refresh_token: "mock-refresh-token",
|
|
329
|
+
expires_in: 3600
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
getAuthorizeUrl({ connection, state, shop, appUrl }) {
|
|
334
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
335
|
+
url.searchParams.set("connection", connection);
|
|
336
|
+
url.searchParams.set("state", state);
|
|
337
|
+
url.searchParams.set("shop", shop);
|
|
338
|
+
return url.toString();
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
function createApiLoginAdapter(fetcher, opts) {
|
|
343
|
+
const { shopDomain, appUrl } = opts;
|
|
344
|
+
async function callAuthEndpoint(path, body, lang) {
|
|
345
|
+
const url = lang ? `${path}?lang=${lang}` : path;
|
|
346
|
+
const res = await fetcher(url, {
|
|
347
|
+
method: "POST",
|
|
348
|
+
headers: {
|
|
349
|
+
Accept: "application/json",
|
|
350
|
+
"Content-Type": "application/json",
|
|
351
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
352
|
+
},
|
|
353
|
+
body: JSON.stringify(body)
|
|
354
|
+
});
|
|
355
|
+
if (res.status === 403) {
|
|
356
|
+
const data = await res.json();
|
|
357
|
+
if ("error_description" in data) {
|
|
358
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
359
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
360
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
361
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
362
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
363
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
364
|
+
}
|
|
365
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
366
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
367
|
+
connection: data.connection
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (!res.ok) {
|
|
372
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
373
|
+
}
|
|
374
|
+
return { _tag: "Success", data: void 0 };
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
async passwordlessStart({ email, lang }) {
|
|
378
|
+
try {
|
|
379
|
+
await callAuthEndpoint(
|
|
380
|
+
"/auth/customer/passwordless/start",
|
|
381
|
+
{ email },
|
|
382
|
+
lang
|
|
383
|
+
);
|
|
384
|
+
return { _tag: "Success", data: void 0 };
|
|
385
|
+
} catch (e2) {
|
|
386
|
+
return { _tag: "Failure", error: e2 };
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
async authenticate(params) {
|
|
390
|
+
try {
|
|
391
|
+
const res = await fetcher("/auth/customer/token", {
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers: {
|
|
394
|
+
Accept: "application/json",
|
|
395
|
+
"Content-Type": "application/json",
|
|
396
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
397
|
+
},
|
|
398
|
+
body: JSON.stringify(params)
|
|
399
|
+
});
|
|
400
|
+
if (res.status === 403) {
|
|
401
|
+
const data = await res.json();
|
|
402
|
+
if ("error_description" in data) {
|
|
403
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
404
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
405
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
406
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
407
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
408
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
409
|
+
}
|
|
410
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
411
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
412
|
+
connection: data.connection
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (!res.ok) {
|
|
417
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
418
|
+
}
|
|
419
|
+
return { _tag: "Success", data: await res.json() };
|
|
420
|
+
} catch (e2) {
|
|
421
|
+
return { _tag: "Failure", error: e2 };
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
getAuthorizeUrl({ connection, state, shop }) {
|
|
425
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
426
|
+
url.searchParams.set("connection", connection);
|
|
427
|
+
url.searchParams.set("state", state);
|
|
428
|
+
url.searchParams.set("shop", shop);
|
|
429
|
+
return url.toString();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
const __vite_import_meta_env__ = {};
|
|
182
434
|
var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
|
|
183
435
|
AuthenticationErrorReason2["Expired"] = "expired";
|
|
184
436
|
AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
|
|
@@ -197,6 +449,9 @@ class AuthenticationError extends Error {
|
|
|
197
449
|
this.data = data ?? {};
|
|
198
450
|
}
|
|
199
451
|
}
|
|
452
|
+
const STORAGE_DELEGATED_TOKEN = "delegated-token";
|
|
453
|
+
const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
|
|
454
|
+
const STORAGE_STATE_KEY = "state";
|
|
200
455
|
function storage(key, value) {
|
|
201
456
|
const storageKey = `auth:${key}`;
|
|
202
457
|
try {
|
|
@@ -212,83 +467,33 @@ function storage(key, value) {
|
|
|
212
467
|
}
|
|
213
468
|
return null;
|
|
214
469
|
}
|
|
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
470
|
function nonce(len = 10) {
|
|
281
471
|
return uuid().replace(/-/g, "").slice(0, len);
|
|
282
472
|
}
|
|
283
|
-
function
|
|
284
|
-
const
|
|
473
|
+
function loginService(deps) {
|
|
474
|
+
const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
|
|
475
|
+
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";
|
|
476
|
+
const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
|
|
477
|
+
const adapter = createApiLoginAdapter(fetcher, {
|
|
478
|
+
shopDomain: deps.shop.domain,
|
|
479
|
+
appUrl
|
|
480
|
+
});
|
|
481
|
+
return createLoginService({
|
|
482
|
+
adapter,
|
|
483
|
+
router: deps.router,
|
|
484
|
+
formatError: deps.formatError,
|
|
485
|
+
shop: {
|
|
486
|
+
domain: deps.shop.domain,
|
|
487
|
+
locale: w(() => deps.getLocale())
|
|
488
|
+
},
|
|
489
|
+
onLoginSuccess: deps.onLoginSuccess
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
function createLoginService(init) {
|
|
493
|
+
const { adapter, formatError, router, shop, onLoginSuccess } = init;
|
|
285
494
|
const accessToken = d(null);
|
|
286
|
-
const delegatedToken = d(
|
|
287
|
-
|
|
288
|
-
);
|
|
289
|
-
const refreshToken = d(
|
|
290
|
-
storage(STORAGE_REFRESH_TOKEN_KEY)
|
|
291
|
-
);
|
|
495
|
+
const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
|
|
496
|
+
const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
|
|
292
497
|
const expires = d(null);
|
|
293
498
|
const state = d(storage(STORAGE_STATE_KEY));
|
|
294
499
|
E(() => {
|
|
@@ -300,67 +505,28 @@ function createAuthService(apiUrl, shopDomain) {
|
|
|
300
505
|
E(() => {
|
|
301
506
|
storage(STORAGE_STATE_KEY, state.value);
|
|
302
507
|
});
|
|
303
|
-
function
|
|
508
|
+
function storeTokens(data) {
|
|
304
509
|
const { access_token, refresh_token, expires_in } = data;
|
|
305
510
|
accessToken.value = access_token;
|
|
306
511
|
refreshToken.value = refresh_token;
|
|
307
512
|
expires.value = new Date(Date.now() + expires_in);
|
|
308
513
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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);
|
|
514
|
+
async function getToken() {
|
|
515
|
+
if (refreshToken.value == null) return null;
|
|
516
|
+
if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
|
|
517
|
+
accessToken.value = new Promise(
|
|
518
|
+
(resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
|
|
519
|
+
if (result._tag === "Failure") {
|
|
520
|
+
reject(result.error);
|
|
332
521
|
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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);
|
|
522
|
+
}
|
|
523
|
+
storeTokens(result.data);
|
|
524
|
+
resolve(result.data.access_token);
|
|
525
|
+
}).catch(reject)
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
return Promise.resolve(accessToken.value);
|
|
529
|
+
}
|
|
364
530
|
const userEmail = d(null);
|
|
365
531
|
async function onSuccess(query) {
|
|
366
532
|
await (onLoginSuccess == null ? void 0 : onLoginSuccess());
|
|
@@ -373,13 +539,9 @@ function createLoginService(init) {
|
|
|
373
539
|
queryParams[key] = value;
|
|
374
540
|
}
|
|
375
541
|
});
|
|
376
|
-
router.push({
|
|
377
|
-
path: decodeURIComponent(redirect),
|
|
378
|
-
query: queryParams
|
|
379
|
-
});
|
|
542
|
+
router.push({ path: decodeURIComponent(redirect), query: queryParams });
|
|
380
543
|
} else {
|
|
381
|
-
|
|
382
|
-
router.replace(decodeURIComponent(redirect));
|
|
544
|
+
router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
|
|
383
545
|
}
|
|
384
546
|
} else {
|
|
385
547
|
router.replace("/");
|
|
@@ -391,16 +553,17 @@ function createLoginService(init) {
|
|
|
391
553
|
async start(email) {
|
|
392
554
|
try {
|
|
393
555
|
userEmail.value = email;
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
556
|
+
const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
|
|
557
|
+
const result = await adapter.passwordlessStart({
|
|
558
|
+
email: userEmail.value,
|
|
559
|
+
lang
|
|
560
|
+
});
|
|
561
|
+
if (result._tag === "Failure") {
|
|
562
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
563
|
+
}
|
|
398
564
|
return { _tag: "Success", data: void 0 };
|
|
399
565
|
} catch (error) {
|
|
400
|
-
return {
|
|
401
|
-
_tag: "Failure",
|
|
402
|
-
error: formatError(error)
|
|
403
|
-
};
|
|
566
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
404
567
|
}
|
|
405
568
|
},
|
|
406
569
|
async verify(email, code) {
|
|
@@ -411,63 +574,61 @@ function createLoginService(init) {
|
|
|
411
574
|
};
|
|
412
575
|
}
|
|
413
576
|
try {
|
|
414
|
-
const result = await
|
|
577
|
+
const result = await adapter.authenticate({
|
|
415
578
|
email: userEmail.value,
|
|
416
579
|
code
|
|
417
580
|
});
|
|
581
|
+
if (result._tag === "Failure") {
|
|
582
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
583
|
+
}
|
|
584
|
+
storeTokens(result.data);
|
|
418
585
|
await onSuccess();
|
|
419
|
-
return { _tag: "Success", data:
|
|
586
|
+
return { _tag: "Success", data: void 0 };
|
|
420
587
|
} catch (error) {
|
|
421
|
-
return {
|
|
422
|
-
_tag: "Failure",
|
|
423
|
-
error: formatError(error)
|
|
424
|
-
};
|
|
588
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
425
589
|
}
|
|
426
590
|
}
|
|
427
591
|
}
|
|
428
592
|
},
|
|
429
593
|
social: {
|
|
430
594
|
async start(provider) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
provider === "Facebook" ? "facebook" : "google-oauth2",
|
|
444
|
-
shop.domain
|
|
445
|
-
);
|
|
446
|
-
return {
|
|
447
|
-
_tag: "Success",
|
|
448
|
-
data: void 0
|
|
449
|
-
};
|
|
595
|
+
state.value = nonce();
|
|
596
|
+
const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
|
|
597
|
+
window.location.href = adapter.getAuthorizeUrl({
|
|
598
|
+
connection,
|
|
599
|
+
state: state.value ?? "",
|
|
600
|
+
shop: shop.domain,
|
|
601
|
+
appUrl: (
|
|
602
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
603
|
+
(typeof import.meta !== "undefined" ? __vite_import_meta_env__ == null ? void 0 : __vite_import_meta_env__.VITE_APP_URL : void 0) ?? "https://admin.juo.io"
|
|
604
|
+
)
|
|
605
|
+
});
|
|
606
|
+
return { _tag: "Success", data: void 0 };
|
|
450
607
|
},
|
|
451
608
|
async verify(code) {
|
|
452
609
|
try {
|
|
453
|
-
const result = await
|
|
610
|
+
const result = await adapter.authenticate({ code });
|
|
611
|
+
if (result._tag === "Failure") {
|
|
612
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
613
|
+
}
|
|
614
|
+
storeTokens(result.data);
|
|
454
615
|
await onSuccess();
|
|
455
|
-
return { _tag: "Success", data:
|
|
616
|
+
return { _tag: "Success", data: void 0 };
|
|
456
617
|
} catch (error) {
|
|
457
|
-
return {
|
|
458
|
-
_tag: "Failure",
|
|
459
|
-
error: formatError(error)
|
|
460
|
-
};
|
|
618
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
461
619
|
}
|
|
462
620
|
}
|
|
463
621
|
},
|
|
464
622
|
async logout() {
|
|
465
|
-
|
|
623
|
+
accessToken.value = null;
|
|
624
|
+
delegatedToken.value = null;
|
|
625
|
+
refreshToken.value = null;
|
|
626
|
+
expires.value = null;
|
|
466
627
|
return { _tag: "Success", data: void 0 };
|
|
467
628
|
},
|
|
468
629
|
async handleQueryParams(query) {
|
|
469
630
|
if (query.has(PARAMS.DELETGATED_TOKEN)) {
|
|
470
|
-
|
|
631
|
+
delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
|
|
471
632
|
await onSuccess(query);
|
|
472
633
|
return { _tag: "Success", data: void 0 };
|
|
473
634
|
}
|
|
@@ -479,16 +640,13 @@ function createLoginService(init) {
|
|
|
479
640
|
}
|
|
480
641
|
const code = query.get(PARAMS.CODE);
|
|
481
642
|
if (code != null) {
|
|
482
|
-
if (query.get(PARAMS.STATE) !==
|
|
483
|
-
return {
|
|
484
|
-
_tag: "Failure",
|
|
485
|
-
error: formatError("Invalid state")
|
|
486
|
-
};
|
|
643
|
+
if (query.get(PARAMS.STATE) !== state.value) {
|
|
644
|
+
return { _tag: "Failure", error: formatError("Invalid state") };
|
|
487
645
|
} else {
|
|
488
646
|
return service.social.verify(code);
|
|
489
647
|
}
|
|
490
648
|
}
|
|
491
|
-
if (query.get(PARAMS.REFRESH) === "1" && await
|
|
649
|
+
if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
|
|
492
650
|
await onSuccess();
|
|
493
651
|
return { _tag: "Success", data: void 0 };
|
|
494
652
|
}
|
|
@@ -505,8 +663,20 @@ function createLoginService(init) {
|
|
|
505
663
|
}
|
|
506
664
|
return { _tag: "Success", data: void 0 };
|
|
507
665
|
},
|
|
508
|
-
isAuthenticated:
|
|
509
|
-
|
|
666
|
+
isAuthenticated: w(
|
|
667
|
+
() => delegatedToken.value != null || refreshToken.value != null
|
|
668
|
+
),
|
|
669
|
+
getToken,
|
|
670
|
+
async getAuthHeaders() {
|
|
671
|
+
if (delegatedToken.value) {
|
|
672
|
+
return { "X-Delegated-Token": delegatedToken.value };
|
|
673
|
+
}
|
|
674
|
+
const token = await getToken();
|
|
675
|
+
if (token) {
|
|
676
|
+
return { Authorization: `Bearer ${token}` };
|
|
677
|
+
}
|
|
678
|
+
return {};
|
|
679
|
+
}
|
|
510
680
|
};
|
|
511
681
|
return service;
|
|
512
682
|
}
|
|
@@ -591,7 +761,7 @@ function createMockProductAdapter() {
|
|
|
591
761
|
id: "sample-1",
|
|
592
762
|
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
593
763
|
featuredImage: {
|
|
594
|
-
url: "https://
|
|
764
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
595
765
|
},
|
|
596
766
|
hasOnlyDefaultVariant: true,
|
|
597
767
|
variants: [
|
|
@@ -599,7 +769,7 @@ function createMockProductAdapter() {
|
|
|
599
769
|
id: "sample-1-1",
|
|
600
770
|
title: "150 ml bottle",
|
|
601
771
|
image: {
|
|
602
|
-
url: "https://
|
|
772
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
603
773
|
},
|
|
604
774
|
price: {
|
|
605
775
|
amount: "12.49",
|
|
@@ -625,7 +795,7 @@ function createMockProductAdapter() {
|
|
|
625
795
|
id: "sample-2",
|
|
626
796
|
title: "More COLLAGEN",
|
|
627
797
|
featuredImage: {
|
|
628
|
-
url: "https://
|
|
798
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
629
799
|
},
|
|
630
800
|
hasOnlyDefaultVariant: true,
|
|
631
801
|
variants: [
|
|
@@ -633,7 +803,7 @@ function createMockProductAdapter() {
|
|
|
633
803
|
id: "sample-2-1",
|
|
634
804
|
title: "blackcurrant",
|
|
635
805
|
image: {
|
|
636
|
-
url: "https://
|
|
806
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
637
807
|
},
|
|
638
808
|
price: {
|
|
639
809
|
amount: "12.49",
|
|
@@ -654,7 +824,7 @@ function createMockProductAdapter() {
|
|
|
654
824
|
id: "sample-3",
|
|
655
825
|
title: "Gentle deodorant for underarms and bust",
|
|
656
826
|
featuredImage: {
|
|
657
|
-
url: "https://
|
|
827
|
+
url: "https://placehold.co/400x400?text=Product+3"
|
|
658
828
|
},
|
|
659
829
|
hasOnlyDefaultVariant: false,
|
|
660
830
|
variants: [
|
|
@@ -662,7 +832,7 @@ function createMockProductAdapter() {
|
|
|
662
832
|
id: "sample-3-1",
|
|
663
833
|
title: "Green tea and jasmine",
|
|
664
834
|
image: {
|
|
665
|
-
url: "https://
|
|
835
|
+
url: "https://placehold.co/400x400?text=Product+3a"
|
|
666
836
|
},
|
|
667
837
|
price: {
|
|
668
838
|
amount: "6.7",
|
|
@@ -680,7 +850,7 @@ function createMockProductAdapter() {
|
|
|
680
850
|
id: "sample-3-2",
|
|
681
851
|
title: "Lavender and mint",
|
|
682
852
|
image: {
|
|
683
|
-
url: "https://
|
|
853
|
+
url: "https://placehold.co/400x400?text=Product+3b"
|
|
684
854
|
},
|
|
685
855
|
price: {
|
|
686
856
|
amount: "7.5",
|
|
@@ -712,6 +882,48 @@ function createMockProductAdapter() {
|
|
|
712
882
|
}
|
|
713
883
|
};
|
|
714
884
|
}
|
|
885
|
+
function toAvailableProduct(product) {
|
|
886
|
+
return {
|
|
887
|
+
id: product.id,
|
|
888
|
+
title: product.title,
|
|
889
|
+
description: `Sample description for ${product.title}.`,
|
|
890
|
+
featuredImage: product.featuredImage ?? void 0,
|
|
891
|
+
hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
|
|
892
|
+
variants: product.variants.map((variant) => {
|
|
893
|
+
var _a, _b, _c;
|
|
894
|
+
const base = Number.parseFloat(variant.price.amount);
|
|
895
|
+
const sub = (_b = (_a = variant.purchaseOptions) == null ? void 0 : _a[0]) == null ? void 0 : _b.price.amount;
|
|
896
|
+
const current = sub == null ? base : Number.parseFloat(sub);
|
|
897
|
+
return {
|
|
898
|
+
id: variant.id,
|
|
899
|
+
availableForSale: true,
|
|
900
|
+
title: variant.title,
|
|
901
|
+
currentPrice: { amount: current, currencyCode: variant.price.currencyCode },
|
|
902
|
+
basePrice: { amount: base, currencyCode: variant.price.currencyCode },
|
|
903
|
+
image: variant.image ?? void 0,
|
|
904
|
+
recurring: (((_c = variant.purchaseOptions) == null ? void 0 : _c.length) ?? 0) > 0
|
|
905
|
+
};
|
|
906
|
+
})
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const sampleBox = {
|
|
910
|
+
id: "sample-box-1",
|
|
911
|
+
title: "Build Your Own Box",
|
|
912
|
+
minProductsCount: 3,
|
|
913
|
+
parentProduct: "sample-1",
|
|
914
|
+
tiers: [
|
|
915
|
+
{ value: 5, threshold: 3 },
|
|
916
|
+
{ value: 10, threshold: 5 }
|
|
917
|
+
],
|
|
918
|
+
variants: products.map((product) => ({
|
|
919
|
+
id: product.id,
|
|
920
|
+
variants: product.variants.map((variant) => ({ id: variant.id }))
|
|
921
|
+
})),
|
|
922
|
+
freeVariants: [],
|
|
923
|
+
thresholdType: "LINES_COUNT",
|
|
924
|
+
type: "PERCENTAGE",
|
|
925
|
+
restrictQuantityToTiers: false
|
|
926
|
+
};
|
|
715
927
|
return {
|
|
716
928
|
async getById(id) {
|
|
717
929
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -725,6 +937,27 @@ function createMockProductAdapter() {
|
|
|
725
937
|
);
|
|
726
938
|
return product == null ? null : processProduct(product);
|
|
727
939
|
},
|
|
940
|
+
async getProductsByIds({ ids }) {
|
|
941
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
942
|
+
return products.filter((product) => ids.includes(product.id)).map(({ variants: _variants, ...product }) => product);
|
|
943
|
+
},
|
|
944
|
+
async getBoxVariants({ ids }) {
|
|
945
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
946
|
+
return products.filter((product) => ids.includes(product.id)).map(toAvailableProduct);
|
|
947
|
+
},
|
|
948
|
+
async getAvailableProducts({ q: q2 }) {
|
|
949
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
950
|
+
const queryLower = q2 == null ? void 0 : q2.toLowerCase();
|
|
951
|
+
return products.filter(
|
|
952
|
+
(product) => !queryLower || product.title.toLowerCase().includes(queryLower) || product.variants.some(
|
|
953
|
+
(variant) => variant.title.toLowerCase().includes(queryLower)
|
|
954
|
+
)
|
|
955
|
+
).map(toAvailableProduct);
|
|
956
|
+
},
|
|
957
|
+
async getBox({ id }) {
|
|
958
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
959
|
+
return id === sampleBox.id ? sampleBox : null;
|
|
960
|
+
},
|
|
728
961
|
async search(opts) {
|
|
729
962
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
730
963
|
let filteredProducts = [...products];
|
|
@@ -765,49 +998,232 @@ function createMockProductAdapter() {
|
|
|
765
998
|
}
|
|
766
999
|
};
|
|
767
1000
|
}
|
|
768
|
-
|
|
769
|
-
|
|
1001
|
+
function mapVariant(variant, currencyCode) {
|
|
1002
|
+
var _a;
|
|
770
1003
|
return {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
},
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
1004
|
+
id: variant.id,
|
|
1005
|
+
title: variant.title,
|
|
1006
|
+
image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
|
|
1007
|
+
price: { amount: variant.price.toFixed(2), currencyCode },
|
|
1008
|
+
...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
|
|
1009
|
+
purchaseOptions: variant.purchaseOptions.map((opt) => ({
|
|
1010
|
+
name: opt.name,
|
|
1011
|
+
price: { amount: opt.price.toFixed(2), currencyCode },
|
|
1012
|
+
interval: opt.interval
|
|
1013
|
+
}))
|
|
1014
|
+
} : {}
|
|
780
1015
|
};
|
|
781
1016
|
}
|
|
782
|
-
function
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1017
|
+
function mapProduct(product, currencyCode) {
|
|
1018
|
+
return {
|
|
1019
|
+
id: product.id,
|
|
1020
|
+
title: product.title,
|
|
1021
|
+
featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
|
|
1022
|
+
hasOnlyDefaultVariant: product.variants.length <= 1,
|
|
1023
|
+
variants: {
|
|
1024
|
+
items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
|
|
1025
|
+
pageInfo: {
|
|
1026
|
+
hasNextPage: false,
|
|
1027
|
+
hasPrevPage: false,
|
|
1028
|
+
startCursor: null,
|
|
1029
|
+
endCursor: null
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
collections: product.collections
|
|
1033
|
+
};
|
|
792
1034
|
}
|
|
793
|
-
function
|
|
794
|
-
const {
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
function notifyRouteName(routeName) {
|
|
802
|
-
const pagePath = routeNameToPagePath[String(routeName)];
|
|
803
|
-
if (pagePath != null) notifySubscribers(pagePath);
|
|
804
|
-
}
|
|
805
|
-
function notifyLogicalPath(logicalPath) {
|
|
806
|
-
if (pathSet.has(logicalPath)) notifySubscribers(logicalPath);
|
|
1035
|
+
function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
|
|
1036
|
+
const { currencyCode } = opts;
|
|
1037
|
+
async function fetchProducts(params) {
|
|
1038
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1039
|
+
const res = await fetcher(`/customer/v1/products${query}`);
|
|
1040
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1041
|
+
return res.json();
|
|
807
1042
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1043
|
+
return {
|
|
1044
|
+
async getById(id) {
|
|
1045
|
+
try {
|
|
1046
|
+
const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
|
|
1047
|
+
const body = await fetchProducts(params);
|
|
1048
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1049
|
+
} catch {
|
|
1050
|
+
return null;
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
async getByVariantId(variantId) {
|
|
1054
|
+
try {
|
|
1055
|
+
const params = new URLSearchParams({
|
|
1056
|
+
query: `variants.id:"${variantId}"`,
|
|
1057
|
+
limit: "1"
|
|
1058
|
+
});
|
|
1059
|
+
const body = await fetchProducts(params);
|
|
1060
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1061
|
+
} catch {
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
async search(opts2) {
|
|
1066
|
+
try {
|
|
1067
|
+
const params = new URLSearchParams();
|
|
1068
|
+
if (opts2 == null ? void 0 : opts2.query) {
|
|
1069
|
+
params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
|
|
1070
|
+
}
|
|
1071
|
+
if (opts2 == null ? void 0 : opts2.pagination) {
|
|
1072
|
+
if ("after" in opts2.pagination)
|
|
1073
|
+
params.set("after", opts2.pagination.after);
|
|
1074
|
+
else if ("before" in opts2.pagination)
|
|
1075
|
+
params.set("before", opts2.pagination.before);
|
|
1076
|
+
}
|
|
1077
|
+
const body = await fetchProducts(params);
|
|
1078
|
+
return {
|
|
1079
|
+
items: body.data.map((p2) => mapProduct(p2, currencyCode)),
|
|
1080
|
+
pageInfo: {
|
|
1081
|
+
hasNextPage: body.hasNextPage,
|
|
1082
|
+
hasPrevPage: body.hasPrevPage,
|
|
1083
|
+
startCursor: body.startCursor,
|
|
1084
|
+
endCursor: body.endCursor
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
} catch {
|
|
1088
|
+
return {
|
|
1089
|
+
items: [],
|
|
1090
|
+
pageInfo: {
|
|
1091
|
+
hasNextPage: false,
|
|
1092
|
+
hasPrevPage: false,
|
|
1093
|
+
startCursor: null,
|
|
1094
|
+
endCursor: null
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
},
|
|
1099
|
+
async getProductsByIds({ ids }) {
|
|
1100
|
+
const params = new URLSearchParams();
|
|
1101
|
+
for (const id of ids) params.append("ids", id);
|
|
1102
|
+
const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
|
|
1103
|
+
if (!res.ok) return [];
|
|
1104
|
+
const data = await res.json();
|
|
1105
|
+
return (data ?? []).map((p2) => ({
|
|
1106
|
+
id: p2.id,
|
|
1107
|
+
title: p2.title ?? "",
|
|
1108
|
+
featuredImage: p2.featuredImage ? { url: p2.featuredImage.url, altText: p2.featuredImage.altText } : null,
|
|
1109
|
+
hasOnlyDefaultVariant: true,
|
|
1110
|
+
collections: []
|
|
1111
|
+
}));
|
|
1112
|
+
},
|
|
1113
|
+
async getBoxVariants({ ids, subscriptionId }) {
|
|
1114
|
+
const params = new URLSearchParams();
|
|
1115
|
+
for (const id of ids) params.append("ids", id);
|
|
1116
|
+
params.set("subscriptionId", subscriptionId);
|
|
1117
|
+
const res = await fetcher(`/customer/products/variants?${params.toString()}`);
|
|
1118
|
+
if (!res.ok) return [];
|
|
1119
|
+
const data = await res.json();
|
|
1120
|
+
return (data ?? []).map((p2) => {
|
|
1121
|
+
var _a;
|
|
1122
|
+
return {
|
|
1123
|
+
id: p2.id,
|
|
1124
|
+
title: p2.title,
|
|
1125
|
+
description: p2.description ?? "",
|
|
1126
|
+
featuredImage: p2.featuredImage,
|
|
1127
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1128
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1129
|
+
id: v2.id,
|
|
1130
|
+
availableForSale: v2.availableForSale,
|
|
1131
|
+
title: v2.title,
|
|
1132
|
+
currentPrice: v2.currentPrice,
|
|
1133
|
+
basePrice: v2.basePrice,
|
|
1134
|
+
image: v2.image,
|
|
1135
|
+
recurring: v2.recurring
|
|
1136
|
+
}))
|
|
1137
|
+
};
|
|
1138
|
+
});
|
|
1139
|
+
},
|
|
1140
|
+
async getAvailableProducts({ subscriptionId, lineId, q: q2, mode, action, pagination }) {
|
|
1141
|
+
const params = new URLSearchParams();
|
|
1142
|
+
params.set("action", action);
|
|
1143
|
+
if (q2) params.set("q", q2);
|
|
1144
|
+
if (mode) params.set("mode", mode);
|
|
1145
|
+
if (pagination && "after" in pagination) params.set("after", pagination.after);
|
|
1146
|
+
if (pagination && "before" in pagination) params.set("before", pagination.before);
|
|
1147
|
+
const res = await fetcher(
|
|
1148
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
|
|
1149
|
+
);
|
|
1150
|
+
if (!res.ok) return [];
|
|
1151
|
+
const data = await res.json();
|
|
1152
|
+
return (data ?? []).map((p2) => {
|
|
1153
|
+
var _a;
|
|
1154
|
+
return {
|
|
1155
|
+
id: p2.id,
|
|
1156
|
+
title: p2.title,
|
|
1157
|
+
description: p2.description ?? "",
|
|
1158
|
+
featuredImage: p2.featuredImage,
|
|
1159
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1160
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1161
|
+
id: v2.id,
|
|
1162
|
+
availableForSale: v2.availableForSale,
|
|
1163
|
+
title: v2.title,
|
|
1164
|
+
currentPrice: v2.currentPrice,
|
|
1165
|
+
basePrice: v2.basePrice,
|
|
1166
|
+
image: v2.image,
|
|
1167
|
+
recurring: v2.recurring
|
|
1168
|
+
}))
|
|
1169
|
+
};
|
|
1170
|
+
});
|
|
1171
|
+
},
|
|
1172
|
+
async getBox({ id }) {
|
|
1173
|
+
const res = await fetcher(`/customer/boxes/${id}`);
|
|
1174
|
+
if (!res.ok) return null;
|
|
1175
|
+
const data = await res.json();
|
|
1176
|
+
return { id, ...data };
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1180
|
+
const ProductServiceContext = createContext("services/product");
|
|
1181
|
+
function createProductService(adapter) {
|
|
1182
|
+
return {
|
|
1183
|
+
async getById(id) {
|
|
1184
|
+
return adapter.getById(id);
|
|
1185
|
+
},
|
|
1186
|
+
async getByVariantId(variantId) {
|
|
1187
|
+
return adapter.getByVariantId(variantId);
|
|
1188
|
+
},
|
|
1189
|
+
async search(opts) {
|
|
1190
|
+
return adapter.search(opts);
|
|
1191
|
+
},
|
|
1192
|
+
getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
|
|
1193
|
+
getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
|
|
1194
|
+
getAvailableProducts: (params) => adapter.getAvailableProducts(params),
|
|
1195
|
+
getBox: (id) => adapter.getBox({ id })
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
function routeNameTableFromPages(pages) {
|
|
1199
|
+
const table = {};
|
|
1200
|
+
for (const page of pages) {
|
|
1201
|
+
const names = page.routeNames;
|
|
1202
|
+
if (names.length === 0) continue;
|
|
1203
|
+
for (const name of names) {
|
|
1204
|
+
table[name] = page.path;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return table;
|
|
1208
|
+
}
|
|
1209
|
+
function createRouterService(options) {
|
|
1210
|
+
const { pages, pushImplementation } = options;
|
|
1211
|
+
const routeNameToPagePath = routeNameTableFromPages(pages);
|
|
1212
|
+
const pathSet = new Set(pages.map((p2) => p2.path));
|
|
1213
|
+
const navigationSubscribers = [];
|
|
1214
|
+
function notifySubscribers(logicalPath) {
|
|
1215
|
+
for (const cb of navigationSubscribers) cb({ path: logicalPath });
|
|
1216
|
+
}
|
|
1217
|
+
function notifyRouteName(routeName) {
|
|
1218
|
+
const pagePath = routeNameToPagePath[String(routeName)];
|
|
1219
|
+
if (pagePath != null) notifySubscribers(pagePath);
|
|
1220
|
+
}
|
|
1221
|
+
function notifyLogicalPath(logicalPath) {
|
|
1222
|
+
if (pathSet.has(logicalPath)) notifySubscribers(logicalPath);
|
|
1223
|
+
}
|
|
1224
|
+
const routerService = {
|
|
1225
|
+
push(to) {
|
|
1226
|
+
pushImplementation(to);
|
|
811
1227
|
},
|
|
812
1228
|
subscribe(callback) {
|
|
813
1229
|
navigationSubscribers.push(callback);
|
|
@@ -831,6 +1247,70 @@ function createMockRouterService() {
|
|
|
831
1247
|
}).routerService;
|
|
832
1248
|
}
|
|
833
1249
|
function createMockSubscriptionAdapter(options = {}) {
|
|
1250
|
+
const richBase = (id, itemId, renewalId) => ({
|
|
1251
|
+
id,
|
|
1252
|
+
customerId: "customer_1",
|
|
1253
|
+
items: [
|
|
1254
|
+
{
|
|
1255
|
+
id: itemId,
|
|
1256
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1257
|
+
// sample-3 "Gentle deodorant", variant sample-3-1 "Green tea and jasmine",
|
|
1258
|
+
// recurring price 5.36 EUR (10% off → 4.82 with SAVE10).
|
|
1259
|
+
productId: "sample-3",
|
|
1260
|
+
variantId: "sample-3-1",
|
|
1261
|
+
quantity: 2,
|
|
1262
|
+
price: { amount: "5.36", currencyCode: "EUR" },
|
|
1263
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1264
|
+
isUpsell: false,
|
|
1265
|
+
isVirtual: false,
|
|
1266
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1267
|
+
currentPrice: { amount: "5.36", currencyCode: "EUR" },
|
|
1268
|
+
lineDiscountedPrice: { amount: "4.82", currencyCode: "EUR" },
|
|
1269
|
+
variantAvailableForSale: true,
|
|
1270
|
+
title: "Gentle deodorant for underarms and bust",
|
|
1271
|
+
variantTitle: "Green tea and jasmine",
|
|
1272
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+3a" },
|
|
1273
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1274
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
|
|
1275
|
+
}
|
|
1276
|
+
],
|
|
1277
|
+
discounts: [
|
|
1278
|
+
{
|
|
1279
|
+
id: "discount_1",
|
|
1280
|
+
type: "percentage",
|
|
1281
|
+
value: 10,
|
|
1282
|
+
code: "SAVE10",
|
|
1283
|
+
description: "10% off subscription"
|
|
1284
|
+
}
|
|
1285
|
+
],
|
|
1286
|
+
status: "active",
|
|
1287
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1288
|
+
frequency: "1 MONTH",
|
|
1289
|
+
createdAt: "2024-10-01T00:00:00Z",
|
|
1290
|
+
updatedAt: "2024-10-14T00:00:00Z",
|
|
1291
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1292
|
+
deliveryPolicy: {
|
|
1293
|
+
intervalCount: 1,
|
|
1294
|
+
interval: "MONTH",
|
|
1295
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1296
|
+
},
|
|
1297
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1298
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1299
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1300
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1301
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1302
|
+
customerPaymentMethod: {
|
|
1303
|
+
id: "pm_1",
|
|
1304
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1305
|
+
active: true,
|
|
1306
|
+
isRecurring: true
|
|
1307
|
+
},
|
|
1308
|
+
requiredBillingCyclesLeft: 3,
|
|
1309
|
+
renewal: { id: renewalId, isInActiveProcessState: false },
|
|
1310
|
+
lastPaymentStatus: "succeeded",
|
|
1311
|
+
pausePeriod: null,
|
|
1312
|
+
isEditable: true
|
|
1313
|
+
});
|
|
834
1314
|
const mockSubscriptions = [
|
|
835
1315
|
{
|
|
836
1316
|
id: "sub_1",
|
|
@@ -838,14 +1318,27 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
838
1318
|
items: [
|
|
839
1319
|
{
|
|
840
1320
|
id: "item_1",
|
|
841
|
-
|
|
842
|
-
|
|
1321
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1322
|
+
// sample-1 "Hydrating Gel Cleanser" (default variant), recurring price
|
|
1323
|
+
// 9.99 EUR (10% off → 8.99 with SAVE10).
|
|
1324
|
+
productId: "sample-1",
|
|
1325
|
+
variantId: "sample-1-1",
|
|
843
1326
|
quantity: 2,
|
|
844
|
-
price: { amount: "
|
|
1327
|
+
price: { amount: "9.99", currencyCode: "EUR" },
|
|
845
1328
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1329
|
+
isUpsell: false,
|
|
1330
|
+
isVirtual: false,
|
|
1331
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1332
|
+
currentPrice: { amount: "9.99", currencyCode: "EUR" },
|
|
1333
|
+
lineDiscountedPrice: { amount: "8.99", currencyCode: "EUR" },
|
|
1334
|
+
variantAvailableForSale: true,
|
|
1335
|
+
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
1336
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+1" },
|
|
1337
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1338
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
846
1339
|
customAttributes: [
|
|
847
|
-
{ key: "
|
|
848
|
-
{ key: "
|
|
1340
|
+
{ key: "skin_type", value: "combination" },
|
|
1341
|
+
{ key: "fragrance", value: "fragrance_free" }
|
|
849
1342
|
]
|
|
850
1343
|
}
|
|
851
1344
|
],
|
|
@@ -863,9 +1356,31 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
863
1356
|
frequency: "1 MONTH",
|
|
864
1357
|
createdAt: "2024-10-01T00:00:00Z",
|
|
865
1358
|
updatedAt: "2024-10-14T00:00:00Z",
|
|
1359
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1360
|
+
deliveryPolicy: {
|
|
1361
|
+
intervalCount: 1,
|
|
1362
|
+
interval: "MONTH",
|
|
1363
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1364
|
+
},
|
|
1365
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1366
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1367
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1368
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1369
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1370
|
+
customerPaymentMethod: {
|
|
1371
|
+
id: "pm_1",
|
|
1372
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1373
|
+
active: true,
|
|
1374
|
+
isRecurring: true
|
|
1375
|
+
},
|
|
1376
|
+
requiredBillingCyclesLeft: 3,
|
|
1377
|
+
renewal: { id: "ren_1", isInActiveProcessState: false },
|
|
1378
|
+
lastPaymentStatus: "succeeded",
|
|
1379
|
+
pausePeriod: null,
|
|
1380
|
+
isEditable: true,
|
|
866
1381
|
customAttributes: [
|
|
867
1382
|
{ key: "subscription_tier", value: "premium" },
|
|
868
|
-
{ key: "gift_message", value: "Enjoy your
|
|
1383
|
+
{ key: "gift_message", value: "Enjoy your skincare routine!" }
|
|
869
1384
|
]
|
|
870
1385
|
},
|
|
871
1386
|
{
|
|
@@ -874,11 +1389,17 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
874
1389
|
items: [
|
|
875
1390
|
{
|
|
876
1391
|
id: "item_2",
|
|
877
|
-
|
|
878
|
-
|
|
1392
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1393
|
+
// sample-2 "More COLLAGEN" (default variant), recurring price 10.99 EUR.
|
|
1394
|
+
productId: "sample-2",
|
|
1395
|
+
variantId: "sample-2-1",
|
|
879
1396
|
quantity: 1,
|
|
880
|
-
price: { amount: "
|
|
1397
|
+
price: { amount: "10.99", currencyCode: "EUR" },
|
|
881
1398
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
|
|
1399
|
+
title: "More COLLAGEN",
|
|
1400
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+2" },
|
|
1401
|
+
billingPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
1402
|
+
deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
882
1403
|
customAttributes: []
|
|
883
1404
|
}
|
|
884
1405
|
],
|
|
@@ -889,17 +1410,66 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
889
1410
|
createdAt: "2024-09-15T00:00:00Z",
|
|
890
1411
|
updatedAt: "2024-10-10T00:00:00Z",
|
|
891
1412
|
customAttributes: []
|
|
1413
|
+
},
|
|
1414
|
+
// Per-status fixtures: groundwork for the deferred editor preview picker
|
|
1415
|
+
// (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
|
|
1416
|
+
// Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
|
|
1417
|
+
{
|
|
1418
|
+
...richBase("sub_paused", "item_paused", "ren_paused"),
|
|
1419
|
+
status: "paused",
|
|
1420
|
+
pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
|
|
1421
|
+
},
|
|
1422
|
+
{
|
|
1423
|
+
...richBase("sub_canceled", "item_canceled", "ren_canceled"),
|
|
1424
|
+
status: "canceled",
|
|
1425
|
+
pausePeriod: null
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
...richBase("sub_failed", "item_failed", "ren_failed"),
|
|
1429
|
+
status: "failed",
|
|
1430
|
+
lastPaymentStatus: "failed",
|
|
1431
|
+
pausePeriod: null
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
...richBase("sub_expired", "item_expired", "ren_expired"),
|
|
1435
|
+
status: "expired",
|
|
1436
|
+
pausePeriod: null
|
|
1437
|
+
}
|
|
1438
|
+
];
|
|
1439
|
+
const mockUpsellProducts = [
|
|
1440
|
+
{
|
|
1441
|
+
id: "upsell-1",
|
|
1442
|
+
variantId: "upsell-v-1",
|
|
1443
|
+
title: "Retinol Night Cream",
|
|
1444
|
+
imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
|
|
1445
|
+
price: { amount: "38.00", currencyCode: "USD" },
|
|
1446
|
+
description: "Anti-aging night treatment"
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
id: "upsell-2",
|
|
1450
|
+
variantId: "upsell-v-2",
|
|
1451
|
+
title: "SPF 50 Day Shield",
|
|
1452
|
+
imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
|
|
1453
|
+
price: { amount: "24.00", currencyCode: "USD" },
|
|
1454
|
+
description: "Lightweight daily sun protection"
|
|
892
1455
|
}
|
|
893
1456
|
];
|
|
894
1457
|
return {
|
|
895
1458
|
async getById(id) {
|
|
896
1459
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
897
|
-
const subscription = mockSubscriptions.find((sub) => sub.id === id)
|
|
898
|
-
return {
|
|
1460
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === id);
|
|
1461
|
+
return {
|
|
1462
|
+
_tag: "Success",
|
|
1463
|
+
data: subscription ? {
|
|
1464
|
+
...subscription,
|
|
1465
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1466
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1467
|
+
} : null
|
|
1468
|
+
};
|
|
899
1469
|
},
|
|
900
1470
|
async search(options2 = {}) {
|
|
901
1471
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
902
|
-
const pageSize =
|
|
1472
|
+
const pageSize = 2;
|
|
903
1473
|
const allItems = mockSubscriptions;
|
|
904
1474
|
let startIndex = 0;
|
|
905
1475
|
let endIndex = pageSize;
|
|
@@ -915,27 +1485,82 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
915
1485
|
}
|
|
916
1486
|
}
|
|
917
1487
|
const pageItems = allItems.slice(startIndex, endIndex);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
items:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1488
|
+
return {
|
|
1489
|
+
_tag: "Success",
|
|
1490
|
+
data: {
|
|
1491
|
+
items: pageItems.map((sub) => ({
|
|
1492
|
+
...sub,
|
|
1493
|
+
items: sub.items.map((item) => ({ ...item })),
|
|
1494
|
+
discounts: sub.discounts.map((discount) => ({ ...discount }))
|
|
1495
|
+
})),
|
|
1496
|
+
pageInfo: {
|
|
1497
|
+
hasNextPage: endIndex < allItems.length,
|
|
1498
|
+
hasPrevPage: startIndex > 0,
|
|
1499
|
+
startCursor: pageItems.length > 0 ? startIndex.toString() : null,
|
|
1500
|
+
endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
|
|
1501
|
+
}
|
|
929
1502
|
}
|
|
930
1503
|
};
|
|
931
|
-
return { _tag: "Success", data: paginatedList };
|
|
932
1504
|
},
|
|
933
|
-
async addItem() {
|
|
1505
|
+
async addItem({ subscriptionId, item }) {
|
|
1506
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
934
1507
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1508
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1509
|
+
if (!subscription) {
|
|
1510
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1511
|
+
}
|
|
1512
|
+
const currencyCode = ((_a = subscription.items[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD";
|
|
1513
|
+
const product = await ((_b = options.productAdapter) == null ? void 0 : _b.getByVariantId(item.variantId)) ?? await ((_c = options.productAdapter) == null ? void 0 : _c.getById(item.variantId)) ?? null;
|
|
1514
|
+
const variant = (product == null ? void 0 : product.variants.items.find((v2) => v2.id === item.variantId)) ?? (product == null ? void 0 : product.variants.items[0]);
|
|
1515
|
+
const linePrice = ((_e = (_d = variant == null ? void 0 : variant.purchaseOptions) == null ? void 0 : _d[0]) == null ? void 0 : _e.price) ?? (variant == null ? void 0 : variant.price);
|
|
1516
|
+
subscription.items.push({
|
|
1517
|
+
id: `item_${Date.now()}_${subscription.items.length}`,
|
|
1518
|
+
productId: (product == null ? void 0 : product.id) ?? "",
|
|
1519
|
+
variantId: item.variantId,
|
|
1520
|
+
quantity: item.quantity,
|
|
1521
|
+
price: linePrice ? { ...linePrice } : { amount: "0.00", currencyCode },
|
|
1522
|
+
...linePrice ? { currentPrice: { ...linePrice } } : {},
|
|
1523
|
+
title: (product == null ? void 0 : product.title) ?? `Product ${item.variantId}`,
|
|
1524
|
+
...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
|
|
1525
|
+
...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
|
|
1526
|
+
variantAvailableForSale: true,
|
|
1527
|
+
billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
|
|
1528
|
+
deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
|
|
1529
|
+
});
|
|
1530
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
935
1531
|
return { _tag: "Success", data: void 0 };
|
|
936
1532
|
},
|
|
937
|
-
async removeItem() {
|
|
1533
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
938
1534
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1535
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1536
|
+
if (!subscription) {
|
|
1537
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1538
|
+
}
|
|
1539
|
+
subscription.items = subscription.items.filter((i3) => i3.id !== itemId);
|
|
1540
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1541
|
+
return { _tag: "Success", data: void 0 };
|
|
1542
|
+
},
|
|
1543
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1544
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1545
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1546
|
+
if (!subscription) {
|
|
1547
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1548
|
+
}
|
|
1549
|
+
const item = subscription.items.find((i3) => i3.id === lineId);
|
|
1550
|
+
if (!item) {
|
|
1551
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1552
|
+
}
|
|
1553
|
+
if (update.variantId !== void 0) item.variantId = update.variantId;
|
|
1554
|
+
if (update.quantity !== void 0) item.quantity = update.quantity;
|
|
1555
|
+
if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
|
|
1556
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1557
|
+
return { _tag: "Success", data: void 0 };
|
|
1558
|
+
},
|
|
1559
|
+
async skipLineItem() {
|
|
1560
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1561
|
+
return { _tag: "Success", data: void 0 };
|
|
1562
|
+
},
|
|
1563
|
+
async updateBoxItems() {
|
|
939
1564
|
return { _tag: "Success", data: void 0 };
|
|
940
1565
|
},
|
|
941
1566
|
async addDiscount() {
|
|
@@ -966,24 +1591,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
966
1591
|
(sub) => sub.id === params.subscriptionId
|
|
967
1592
|
);
|
|
968
1593
|
if (!subscription) {
|
|
969
|
-
return {
|
|
970
|
-
_tag: "Failure",
|
|
971
|
-
error: "Subscription not found"
|
|
972
|
-
};
|
|
1594
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
973
1595
|
}
|
|
974
1596
|
subscription.nextDeliveryDate = new Date(params.newDate);
|
|
975
1597
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
976
|
-
subscription
|
|
977
|
-
item.nextDeliveryDate = new Date(params.newDate);
|
|
978
|
-
});
|
|
979
|
-
return {
|
|
980
|
-
_tag: "Success",
|
|
981
|
-
data: {
|
|
982
|
-
...subscription,
|
|
983
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
984
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
985
|
-
}
|
|
986
|
-
};
|
|
1598
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
987
1599
|
},
|
|
988
1600
|
async resume(params) {
|
|
989
1601
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -991,21 +1603,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
991
1603
|
(sub) => sub.id === params.subscriptionId
|
|
992
1604
|
);
|
|
993
1605
|
if (!subscription) {
|
|
994
|
-
return {
|
|
995
|
-
_tag: "Failure",
|
|
996
|
-
error: "Subscription not found"
|
|
997
|
-
};
|
|
1606
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
998
1607
|
}
|
|
999
1608
|
subscription.status = "active";
|
|
1000
1609
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1001
|
-
return {
|
|
1002
|
-
_tag: "Success",
|
|
1003
|
-
data: {
|
|
1004
|
-
...subscription,
|
|
1005
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1006
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1610
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1009
1611
|
},
|
|
1010
1612
|
async pause(params) {
|
|
1011
1613
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1013,21 +1615,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1013
1615
|
(sub) => sub.id === params.subscriptionId
|
|
1014
1616
|
);
|
|
1015
1617
|
if (!subscription) {
|
|
1016
|
-
return {
|
|
1017
|
-
_tag: "Failure",
|
|
1018
|
-
error: "Subscription not found"
|
|
1019
|
-
};
|
|
1618
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1020
1619
|
}
|
|
1021
1620
|
subscription.status = "paused";
|
|
1022
1621
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1023
|
-
return {
|
|
1024
|
-
_tag: "Success",
|
|
1025
|
-
data: {
|
|
1026
|
-
...subscription,
|
|
1027
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1028
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1029
|
-
}
|
|
1030
|
-
};
|
|
1622
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1031
1623
|
},
|
|
1032
1624
|
async reactivate(params) {
|
|
1033
1625
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1035,21 +1627,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1035
1627
|
(sub) => sub.id === params.subscriptionId
|
|
1036
1628
|
);
|
|
1037
1629
|
if (!subscription) {
|
|
1038
|
-
return {
|
|
1039
|
-
_tag: "Failure",
|
|
1040
|
-
error: "Subscription not found"
|
|
1041
|
-
};
|
|
1630
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1042
1631
|
}
|
|
1043
1632
|
subscription.status = "active";
|
|
1044
1633
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1045
|
-
return {
|
|
1046
|
-
_tag: "Success",
|
|
1047
|
-
data: {
|
|
1048
|
-
...subscription,
|
|
1049
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1050
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1051
|
-
}
|
|
1052
|
-
};
|
|
1634
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1053
1635
|
},
|
|
1054
1636
|
async cancel(params) {
|
|
1055
1637
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1057,21 +1639,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1057
1639
|
(sub) => sub.id === params.subscriptionId
|
|
1058
1640
|
);
|
|
1059
1641
|
if (!subscription) {
|
|
1060
|
-
return {
|
|
1061
|
-
_tag: "Failure",
|
|
1062
|
-
error: "Subscription not found"
|
|
1063
|
-
};
|
|
1642
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1064
1643
|
}
|
|
1065
1644
|
subscription.status = "canceled";
|
|
1066
1645
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1067
|
-
return {
|
|
1068
|
-
_tag: "Success",
|
|
1069
|
-
data: {
|
|
1070
|
-
...subscription,
|
|
1071
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1072
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1646
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1075
1647
|
},
|
|
1076
1648
|
async getAvailablePlans() {
|
|
1077
1649
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1099,13 +1671,875 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1099
1671
|
]
|
|
1100
1672
|
};
|
|
1101
1673
|
},
|
|
1674
|
+
async getLastOrder(params) {
|
|
1675
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1676
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1677
|
+
if (!subscription) return { _tag: "Success", data: null };
|
|
1678
|
+
return {
|
|
1679
|
+
_tag: "Success",
|
|
1680
|
+
data: {
|
|
1681
|
+
id: `mock_last_order_${params.subscriptionId}`,
|
|
1682
|
+
createdAt: "2026-04-22T12:00:00Z"
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
},
|
|
1686
|
+
async renewNow(_params) {
|
|
1687
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1688
|
+
return { _tag: "Success", data: { result: "success" } };
|
|
1689
|
+
},
|
|
1690
|
+
async changeFrequency(params) {
|
|
1691
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1692
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1693
|
+
if (!subscription) {
|
|
1694
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1695
|
+
}
|
|
1696
|
+
if (typeof params.plan === "string") {
|
|
1697
|
+
subscription.frequency = `1 ${params.plan}`;
|
|
1698
|
+
} else {
|
|
1699
|
+
subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
|
|
1700
|
+
if (subscription.billingPolicy) {
|
|
1701
|
+
subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
|
|
1702
|
+
}
|
|
1703
|
+
if (subscription.deliveryPolicy) {
|
|
1704
|
+
subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1708
|
+
return {
|
|
1709
|
+
_tag: "Success",
|
|
1710
|
+
data: {
|
|
1711
|
+
...subscription,
|
|
1712
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1713
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
},
|
|
1102
1717
|
async getDefaultSubscriptionId() {
|
|
1103
1718
|
var _a;
|
|
1104
1719
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1105
1720
|
return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
|
|
1721
|
+
},
|
|
1722
|
+
async getUpsellProducts() {
|
|
1723
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1724
|
+
return { _tag: "Success", data: [...mockUpsellProducts] };
|
|
1725
|
+
},
|
|
1726
|
+
async addUpsellProduct() {
|
|
1727
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
1728
|
+
return { _tag: "Success", data: void 0 };
|
|
1106
1729
|
}
|
|
1107
1730
|
};
|
|
1108
1731
|
}
|
|
1732
|
+
function normalizeInstrumentType(type) {
|
|
1733
|
+
return type.replace(/-/g, "_");
|
|
1734
|
+
}
|
|
1735
|
+
function mapPaymentMethod(rawPm) {
|
|
1736
|
+
if (!rawPm) return null;
|
|
1737
|
+
if (typeof rawPm === "string") {
|
|
1738
|
+
return { id: rawPm };
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
id: rawPm.id ?? "",
|
|
1742
|
+
...rawPm.instrument != null ? {
|
|
1743
|
+
instrument: {
|
|
1744
|
+
...rawPm.instrument,
|
|
1745
|
+
type: normalizeInstrumentType(rawPm.instrument.type)
|
|
1746
|
+
}
|
|
1747
|
+
} : {},
|
|
1748
|
+
...rawPm.active != null ? { active: rawPm.active } : {},
|
|
1749
|
+
...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
function mapSubscription(raw) {
|
|
1753
|
+
var _a, _b;
|
|
1754
|
+
const status = raw.status === "merged" ? "canceled" : raw.status;
|
|
1755
|
+
const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
|
|
1756
|
+
const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
|
|
1757
|
+
return {
|
|
1758
|
+
id: raw.id,
|
|
1759
|
+
customerId: "",
|
|
1760
|
+
serial: raw.serial,
|
|
1761
|
+
items: (raw.items ?? []).map(
|
|
1762
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1763
|
+
(item) => ({
|
|
1764
|
+
id: item.id,
|
|
1765
|
+
productId: item.productId ?? item.product ?? "",
|
|
1766
|
+
variantId: item.variantId ?? item.variant ?? "",
|
|
1767
|
+
quantity: item.quantity,
|
|
1768
|
+
price: item.price ?? {
|
|
1769
|
+
amount: String(item.totalPrice ?? 0),
|
|
1770
|
+
currencyCode: raw.currencyCode ?? ""
|
|
1771
|
+
},
|
|
1772
|
+
nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
|
|
1773
|
+
billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
|
|
1774
|
+
deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
|
|
1775
|
+
// Map human-readable titles from the v1 SubscriptionItemResource
|
|
1776
|
+
title: item.title,
|
|
1777
|
+
variantTitle: item.subtitle ?? void 0,
|
|
1778
|
+
// recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
|
|
1779
|
+
isOneTime: item.recurringCycleLimit === 1
|
|
1780
|
+
})
|
|
1781
|
+
),
|
|
1782
|
+
discounts: (raw.discounts ?? []).map(
|
|
1783
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1784
|
+
(d3) => {
|
|
1785
|
+
var _a2, _b2, _c;
|
|
1786
|
+
return {
|
|
1787
|
+
id: d3.id,
|
|
1788
|
+
type: ((_a2 = d3.value) == null ? void 0 : _a2.type) === "fixed-amount" || d3.type === "fixed_amount" ? "fixed_amount" : "percentage",
|
|
1789
|
+
value: ((_b2 = d3.value) == null ? void 0 : _b2.amount) ?? ((_c = d3.value) == null ? void 0 : _c.percentage) ?? d3.value ?? 0,
|
|
1790
|
+
code: d3.title ?? d3.code ?? "",
|
|
1791
|
+
description: d3.description ?? ""
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
),
|
|
1795
|
+
status,
|
|
1796
|
+
nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
|
|
1797
|
+
// Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
|
|
1798
|
+
nextBillingDate: raw.nextBillingDate ?? null,
|
|
1799
|
+
// Map customerPaymentMethod — note: arrives as bare id (not expanded) since the
|
|
1800
|
+
// subscriptions endpoint does not support ?expand=paymentMethod; instrument digits
|
|
1801
|
+
// remain a data gap pending backend API extension.
|
|
1802
|
+
customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
|
|
1803
|
+
frequency,
|
|
1804
|
+
createdAt: raw.createdAt,
|
|
1805
|
+
updatedAt: raw.updatedAt,
|
|
1806
|
+
billingPolicy: raw.billingPolicy ?? null,
|
|
1807
|
+
deliveryPolicy: raw.deliveryPolicy ?? null
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
function createApiSubscriptionAdapter(fetcher) {
|
|
1811
|
+
const adapter = {
|
|
1812
|
+
async search(options) {
|
|
1813
|
+
var _a;
|
|
1814
|
+
try {
|
|
1815
|
+
const params = new URLSearchParams();
|
|
1816
|
+
if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
|
|
1817
|
+
const q2 = options.filters.filter((f2) => Object.keys(f2).length > 0).flatMap((f2) => Object.entries(f2).filter(([, v2]) => Boolean(v2))).map(([k, v2]) => `${k}:'${v2}'`).join(" AND ");
|
|
1818
|
+
if (q2) params.set("query", q2);
|
|
1819
|
+
}
|
|
1820
|
+
if (options == null ? void 0 : options.pagination) {
|
|
1821
|
+
if ("after" in options.pagination)
|
|
1822
|
+
params.set("after", options.pagination.after);
|
|
1823
|
+
else if ("before" in options.pagination)
|
|
1824
|
+
params.set("before", options.pagination.before);
|
|
1825
|
+
}
|
|
1826
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1827
|
+
const res = await fetcher(`/customer/v1/subscriptions${query}`);
|
|
1828
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1829
|
+
const body = await res.json();
|
|
1830
|
+
return {
|
|
1831
|
+
_tag: "Success",
|
|
1832
|
+
data: {
|
|
1833
|
+
items: body.data.map(mapSubscription),
|
|
1834
|
+
pageInfo: {
|
|
1835
|
+
hasNextPage: body.hasNextPage,
|
|
1836
|
+
hasPrevPage: body.hasPrevPage,
|
|
1837
|
+
startCursor: body.startCursor,
|
|
1838
|
+
endCursor: body.endCursor
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
} catch (e2) {
|
|
1843
|
+
return { _tag: "Failure", error: e2 };
|
|
1844
|
+
}
|
|
1845
|
+
},
|
|
1846
|
+
async getById(id) {
|
|
1847
|
+
try {
|
|
1848
|
+
const res = await fetcher(
|
|
1849
|
+
`/customer/v1/subscriptions?query=id:"${id}"&limit=1`
|
|
1850
|
+
);
|
|
1851
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1852
|
+
const body = await res.json();
|
|
1853
|
+
const raw = body.data[0];
|
|
1854
|
+
return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
|
|
1855
|
+
} catch (e2) {
|
|
1856
|
+
return { _tag: "Failure", error: e2 };
|
|
1857
|
+
}
|
|
1858
|
+
},
|
|
1859
|
+
async addItem({ subscriptionId, item }) {
|
|
1860
|
+
try {
|
|
1861
|
+
const res = await fetcher(
|
|
1862
|
+
`/customer/v1/subscriptions/${subscriptionId}/items`,
|
|
1863
|
+
{
|
|
1864
|
+
method: "POST",
|
|
1865
|
+
headers: { "Content-Type": "application/json" },
|
|
1866
|
+
body: JSON.stringify(item)
|
|
1867
|
+
}
|
|
1868
|
+
);
|
|
1869
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1870
|
+
return { _tag: "Success", data: void 0 };
|
|
1871
|
+
} catch (e2) {
|
|
1872
|
+
return { _tag: "Failure", error: e2 };
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
1876
|
+
try {
|
|
1877
|
+
const res = await fetcher(
|
|
1878
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
|
|
1879
|
+
{ method: "DELETE" }
|
|
1880
|
+
);
|
|
1881
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1882
|
+
return { _tag: "Success", data: void 0 };
|
|
1883
|
+
} catch (e2) {
|
|
1884
|
+
return { _tag: "Failure", error: e2 };
|
|
1885
|
+
}
|
|
1886
|
+
},
|
|
1887
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1888
|
+
try {
|
|
1889
|
+
const body = {
|
|
1890
|
+
...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
|
|
1891
|
+
...update.quantity !== void 0 ? { quantity: update.quantity } : {},
|
|
1892
|
+
...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
|
|
1893
|
+
...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
|
|
1894
|
+
};
|
|
1895
|
+
const res = await fetcher(
|
|
1896
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1897
|
+
{
|
|
1898
|
+
method: "PUT",
|
|
1899
|
+
headers: { "Content-Type": "application/json" },
|
|
1900
|
+
body: JSON.stringify(body)
|
|
1901
|
+
}
|
|
1902
|
+
);
|
|
1903
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1904
|
+
return { _tag: "Success", data: void 0 };
|
|
1905
|
+
} catch (e2) {
|
|
1906
|
+
return { _tag: "Failure", error: e2 };
|
|
1907
|
+
}
|
|
1908
|
+
},
|
|
1909
|
+
async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
|
|
1910
|
+
try {
|
|
1911
|
+
const res = await fetcher(
|
|
1912
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1913
|
+
{
|
|
1914
|
+
method: "PUT",
|
|
1915
|
+
headers: { "Content-Type": "application/json" },
|
|
1916
|
+
body: JSON.stringify({ nextBillingDate })
|
|
1917
|
+
}
|
|
1918
|
+
);
|
|
1919
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1920
|
+
return { _tag: "Success", data: void 0 };
|
|
1921
|
+
} catch (e2) {
|
|
1922
|
+
return { _tag: "Failure", error: e2 };
|
|
1923
|
+
}
|
|
1924
|
+
},
|
|
1925
|
+
async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
|
|
1926
|
+
try {
|
|
1927
|
+
const res = await fetcher(
|
|
1928
|
+
`/customer/subscriptions/${subscriptionId}/update-box-products`,
|
|
1929
|
+
{
|
|
1930
|
+
method: "POST",
|
|
1931
|
+
headers: { "Content-Type": "application/json" },
|
|
1932
|
+
body: JSON.stringify({ parentId, items, shippingMethodHandle })
|
|
1933
|
+
}
|
|
1934
|
+
);
|
|
1935
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1936
|
+
return { _tag: "Success", data: void 0 };
|
|
1937
|
+
} catch (e2) {
|
|
1938
|
+
return { _tag: "Failure", error: e2 };
|
|
1939
|
+
}
|
|
1940
|
+
},
|
|
1941
|
+
async addDiscount({ subscriptionId, code }) {
|
|
1942
|
+
try {
|
|
1943
|
+
const res = await fetcher(
|
|
1944
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts`,
|
|
1945
|
+
{
|
|
1946
|
+
method: "POST",
|
|
1947
|
+
headers: { "Content-Type": "application/json" },
|
|
1948
|
+
body: JSON.stringify({ code })
|
|
1949
|
+
}
|
|
1950
|
+
);
|
|
1951
|
+
if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
|
|
1952
|
+
return { _tag: "Success", data: void 0 };
|
|
1953
|
+
} catch (e2) {
|
|
1954
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
async removeDiscount({ subscriptionId, discountId }) {
|
|
1958
|
+
try {
|
|
1959
|
+
const res = await fetcher(
|
|
1960
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
|
|
1961
|
+
{ method: "DELETE" }
|
|
1962
|
+
);
|
|
1963
|
+
if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
|
|
1964
|
+
return { _tag: "Success", data: void 0 };
|
|
1965
|
+
} catch (e2) {
|
|
1966
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
|
|
1967
|
+
}
|
|
1968
|
+
},
|
|
1969
|
+
// Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
|
|
1970
|
+
async reschedule({ subscriptionId, newDate }) {
|
|
1971
|
+
try {
|
|
1972
|
+
const res = await fetcher(
|
|
1973
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
1974
|
+
{
|
|
1975
|
+
method: "PATCH",
|
|
1976
|
+
headers: { "Content-Type": "application/json" },
|
|
1977
|
+
body: JSON.stringify({ nextBillingDate: newDate })
|
|
1978
|
+
}
|
|
1979
|
+
);
|
|
1980
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1981
|
+
const data = await res.json();
|
|
1982
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
1983
|
+
} catch (e2) {
|
|
1984
|
+
return { _tag: "Failure", error: e2 };
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
async resume({ subscriptionId }) {
|
|
1988
|
+
try {
|
|
1989
|
+
const res = await fetcher(
|
|
1990
|
+
`/customer/v1/subscriptions/${subscriptionId}/resume`,
|
|
1991
|
+
{ method: "POST" }
|
|
1992
|
+
);
|
|
1993
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1994
|
+
const data = await res.json();
|
|
1995
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
1996
|
+
} catch (e2) {
|
|
1997
|
+
return { _tag: "Failure", error: e2 };
|
|
1998
|
+
}
|
|
1999
|
+
},
|
|
2000
|
+
async pause({ subscriptionId }) {
|
|
2001
|
+
try {
|
|
2002
|
+
const res = await fetcher(
|
|
2003
|
+
`/customer/v1/subscriptions/${subscriptionId}/pause`,
|
|
2004
|
+
{ method: "POST" }
|
|
2005
|
+
);
|
|
2006
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2007
|
+
const data = await res.json();
|
|
2008
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2009
|
+
} catch (e2) {
|
|
2010
|
+
return { _tag: "Failure", error: e2 };
|
|
2011
|
+
}
|
|
2012
|
+
},
|
|
2013
|
+
async reactivate({ subscriptionId }) {
|
|
2014
|
+
try {
|
|
2015
|
+
const res = await fetcher(
|
|
2016
|
+
`/customer/v1/subscriptions/${subscriptionId}/reactivate`,
|
|
2017
|
+
{ method: "POST" }
|
|
2018
|
+
);
|
|
2019
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2020
|
+
const data = await res.json();
|
|
2021
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2022
|
+
} catch (e2) {
|
|
2023
|
+
return { _tag: "Failure", error: e2 };
|
|
2024
|
+
}
|
|
2025
|
+
},
|
|
2026
|
+
async cancel({ subscriptionId, reason }) {
|
|
2027
|
+
try {
|
|
2028
|
+
const res = await fetcher(
|
|
2029
|
+
`/customer/v1/subscriptions/${subscriptionId}/cancel`,
|
|
2030
|
+
{
|
|
2031
|
+
method: "POST",
|
|
2032
|
+
headers: { "Content-Type": "application/json" },
|
|
2033
|
+
body: JSON.stringify({ cancellationReason: reason })
|
|
2034
|
+
}
|
|
2035
|
+
);
|
|
2036
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2037
|
+
const data = await res.json();
|
|
2038
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2039
|
+
} catch (e2) {
|
|
2040
|
+
return { _tag: "Failure", error: e2 };
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
|
|
2044
|
+
async getAvailablePlans({ subscriptionId }) {
|
|
2045
|
+
try {
|
|
2046
|
+
const res = await fetcher(
|
|
2047
|
+
`/customer/subscriptions/${subscriptionId}/plans`
|
|
2048
|
+
);
|
|
2049
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2050
|
+
const data = await res.json();
|
|
2051
|
+
return { _tag: "Success", data };
|
|
2052
|
+
} catch (e2) {
|
|
2053
|
+
return { _tag: "Failure", error: e2 };
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2057
|
+
async getDefaultSubscriptionId() {
|
|
2058
|
+
try {
|
|
2059
|
+
const res = await fetcher(
|
|
2060
|
+
`/customer/subscriptions/one-by-next-billing-date`
|
|
2061
|
+
);
|
|
2062
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2063
|
+
const data = await res.json();
|
|
2064
|
+
return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
|
|
2065
|
+
} catch (e2) {
|
|
2066
|
+
return { _tag: "Failure", error: e2 };
|
|
2067
|
+
}
|
|
2068
|
+
},
|
|
2069
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
|
|
2070
|
+
async getUpsellProducts({ subscriptionId }) {
|
|
2071
|
+
try {
|
|
2072
|
+
const res = await fetcher(
|
|
2073
|
+
`/customer/subscriptions/${subscriptionId}/upsell-products`
|
|
2074
|
+
);
|
|
2075
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2076
|
+
const data = await res.json();
|
|
2077
|
+
const products = data.map((p2) => {
|
|
2078
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2079
|
+
return {
|
|
2080
|
+
id: p2.id,
|
|
2081
|
+
variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
|
|
2082
|
+
title: p2.title ?? "",
|
|
2083
|
+
imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
|
|
2084
|
+
price: {
|
|
2085
|
+
amount: String(((_f = (_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
|
|
2086
|
+
currencyCode: ((_i = (_h = (_g = p2.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
|
|
2087
|
+
},
|
|
2088
|
+
// recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
|
|
2089
|
+
recurring: (_k = (_j = p2.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
|
|
2090
|
+
};
|
|
2091
|
+
});
|
|
2092
|
+
return { _tag: "Success", data: products };
|
|
2093
|
+
} catch (e2) {
|
|
2094
|
+
return { _tag: "Failure", error: e2 };
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
|
|
2098
|
+
async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
|
|
2099
|
+
try {
|
|
2100
|
+
const res = await fetcher(
|
|
2101
|
+
`/customer/subscriptions/${subscriptionId}/upsell`,
|
|
2102
|
+
{
|
|
2103
|
+
method: "POST",
|
|
2104
|
+
headers: { "Content-Type": "application/json" },
|
|
2105
|
+
body: JSON.stringify({
|
|
2106
|
+
productVariantId: variantId,
|
|
2107
|
+
quantity,
|
|
2108
|
+
...oneTime ? { renewalLimit: 1 } : {}
|
|
2109
|
+
})
|
|
2110
|
+
}
|
|
2111
|
+
);
|
|
2112
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2113
|
+
return { _tag: "Success", data: void 0 };
|
|
2114
|
+
} catch (e2) {
|
|
2115
|
+
return { _tag: "Failure", error: e2 };
|
|
2116
|
+
}
|
|
2117
|
+
},
|
|
2118
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2119
|
+
async getLastOrder({ subscriptionId }) {
|
|
2120
|
+
try {
|
|
2121
|
+
const res = await fetcher(
|
|
2122
|
+
`/customer/subscriptions/${subscriptionId}/last-order`
|
|
2123
|
+
);
|
|
2124
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2125
|
+
const data = await res.json();
|
|
2126
|
+
if (!data) return { _tag: "Success", data: null };
|
|
2127
|
+
return {
|
|
2128
|
+
_tag: "Success",
|
|
2129
|
+
data: { id: data.id, createdAt: data.createdAt }
|
|
2130
|
+
};
|
|
2131
|
+
} catch (e2) {
|
|
2132
|
+
return { _tag: "Failure", error: e2 };
|
|
2133
|
+
}
|
|
2134
|
+
},
|
|
2135
|
+
// Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
|
|
2136
|
+
// retry → POST /customer/subscriptions/{id}/retry
|
|
2137
|
+
// renewalId → POST /customer/renewals/{renewalId}/renew
|
|
2138
|
+
// default → POST /customer/subscriptions/{id}/order-now
|
|
2139
|
+
async renewNow({ subscriptionId, renewalId, retry }) {
|
|
2140
|
+
try {
|
|
2141
|
+
const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
|
|
2142
|
+
const res = await fetcher(url, { method: "POST" });
|
|
2143
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2144
|
+
const body = res.status === 204 ? null : await res.json();
|
|
2145
|
+
return {
|
|
2146
|
+
_tag: "Success",
|
|
2147
|
+
data: {
|
|
2148
|
+
nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
|
|
2149
|
+
result: (body == null ? void 0 : body.result) ?? "success"
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
} catch (e2) {
|
|
2153
|
+
return { _tag: "Failure", error: e2 };
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
// Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
|
|
2157
|
+
async changeFrequency({ subscriptionId, plan }) {
|
|
2158
|
+
try {
|
|
2159
|
+
const res = await fetcher(
|
|
2160
|
+
`/customer/subscriptions/${subscriptionId}/plan`,
|
|
2161
|
+
{
|
|
2162
|
+
method: "POST",
|
|
2163
|
+
headers: { "Content-Type": "application/json" },
|
|
2164
|
+
body: JSON.stringify({ plan })
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2168
|
+
const refreshed = await adapter.getById(subscriptionId);
|
|
2169
|
+
if (refreshed._tag === "Failure") return refreshed;
|
|
2170
|
+
if (refreshed.data) return { _tag: "Success", data: refreshed.data };
|
|
2171
|
+
return {
|
|
2172
|
+
_tag: "Failure",
|
|
2173
|
+
error: "Subscription not found after frequency change"
|
|
2174
|
+
};
|
|
2175
|
+
} catch (e2) {
|
|
2176
|
+
return { _tag: "Failure", error: e2 };
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
return adapter;
|
|
2181
|
+
}
|
|
2182
|
+
const DELIVERY_PRICE_CENTS = 500;
|
|
2183
|
+
const DELIVERY_OFFSET_DAYS = 2;
|
|
2184
|
+
function toCents(amount) {
|
|
2185
|
+
const n2 = Number.parseFloat(amount ?? "0");
|
|
2186
|
+
return Number.isFinite(n2) ? Math.round(n2 * 100) : 0;
|
|
2187
|
+
}
|
|
2188
|
+
function addInterval(date, interval, units) {
|
|
2189
|
+
const d3 = new Date(date);
|
|
2190
|
+
switch (interval) {
|
|
2191
|
+
case "DAY":
|
|
2192
|
+
d3.setDate(d3.getDate() + units);
|
|
2193
|
+
break;
|
|
2194
|
+
case "WEEK":
|
|
2195
|
+
d3.setDate(d3.getDate() + units * 7);
|
|
2196
|
+
break;
|
|
2197
|
+
case "YEAR":
|
|
2198
|
+
d3.setFullYear(d3.getFullYear() + units);
|
|
2199
|
+
break;
|
|
2200
|
+
default:
|
|
2201
|
+
d3.setMonth(d3.getMonth() + units);
|
|
2202
|
+
break;
|
|
2203
|
+
}
|
|
2204
|
+
return d3;
|
|
2205
|
+
}
|
|
2206
|
+
function mapItem(sub, item, cycle) {
|
|
2207
|
+
var _a, _b;
|
|
2208
|
+
const price = toCents(((_a = item.currentPrice) == null ? void 0 : _a.amount) ?? item.price.amount);
|
|
2209
|
+
return {
|
|
2210
|
+
id: `${sub.id}_cycle_${cycle}_${item.id}`,
|
|
2211
|
+
title: item.title ?? `Product ${item.variantId}`,
|
|
2212
|
+
subtitle: item.variantTitle ?? null,
|
|
2213
|
+
imageUrl: (_b = item.variantImage) == null ? void 0 : _b.url,
|
|
2214
|
+
variantId: item.variantId,
|
|
2215
|
+
quantity: item.quantity,
|
|
2216
|
+
price,
|
|
2217
|
+
totalPrice: price * item.quantity,
|
|
2218
|
+
variantImage: item.variantImage ?? null,
|
|
2219
|
+
variantAvailableForSale: item.variantAvailableForSale ?? true,
|
|
2220
|
+
subscriptionId: sub.id,
|
|
2221
|
+
subscriptionLineId: item.id,
|
|
2222
|
+
productId: item.productId,
|
|
2223
|
+
parentProductId: item.parentProductId ?? null,
|
|
2224
|
+
parentLineId: item.parentLineId ?? null,
|
|
2225
|
+
isVirtual: item.isVirtual ?? false,
|
|
2226
|
+
isUpsell: item.isUpsell ?? false,
|
|
2227
|
+
isOneTime: item.isOneTime ?? false,
|
|
2228
|
+
customAttributes: (item.customAttributes ?? []).map((a3) => ({
|
|
2229
|
+
name: a3.key,
|
|
2230
|
+
value: a3.value
|
|
2231
|
+
}))
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
function projectUpcomingOrders(sub, count) {
|
|
2235
|
+
var _a, _b;
|
|
2236
|
+
if (sub.status !== "active") return [];
|
|
2237
|
+
const billing = sub.billingPolicy ?? ((_a = sub.items[0]) == null ? void 0 : _a.billingPolicy) ?? { interval: "MONTH", intervalCount: 1 };
|
|
2238
|
+
const baseStr = sub.nextBillingDate ?? (sub.nextDeliveryDate ? sub.nextDeliveryDate.toISOString() : null);
|
|
2239
|
+
const base = baseStr ? new Date(baseStr) : /* @__PURE__ */ new Date();
|
|
2240
|
+
const currencyCode = ((_b = sub.items[0]) == null ? void 0 : _b.price.currencyCode) ?? "USD";
|
|
2241
|
+
const orders = [];
|
|
2242
|
+
for (let cycle = 0; cycle < count; cycle++) {
|
|
2243
|
+
const billingDate = addInterval(
|
|
2244
|
+
base,
|
|
2245
|
+
billing.interval,
|
|
2246
|
+
cycle * billing.intervalCount
|
|
2247
|
+
);
|
|
2248
|
+
const deliveryDate = addInterval(billingDate, "DAY", DELIVERY_OFFSET_DAYS);
|
|
2249
|
+
const items = sub.items.map((item) => mapItem(sub, item, cycle));
|
|
2250
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2251
|
+
orders.push({
|
|
2252
|
+
id: `${sub.id}_cycle_${cycle}`,
|
|
2253
|
+
customerId: sub.customerId,
|
|
2254
|
+
subscriptionId: sub.id,
|
|
2255
|
+
date: billingDate.toISOString(),
|
|
2256
|
+
skipped: false,
|
|
2257
|
+
currencyCode,
|
|
2258
|
+
subtotal,
|
|
2259
|
+
deliveryPrice: DELIVERY_PRICE_CENTS,
|
|
2260
|
+
cycleIndex: cycle,
|
|
2261
|
+
deliveryDate: deliveryDate.toISOString(),
|
|
2262
|
+
status: "scheduled",
|
|
2263
|
+
items,
|
|
2264
|
+
discounts: sub.discounts.map((d3) => ({
|
|
2265
|
+
id: d3.id,
|
|
2266
|
+
title: d3.code,
|
|
2267
|
+
type: d3.type,
|
|
2268
|
+
value: d3.value
|
|
2269
|
+
}))
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
return orders;
|
|
2273
|
+
}
|
|
2274
|
+
function applyAdjustments(orders, adjustments) {
|
|
2275
|
+
return orders.map((order) => {
|
|
2276
|
+
let next = order;
|
|
2277
|
+
for (const adj of adjustments) {
|
|
2278
|
+
if (adj.subscriptionId !== order.subscriptionId) continue;
|
|
2279
|
+
if (adj.cycle !== order.cycleIndex) continue;
|
|
2280
|
+
if (adj.type === "skip") {
|
|
2281
|
+
next = { ...next, skipped: true, status: "skipped" };
|
|
2282
|
+
} else if (adj.type === "date_change" && adj.newDate) {
|
|
2283
|
+
next = { ...next, date: adj.newDate, deliveryDate: adj.newDate };
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
return next;
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
function createMockSchedulesAdapter(subscriptions = createMockSubscriptionAdapter()) {
|
|
2290
|
+
const RESOLVE_DEPTH = 12;
|
|
2291
|
+
let adjustments = [];
|
|
2292
|
+
async function subscriptionsToProject(subscriptionId) {
|
|
2293
|
+
if (subscriptionId) {
|
|
2294
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2295
|
+
return res._tag === "Success" && res.data ? [res.data] : [];
|
|
2296
|
+
}
|
|
2297
|
+
const all = [];
|
|
2298
|
+
let pagination;
|
|
2299
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2300
|
+
const res = await subscriptions.search(pagination ? { pagination } : {});
|
|
2301
|
+
if (res._tag !== "Success") break;
|
|
2302
|
+
all.push(...res.data.items);
|
|
2303
|
+
const { hasNextPage, endCursor } = res.data.pageInfo;
|
|
2304
|
+
if (!hasNextPage || !endCursor) break;
|
|
2305
|
+
pagination = { after: endCursor };
|
|
2306
|
+
}
|
|
2307
|
+
return all;
|
|
2308
|
+
}
|
|
2309
|
+
function resolveCycle(orders, opts) {
|
|
2310
|
+
if (opts.cycle != null) return opts.cycle;
|
|
2311
|
+
if (opts.date) {
|
|
2312
|
+
const target = new Date(opts.date).toISOString().split("T")[0];
|
|
2313
|
+
const match = orders.find(
|
|
2314
|
+
(o2) => o2.deliveryDate.split("T")[0] === target || o2.date.split("T")[0] === target
|
|
2315
|
+
);
|
|
2316
|
+
if (match) return match.cycleIndex;
|
|
2317
|
+
}
|
|
2318
|
+
const firstScheduled = orders.find((o2) => o2.status === "scheduled");
|
|
2319
|
+
return (firstScheduled == null ? void 0 : firstScheduled.cycleIndex) ?? null;
|
|
2320
|
+
}
|
|
2321
|
+
async function recordAdjustment(subscriptionId, opts, build) {
|
|
2322
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2323
|
+
const sub = res._tag === "Success" ? res.data : null;
|
|
2324
|
+
if (!sub) return { _tag: "Failure", error: "No scheduled order found" };
|
|
2325
|
+
const orders = applyAdjustments(
|
|
2326
|
+
projectUpcomingOrders(sub, RESOLVE_DEPTH),
|
|
2327
|
+
adjustments
|
|
2328
|
+
);
|
|
2329
|
+
const cycle = resolveCycle(orders, opts);
|
|
2330
|
+
if (cycle == null) {
|
|
2331
|
+
return { _tag: "Failure", error: "No scheduled order found" };
|
|
2332
|
+
}
|
|
2333
|
+
adjustments = [...adjustments, build(cycle)];
|
|
2334
|
+
return { _tag: "Success", data: void 0 };
|
|
2335
|
+
}
|
|
2336
|
+
return {
|
|
2337
|
+
async getUpcomingOrders({ count, subscriptionId }) {
|
|
2338
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2339
|
+
const subs = await subscriptionsToProject(subscriptionId);
|
|
2340
|
+
const orders = subs.flatMap(
|
|
2341
|
+
(sub) => applyAdjustments(projectUpcomingOrders(sub, count), adjustments)
|
|
2342
|
+
).sort((a3, b3) => a3.date.localeCompare(b3.date));
|
|
2343
|
+
return { _tag: "Success", data: orders.slice(0, count) };
|
|
2344
|
+
},
|
|
2345
|
+
async getAdjustments() {
|
|
2346
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2347
|
+
return { _tag: "Success", data: [...adjustments] };
|
|
2348
|
+
},
|
|
2349
|
+
async skipOrder({ subscriptionId, cycle, date }) {
|
|
2350
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
2351
|
+
return recordAdjustment(subscriptionId, { cycle, date }, (resolved) => ({
|
|
2352
|
+
id: `adj-${Date.now()}`,
|
|
2353
|
+
subscriptionId,
|
|
2354
|
+
type: "skip",
|
|
2355
|
+
cycle: resolved
|
|
2356
|
+
}));
|
|
2357
|
+
},
|
|
2358
|
+
async changeDateAdjustment({ subscriptionId, newDate, currentDate }) {
|
|
2359
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
2360
|
+
return recordAdjustment(
|
|
2361
|
+
subscriptionId,
|
|
2362
|
+
{ date: currentDate },
|
|
2363
|
+
(resolved) => ({
|
|
2364
|
+
id: `adj-${Date.now()}`,
|
|
2365
|
+
subscriptionId,
|
|
2366
|
+
type: "date_change",
|
|
2367
|
+
cycle: resolved,
|
|
2368
|
+
newDate
|
|
2369
|
+
})
|
|
2370
|
+
);
|
|
2371
|
+
},
|
|
2372
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2373
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2374
|
+
adjustments = adjustments.filter((a3) => a3.id !== adjustmentId);
|
|
2375
|
+
return { _tag: "Success", data: void 0 };
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function createApiSchedulesAdapter(fetcher) {
|
|
2380
|
+
return {
|
|
2381
|
+
async getUpcomingOrders({ count, subscriptionId }) {
|
|
2382
|
+
try {
|
|
2383
|
+
const params = new URLSearchParams({ count: String(count) });
|
|
2384
|
+
if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
|
|
2385
|
+
const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
|
|
2386
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2387
|
+
const data = await res.json();
|
|
2388
|
+
return { _tag: "Success", data: data.orders };
|
|
2389
|
+
} catch (e2) {
|
|
2390
|
+
return { _tag: "Failure", error: e2 };
|
|
2391
|
+
}
|
|
2392
|
+
},
|
|
2393
|
+
async getAdjustments() {
|
|
2394
|
+
try {
|
|
2395
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`);
|
|
2396
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2397
|
+
const data = await res.json();
|
|
2398
|
+
const adjustments = data.data.map((a3) => ({
|
|
2399
|
+
id: a3.id,
|
|
2400
|
+
subscriptionId: a3.subscriptionId ?? "",
|
|
2401
|
+
type: a3.type === "CHANGE_DATE" ? "date_change" : "skip",
|
|
2402
|
+
newDate: a3.newDate
|
|
2403
|
+
}));
|
|
2404
|
+
return { _tag: "Success", data: adjustments };
|
|
2405
|
+
} catch (e2) {
|
|
2406
|
+
return { _tag: "Failure", error: e2 };
|
|
2407
|
+
}
|
|
2408
|
+
},
|
|
2409
|
+
async skipOrder({ cycle, date }) {
|
|
2410
|
+
if (cycle == null && date == null) {
|
|
2411
|
+
return { _tag: "Failure", error: "Either cycle or date is required for skipOrder" };
|
|
2412
|
+
}
|
|
2413
|
+
try {
|
|
2414
|
+
const matcher = cycle != null ? { type: "CYCLE", input: { cycle } } : { type: "DATE", input: { date: new Date(date).toISOString() } };
|
|
2415
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2416
|
+
method: "POST",
|
|
2417
|
+
headers: { "Content-Type": "application/json" },
|
|
2418
|
+
body: JSON.stringify({
|
|
2419
|
+
matcher,
|
|
2420
|
+
action: { type: "SKIP_ORDER", input: { reason: "" } }
|
|
2421
|
+
})
|
|
2422
|
+
});
|
|
2423
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2424
|
+
return { _tag: "Success", data: void 0 };
|
|
2425
|
+
} catch (e2) {
|
|
2426
|
+
return { _tag: "Failure", error: e2 };
|
|
2427
|
+
}
|
|
2428
|
+
},
|
|
2429
|
+
async changeDateAdjustment({ newDate, currentDate }) {
|
|
2430
|
+
if (currentDate == null) {
|
|
2431
|
+
return {
|
|
2432
|
+
_tag: "Failure",
|
|
2433
|
+
error: "currentDate is required for changeDateAdjustment"
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
const matcher = { type: "DATE", input: { date: new Date(currentDate).toISOString() } };
|
|
2438
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2439
|
+
method: "POST",
|
|
2440
|
+
headers: { "Content-Type": "application/json" },
|
|
2441
|
+
body: JSON.stringify({
|
|
2442
|
+
matcher,
|
|
2443
|
+
action: {
|
|
2444
|
+
type: "CHANGE_DATE",
|
|
2445
|
+
input: { newDate: new Date(newDate).toISOString() }
|
|
2446
|
+
}
|
|
2447
|
+
})
|
|
2448
|
+
});
|
|
2449
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2450
|
+
return { _tag: "Success", data: void 0 };
|
|
2451
|
+
} catch (e2) {
|
|
2452
|
+
return { _tag: "Failure", error: e2 };
|
|
2453
|
+
}
|
|
2454
|
+
},
|
|
2455
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2456
|
+
try {
|
|
2457
|
+
const res = await fetcher(
|
|
2458
|
+
`/customer/v1/schedules/adjustments/${adjustmentId}`,
|
|
2459
|
+
{ method: "DELETE" }
|
|
2460
|
+
);
|
|
2461
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2462
|
+
return { _tag: "Success", data: void 0 };
|
|
2463
|
+
} catch (e2) {
|
|
2464
|
+
return { _tag: "Failure", error: e2 };
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
const SchedulesServiceContext = createContext("services/schedules");
|
|
2470
|
+
function createSchedulesService(adapter, opts) {
|
|
2471
|
+
const current = d(null);
|
|
2472
|
+
const currentLoading = d(false);
|
|
2473
|
+
const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
|
|
2474
|
+
let reloadToken = 0;
|
|
2475
|
+
async function getUpcomingOrder() {
|
|
2476
|
+
const result = await adapter.getUpcomingOrders({ count: 1 });
|
|
2477
|
+
if (result._tag === "Failure") return result;
|
|
2478
|
+
return { _tag: "Success", data: result.data[0] ?? null };
|
|
2479
|
+
}
|
|
2480
|
+
async function getUpcomingOrders(count) {
|
|
2481
|
+
return adapter.getUpcomingOrders({ count });
|
|
2482
|
+
}
|
|
2483
|
+
async function reloadCurrent(subscriptionId) {
|
|
2484
|
+
var _a;
|
|
2485
|
+
const token = ++reloadToken;
|
|
2486
|
+
currentLoading.value = true;
|
|
2487
|
+
const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
|
|
2488
|
+
const result = await adapter.getUpcomingOrders({
|
|
2489
|
+
count,
|
|
2490
|
+
subscriptionId
|
|
2491
|
+
});
|
|
2492
|
+
if (token !== reloadToken) return;
|
|
2493
|
+
currentLoading.value = false;
|
|
2494
|
+
if (result._tag === "Success") {
|
|
2495
|
+
current.value = result.data;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return {
|
|
2499
|
+
current,
|
|
2500
|
+
currentLoading,
|
|
2501
|
+
hasPendingBilling,
|
|
2502
|
+
getUpcomingOrder,
|
|
2503
|
+
getUpcomingOrders,
|
|
2504
|
+
async getAdjustments() {
|
|
2505
|
+
return adapter.getAdjustments();
|
|
2506
|
+
},
|
|
2507
|
+
async skipOrder(params) {
|
|
2508
|
+
const result = await adapter.skipOrder(params);
|
|
2509
|
+
if (result._tag === "Success") {
|
|
2510
|
+
await reloadCurrent(params.subscriptionId);
|
|
2511
|
+
}
|
|
2512
|
+
return result;
|
|
2513
|
+
},
|
|
2514
|
+
async changeDateAdjustment(params) {
|
|
2515
|
+
const result = await adapter.changeDateAdjustment(params);
|
|
2516
|
+
if (result._tag === "Success") {
|
|
2517
|
+
await reloadCurrent(params.subscriptionId);
|
|
2518
|
+
}
|
|
2519
|
+
return result;
|
|
2520
|
+
},
|
|
2521
|
+
async removeAdjustment(params) {
|
|
2522
|
+
return adapter.removeAdjustment(params);
|
|
2523
|
+
},
|
|
2524
|
+
reloadCurrent
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
const SettingsServiceContext = createContext("services/settings");
|
|
2528
|
+
function createSettingsService(initial = {}) {
|
|
2529
|
+
const current = d(initial);
|
|
2530
|
+
return {
|
|
2531
|
+
current,
|
|
2532
|
+
get(key) {
|
|
2533
|
+
return current.value[key];
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
function createShopService(options) {
|
|
2538
|
+
const contactEmail = d(options.getContactEmail());
|
|
2539
|
+
const service = { contactEmail };
|
|
2540
|
+
return service;
|
|
2541
|
+
}
|
|
2542
|
+
const ShopServiceContext = createContext("services/shop");
|
|
1109
2543
|
const SubscriptionServiceContext = createContext("services/subscription");
|
|
1110
2544
|
function getCurrentSubscriptionId() {
|
|
1111
2545
|
if (typeof window === "undefined") {
|
|
@@ -1128,41 +2562,83 @@ function getCurrentSubscriptionId() {
|
|
|
1128
2562
|
}
|
|
1129
2563
|
function createSubscriptionService(adapter) {
|
|
1130
2564
|
const current = d(null);
|
|
1131
|
-
const
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
2565
|
+
const hasPendingBilling = d(false);
|
|
2566
|
+
const discountsChangedAt = d(0);
|
|
2567
|
+
const itemsChangedAt = d(0);
|
|
2568
|
+
const lastOrder = d(null);
|
|
2569
|
+
async function refreshAfterDiscountChange() {
|
|
2570
|
+
await reload();
|
|
2571
|
+
discountsChangedAt.value = discountsChangedAt.value + 1;
|
|
1138
2572
|
}
|
|
1139
|
-
async function
|
|
1140
|
-
await
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
async function refreshAfterItemChange(subscriptionId) {
|
|
1144
|
-
await refreshCurrent(subscriptionId);
|
|
1145
|
-
itemsChangedAtSignal.value = itemsChangedAtSignal.value + 1;
|
|
2573
|
+
async function refreshAfterItemChange() {
|
|
2574
|
+
await reload();
|
|
2575
|
+
itemsChangedAt.value = itemsChangedAt.value + 1;
|
|
1146
2576
|
}
|
|
1147
2577
|
function subscriptionIdFromCurrent() {
|
|
1148
2578
|
var _a;
|
|
1149
2579
|
return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
|
|
1150
2580
|
}
|
|
2581
|
+
async function reload() {
|
|
2582
|
+
var _a, _b;
|
|
2583
|
+
const id = (_a = current.value) == null ? void 0 : _a.id;
|
|
2584
|
+
if (!id) return;
|
|
2585
|
+
const [subResult, lastOrderResult] = await Promise.all([
|
|
2586
|
+
adapter.getById(id),
|
|
2587
|
+
adapter.getLastOrder({ subscriptionId: id })
|
|
2588
|
+
]);
|
|
2589
|
+
if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
|
|
2590
|
+
if (subResult._tag === "Success") current.value = subResult.data;
|
|
2591
|
+
if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
|
|
2592
|
+
}
|
|
1151
2593
|
const service = {
|
|
2594
|
+
current,
|
|
2595
|
+
hasPendingBilling,
|
|
2596
|
+
discountsChangedAt,
|
|
2597
|
+
itemsChangedAt,
|
|
2598
|
+
lastOrder,
|
|
1152
2599
|
search: adapter.search,
|
|
2600
|
+
// Prefer the adapter's rich cross-subscription list (legacy endpoint, active-first).
|
|
2601
|
+
// Fall back to a single `search` page for adapters that don't implement it
|
|
2602
|
+
// (e.g. the editor mock) so the cross-subscription views still render.
|
|
2603
|
+
listAll: adapter.listAll ? adapter.listAll.bind(adapter) : async () => {
|
|
2604
|
+
const result = await adapter.search();
|
|
2605
|
+
if (result._tag !== "Success") return result;
|
|
2606
|
+
return { _tag: "Success", data: result.data.items };
|
|
2607
|
+
},
|
|
1153
2608
|
getById: adapter.getById,
|
|
1154
2609
|
getCurrentSubscriptionId,
|
|
1155
2610
|
addItem: async (subscriptionId, item) => {
|
|
1156
2611
|
const result = await adapter.addItem({ subscriptionId, item });
|
|
1157
2612
|
if (result._tag === "Success") {
|
|
1158
|
-
await refreshAfterItemChange(
|
|
2613
|
+
await refreshAfterItemChange();
|
|
1159
2614
|
}
|
|
1160
2615
|
return result;
|
|
1161
2616
|
},
|
|
1162
2617
|
removeItem: async (subscriptionId, itemId) => {
|
|
1163
2618
|
const result = await adapter.removeItem({ subscriptionId, itemId });
|
|
1164
2619
|
if (result._tag === "Success") {
|
|
1165
|
-
await refreshAfterItemChange(
|
|
2620
|
+
await refreshAfterItemChange();
|
|
2621
|
+
}
|
|
2622
|
+
return result;
|
|
2623
|
+
},
|
|
2624
|
+
updateItem: async (subscriptionId, lineId, update) => {
|
|
2625
|
+
const result = await adapter.updateItem({ subscriptionId, lineId, update });
|
|
2626
|
+
if (result._tag === "Success") {
|
|
2627
|
+
await refreshAfterItemChange();
|
|
2628
|
+
}
|
|
2629
|
+
return result;
|
|
2630
|
+
},
|
|
2631
|
+
skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
|
|
2632
|
+
const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
|
|
2633
|
+
if (result._tag === "Success") {
|
|
2634
|
+
await refreshAfterItemChange();
|
|
2635
|
+
}
|
|
2636
|
+
return result;
|
|
2637
|
+
},
|
|
2638
|
+
updateBoxItems: async (subscriptionId, params) => {
|
|
2639
|
+
const result = await adapter.updateBoxItems({ subscriptionId, ...params });
|
|
2640
|
+
if (result._tag === "Success") {
|
|
2641
|
+
await refreshAfterItemChange();
|
|
1166
2642
|
}
|
|
1167
2643
|
return result;
|
|
1168
2644
|
},
|
|
@@ -1176,7 +2652,7 @@ function createSubscriptionService(adapter) {
|
|
|
1176
2652
|
}
|
|
1177
2653
|
const result = await adapter.addDiscount({ subscriptionId: id, code });
|
|
1178
2654
|
if (result._tag === "Success") {
|
|
1179
|
-
await refreshAfterDiscountChange(
|
|
2655
|
+
await refreshAfterDiscountChange();
|
|
1180
2656
|
}
|
|
1181
2657
|
return result;
|
|
1182
2658
|
},
|
|
@@ -1193,7 +2669,7 @@ function createSubscriptionService(adapter) {
|
|
|
1193
2669
|
discountId
|
|
1194
2670
|
});
|
|
1195
2671
|
if (result._tag === "Success") {
|
|
1196
|
-
await refreshAfterDiscountChange(
|
|
2672
|
+
await refreshAfterDiscountChange();
|
|
1197
2673
|
}
|
|
1198
2674
|
return result;
|
|
1199
2675
|
},
|
|
@@ -1202,11 +2678,13 @@ function createSubscriptionService(adapter) {
|
|
|
1202
2678
|
pause: (subscriptionId) => adapter.pause({ subscriptionId }),
|
|
1203
2679
|
reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
|
|
1204
2680
|
cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
|
|
2681
|
+
renewNow: (params) => adapter.renewNow(params),
|
|
2682
|
+
changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
|
|
2683
|
+
reload,
|
|
1205
2684
|
getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
|
|
1206
2685
|
getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
itemsChangedAt: itemsChangedAtSignal
|
|
2686
|
+
getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
|
|
2687
|
+
addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
|
|
1210
2688
|
};
|
|
1211
2689
|
return service;
|
|
1212
2690
|
}
|
|
@@ -1475,66 +2953,88 @@ export {
|
|
|
1475
2953
|
AuthenticationError,
|
|
1476
2954
|
AuthenticationErrorReason,
|
|
1477
2955
|
B as BlockWrapperEvent,
|
|
1478
|
-
|
|
1479
|
-
|
|
2956
|
+
m as ContextProvideEvent,
|
|
2957
|
+
p as ContextRequestEvent,
|
|
1480
2958
|
CustomerServiceContext,
|
|
1481
|
-
|
|
2959
|
+
D2 as DEFAULT_GLOBAL_STYLES,
|
|
1482
2960
|
DISCOUNT_REJECTION_REASONS,
|
|
1483
2961
|
LoginServiceContext,
|
|
1484
2962
|
OrdersServiceContext,
|
|
2963
|
+
OverlayServiceContext,
|
|
1485
2964
|
ProductServiceContext,
|
|
1486
2965
|
RouterServiceContext,
|
|
2966
|
+
SchedulesServiceContext,
|
|
1487
2967
|
S as SelectedBlockContext,
|
|
2968
|
+
SettingsServiceContext,
|
|
2969
|
+
ShopServiceContext,
|
|
1488
2970
|
u as Signal,
|
|
1489
2971
|
StateContext,
|
|
1490
2972
|
SubscriptionServiceContext,
|
|
1491
|
-
|
|
1492
|
-
|
|
2973
|
+
a2 as ThemeStateContext,
|
|
2974
|
+
T as TranslationContext,
|
|
1493
2975
|
W as WorkflowServiceContext,
|
|
1494
|
-
|
|
1495
|
-
|
|
2976
|
+
q as __recordDevtoolsInstance,
|
|
2977
|
+
_ as __recordDevtoolsPropSetter,
|
|
2978
|
+
t as attachEmptyTracking,
|
|
2979
|
+
b as backfillInstanceDefaults,
|
|
1496
2980
|
w as computed,
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2981
|
+
createApiCustomerAdapter,
|
|
2982
|
+
createApiLoginAdapter,
|
|
2983
|
+
createApiProductAdapter,
|
|
2984
|
+
createApiSchedulesAdapter,
|
|
2985
|
+
createApiSubscriptionAdapter,
|
|
2986
|
+
createBlockInstance,
|
|
2987
|
+
v as createBlockInstanceFromJSON,
|
|
2988
|
+
f as createBlockInstanceFromObject,
|
|
2989
|
+
x as createCeRenderer,
|
|
1501
2990
|
createContext,
|
|
1502
2991
|
createCustomerService,
|
|
2992
|
+
createLoginService,
|
|
2993
|
+
createMockCustomerAdapter,
|
|
2994
|
+
createMockLoginAdapter,
|
|
1503
2995
|
createMockOrdersAdapter,
|
|
1504
2996
|
createMockProductAdapter,
|
|
1505
2997
|
createMockRouterService,
|
|
2998
|
+
createMockSchedulesAdapter,
|
|
1506
2999
|
createMockSubscriptionAdapter,
|
|
1507
3000
|
createMockWorkflowApiAdapter,
|
|
1508
3001
|
createOrdersService,
|
|
3002
|
+
createOverlayService,
|
|
1509
3003
|
createProductService,
|
|
1510
3004
|
createRouterService,
|
|
1511
|
-
|
|
3005
|
+
createSchedulesService,
|
|
3006
|
+
y as createSelectedBlock,
|
|
3007
|
+
createSettingsService,
|
|
3008
|
+
createShopService,
|
|
1512
3009
|
createState,
|
|
1513
3010
|
createSubscriptionService,
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
3011
|
+
c as createThemeState,
|
|
3012
|
+
b2 as createTranslationService,
|
|
3013
|
+
d2 as createWorkflowApiAdapter,
|
|
3014
|
+
e as createWorkflowService,
|
|
3015
|
+
defineBlock,
|
|
3016
|
+
z as defineBlockRuntime,
|
|
3017
|
+
defineOverlayWrapper,
|
|
1520
3018
|
E as effect,
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
F as
|
|
3019
|
+
g as getDefaultBlockForAction,
|
|
3020
|
+
getDefinedBlocks,
|
|
3021
|
+
A as getDevtoolsBlockState,
|
|
3022
|
+
h as hasDefaultBlockForAction,
|
|
3023
|
+
D as hasRenderedContent,
|
|
3024
|
+
i2 as indexOverrides,
|
|
3025
|
+
i as injectContext,
|
|
3026
|
+
F as isEditorPreview,
|
|
3027
|
+
a as isStubDefinition,
|
|
1529
3028
|
loadBlockRegistry,
|
|
1530
3029
|
loginService,
|
|
1531
|
-
|
|
1532
|
-
|
|
3030
|
+
o as onRegisterBlock,
|
|
3031
|
+
G as provideContext,
|
|
1533
3032
|
registerBlock,
|
|
1534
|
-
|
|
3033
|
+
j as serializeBlocks,
|
|
3034
|
+
H as setDevtoolsBlockProp,
|
|
1535
3035
|
d as signal,
|
|
1536
|
-
|
|
1537
|
-
|
|
3036
|
+
s as subscribeContext,
|
|
3037
|
+
n as untracked,
|
|
1538
3038
|
waitForDevBlocksUrl,
|
|
1539
|
-
|
|
3039
|
+
w2 as withWrapper
|
|
1540
3040
|
};
|