@juo/blocks 1.12.1 → 1.13.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +1910 -362
- 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/orders/adapter.d.ts +2 -0
- package/dist/services/orders/adapter.d.ts.map +1 -1
- package/dist/services/orders/index.d.ts +1 -1
- package/dist/services/orders/index.d.ts.map +1 -1
- 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 +212 -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
|
}
|
|
@@ -572,6 +742,55 @@ function createMockOrdersAdapter() {
|
|
|
572
742
|
}
|
|
573
743
|
};
|
|
574
744
|
}
|
|
745
|
+
function mapOrderLine(item) {
|
|
746
|
+
var _a;
|
|
747
|
+
return {
|
|
748
|
+
id: item.id,
|
|
749
|
+
title: item.title,
|
|
750
|
+
quantity: item.quantity,
|
|
751
|
+
variantImage: ((_a = item.variantImage) == null ? void 0 : _a.url) ? { url: item.variantImage.url, altText: item.variantImage.altText ?? void 0 } : null,
|
|
752
|
+
customAttributes: item.customAttributes
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
function mapOrder(order) {
|
|
756
|
+
return {
|
|
757
|
+
id: order.id,
|
|
758
|
+
createdAt: order.createdAt,
|
|
759
|
+
placedAt: order.placedAt,
|
|
760
|
+
currencyCode: order.currencyCode,
|
|
761
|
+
totalPrice: order.totalPrice,
|
|
762
|
+
financialStatus: order.financialStatus,
|
|
763
|
+
fulfillmentStatus: order.fulfillmentStatus,
|
|
764
|
+
lines: order.items.map(mapOrderLine),
|
|
765
|
+
customAttributes: order.customAttributes
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function createApiOrdersAdapter(fetcher) {
|
|
769
|
+
return {
|
|
770
|
+
async getOrders(params) {
|
|
771
|
+
const query = new URLSearchParams();
|
|
772
|
+
if ((params == null ? void 0 : params.limit) != null) query.set("limit", String(params.limit));
|
|
773
|
+
if ((params == null ? void 0 : params.after) != null) query.set("after", params.after);
|
|
774
|
+
if ((params == null ? void 0 : params.before) != null) query.set("before", params.before);
|
|
775
|
+
if ((params == null ? void 0 : params.query) != null) query.set("query", params.query);
|
|
776
|
+
if ((params == null ? void 0 : params.includeOneTime) != null) query.set("includeOneTime", String(params.includeOneTime));
|
|
777
|
+
if ((params == null ? void 0 : params.sort) != null) query.set("sort", params.sort);
|
|
778
|
+
const qs = query.toString();
|
|
779
|
+
const res = await fetcher(`/customer/v1/orders${qs ? `?${qs}` : ""}`);
|
|
780
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
781
|
+
const body = await res.json();
|
|
782
|
+
return {
|
|
783
|
+
items: body.data.map(mapOrder),
|
|
784
|
+
pageInfo: {
|
|
785
|
+
hasNextPage: body.hasNextPage,
|
|
786
|
+
hasPrevPage: body.hasPrevPage,
|
|
787
|
+
startCursor: body.startCursor,
|
|
788
|
+
endCursor: body.endCursor
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
575
794
|
function createOrdersService(adapter) {
|
|
576
795
|
return {
|
|
577
796
|
async getOrders(params) {
|
|
@@ -591,7 +810,7 @@ function createMockProductAdapter() {
|
|
|
591
810
|
id: "sample-1",
|
|
592
811
|
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
593
812
|
featuredImage: {
|
|
594
|
-
url: "https://
|
|
813
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
595
814
|
},
|
|
596
815
|
hasOnlyDefaultVariant: true,
|
|
597
816
|
variants: [
|
|
@@ -599,7 +818,7 @@ function createMockProductAdapter() {
|
|
|
599
818
|
id: "sample-1-1",
|
|
600
819
|
title: "150 ml bottle",
|
|
601
820
|
image: {
|
|
602
|
-
url: "https://
|
|
821
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
603
822
|
},
|
|
604
823
|
price: {
|
|
605
824
|
amount: "12.49",
|
|
@@ -625,7 +844,7 @@ function createMockProductAdapter() {
|
|
|
625
844
|
id: "sample-2",
|
|
626
845
|
title: "More COLLAGEN",
|
|
627
846
|
featuredImage: {
|
|
628
|
-
url: "https://
|
|
847
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
629
848
|
},
|
|
630
849
|
hasOnlyDefaultVariant: true,
|
|
631
850
|
variants: [
|
|
@@ -633,7 +852,7 @@ function createMockProductAdapter() {
|
|
|
633
852
|
id: "sample-2-1",
|
|
634
853
|
title: "blackcurrant",
|
|
635
854
|
image: {
|
|
636
|
-
url: "https://
|
|
855
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
637
856
|
},
|
|
638
857
|
price: {
|
|
639
858
|
amount: "12.49",
|
|
@@ -654,7 +873,7 @@ function createMockProductAdapter() {
|
|
|
654
873
|
id: "sample-3",
|
|
655
874
|
title: "Gentle deodorant for underarms and bust",
|
|
656
875
|
featuredImage: {
|
|
657
|
-
url: "https://
|
|
876
|
+
url: "https://placehold.co/400x400?text=Product+3"
|
|
658
877
|
},
|
|
659
878
|
hasOnlyDefaultVariant: false,
|
|
660
879
|
variants: [
|
|
@@ -662,7 +881,7 @@ function createMockProductAdapter() {
|
|
|
662
881
|
id: "sample-3-1",
|
|
663
882
|
title: "Green tea and jasmine",
|
|
664
883
|
image: {
|
|
665
|
-
url: "https://
|
|
884
|
+
url: "https://placehold.co/400x400?text=Product+3a"
|
|
666
885
|
},
|
|
667
886
|
price: {
|
|
668
887
|
amount: "6.7",
|
|
@@ -680,7 +899,7 @@ function createMockProductAdapter() {
|
|
|
680
899
|
id: "sample-3-2",
|
|
681
900
|
title: "Lavender and mint",
|
|
682
901
|
image: {
|
|
683
|
-
url: "https://
|
|
902
|
+
url: "https://placehold.co/400x400?text=Product+3b"
|
|
684
903
|
},
|
|
685
904
|
price: {
|
|
686
905
|
amount: "7.5",
|
|
@@ -712,6 +931,48 @@ function createMockProductAdapter() {
|
|
|
712
931
|
}
|
|
713
932
|
};
|
|
714
933
|
}
|
|
934
|
+
function toAvailableProduct(product) {
|
|
935
|
+
return {
|
|
936
|
+
id: product.id,
|
|
937
|
+
title: product.title,
|
|
938
|
+
description: `Sample description for ${product.title}.`,
|
|
939
|
+
featuredImage: product.featuredImage ?? void 0,
|
|
940
|
+
hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
|
|
941
|
+
variants: product.variants.map((variant) => {
|
|
942
|
+
var _a, _b, _c;
|
|
943
|
+
const base = Number.parseFloat(variant.price.amount);
|
|
944
|
+
const sub = (_b = (_a = variant.purchaseOptions) == null ? void 0 : _a[0]) == null ? void 0 : _b.price.amount;
|
|
945
|
+
const current = sub == null ? base : Number.parseFloat(sub);
|
|
946
|
+
return {
|
|
947
|
+
id: variant.id,
|
|
948
|
+
availableForSale: true,
|
|
949
|
+
title: variant.title,
|
|
950
|
+
currentPrice: { amount: current, currencyCode: variant.price.currencyCode },
|
|
951
|
+
basePrice: { amount: base, currencyCode: variant.price.currencyCode },
|
|
952
|
+
image: variant.image ?? void 0,
|
|
953
|
+
recurring: (((_c = variant.purchaseOptions) == null ? void 0 : _c.length) ?? 0) > 0
|
|
954
|
+
};
|
|
955
|
+
})
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const sampleBox = {
|
|
959
|
+
id: "sample-box-1",
|
|
960
|
+
title: "Build Your Own Box",
|
|
961
|
+
minProductsCount: 3,
|
|
962
|
+
parentProduct: "sample-1",
|
|
963
|
+
tiers: [
|
|
964
|
+
{ value: 5, threshold: 3 },
|
|
965
|
+
{ value: 10, threshold: 5 }
|
|
966
|
+
],
|
|
967
|
+
variants: products.map((product) => ({
|
|
968
|
+
id: product.id,
|
|
969
|
+
variants: product.variants.map((variant) => ({ id: variant.id }))
|
|
970
|
+
})),
|
|
971
|
+
freeVariants: [],
|
|
972
|
+
thresholdType: "LINES_COUNT",
|
|
973
|
+
type: "PERCENTAGE",
|
|
974
|
+
restrictQuantityToTiers: false
|
|
975
|
+
};
|
|
715
976
|
return {
|
|
716
977
|
async getById(id) {
|
|
717
978
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -725,6 +986,27 @@ function createMockProductAdapter() {
|
|
|
725
986
|
);
|
|
726
987
|
return product == null ? null : processProduct(product);
|
|
727
988
|
},
|
|
989
|
+
async getProductsByIds({ ids }) {
|
|
990
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
991
|
+
return products.filter((product) => ids.includes(product.id)).map(({ variants: _variants, ...product }) => product);
|
|
992
|
+
},
|
|
993
|
+
async getBoxVariants({ ids }) {
|
|
994
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
995
|
+
return products.filter((product) => ids.includes(product.id)).map(toAvailableProduct);
|
|
996
|
+
},
|
|
997
|
+
async getAvailableProducts({ q: q2 }) {
|
|
998
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
999
|
+
const queryLower = q2 == null ? void 0 : q2.toLowerCase();
|
|
1000
|
+
return products.filter(
|
|
1001
|
+
(product) => !queryLower || product.title.toLowerCase().includes(queryLower) || product.variants.some(
|
|
1002
|
+
(variant) => variant.title.toLowerCase().includes(queryLower)
|
|
1003
|
+
)
|
|
1004
|
+
).map(toAvailableProduct);
|
|
1005
|
+
},
|
|
1006
|
+
async getBox({ id }) {
|
|
1007
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1008
|
+
return id === sampleBox.id ? sampleBox : null;
|
|
1009
|
+
},
|
|
728
1010
|
async search(opts) {
|
|
729
1011
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
730
1012
|
let filteredProducts = [...products];
|
|
@@ -765,34 +1047,217 @@ function createMockProductAdapter() {
|
|
|
765
1047
|
}
|
|
766
1048
|
};
|
|
767
1049
|
}
|
|
768
|
-
|
|
769
|
-
|
|
1050
|
+
function mapVariant(variant, currencyCode) {
|
|
1051
|
+
var _a;
|
|
770
1052
|
return {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
},
|
|
774
|
-
|
|
775
|
-
|
|
1053
|
+
id: variant.id,
|
|
1054
|
+
title: variant.title,
|
|
1055
|
+
image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
|
|
1056
|
+
price: { amount: variant.price.toFixed(2), currencyCode },
|
|
1057
|
+
...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
|
|
1058
|
+
purchaseOptions: variant.purchaseOptions.map((opt) => ({
|
|
1059
|
+
name: opt.name,
|
|
1060
|
+
price: { amount: opt.price.toFixed(2), currencyCode },
|
|
1061
|
+
interval: opt.interval
|
|
1062
|
+
}))
|
|
1063
|
+
} : {}
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
function mapProduct(product, currencyCode) {
|
|
1067
|
+
return {
|
|
1068
|
+
id: product.id,
|
|
1069
|
+
title: product.title,
|
|
1070
|
+
featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
|
|
1071
|
+
hasOnlyDefaultVariant: product.variants.length <= 1,
|
|
1072
|
+
variants: {
|
|
1073
|
+
items: product.variants.map((v2) => mapVariant(v2, currencyCode)),
|
|
1074
|
+
pageInfo: {
|
|
1075
|
+
hasNextPage: false,
|
|
1076
|
+
hasPrevPage: false,
|
|
1077
|
+
startCursor: null,
|
|
1078
|
+
endCursor: null
|
|
1079
|
+
}
|
|
776
1080
|
},
|
|
777
|
-
|
|
778
|
-
return adapter.search(opts);
|
|
779
|
-
}
|
|
1081
|
+
collections: product.collections
|
|
780
1082
|
};
|
|
781
1083
|
}
|
|
782
|
-
function
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
}
|
|
1084
|
+
function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
|
|
1085
|
+
const { currencyCode } = opts;
|
|
1086
|
+
async function fetchProducts(params) {
|
|
1087
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1088
|
+
const res = await fetcher(`/customer/v1/products${query}`);
|
|
1089
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1090
|
+
return res.json();
|
|
790
1091
|
}
|
|
791
|
-
return
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1092
|
+
return {
|
|
1093
|
+
async getById(id) {
|
|
1094
|
+
try {
|
|
1095
|
+
const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
|
|
1096
|
+
const body = await fetchProducts(params);
|
|
1097
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1098
|
+
} catch {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
},
|
|
1102
|
+
async getByVariantId(variantId) {
|
|
1103
|
+
try {
|
|
1104
|
+
const params = new URLSearchParams({
|
|
1105
|
+
query: `variants.id:"${variantId}"`,
|
|
1106
|
+
limit: "1"
|
|
1107
|
+
});
|
|
1108
|
+
const body = await fetchProducts(params);
|
|
1109
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1110
|
+
} catch {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
async search(opts2) {
|
|
1115
|
+
try {
|
|
1116
|
+
const params = new URLSearchParams();
|
|
1117
|
+
if (opts2 == null ? void 0 : opts2.query) {
|
|
1118
|
+
params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
|
|
1119
|
+
}
|
|
1120
|
+
if (opts2 == null ? void 0 : opts2.pagination) {
|
|
1121
|
+
if ("after" in opts2.pagination)
|
|
1122
|
+
params.set("after", opts2.pagination.after);
|
|
1123
|
+
else if ("before" in opts2.pagination)
|
|
1124
|
+
params.set("before", opts2.pagination.before);
|
|
1125
|
+
}
|
|
1126
|
+
const body = await fetchProducts(params);
|
|
1127
|
+
return {
|
|
1128
|
+
items: body.data.map((p2) => mapProduct(p2, currencyCode)),
|
|
1129
|
+
pageInfo: {
|
|
1130
|
+
hasNextPage: body.hasNextPage,
|
|
1131
|
+
hasPrevPage: body.hasPrevPage,
|
|
1132
|
+
startCursor: body.startCursor,
|
|
1133
|
+
endCursor: body.endCursor
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
} catch {
|
|
1137
|
+
return {
|
|
1138
|
+
items: [],
|
|
1139
|
+
pageInfo: {
|
|
1140
|
+
hasNextPage: false,
|
|
1141
|
+
hasPrevPage: false,
|
|
1142
|
+
startCursor: null,
|
|
1143
|
+
endCursor: null
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
},
|
|
1148
|
+
async getProductsByIds({ ids }) {
|
|
1149
|
+
const params = new URLSearchParams();
|
|
1150
|
+
for (const id of ids) params.append("ids", id);
|
|
1151
|
+
const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
|
|
1152
|
+
if (!res.ok) return [];
|
|
1153
|
+
const data = await res.json();
|
|
1154
|
+
return (data ?? []).map((p2) => ({
|
|
1155
|
+
id: p2.id,
|
|
1156
|
+
title: p2.title ?? "",
|
|
1157
|
+
featuredImage: p2.featuredImage ? { url: p2.featuredImage.url, altText: p2.featuredImage.altText } : null,
|
|
1158
|
+
hasOnlyDefaultVariant: true,
|
|
1159
|
+
collections: []
|
|
1160
|
+
}));
|
|
1161
|
+
},
|
|
1162
|
+
async getBoxVariants({ ids, subscriptionId }) {
|
|
1163
|
+
const params = new URLSearchParams();
|
|
1164
|
+
for (const id of ids) params.append("ids", id);
|
|
1165
|
+
params.set("subscriptionId", subscriptionId);
|
|
1166
|
+
const res = await fetcher(`/customer/products/variants?${params.toString()}`);
|
|
1167
|
+
if (!res.ok) return [];
|
|
1168
|
+
const data = await res.json();
|
|
1169
|
+
return (data ?? []).map((p2) => {
|
|
1170
|
+
var _a;
|
|
1171
|
+
return {
|
|
1172
|
+
id: p2.id,
|
|
1173
|
+
title: p2.title,
|
|
1174
|
+
description: p2.description ?? "",
|
|
1175
|
+
featuredImage: p2.featuredImage,
|
|
1176
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1177
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1178
|
+
id: v2.id,
|
|
1179
|
+
availableForSale: v2.availableForSale,
|
|
1180
|
+
title: v2.title,
|
|
1181
|
+
currentPrice: v2.currentPrice,
|
|
1182
|
+
basePrice: v2.basePrice,
|
|
1183
|
+
image: v2.image,
|
|
1184
|
+
recurring: v2.recurring
|
|
1185
|
+
}))
|
|
1186
|
+
};
|
|
1187
|
+
});
|
|
1188
|
+
},
|
|
1189
|
+
async getAvailableProducts({ subscriptionId, lineId, q: q2, mode, action, pagination }) {
|
|
1190
|
+
const params = new URLSearchParams();
|
|
1191
|
+
params.set("action", action);
|
|
1192
|
+
if (q2) params.set("q", q2);
|
|
1193
|
+
if (mode) params.set("mode", mode);
|
|
1194
|
+
if (pagination && "after" in pagination) params.set("after", pagination.after);
|
|
1195
|
+
if (pagination && "before" in pagination) params.set("before", pagination.before);
|
|
1196
|
+
const res = await fetcher(
|
|
1197
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
|
|
1198
|
+
);
|
|
1199
|
+
if (!res.ok) return [];
|
|
1200
|
+
const data = await res.json();
|
|
1201
|
+
return (data ?? []).map((p2) => {
|
|
1202
|
+
var _a;
|
|
1203
|
+
return {
|
|
1204
|
+
id: p2.id,
|
|
1205
|
+
title: p2.title,
|
|
1206
|
+
description: p2.description ?? "",
|
|
1207
|
+
featuredImage: p2.featuredImage,
|
|
1208
|
+
hasOnlyDefaultVariant: p2.hasOnlyDefaultVariant ?? (((_a = p2.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1209
|
+
variants: (p2.variants ?? []).map((v2) => ({
|
|
1210
|
+
id: v2.id,
|
|
1211
|
+
availableForSale: v2.availableForSale,
|
|
1212
|
+
title: v2.title,
|
|
1213
|
+
currentPrice: v2.currentPrice,
|
|
1214
|
+
basePrice: v2.basePrice,
|
|
1215
|
+
image: v2.image,
|
|
1216
|
+
recurring: v2.recurring
|
|
1217
|
+
}))
|
|
1218
|
+
};
|
|
1219
|
+
});
|
|
1220
|
+
},
|
|
1221
|
+
async getBox({ id }) {
|
|
1222
|
+
const res = await fetcher(`/customer/boxes/${id}`);
|
|
1223
|
+
if (!res.ok) return null;
|
|
1224
|
+
const data = await res.json();
|
|
1225
|
+
return { id, ...data };
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
const ProductServiceContext = createContext("services/product");
|
|
1230
|
+
function createProductService(adapter) {
|
|
1231
|
+
return {
|
|
1232
|
+
async getById(id) {
|
|
1233
|
+
return adapter.getById(id);
|
|
1234
|
+
},
|
|
1235
|
+
async getByVariantId(variantId) {
|
|
1236
|
+
return adapter.getByVariantId(variantId);
|
|
1237
|
+
},
|
|
1238
|
+
async search(opts) {
|
|
1239
|
+
return adapter.search(opts);
|
|
1240
|
+
},
|
|
1241
|
+
getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
|
|
1242
|
+
getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
|
|
1243
|
+
getAvailableProducts: (params) => adapter.getAvailableProducts(params),
|
|
1244
|
+
getBox: (id) => adapter.getBox({ id })
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
function routeNameTableFromPages(pages) {
|
|
1248
|
+
const table = {};
|
|
1249
|
+
for (const page of pages) {
|
|
1250
|
+
const names = page.routeNames;
|
|
1251
|
+
if (names.length === 0) continue;
|
|
1252
|
+
for (const name of names) {
|
|
1253
|
+
table[name] = page.path;
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
return table;
|
|
1257
|
+
}
|
|
1258
|
+
function createRouterService(options) {
|
|
1259
|
+
const { pages, pushImplementation } = options;
|
|
1260
|
+
const routeNameToPagePath = routeNameTableFromPages(pages);
|
|
796
1261
|
const pathSet = new Set(pages.map((p2) => p2.path));
|
|
797
1262
|
const navigationSubscribers = [];
|
|
798
1263
|
function notifySubscribers(logicalPath) {
|
|
@@ -831,6 +1296,70 @@ function createMockRouterService() {
|
|
|
831
1296
|
}).routerService;
|
|
832
1297
|
}
|
|
833
1298
|
function createMockSubscriptionAdapter(options = {}) {
|
|
1299
|
+
const richBase = (id, itemId, renewalId) => ({
|
|
1300
|
+
id,
|
|
1301
|
+
customerId: "customer_1",
|
|
1302
|
+
items: [
|
|
1303
|
+
{
|
|
1304
|
+
id: itemId,
|
|
1305
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1306
|
+
// sample-3 "Gentle deodorant", variant sample-3-1 "Green tea and jasmine",
|
|
1307
|
+
// recurring price 5.36 EUR (10% off → 4.82 with SAVE10).
|
|
1308
|
+
productId: "sample-3",
|
|
1309
|
+
variantId: "sample-3-1",
|
|
1310
|
+
quantity: 2,
|
|
1311
|
+
price: { amount: "5.36", currencyCode: "EUR" },
|
|
1312
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1313
|
+
isUpsell: false,
|
|
1314
|
+
isVirtual: false,
|
|
1315
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1316
|
+
currentPrice: { amount: "5.36", currencyCode: "EUR" },
|
|
1317
|
+
lineDiscountedPrice: { amount: "4.82", currencyCode: "EUR" },
|
|
1318
|
+
variantAvailableForSale: true,
|
|
1319
|
+
title: "Gentle deodorant for underarms and bust",
|
|
1320
|
+
variantTitle: "Green tea and jasmine",
|
|
1321
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+3a" },
|
|
1322
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1323
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
|
|
1324
|
+
}
|
|
1325
|
+
],
|
|
1326
|
+
discounts: [
|
|
1327
|
+
{
|
|
1328
|
+
id: "discount_1",
|
|
1329
|
+
type: "percentage",
|
|
1330
|
+
value: 10,
|
|
1331
|
+
code: "SAVE10",
|
|
1332
|
+
description: "10% off subscription"
|
|
1333
|
+
}
|
|
1334
|
+
],
|
|
1335
|
+
status: "active",
|
|
1336
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1337
|
+
frequency: "1 MONTH",
|
|
1338
|
+
createdAt: "2024-10-01T00:00:00Z",
|
|
1339
|
+
updatedAt: "2024-10-14T00:00:00Z",
|
|
1340
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1341
|
+
deliveryPolicy: {
|
|
1342
|
+
intervalCount: 1,
|
|
1343
|
+
interval: "MONTH",
|
|
1344
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1345
|
+
},
|
|
1346
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1347
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1348
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1349
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1350
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1351
|
+
customerPaymentMethod: {
|
|
1352
|
+
id: "pm_1",
|
|
1353
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1354
|
+
active: true,
|
|
1355
|
+
isRecurring: true
|
|
1356
|
+
},
|
|
1357
|
+
requiredBillingCyclesLeft: 3,
|
|
1358
|
+
renewal: { id: renewalId, isInActiveProcessState: false },
|
|
1359
|
+
lastPaymentStatus: "succeeded",
|
|
1360
|
+
pausePeriod: null,
|
|
1361
|
+
isEditable: true
|
|
1362
|
+
});
|
|
834
1363
|
const mockSubscriptions = [
|
|
835
1364
|
{
|
|
836
1365
|
id: "sub_1",
|
|
@@ -838,14 +1367,27 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
838
1367
|
items: [
|
|
839
1368
|
{
|
|
840
1369
|
id: "item_1",
|
|
841
|
-
|
|
842
|
-
|
|
1370
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1371
|
+
// sample-1 "Hydrating Gel Cleanser" (default variant), recurring price
|
|
1372
|
+
// 9.99 EUR (10% off → 8.99 with SAVE10).
|
|
1373
|
+
productId: "sample-1",
|
|
1374
|
+
variantId: "sample-1-1",
|
|
843
1375
|
quantity: 2,
|
|
844
|
-
price: { amount: "
|
|
1376
|
+
price: { amount: "9.99", currencyCode: "EUR" },
|
|
845
1377
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1378
|
+
isUpsell: false,
|
|
1379
|
+
isVirtual: false,
|
|
1380
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1381
|
+
currentPrice: { amount: "9.99", currencyCode: "EUR" },
|
|
1382
|
+
lineDiscountedPrice: { amount: "8.99", currencyCode: "EUR" },
|
|
1383
|
+
variantAvailableForSale: true,
|
|
1384
|
+
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
1385
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+1" },
|
|
1386
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1387
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
846
1388
|
customAttributes: [
|
|
847
|
-
{ key: "
|
|
848
|
-
{ key: "
|
|
1389
|
+
{ key: "skin_type", value: "combination" },
|
|
1390
|
+
{ key: "fragrance", value: "fragrance_free" }
|
|
849
1391
|
]
|
|
850
1392
|
}
|
|
851
1393
|
],
|
|
@@ -863,9 +1405,31 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
863
1405
|
frequency: "1 MONTH",
|
|
864
1406
|
createdAt: "2024-10-01T00:00:00Z",
|
|
865
1407
|
updatedAt: "2024-10-14T00:00:00Z",
|
|
1408
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1409
|
+
deliveryPolicy: {
|
|
1410
|
+
intervalCount: 1,
|
|
1411
|
+
interval: "MONTH",
|
|
1412
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1413
|
+
},
|
|
1414
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1415
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1416
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1417
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1418
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1419
|
+
customerPaymentMethod: {
|
|
1420
|
+
id: "pm_1",
|
|
1421
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1422
|
+
active: true,
|
|
1423
|
+
isRecurring: true
|
|
1424
|
+
},
|
|
1425
|
+
requiredBillingCyclesLeft: 3,
|
|
1426
|
+
renewal: { id: "ren_1", isInActiveProcessState: false },
|
|
1427
|
+
lastPaymentStatus: "succeeded",
|
|
1428
|
+
pausePeriod: null,
|
|
1429
|
+
isEditable: true,
|
|
866
1430
|
customAttributes: [
|
|
867
1431
|
{ key: "subscription_tier", value: "premium" },
|
|
868
|
-
{ key: "gift_message", value: "Enjoy your
|
|
1432
|
+
{ key: "gift_message", value: "Enjoy your skincare routine!" }
|
|
869
1433
|
]
|
|
870
1434
|
},
|
|
871
1435
|
{
|
|
@@ -874,11 +1438,17 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
874
1438
|
items: [
|
|
875
1439
|
{
|
|
876
1440
|
id: "item_2",
|
|
877
|
-
|
|
878
|
-
|
|
1441
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1442
|
+
// sample-2 "More COLLAGEN" (default variant), recurring price 10.99 EUR.
|
|
1443
|
+
productId: "sample-2",
|
|
1444
|
+
variantId: "sample-2-1",
|
|
879
1445
|
quantity: 1,
|
|
880
|
-
price: { amount: "
|
|
1446
|
+
price: { amount: "10.99", currencyCode: "EUR" },
|
|
881
1447
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
|
|
1448
|
+
title: "More COLLAGEN",
|
|
1449
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+2" },
|
|
1450
|
+
billingPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
1451
|
+
deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
882
1452
|
customAttributes: []
|
|
883
1453
|
}
|
|
884
1454
|
],
|
|
@@ -889,17 +1459,66 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
889
1459
|
createdAt: "2024-09-15T00:00:00Z",
|
|
890
1460
|
updatedAt: "2024-10-10T00:00:00Z",
|
|
891
1461
|
customAttributes: []
|
|
1462
|
+
},
|
|
1463
|
+
// Per-status fixtures: groundwork for the deferred editor preview picker
|
|
1464
|
+
// (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
|
|
1465
|
+
// Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
|
|
1466
|
+
{
|
|
1467
|
+
...richBase("sub_paused", "item_paused", "ren_paused"),
|
|
1468
|
+
status: "paused",
|
|
1469
|
+
pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
...richBase("sub_canceled", "item_canceled", "ren_canceled"),
|
|
1473
|
+
status: "canceled",
|
|
1474
|
+
pausePeriod: null
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
...richBase("sub_failed", "item_failed", "ren_failed"),
|
|
1478
|
+
status: "failed",
|
|
1479
|
+
lastPaymentStatus: "failed",
|
|
1480
|
+
pausePeriod: null
|
|
1481
|
+
},
|
|
1482
|
+
{
|
|
1483
|
+
...richBase("sub_expired", "item_expired", "ren_expired"),
|
|
1484
|
+
status: "expired",
|
|
1485
|
+
pausePeriod: null
|
|
1486
|
+
}
|
|
1487
|
+
];
|
|
1488
|
+
const mockUpsellProducts = [
|
|
1489
|
+
{
|
|
1490
|
+
id: "upsell-1",
|
|
1491
|
+
variantId: "upsell-v-1",
|
|
1492
|
+
title: "Retinol Night Cream",
|
|
1493
|
+
imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
|
|
1494
|
+
price: { amount: "38.00", currencyCode: "USD" },
|
|
1495
|
+
description: "Anti-aging night treatment"
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
id: "upsell-2",
|
|
1499
|
+
variantId: "upsell-v-2",
|
|
1500
|
+
title: "SPF 50 Day Shield",
|
|
1501
|
+
imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
|
|
1502
|
+
price: { amount: "24.00", currencyCode: "USD" },
|
|
1503
|
+
description: "Lightweight daily sun protection"
|
|
892
1504
|
}
|
|
893
1505
|
];
|
|
894
1506
|
return {
|
|
895
1507
|
async getById(id) {
|
|
896
1508
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
897
|
-
const subscription = mockSubscriptions.find((sub) => sub.id === id)
|
|
898
|
-
return {
|
|
1509
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === id);
|
|
1510
|
+
return {
|
|
1511
|
+
_tag: "Success",
|
|
1512
|
+
data: subscription ? {
|
|
1513
|
+
...subscription,
|
|
1514
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1515
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1516
|
+
} : null
|
|
1517
|
+
};
|
|
899
1518
|
},
|
|
900
1519
|
async search(options2 = {}) {
|
|
901
1520
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
902
|
-
const pageSize =
|
|
1521
|
+
const pageSize = 2;
|
|
903
1522
|
const allItems = mockSubscriptions;
|
|
904
1523
|
let startIndex = 0;
|
|
905
1524
|
let endIndex = pageSize;
|
|
@@ -915,27 +1534,82 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
915
1534
|
}
|
|
916
1535
|
}
|
|
917
1536
|
const pageItems = allItems.slice(startIndex, endIndex);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
items:
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1537
|
+
return {
|
|
1538
|
+
_tag: "Success",
|
|
1539
|
+
data: {
|
|
1540
|
+
items: pageItems.map((sub) => ({
|
|
1541
|
+
...sub,
|
|
1542
|
+
items: sub.items.map((item) => ({ ...item })),
|
|
1543
|
+
discounts: sub.discounts.map((discount) => ({ ...discount }))
|
|
1544
|
+
})),
|
|
1545
|
+
pageInfo: {
|
|
1546
|
+
hasNextPage: endIndex < allItems.length,
|
|
1547
|
+
hasPrevPage: startIndex > 0,
|
|
1548
|
+
startCursor: pageItems.length > 0 ? startIndex.toString() : null,
|
|
1549
|
+
endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
|
|
1550
|
+
}
|
|
929
1551
|
}
|
|
930
1552
|
};
|
|
931
|
-
return { _tag: "Success", data: paginatedList };
|
|
932
1553
|
},
|
|
933
|
-
async addItem() {
|
|
1554
|
+
async addItem({ subscriptionId, item }) {
|
|
1555
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1556
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1557
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1558
|
+
if (!subscription) {
|
|
1559
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1560
|
+
}
|
|
1561
|
+
const currencyCode = ((_a = subscription.items[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD";
|
|
1562
|
+
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;
|
|
1563
|
+
const variant = (product == null ? void 0 : product.variants.items.find((v2) => v2.id === item.variantId)) ?? (product == null ? void 0 : product.variants.items[0]);
|
|
1564
|
+
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);
|
|
1565
|
+
subscription.items.push({
|
|
1566
|
+
id: `item_${Date.now()}_${subscription.items.length}`,
|
|
1567
|
+
productId: (product == null ? void 0 : product.id) ?? "",
|
|
1568
|
+
variantId: item.variantId,
|
|
1569
|
+
quantity: item.quantity,
|
|
1570
|
+
price: linePrice ? { ...linePrice } : { amount: "0.00", currencyCode },
|
|
1571
|
+
...linePrice ? { currentPrice: { ...linePrice } } : {},
|
|
1572
|
+
title: (product == null ? void 0 : product.title) ?? `Product ${item.variantId}`,
|
|
1573
|
+
...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
|
|
1574
|
+
...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
|
|
1575
|
+
variantAvailableForSale: true,
|
|
1576
|
+
billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
|
|
1577
|
+
deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
|
|
1578
|
+
});
|
|
1579
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1580
|
+
return { _tag: "Success", data: void 0 };
|
|
1581
|
+
},
|
|
1582
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
934
1583
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1584
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1585
|
+
if (!subscription) {
|
|
1586
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1587
|
+
}
|
|
1588
|
+
subscription.items = subscription.items.filter((i3) => i3.id !== itemId);
|
|
1589
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
935
1590
|
return { _tag: "Success", data: void 0 };
|
|
936
1591
|
},
|
|
937
|
-
async
|
|
1592
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
938
1593
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1594
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1595
|
+
if (!subscription) {
|
|
1596
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1597
|
+
}
|
|
1598
|
+
const item = subscription.items.find((i3) => i3.id === lineId);
|
|
1599
|
+
if (!item) {
|
|
1600
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1601
|
+
}
|
|
1602
|
+
if (update.variantId !== void 0) item.variantId = update.variantId;
|
|
1603
|
+
if (update.quantity !== void 0) item.quantity = update.quantity;
|
|
1604
|
+
if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
|
|
1605
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1606
|
+
return { _tag: "Success", data: void 0 };
|
|
1607
|
+
},
|
|
1608
|
+
async skipLineItem() {
|
|
1609
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1610
|
+
return { _tag: "Success", data: void 0 };
|
|
1611
|
+
},
|
|
1612
|
+
async updateBoxItems() {
|
|
939
1613
|
return { _tag: "Success", data: void 0 };
|
|
940
1614
|
},
|
|
941
1615
|
async addDiscount() {
|
|
@@ -966,24 +1640,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
966
1640
|
(sub) => sub.id === params.subscriptionId
|
|
967
1641
|
);
|
|
968
1642
|
if (!subscription) {
|
|
969
|
-
return {
|
|
970
|
-
_tag: "Failure",
|
|
971
|
-
error: "Subscription not found"
|
|
972
|
-
};
|
|
1643
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
973
1644
|
}
|
|
974
1645
|
subscription.nextDeliveryDate = new Date(params.newDate);
|
|
975
1646
|
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
|
-
};
|
|
1647
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
987
1648
|
},
|
|
988
1649
|
async resume(params) {
|
|
989
1650
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -991,21 +1652,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
991
1652
|
(sub) => sub.id === params.subscriptionId
|
|
992
1653
|
);
|
|
993
1654
|
if (!subscription) {
|
|
994
|
-
return {
|
|
995
|
-
_tag: "Failure",
|
|
996
|
-
error: "Subscription not found"
|
|
997
|
-
};
|
|
1655
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
998
1656
|
}
|
|
999
1657
|
subscription.status = "active";
|
|
1000
1658
|
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
|
-
};
|
|
1659
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1009
1660
|
},
|
|
1010
1661
|
async pause(params) {
|
|
1011
1662
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1013,21 +1664,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1013
1664
|
(sub) => sub.id === params.subscriptionId
|
|
1014
1665
|
);
|
|
1015
1666
|
if (!subscription) {
|
|
1016
|
-
return {
|
|
1017
|
-
_tag: "Failure",
|
|
1018
|
-
error: "Subscription not found"
|
|
1019
|
-
};
|
|
1667
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1020
1668
|
}
|
|
1021
1669
|
subscription.status = "paused";
|
|
1022
1670
|
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
|
-
};
|
|
1671
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1031
1672
|
},
|
|
1032
1673
|
async reactivate(params) {
|
|
1033
1674
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1035,21 +1676,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1035
1676
|
(sub) => sub.id === params.subscriptionId
|
|
1036
1677
|
);
|
|
1037
1678
|
if (!subscription) {
|
|
1038
|
-
return {
|
|
1039
|
-
_tag: "Failure",
|
|
1040
|
-
error: "Subscription not found"
|
|
1041
|
-
};
|
|
1679
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1042
1680
|
}
|
|
1043
1681
|
subscription.status = "active";
|
|
1044
1682
|
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
|
-
};
|
|
1683
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1053
1684
|
},
|
|
1054
1685
|
async cancel(params) {
|
|
1055
1686
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1057,21 +1688,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1057
1688
|
(sub) => sub.id === params.subscriptionId
|
|
1058
1689
|
);
|
|
1059
1690
|
if (!subscription) {
|
|
1060
|
-
return {
|
|
1061
|
-
_tag: "Failure",
|
|
1062
|
-
error: "Subscription not found"
|
|
1063
|
-
};
|
|
1691
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1064
1692
|
}
|
|
1065
1693
|
subscription.status = "canceled";
|
|
1066
1694
|
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
|
-
};
|
|
1695
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1075
1696
|
},
|
|
1076
1697
|
async getAvailablePlans() {
|
|
1077
1698
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1099,13 +1720,873 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1099
1720
|
]
|
|
1100
1721
|
};
|
|
1101
1722
|
},
|
|
1723
|
+
async getLastOrder(params) {
|
|
1724
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1725
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1726
|
+
if (!subscription) return { _tag: "Success", data: null };
|
|
1727
|
+
return {
|
|
1728
|
+
_tag: "Success",
|
|
1729
|
+
data: {
|
|
1730
|
+
id: `mock_last_order_${params.subscriptionId}`,
|
|
1731
|
+
createdAt: "2026-04-22T12:00:00Z"
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
},
|
|
1735
|
+
async renewNow(_params) {
|
|
1736
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1737
|
+
return { _tag: "Success", data: { result: "success" } };
|
|
1738
|
+
},
|
|
1739
|
+
async changeFrequency(params) {
|
|
1740
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1741
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1742
|
+
if (!subscription) {
|
|
1743
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1744
|
+
}
|
|
1745
|
+
if (typeof params.plan === "string") {
|
|
1746
|
+
subscription.frequency = `1 ${params.plan}`;
|
|
1747
|
+
} else {
|
|
1748
|
+
subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
|
|
1749
|
+
if (subscription.billingPolicy) {
|
|
1750
|
+
subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
|
|
1751
|
+
}
|
|
1752
|
+
if (subscription.deliveryPolicy) {
|
|
1753
|
+
subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1757
|
+
return {
|
|
1758
|
+
_tag: "Success",
|
|
1759
|
+
data: {
|
|
1760
|
+
...subscription,
|
|
1761
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1762
|
+
discounts: subscription.discounts.map((d3) => ({ ...d3 }))
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
},
|
|
1102
1766
|
async getDefaultSubscriptionId() {
|
|
1103
1767
|
var _a;
|
|
1104
1768
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1105
1769
|
return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
|
|
1770
|
+
},
|
|
1771
|
+
async getUpsellProducts() {
|
|
1772
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1773
|
+
return { _tag: "Success", data: [...mockUpsellProducts] };
|
|
1774
|
+
},
|
|
1775
|
+
async addUpsellProduct() {
|
|
1776
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
1777
|
+
return { _tag: "Success", data: void 0 };
|
|
1778
|
+
}
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
function normalizeInstrumentType(type) {
|
|
1782
|
+
return type.replace(/-/g, "_");
|
|
1783
|
+
}
|
|
1784
|
+
function mapPaymentMethod(rawPm) {
|
|
1785
|
+
if (!rawPm) return null;
|
|
1786
|
+
if (typeof rawPm === "string") {
|
|
1787
|
+
return { id: rawPm };
|
|
1788
|
+
}
|
|
1789
|
+
return {
|
|
1790
|
+
id: rawPm.id ?? "",
|
|
1791
|
+
...rawPm.instrument != null ? {
|
|
1792
|
+
instrument: {
|
|
1793
|
+
...rawPm.instrument,
|
|
1794
|
+
type: normalizeInstrumentType(rawPm.instrument.type)
|
|
1795
|
+
}
|
|
1796
|
+
} : {},
|
|
1797
|
+
...rawPm.active != null ? { active: rawPm.active } : {},
|
|
1798
|
+
...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
|
|
1799
|
+
};
|
|
1800
|
+
}
|
|
1801
|
+
function mapSubscription(raw) {
|
|
1802
|
+
var _a, _b;
|
|
1803
|
+
const status = raw.status === "merged" ? "canceled" : raw.status;
|
|
1804
|
+
const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
|
|
1805
|
+
const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
|
|
1806
|
+
return {
|
|
1807
|
+
id: raw.id,
|
|
1808
|
+
customerId: "",
|
|
1809
|
+
serial: raw.serial,
|
|
1810
|
+
items: (raw.items ?? []).map(
|
|
1811
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1812
|
+
(item) => ({
|
|
1813
|
+
id: item.id,
|
|
1814
|
+
productId: item.productId ?? item.product ?? "",
|
|
1815
|
+
variantId: item.variantId ?? item.variant ?? "",
|
|
1816
|
+
quantity: item.quantity,
|
|
1817
|
+
price: item.price ?? {
|
|
1818
|
+
amount: String(item.totalPrice ?? 0),
|
|
1819
|
+
currencyCode: raw.currencyCode ?? ""
|
|
1820
|
+
},
|
|
1821
|
+
nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
|
|
1822
|
+
billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
|
|
1823
|
+
deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
|
|
1824
|
+
// Map human-readable titles from the v1 SubscriptionItemResource
|
|
1825
|
+
title: item.title,
|
|
1826
|
+
variantTitle: item.subtitle ?? void 0,
|
|
1827
|
+
// recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
|
|
1828
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
1829
|
+
customAttributes: Array.isArray(item.customAttributes) ? item.customAttributes : []
|
|
1830
|
+
})
|
|
1831
|
+
),
|
|
1832
|
+
discounts: (raw.discounts ?? []).map(
|
|
1833
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1834
|
+
(d3) => {
|
|
1835
|
+
var _a2, _b2, _c;
|
|
1836
|
+
return {
|
|
1837
|
+
id: d3.id,
|
|
1838
|
+
type: ((_a2 = d3.value) == null ? void 0 : _a2.type) === "fixed-amount" || d3.type === "fixed_amount" ? "fixed_amount" : "percentage",
|
|
1839
|
+
value: ((_b2 = d3.value) == null ? void 0 : _b2.amount) ?? ((_c = d3.value) == null ? void 0 : _c.percentage) ?? d3.value ?? 0,
|
|
1840
|
+
code: d3.title ?? d3.code ?? "",
|
|
1841
|
+
description: d3.description ?? ""
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
),
|
|
1845
|
+
status,
|
|
1846
|
+
nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
|
|
1847
|
+
// Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
|
|
1848
|
+
nextBillingDate: raw.nextBillingDate ?? null,
|
|
1849
|
+
// Map customerPaymentMethod — note: arrives as bare id (not expanded) since the
|
|
1850
|
+
// subscriptions endpoint does not support ?expand=paymentMethod; instrument digits
|
|
1851
|
+
// remain a data gap pending backend API extension.
|
|
1852
|
+
customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
|
|
1853
|
+
frequency,
|
|
1854
|
+
createdAt: raw.createdAt,
|
|
1855
|
+
updatedAt: raw.updatedAt,
|
|
1856
|
+
billingPolicy: raw.billingPolicy ?? null,
|
|
1857
|
+
deliveryPolicy: raw.deliveryPolicy ?? null
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
function createApiSubscriptionAdapter(fetcher) {
|
|
1861
|
+
const adapter = {
|
|
1862
|
+
async search(options) {
|
|
1863
|
+
var _a;
|
|
1864
|
+
try {
|
|
1865
|
+
const params = new URLSearchParams();
|
|
1866
|
+
if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
|
|
1867
|
+
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 ");
|
|
1868
|
+
if (q2) params.set("query", q2);
|
|
1869
|
+
}
|
|
1870
|
+
if (options == null ? void 0 : options.pagination) {
|
|
1871
|
+
if ("after" in options.pagination)
|
|
1872
|
+
params.set("after", options.pagination.after);
|
|
1873
|
+
else if ("before" in options.pagination)
|
|
1874
|
+
params.set("before", options.pagination.before);
|
|
1875
|
+
}
|
|
1876
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1877
|
+
const res = await fetcher(`/customer/v1/subscriptions${query}`);
|
|
1878
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1879
|
+
const body = await res.json();
|
|
1880
|
+
return {
|
|
1881
|
+
_tag: "Success",
|
|
1882
|
+
data: {
|
|
1883
|
+
items: body.data.map(mapSubscription),
|
|
1884
|
+
pageInfo: {
|
|
1885
|
+
hasNextPage: body.hasNextPage,
|
|
1886
|
+
hasPrevPage: body.hasPrevPage,
|
|
1887
|
+
startCursor: body.startCursor,
|
|
1888
|
+
endCursor: body.endCursor
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
} catch (e2) {
|
|
1893
|
+
return { _tag: "Failure", error: e2 };
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
async getById(id) {
|
|
1897
|
+
try {
|
|
1898
|
+
const res = await fetcher(
|
|
1899
|
+
`/customer/v1/subscriptions?query=id:"${id}"&limit=1`
|
|
1900
|
+
);
|
|
1901
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1902
|
+
const body = await res.json();
|
|
1903
|
+
const raw = body.data[0];
|
|
1904
|
+
return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
|
|
1905
|
+
} catch (e2) {
|
|
1906
|
+
return { _tag: "Failure", error: e2 };
|
|
1907
|
+
}
|
|
1908
|
+
},
|
|
1909
|
+
async addItem({ subscriptionId, item }) {
|
|
1910
|
+
try {
|
|
1911
|
+
const res = await fetcher(
|
|
1912
|
+
`/customer/v1/subscriptions/${subscriptionId}/items`,
|
|
1913
|
+
{
|
|
1914
|
+
method: "POST",
|
|
1915
|
+
headers: { "Content-Type": "application/json" },
|
|
1916
|
+
body: JSON.stringify(item)
|
|
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 removeItem({ subscriptionId, itemId }) {
|
|
1926
|
+
try {
|
|
1927
|
+
const res = await fetcher(
|
|
1928
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
|
|
1929
|
+
{ method: "DELETE" }
|
|
1930
|
+
);
|
|
1931
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1932
|
+
return { _tag: "Success", data: void 0 };
|
|
1933
|
+
} catch (e2) {
|
|
1934
|
+
return { _tag: "Failure", error: e2 };
|
|
1935
|
+
}
|
|
1936
|
+
},
|
|
1937
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1938
|
+
try {
|
|
1939
|
+
const body = {
|
|
1940
|
+
...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
|
|
1941
|
+
...update.quantity !== void 0 ? { quantity: update.quantity } : {},
|
|
1942
|
+
...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
|
|
1943
|
+
...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
|
|
1944
|
+
};
|
|
1945
|
+
const res = await fetcher(
|
|
1946
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1947
|
+
{
|
|
1948
|
+
method: "PUT",
|
|
1949
|
+
headers: { "Content-Type": "application/json" },
|
|
1950
|
+
body: JSON.stringify(body)
|
|
1951
|
+
}
|
|
1952
|
+
);
|
|
1953
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1954
|
+
return { _tag: "Success", data: void 0 };
|
|
1955
|
+
} catch (e2) {
|
|
1956
|
+
return { _tag: "Failure", error: e2 };
|
|
1957
|
+
}
|
|
1958
|
+
},
|
|
1959
|
+
async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
|
|
1960
|
+
try {
|
|
1961
|
+
const res = await fetcher(
|
|
1962
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
1963
|
+
{
|
|
1964
|
+
method: "PUT",
|
|
1965
|
+
headers: { "Content-Type": "application/json" },
|
|
1966
|
+
body: JSON.stringify({ nextBillingDate })
|
|
1967
|
+
}
|
|
1968
|
+
);
|
|
1969
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1970
|
+
return { _tag: "Success", data: void 0 };
|
|
1971
|
+
} catch (e2) {
|
|
1972
|
+
return { _tag: "Failure", error: e2 };
|
|
1973
|
+
}
|
|
1974
|
+
},
|
|
1975
|
+
async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
|
|
1976
|
+
try {
|
|
1977
|
+
const res = await fetcher(
|
|
1978
|
+
`/customer/subscriptions/${subscriptionId}/update-box-products`,
|
|
1979
|
+
{
|
|
1980
|
+
method: "POST",
|
|
1981
|
+
headers: { "Content-Type": "application/json" },
|
|
1982
|
+
body: JSON.stringify({ parentId, items, shippingMethodHandle })
|
|
1983
|
+
}
|
|
1984
|
+
);
|
|
1985
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1986
|
+
return { _tag: "Success", data: void 0 };
|
|
1987
|
+
} catch (e2) {
|
|
1988
|
+
return { _tag: "Failure", error: e2 };
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
async addDiscount({ subscriptionId, code }) {
|
|
1992
|
+
try {
|
|
1993
|
+
const res = await fetcher(
|
|
1994
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts`,
|
|
1995
|
+
{
|
|
1996
|
+
method: "POST",
|
|
1997
|
+
headers: { "Content-Type": "application/json" },
|
|
1998
|
+
body: JSON.stringify({ code })
|
|
1999
|
+
}
|
|
2000
|
+
);
|
|
2001
|
+
if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
|
|
2002
|
+
return { _tag: "Success", data: void 0 };
|
|
2003
|
+
} catch (e2) {
|
|
2004
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
async removeDiscount({ subscriptionId, discountId }) {
|
|
2008
|
+
try {
|
|
2009
|
+
const res = await fetcher(
|
|
2010
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
|
|
2011
|
+
{ method: "DELETE" }
|
|
2012
|
+
);
|
|
2013
|
+
if (!res.ok) return { _tag: "Failure", error: { kind: "NETWORK", message: `HTTP ${res.status}` } };
|
|
2014
|
+
return { _tag: "Success", data: void 0 };
|
|
2015
|
+
} catch (e2) {
|
|
2016
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: e2 instanceof Error ? e2.message : void 0 } };
|
|
2017
|
+
}
|
|
2018
|
+
},
|
|
2019
|
+
// Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
|
|
2020
|
+
async reschedule({ subscriptionId, newDate }) {
|
|
2021
|
+
try {
|
|
2022
|
+
const res = await fetcher(
|
|
2023
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2024
|
+
{
|
|
2025
|
+
method: "PATCH",
|
|
2026
|
+
headers: { "Content-Type": "application/json" },
|
|
2027
|
+
body: JSON.stringify({ nextBillingDate: newDate })
|
|
2028
|
+
}
|
|
2029
|
+
);
|
|
2030
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2031
|
+
const data = await res.json();
|
|
2032
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2033
|
+
} catch (e2) {
|
|
2034
|
+
return { _tag: "Failure", error: e2 };
|
|
2035
|
+
}
|
|
2036
|
+
},
|
|
2037
|
+
async resume({ subscriptionId }) {
|
|
2038
|
+
try {
|
|
2039
|
+
const res = await fetcher(
|
|
2040
|
+
`/customer/v1/subscriptions/${subscriptionId}/resume`,
|
|
2041
|
+
{ method: "POST" }
|
|
2042
|
+
);
|
|
2043
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2044
|
+
const data = await res.json();
|
|
2045
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2046
|
+
} catch (e2) {
|
|
2047
|
+
return { _tag: "Failure", error: e2 };
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
2050
|
+
async pause({ subscriptionId }) {
|
|
2051
|
+
try {
|
|
2052
|
+
const res = await fetcher(
|
|
2053
|
+
`/customer/v1/subscriptions/${subscriptionId}/pause`,
|
|
2054
|
+
{ method: "POST" }
|
|
2055
|
+
);
|
|
2056
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2057
|
+
const data = await res.json();
|
|
2058
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2059
|
+
} catch (e2) {
|
|
2060
|
+
return { _tag: "Failure", error: e2 };
|
|
2061
|
+
}
|
|
2062
|
+
},
|
|
2063
|
+
async reactivate({ subscriptionId }) {
|
|
2064
|
+
try {
|
|
2065
|
+
const res = await fetcher(
|
|
2066
|
+
`/customer/v1/subscriptions/${subscriptionId}/reactivate`,
|
|
2067
|
+
{ method: "POST" }
|
|
2068
|
+
);
|
|
2069
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2070
|
+
const data = await res.json();
|
|
2071
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2072
|
+
} catch (e2) {
|
|
2073
|
+
return { _tag: "Failure", error: e2 };
|
|
2074
|
+
}
|
|
2075
|
+
},
|
|
2076
|
+
async cancel({ subscriptionId, reason }) {
|
|
2077
|
+
try {
|
|
2078
|
+
const res = await fetcher(
|
|
2079
|
+
`/customer/v1/subscriptions/${subscriptionId}/cancel`,
|
|
2080
|
+
{
|
|
2081
|
+
method: "POST",
|
|
2082
|
+
headers: { "Content-Type": "application/json" },
|
|
2083
|
+
body: JSON.stringify({ cancellationReason: reason })
|
|
2084
|
+
}
|
|
2085
|
+
);
|
|
2086
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2087
|
+
const data = await res.json();
|
|
2088
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2089
|
+
} catch (e2) {
|
|
2090
|
+
return { _tag: "Failure", error: e2 };
|
|
2091
|
+
}
|
|
2092
|
+
},
|
|
2093
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
|
|
2094
|
+
async getAvailablePlans({ subscriptionId }) {
|
|
2095
|
+
try {
|
|
2096
|
+
const res = await fetcher(
|
|
2097
|
+
`/customer/subscriptions/${subscriptionId}/plans`
|
|
2098
|
+
);
|
|
2099
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2100
|
+
const data = await res.json();
|
|
2101
|
+
return { _tag: "Success", data };
|
|
2102
|
+
} catch (e2) {
|
|
2103
|
+
return { _tag: "Failure", error: e2 };
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2107
|
+
async getDefaultSubscriptionId() {
|
|
2108
|
+
try {
|
|
2109
|
+
const res = await fetcher(
|
|
2110
|
+
`/customer/subscriptions/one-by-next-billing-date`
|
|
2111
|
+
);
|
|
2112
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2113
|
+
const data = await res.json();
|
|
2114
|
+
return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
|
|
2115
|
+
} catch (e2) {
|
|
2116
|
+
return { _tag: "Failure", error: e2 };
|
|
2117
|
+
}
|
|
2118
|
+
},
|
|
2119
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
|
|
2120
|
+
async getUpsellProducts({ subscriptionId }) {
|
|
2121
|
+
try {
|
|
2122
|
+
const res = await fetcher(
|
|
2123
|
+
`/customer/subscriptions/${subscriptionId}/upsell-products`
|
|
2124
|
+
);
|
|
2125
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2126
|
+
const data = await res.json();
|
|
2127
|
+
const products = data.map((p2) => {
|
|
2128
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2129
|
+
return {
|
|
2130
|
+
id: p2.id,
|
|
2131
|
+
variantId: ((_b = (_a = p2.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
|
|
2132
|
+
title: p2.title ?? "",
|
|
2133
|
+
imageUrl: ((_c = p2.featuredImage) == null ? void 0 : _c.url) ?? void 0,
|
|
2134
|
+
price: {
|
|
2135
|
+
amount: String(((_f = (_e = (_d = p2.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
|
|
2136
|
+
currencyCode: ((_i = (_h = (_g = p2.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
|
|
2137
|
+
},
|
|
2138
|
+
// recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
|
|
2139
|
+
recurring: (_k = (_j = p2.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
|
|
2140
|
+
};
|
|
2141
|
+
});
|
|
2142
|
+
return { _tag: "Success", data: products };
|
|
2143
|
+
} catch (e2) {
|
|
2144
|
+
return { _tag: "Failure", error: e2 };
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
|
|
2148
|
+
async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
|
|
2149
|
+
try {
|
|
2150
|
+
const res = await fetcher(
|
|
2151
|
+
`/customer/subscriptions/${subscriptionId}/upsell`,
|
|
2152
|
+
{
|
|
2153
|
+
method: "POST",
|
|
2154
|
+
headers: { "Content-Type": "application/json" },
|
|
2155
|
+
body: JSON.stringify({
|
|
2156
|
+
productVariantId: variantId,
|
|
2157
|
+
quantity,
|
|
2158
|
+
...oneTime ? { renewalLimit: 1 } : {}
|
|
2159
|
+
})
|
|
2160
|
+
}
|
|
2161
|
+
);
|
|
2162
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2163
|
+
return { _tag: "Success", data: void 0 };
|
|
2164
|
+
} catch (e2) {
|
|
2165
|
+
return { _tag: "Failure", error: e2 };
|
|
2166
|
+
}
|
|
2167
|
+
},
|
|
2168
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2169
|
+
async getLastOrder({ subscriptionId }) {
|
|
2170
|
+
try {
|
|
2171
|
+
const res = await fetcher(
|
|
2172
|
+
`/customer/subscriptions/${subscriptionId}/last-order`
|
|
2173
|
+
);
|
|
2174
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2175
|
+
const data = await res.json();
|
|
2176
|
+
if (!data) return { _tag: "Success", data: null };
|
|
2177
|
+
return {
|
|
2178
|
+
_tag: "Success",
|
|
2179
|
+
data: { id: data.id, createdAt: data.createdAt }
|
|
2180
|
+
};
|
|
2181
|
+
} catch (e2) {
|
|
2182
|
+
return { _tag: "Failure", error: e2 };
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
// Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
|
|
2186
|
+
// retry → POST /customer/subscriptions/{id}/retry
|
|
2187
|
+
// renewalId → POST /customer/renewals/{renewalId}/renew
|
|
2188
|
+
// default → POST /customer/subscriptions/{id}/order-now
|
|
2189
|
+
async renewNow({ subscriptionId, renewalId, retry }) {
|
|
2190
|
+
try {
|
|
2191
|
+
const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
|
|
2192
|
+
const res = await fetcher(url, { method: "POST" });
|
|
2193
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2194
|
+
const body = res.status === 204 ? null : await res.json();
|
|
2195
|
+
return {
|
|
2196
|
+
_tag: "Success",
|
|
2197
|
+
data: {
|
|
2198
|
+
nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
|
|
2199
|
+
result: (body == null ? void 0 : body.result) ?? "success"
|
|
2200
|
+
}
|
|
2201
|
+
};
|
|
2202
|
+
} catch (e2) {
|
|
2203
|
+
return { _tag: "Failure", error: e2 };
|
|
2204
|
+
}
|
|
2205
|
+
},
|
|
2206
|
+
// Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
|
|
2207
|
+
async changeFrequency({ subscriptionId, plan }) {
|
|
2208
|
+
try {
|
|
2209
|
+
const res = await fetcher(
|
|
2210
|
+
`/customer/subscriptions/${subscriptionId}/plan`,
|
|
2211
|
+
{
|
|
2212
|
+
method: "POST",
|
|
2213
|
+
headers: { "Content-Type": "application/json" },
|
|
2214
|
+
body: JSON.stringify({ plan })
|
|
2215
|
+
}
|
|
2216
|
+
);
|
|
2217
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2218
|
+
const refreshed = await adapter.getById(subscriptionId);
|
|
2219
|
+
if (refreshed._tag === "Failure") return refreshed;
|
|
2220
|
+
if (refreshed.data) return { _tag: "Success", data: refreshed.data };
|
|
2221
|
+
return {
|
|
2222
|
+
_tag: "Failure",
|
|
2223
|
+
error: "Subscription not found after frequency change"
|
|
2224
|
+
};
|
|
2225
|
+
} catch (e2) {
|
|
2226
|
+
return { _tag: "Failure", error: e2 };
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
return adapter;
|
|
2231
|
+
}
|
|
2232
|
+
const DELIVERY_PRICE_CENTS = 500;
|
|
2233
|
+
const DELIVERY_OFFSET_DAYS = 2;
|
|
2234
|
+
function toCents(amount) {
|
|
2235
|
+
const n2 = Number.parseFloat(amount ?? "0");
|
|
2236
|
+
return Number.isFinite(n2) ? Math.round(n2 * 100) : 0;
|
|
2237
|
+
}
|
|
2238
|
+
function addInterval(date, interval, units) {
|
|
2239
|
+
const d3 = new Date(date);
|
|
2240
|
+
switch (interval) {
|
|
2241
|
+
case "DAY":
|
|
2242
|
+
d3.setDate(d3.getDate() + units);
|
|
2243
|
+
break;
|
|
2244
|
+
case "WEEK":
|
|
2245
|
+
d3.setDate(d3.getDate() + units * 7);
|
|
2246
|
+
break;
|
|
2247
|
+
case "YEAR":
|
|
2248
|
+
d3.setFullYear(d3.getFullYear() + units);
|
|
2249
|
+
break;
|
|
2250
|
+
default:
|
|
2251
|
+
d3.setMonth(d3.getMonth() + units);
|
|
2252
|
+
break;
|
|
2253
|
+
}
|
|
2254
|
+
return d3;
|
|
2255
|
+
}
|
|
2256
|
+
function mapItem(sub, item, cycle) {
|
|
2257
|
+
var _a, _b;
|
|
2258
|
+
const price = toCents(((_a = item.currentPrice) == null ? void 0 : _a.amount) ?? item.price.amount);
|
|
2259
|
+
return {
|
|
2260
|
+
id: `${sub.id}_cycle_${cycle}_${item.id}`,
|
|
2261
|
+
title: item.title ?? `Product ${item.variantId}`,
|
|
2262
|
+
subtitle: item.variantTitle ?? null,
|
|
2263
|
+
imageUrl: (_b = item.variantImage) == null ? void 0 : _b.url,
|
|
2264
|
+
variantId: item.variantId,
|
|
2265
|
+
quantity: item.quantity,
|
|
2266
|
+
price,
|
|
2267
|
+
totalPrice: price * item.quantity,
|
|
2268
|
+
variantImage: item.variantImage ?? null,
|
|
2269
|
+
variantAvailableForSale: item.variantAvailableForSale ?? true,
|
|
2270
|
+
subscriptionId: sub.id,
|
|
2271
|
+
subscriptionLineId: item.id,
|
|
2272
|
+
productId: item.productId,
|
|
2273
|
+
parentProductId: item.parentProductId ?? null,
|
|
2274
|
+
parentLineId: item.parentLineId ?? null,
|
|
2275
|
+
isVirtual: item.isVirtual ?? false,
|
|
2276
|
+
isUpsell: item.isUpsell ?? false,
|
|
2277
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
2278
|
+
customAttributes: item.customAttributes ?? []
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
function projectUpcomingOrders(sub, count) {
|
|
2282
|
+
var _a, _b;
|
|
2283
|
+
if (sub.status !== "active") return [];
|
|
2284
|
+
const billing = sub.billingPolicy ?? ((_a = sub.items[0]) == null ? void 0 : _a.billingPolicy) ?? { interval: "MONTH", intervalCount: 1 };
|
|
2285
|
+
const baseStr = sub.nextBillingDate ?? (sub.nextDeliveryDate ? sub.nextDeliveryDate.toISOString() : null);
|
|
2286
|
+
const base = baseStr ? new Date(baseStr) : /* @__PURE__ */ new Date();
|
|
2287
|
+
const currencyCode = ((_b = sub.items[0]) == null ? void 0 : _b.price.currencyCode) ?? "USD";
|
|
2288
|
+
const orders = [];
|
|
2289
|
+
for (let cycle = 0; cycle < count; cycle++) {
|
|
2290
|
+
const billingDate = addInterval(
|
|
2291
|
+
base,
|
|
2292
|
+
billing.interval,
|
|
2293
|
+
cycle * billing.intervalCount
|
|
2294
|
+
);
|
|
2295
|
+
const deliveryDate = addInterval(billingDate, "DAY", DELIVERY_OFFSET_DAYS);
|
|
2296
|
+
const items = sub.items.map((item) => mapItem(sub, item, cycle));
|
|
2297
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2298
|
+
orders.push({
|
|
2299
|
+
id: `${sub.id}_cycle_${cycle}`,
|
|
2300
|
+
customerId: sub.customerId,
|
|
2301
|
+
subscriptionId: sub.id,
|
|
2302
|
+
date: billingDate.toISOString(),
|
|
2303
|
+
skipped: false,
|
|
2304
|
+
currencyCode,
|
|
2305
|
+
subtotal,
|
|
2306
|
+
deliveryPrice: DELIVERY_PRICE_CENTS,
|
|
2307
|
+
cycleIndex: cycle,
|
|
2308
|
+
deliveryDate: deliveryDate.toISOString(),
|
|
2309
|
+
status: "scheduled",
|
|
2310
|
+
items,
|
|
2311
|
+
discounts: sub.discounts.map((d3) => ({
|
|
2312
|
+
id: d3.id,
|
|
2313
|
+
title: d3.code,
|
|
2314
|
+
type: d3.type,
|
|
2315
|
+
value: d3.value
|
|
2316
|
+
}))
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
return orders;
|
|
2320
|
+
}
|
|
2321
|
+
function applyAdjustments(orders, adjustments) {
|
|
2322
|
+
return orders.map((order) => {
|
|
2323
|
+
let next = order;
|
|
2324
|
+
for (const adj of adjustments) {
|
|
2325
|
+
if (adj.subscriptionId !== order.subscriptionId) continue;
|
|
2326
|
+
if (adj.cycle !== order.cycleIndex) continue;
|
|
2327
|
+
if (adj.type === "skip") {
|
|
2328
|
+
next = { ...next, skipped: true, status: "skipped" };
|
|
2329
|
+
} else if (adj.type === "date_change" && adj.newDate) {
|
|
2330
|
+
next = { ...next, date: adj.newDate, deliveryDate: adj.newDate };
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return next;
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
function createMockSchedulesAdapter(subscriptions = createMockSubscriptionAdapter()) {
|
|
2337
|
+
const RESOLVE_DEPTH = 12;
|
|
2338
|
+
let adjustments = [];
|
|
2339
|
+
async function subscriptionsToProject(subscriptionId) {
|
|
2340
|
+
if (subscriptionId) {
|
|
2341
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2342
|
+
return res._tag === "Success" && res.data ? [res.data] : [];
|
|
2343
|
+
}
|
|
2344
|
+
const all = [];
|
|
2345
|
+
let pagination;
|
|
2346
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2347
|
+
const res = await subscriptions.search(pagination ? { pagination } : {});
|
|
2348
|
+
if (res._tag !== "Success") break;
|
|
2349
|
+
all.push(...res.data.items);
|
|
2350
|
+
const { hasNextPage, endCursor } = res.data.pageInfo;
|
|
2351
|
+
if (!hasNextPage || !endCursor) break;
|
|
2352
|
+
pagination = { after: endCursor };
|
|
2353
|
+
}
|
|
2354
|
+
return all;
|
|
2355
|
+
}
|
|
2356
|
+
function resolveCycle(orders, opts) {
|
|
2357
|
+
if (opts.cycle != null) return opts.cycle;
|
|
2358
|
+
if (opts.date) {
|
|
2359
|
+
const target = new Date(opts.date).toISOString().split("T")[0];
|
|
2360
|
+
const match = orders.find(
|
|
2361
|
+
(o2) => o2.deliveryDate.split("T")[0] === target || o2.date.split("T")[0] === target
|
|
2362
|
+
);
|
|
2363
|
+
if (match) return match.cycleIndex;
|
|
2364
|
+
}
|
|
2365
|
+
const firstScheduled = orders.find((o2) => o2.status === "scheduled");
|
|
2366
|
+
return (firstScheduled == null ? void 0 : firstScheduled.cycleIndex) ?? null;
|
|
2367
|
+
}
|
|
2368
|
+
async function recordAdjustment(subscriptionId, opts, build) {
|
|
2369
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2370
|
+
const sub = res._tag === "Success" ? res.data : null;
|
|
2371
|
+
if (!sub) return { _tag: "Failure", error: "No scheduled order found" };
|
|
2372
|
+
const orders = applyAdjustments(
|
|
2373
|
+
projectUpcomingOrders(sub, RESOLVE_DEPTH),
|
|
2374
|
+
adjustments
|
|
2375
|
+
);
|
|
2376
|
+
const cycle = resolveCycle(orders, opts);
|
|
2377
|
+
if (cycle == null) {
|
|
2378
|
+
return { _tag: "Failure", error: "No scheduled order found" };
|
|
2379
|
+
}
|
|
2380
|
+
adjustments = [...adjustments, build(cycle)];
|
|
2381
|
+
return { _tag: "Success", data: void 0 };
|
|
2382
|
+
}
|
|
2383
|
+
return {
|
|
2384
|
+
async getUpcomingOrders({ count, subscriptionId }) {
|
|
2385
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2386
|
+
const subs = await subscriptionsToProject(subscriptionId);
|
|
2387
|
+
const orders = subs.flatMap(
|
|
2388
|
+
(sub) => applyAdjustments(projectUpcomingOrders(sub, count), adjustments)
|
|
2389
|
+
).sort((a3, b3) => a3.date.localeCompare(b3.date));
|
|
2390
|
+
return { _tag: "Success", data: orders.slice(0, count) };
|
|
2391
|
+
},
|
|
2392
|
+
async getAdjustments() {
|
|
2393
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2394
|
+
return { _tag: "Success", data: [...adjustments] };
|
|
2395
|
+
},
|
|
2396
|
+
async skipOrder({ subscriptionId, cycle, date }) {
|
|
2397
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
2398
|
+
return recordAdjustment(subscriptionId, { cycle, date }, (resolved) => ({
|
|
2399
|
+
id: `adj-${Date.now()}`,
|
|
2400
|
+
subscriptionId,
|
|
2401
|
+
type: "skip",
|
|
2402
|
+
cycle: resolved
|
|
2403
|
+
}));
|
|
2404
|
+
},
|
|
2405
|
+
async changeDateAdjustment({ subscriptionId, newDate, currentDate }) {
|
|
2406
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
2407
|
+
return recordAdjustment(
|
|
2408
|
+
subscriptionId,
|
|
2409
|
+
{ date: currentDate },
|
|
2410
|
+
(resolved) => ({
|
|
2411
|
+
id: `adj-${Date.now()}`,
|
|
2412
|
+
subscriptionId,
|
|
2413
|
+
type: "date_change",
|
|
2414
|
+
cycle: resolved,
|
|
2415
|
+
newDate
|
|
2416
|
+
})
|
|
2417
|
+
);
|
|
2418
|
+
},
|
|
2419
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2420
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
2421
|
+
adjustments = adjustments.filter((a3) => a3.id !== adjustmentId);
|
|
2422
|
+
return { _tag: "Success", data: void 0 };
|
|
2423
|
+
}
|
|
2424
|
+
};
|
|
2425
|
+
}
|
|
2426
|
+
function createApiSchedulesAdapter(fetcher) {
|
|
2427
|
+
return {
|
|
2428
|
+
async getUpcomingOrders({ count, subscriptionId }) {
|
|
2429
|
+
try {
|
|
2430
|
+
const params = new URLSearchParams({ count: String(count) });
|
|
2431
|
+
if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
|
|
2432
|
+
const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
|
|
2433
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2434
|
+
const data = await res.json();
|
|
2435
|
+
return { _tag: "Success", data: data.orders };
|
|
2436
|
+
} catch (e2) {
|
|
2437
|
+
return { _tag: "Failure", error: e2 };
|
|
2438
|
+
}
|
|
2439
|
+
},
|
|
2440
|
+
async getAdjustments() {
|
|
2441
|
+
try {
|
|
2442
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`);
|
|
2443
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2444
|
+
const data = await res.json();
|
|
2445
|
+
const adjustments = data.data.map((a3) => ({
|
|
2446
|
+
id: a3.id,
|
|
2447
|
+
subscriptionId: a3.subscriptionId ?? "",
|
|
2448
|
+
type: a3.type === "CHANGE_DATE" ? "date_change" : "skip",
|
|
2449
|
+
newDate: a3.newDate
|
|
2450
|
+
}));
|
|
2451
|
+
return { _tag: "Success", data: adjustments };
|
|
2452
|
+
} catch (e2) {
|
|
2453
|
+
return { _tag: "Failure", error: e2 };
|
|
2454
|
+
}
|
|
2455
|
+
},
|
|
2456
|
+
async skipOrder({ cycle, date }) {
|
|
2457
|
+
if (cycle == null && date == null) {
|
|
2458
|
+
return { _tag: "Failure", error: "Either cycle or date is required for skipOrder" };
|
|
2459
|
+
}
|
|
2460
|
+
try {
|
|
2461
|
+
const matcher = cycle != null ? { type: "CYCLE", input: { cycle } } : { type: "DATE", input: { date: new Date(date).toISOString() } };
|
|
2462
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2463
|
+
method: "POST",
|
|
2464
|
+
headers: { "Content-Type": "application/json" },
|
|
2465
|
+
body: JSON.stringify({
|
|
2466
|
+
matcher,
|
|
2467
|
+
action: { type: "SKIP_ORDER", input: { reason: "" } }
|
|
2468
|
+
})
|
|
2469
|
+
});
|
|
2470
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2471
|
+
return { _tag: "Success", data: void 0 };
|
|
2472
|
+
} catch (e2) {
|
|
2473
|
+
return { _tag: "Failure", error: e2 };
|
|
2474
|
+
}
|
|
2475
|
+
},
|
|
2476
|
+
async changeDateAdjustment({ newDate, currentDate }) {
|
|
2477
|
+
if (currentDate == null) {
|
|
2478
|
+
return {
|
|
2479
|
+
_tag: "Failure",
|
|
2480
|
+
error: "currentDate is required for changeDateAdjustment"
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
try {
|
|
2484
|
+
const matcher = { type: "DATE", input: { date: new Date(currentDate).toISOString() } };
|
|
2485
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2486
|
+
method: "POST",
|
|
2487
|
+
headers: { "Content-Type": "application/json" },
|
|
2488
|
+
body: JSON.stringify({
|
|
2489
|
+
matcher,
|
|
2490
|
+
action: {
|
|
2491
|
+
type: "CHANGE_DATE",
|
|
2492
|
+
input: { newDate: new Date(newDate).toISOString() }
|
|
2493
|
+
}
|
|
2494
|
+
})
|
|
2495
|
+
});
|
|
2496
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2497
|
+
return { _tag: "Success", data: void 0 };
|
|
2498
|
+
} catch (e2) {
|
|
2499
|
+
return { _tag: "Failure", error: e2 };
|
|
2500
|
+
}
|
|
2501
|
+
},
|
|
2502
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2503
|
+
try {
|
|
2504
|
+
const res = await fetcher(
|
|
2505
|
+
`/customer/v1/schedules/adjustments/${adjustmentId}`,
|
|
2506
|
+
{ method: "DELETE" }
|
|
2507
|
+
);
|
|
2508
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2509
|
+
return { _tag: "Success", data: void 0 };
|
|
2510
|
+
} catch (e2) {
|
|
2511
|
+
return { _tag: "Failure", error: e2 };
|
|
2512
|
+
}
|
|
1106
2513
|
}
|
|
1107
2514
|
};
|
|
1108
2515
|
}
|
|
2516
|
+
const SchedulesServiceContext = createContext("services/schedules");
|
|
2517
|
+
function createSchedulesService(adapter, opts) {
|
|
2518
|
+
const current = d(null);
|
|
2519
|
+
const currentLoading = d(false);
|
|
2520
|
+
const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
|
|
2521
|
+
let reloadToken = 0;
|
|
2522
|
+
async function getUpcomingOrder() {
|
|
2523
|
+
const result = await adapter.getUpcomingOrders({ count: 1 });
|
|
2524
|
+
if (result._tag === "Failure") return result;
|
|
2525
|
+
return { _tag: "Success", data: result.data[0] ?? null };
|
|
2526
|
+
}
|
|
2527
|
+
async function getUpcomingOrders(count) {
|
|
2528
|
+
return adapter.getUpcomingOrders({ count });
|
|
2529
|
+
}
|
|
2530
|
+
async function reloadCurrent(subscriptionId) {
|
|
2531
|
+
var _a;
|
|
2532
|
+
const token = ++reloadToken;
|
|
2533
|
+
currentLoading.value = true;
|
|
2534
|
+
const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
|
|
2535
|
+
const result = await adapter.getUpcomingOrders({
|
|
2536
|
+
count,
|
|
2537
|
+
subscriptionId
|
|
2538
|
+
});
|
|
2539
|
+
if (token !== reloadToken) return;
|
|
2540
|
+
currentLoading.value = false;
|
|
2541
|
+
if (result._tag === "Success") {
|
|
2542
|
+
current.value = result.data;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
return {
|
|
2546
|
+
current,
|
|
2547
|
+
currentLoading,
|
|
2548
|
+
hasPendingBilling,
|
|
2549
|
+
getUpcomingOrder,
|
|
2550
|
+
getUpcomingOrders,
|
|
2551
|
+
async getAdjustments() {
|
|
2552
|
+
return adapter.getAdjustments();
|
|
2553
|
+
},
|
|
2554
|
+
async skipOrder(params) {
|
|
2555
|
+
const result = await adapter.skipOrder(params);
|
|
2556
|
+
if (result._tag === "Success") {
|
|
2557
|
+
await reloadCurrent(params.subscriptionId);
|
|
2558
|
+
}
|
|
2559
|
+
return result;
|
|
2560
|
+
},
|
|
2561
|
+
async changeDateAdjustment(params) {
|
|
2562
|
+
const result = await adapter.changeDateAdjustment(params);
|
|
2563
|
+
if (result._tag === "Success") {
|
|
2564
|
+
await reloadCurrent(params.subscriptionId);
|
|
2565
|
+
}
|
|
2566
|
+
return result;
|
|
2567
|
+
},
|
|
2568
|
+
async removeAdjustment(params) {
|
|
2569
|
+
return adapter.removeAdjustment(params);
|
|
2570
|
+
},
|
|
2571
|
+
reloadCurrent
|
|
2572
|
+
};
|
|
2573
|
+
}
|
|
2574
|
+
const SettingsServiceContext = createContext("services/settings");
|
|
2575
|
+
function createSettingsService(initial = {}) {
|
|
2576
|
+
const current = d(initial);
|
|
2577
|
+
return {
|
|
2578
|
+
current,
|
|
2579
|
+
get(key) {
|
|
2580
|
+
return current.value[key];
|
|
2581
|
+
}
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
function createShopService(options) {
|
|
2585
|
+
const contactEmail = d(options.getContactEmail());
|
|
2586
|
+
const service = { contactEmail };
|
|
2587
|
+
return service;
|
|
2588
|
+
}
|
|
2589
|
+
const ShopServiceContext = createContext("services/shop");
|
|
1109
2590
|
const SubscriptionServiceContext = createContext("services/subscription");
|
|
1110
2591
|
function getCurrentSubscriptionId() {
|
|
1111
2592
|
if (typeof window === "undefined") {
|
|
@@ -1128,41 +2609,83 @@ function getCurrentSubscriptionId() {
|
|
|
1128
2609
|
}
|
|
1129
2610
|
function createSubscriptionService(adapter) {
|
|
1130
2611
|
const current = d(null);
|
|
1131
|
-
const
|
|
1132
|
-
const
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
2612
|
+
const hasPendingBilling = d(false);
|
|
2613
|
+
const discountsChangedAt = d(0);
|
|
2614
|
+
const itemsChangedAt = d(0);
|
|
2615
|
+
const lastOrder = d(null);
|
|
2616
|
+
async function refreshAfterDiscountChange() {
|
|
2617
|
+
await reload();
|
|
2618
|
+
discountsChangedAt.value = discountsChangedAt.value + 1;
|
|
1138
2619
|
}
|
|
1139
|
-
async function
|
|
1140
|
-
await
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
async function refreshAfterItemChange(subscriptionId) {
|
|
1144
|
-
await refreshCurrent(subscriptionId);
|
|
1145
|
-
itemsChangedAtSignal.value = itemsChangedAtSignal.value + 1;
|
|
2620
|
+
async function refreshAfterItemChange() {
|
|
2621
|
+
await reload();
|
|
2622
|
+
itemsChangedAt.value = itemsChangedAt.value + 1;
|
|
1146
2623
|
}
|
|
1147
2624
|
function subscriptionIdFromCurrent() {
|
|
1148
2625
|
var _a;
|
|
1149
2626
|
return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
|
|
1150
2627
|
}
|
|
2628
|
+
async function reload() {
|
|
2629
|
+
var _a, _b;
|
|
2630
|
+
const id = (_a = current.value) == null ? void 0 : _a.id;
|
|
2631
|
+
if (!id) return;
|
|
2632
|
+
const [subResult, lastOrderResult] = await Promise.all([
|
|
2633
|
+
adapter.getById(id),
|
|
2634
|
+
adapter.getLastOrder({ subscriptionId: id })
|
|
2635
|
+
]);
|
|
2636
|
+
if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
|
|
2637
|
+
if (subResult._tag === "Success") current.value = subResult.data;
|
|
2638
|
+
if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
|
|
2639
|
+
}
|
|
1151
2640
|
const service = {
|
|
2641
|
+
current,
|
|
2642
|
+
hasPendingBilling,
|
|
2643
|
+
discountsChangedAt,
|
|
2644
|
+
itemsChangedAt,
|
|
2645
|
+
lastOrder,
|
|
1152
2646
|
search: adapter.search,
|
|
2647
|
+
// Prefer the adapter's rich cross-subscription list (legacy endpoint, active-first).
|
|
2648
|
+
// Fall back to a single `search` page for adapters that don't implement it
|
|
2649
|
+
// (e.g. the editor mock) so the cross-subscription views still render.
|
|
2650
|
+
listAll: adapter.listAll ? adapter.listAll.bind(adapter) : async () => {
|
|
2651
|
+
const result = await adapter.search();
|
|
2652
|
+
if (result._tag !== "Success") return result;
|
|
2653
|
+
return { _tag: "Success", data: result.data.items };
|
|
2654
|
+
},
|
|
1153
2655
|
getById: adapter.getById,
|
|
1154
2656
|
getCurrentSubscriptionId,
|
|
1155
2657
|
addItem: async (subscriptionId, item) => {
|
|
1156
2658
|
const result = await adapter.addItem({ subscriptionId, item });
|
|
1157
2659
|
if (result._tag === "Success") {
|
|
1158
|
-
await refreshAfterItemChange(
|
|
2660
|
+
await refreshAfterItemChange();
|
|
1159
2661
|
}
|
|
1160
2662
|
return result;
|
|
1161
2663
|
},
|
|
1162
2664
|
removeItem: async (subscriptionId, itemId) => {
|
|
1163
2665
|
const result = await adapter.removeItem({ subscriptionId, itemId });
|
|
1164
2666
|
if (result._tag === "Success") {
|
|
1165
|
-
await refreshAfterItemChange(
|
|
2667
|
+
await refreshAfterItemChange();
|
|
2668
|
+
}
|
|
2669
|
+
return result;
|
|
2670
|
+
},
|
|
2671
|
+
updateItem: async (subscriptionId, lineId, update) => {
|
|
2672
|
+
const result = await adapter.updateItem({ subscriptionId, lineId, update });
|
|
2673
|
+
if (result._tag === "Success") {
|
|
2674
|
+
await refreshAfterItemChange();
|
|
2675
|
+
}
|
|
2676
|
+
return result;
|
|
2677
|
+
},
|
|
2678
|
+
skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
|
|
2679
|
+
const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
|
|
2680
|
+
if (result._tag === "Success") {
|
|
2681
|
+
await refreshAfterItemChange();
|
|
2682
|
+
}
|
|
2683
|
+
return result;
|
|
2684
|
+
},
|
|
2685
|
+
updateBoxItems: async (subscriptionId, params) => {
|
|
2686
|
+
const result = await adapter.updateBoxItems({ subscriptionId, ...params });
|
|
2687
|
+
if (result._tag === "Success") {
|
|
2688
|
+
await refreshAfterItemChange();
|
|
1166
2689
|
}
|
|
1167
2690
|
return result;
|
|
1168
2691
|
},
|
|
@@ -1176,7 +2699,7 @@ function createSubscriptionService(adapter) {
|
|
|
1176
2699
|
}
|
|
1177
2700
|
const result = await adapter.addDiscount({ subscriptionId: id, code });
|
|
1178
2701
|
if (result._tag === "Success") {
|
|
1179
|
-
await refreshAfterDiscountChange(
|
|
2702
|
+
await refreshAfterDiscountChange();
|
|
1180
2703
|
}
|
|
1181
2704
|
return result;
|
|
1182
2705
|
},
|
|
@@ -1193,7 +2716,7 @@ function createSubscriptionService(adapter) {
|
|
|
1193
2716
|
discountId
|
|
1194
2717
|
});
|
|
1195
2718
|
if (result._tag === "Success") {
|
|
1196
|
-
await refreshAfterDiscountChange(
|
|
2719
|
+
await refreshAfterDiscountChange();
|
|
1197
2720
|
}
|
|
1198
2721
|
return result;
|
|
1199
2722
|
},
|
|
@@ -1202,11 +2725,13 @@ function createSubscriptionService(adapter) {
|
|
|
1202
2725
|
pause: (subscriptionId) => adapter.pause({ subscriptionId }),
|
|
1203
2726
|
reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
|
|
1204
2727
|
cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
|
|
2728
|
+
renewNow: (params) => adapter.renewNow(params),
|
|
2729
|
+
changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
|
|
2730
|
+
reload,
|
|
1205
2731
|
getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
|
|
1206
2732
|
getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
itemsChangedAt: itemsChangedAtSignal
|
|
2733
|
+
getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
|
|
2734
|
+
addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
|
|
1210
2735
|
};
|
|
1211
2736
|
return service;
|
|
1212
2737
|
}
|
|
@@ -1475,66 +3000,89 @@ export {
|
|
|
1475
3000
|
AuthenticationError,
|
|
1476
3001
|
AuthenticationErrorReason,
|
|
1477
3002
|
B as BlockWrapperEvent,
|
|
1478
|
-
|
|
1479
|
-
|
|
3003
|
+
m as ContextProvideEvent,
|
|
3004
|
+
p as ContextRequestEvent,
|
|
1480
3005
|
CustomerServiceContext,
|
|
1481
|
-
|
|
3006
|
+
D2 as DEFAULT_GLOBAL_STYLES,
|
|
1482
3007
|
DISCOUNT_REJECTION_REASONS,
|
|
1483
3008
|
LoginServiceContext,
|
|
1484
3009
|
OrdersServiceContext,
|
|
3010
|
+
OverlayServiceContext,
|
|
1485
3011
|
ProductServiceContext,
|
|
1486
3012
|
RouterServiceContext,
|
|
3013
|
+
SchedulesServiceContext,
|
|
1487
3014
|
S as SelectedBlockContext,
|
|
3015
|
+
SettingsServiceContext,
|
|
3016
|
+
ShopServiceContext,
|
|
1488
3017
|
u as Signal,
|
|
1489
3018
|
StateContext,
|
|
1490
3019
|
SubscriptionServiceContext,
|
|
1491
|
-
|
|
1492
|
-
|
|
3020
|
+
a2 as ThemeStateContext,
|
|
3021
|
+
T as TranslationContext,
|
|
1493
3022
|
W as WorkflowServiceContext,
|
|
1494
|
-
|
|
1495
|
-
|
|
3023
|
+
q as __recordDevtoolsInstance,
|
|
3024
|
+
_ as __recordDevtoolsPropSetter,
|
|
3025
|
+
t as attachEmptyTracking,
|
|
3026
|
+
b as backfillInstanceDefaults,
|
|
1496
3027
|
w as computed,
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
3028
|
+
createApiCustomerAdapter,
|
|
3029
|
+
createApiLoginAdapter,
|
|
3030
|
+
createApiOrdersAdapter,
|
|
3031
|
+
createApiProductAdapter,
|
|
3032
|
+
createApiSchedulesAdapter,
|
|
3033
|
+
createApiSubscriptionAdapter,
|
|
3034
|
+
createBlockInstance,
|
|
3035
|
+
v as createBlockInstanceFromJSON,
|
|
3036
|
+
f as createBlockInstanceFromObject,
|
|
3037
|
+
x as createCeRenderer,
|
|
1501
3038
|
createContext,
|
|
1502
3039
|
createCustomerService,
|
|
3040
|
+
createLoginService,
|
|
3041
|
+
createMockCustomerAdapter,
|
|
3042
|
+
createMockLoginAdapter,
|
|
1503
3043
|
createMockOrdersAdapter,
|
|
1504
3044
|
createMockProductAdapter,
|
|
1505
3045
|
createMockRouterService,
|
|
3046
|
+
createMockSchedulesAdapter,
|
|
1506
3047
|
createMockSubscriptionAdapter,
|
|
1507
3048
|
createMockWorkflowApiAdapter,
|
|
1508
3049
|
createOrdersService,
|
|
3050
|
+
createOverlayService,
|
|
1509
3051
|
createProductService,
|
|
1510
3052
|
createRouterService,
|
|
1511
|
-
|
|
3053
|
+
createSchedulesService,
|
|
3054
|
+
y as createSelectedBlock,
|
|
3055
|
+
createSettingsService,
|
|
3056
|
+
createShopService,
|
|
1512
3057
|
createState,
|
|
1513
3058
|
createSubscriptionService,
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
3059
|
+
c as createThemeState,
|
|
3060
|
+
b2 as createTranslationService,
|
|
3061
|
+
d2 as createWorkflowApiAdapter,
|
|
3062
|
+
e as createWorkflowService,
|
|
3063
|
+
defineBlock,
|
|
3064
|
+
z as defineBlockRuntime,
|
|
3065
|
+
defineOverlayWrapper,
|
|
1520
3066
|
E as effect,
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
F as
|
|
3067
|
+
g as getDefaultBlockForAction,
|
|
3068
|
+
getDefinedBlocks,
|
|
3069
|
+
A as getDevtoolsBlockState,
|
|
3070
|
+
h as hasDefaultBlockForAction,
|
|
3071
|
+
D as hasRenderedContent,
|
|
3072
|
+
i2 as indexOverrides,
|
|
3073
|
+
i as injectContext,
|
|
3074
|
+
F as isEditorPreview,
|
|
3075
|
+
a as isStubDefinition,
|
|
1529
3076
|
loadBlockRegistry,
|
|
1530
3077
|
loginService,
|
|
1531
|
-
|
|
1532
|
-
|
|
3078
|
+
o as onRegisterBlock,
|
|
3079
|
+
G as provideContext,
|
|
1533
3080
|
registerBlock,
|
|
1534
|
-
|
|
3081
|
+
j as serializeBlocks,
|
|
3082
|
+
H as setDevtoolsBlockProp,
|
|
1535
3083
|
d as signal,
|
|
1536
|
-
|
|
1537
|
-
|
|
3084
|
+
s as subscribeContext,
|
|
3085
|
+
n as untracked,
|
|
1538
3086
|
waitForDevBlocksUrl,
|
|
1539
|
-
|
|
3087
|
+
w2 as withWrapper
|
|
1540
3088
|
};
|