@juo/blocks 1.15.0 → 2.0.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-C3lVJEBl.js → ExtensionRoot-DRpD5EcC.js} +24 -6
- package/dist/block-BjJztStr.js +993 -0
- package/dist/{block-VhmfGB3N.js → block-DnhzyZ1H.js} +38 -3
- package/dist/block.d.ts +56 -0
- package/dist/block.d.ts.map +1 -1
- package/dist/{bridge-xiKEzCGR.js → bridge-Mz0CqG6E.js} +39 -30
- package/dist/core.js +2429 -399
- package/dist/css-runtime-B7tZrBIp.js +749 -0
- package/dist/devtools/hook.d.ts +0 -5
- package/dist/devtools/hook.d.ts.map +1 -1
- package/dist/devtools/index.d.ts +0 -1
- package/dist/devtools/index.d.ts.map +1 -1
- package/dist/devtools/protocol.d.ts +15 -1
- package/dist/devtools/protocol.d.ts.map +1 -1
- package/dist/devtools.js +42 -9
- 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/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 +52 -33
- package/dist/{gridstack-adapter-CDWbSpbt.js → gridstack-adapter-BGKGC-eO.js} +2 -2
- package/dist/main.d.ts +3 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/{morph-n4LMxOYo.js → morph-CT5hc5Qp.js} +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/block.d.ts.map +1 -1
- package/dist/preact/context.d.ts.map +1 -1
- package/dist/preact/context.test.d.ts +2 -0
- package/dist/preact/context.test.d.ts.map +1 -0
- package/dist/preact/signals.d.ts +19 -0
- package/dist/preact/signals.d.ts.map +1 -1
- package/dist/preact/useContextSubscription.test.d.ts +2 -0
- package/dist/preact/useContextSubscription.test.d.ts.map +1 -0
- package/dist/preact.js +33 -7
- package/dist/{project-gridstack-CM783oKI.js → project-gridstack-PxN_DIfP.js} +1 -1
- package/dist/react/block.d.ts.map +1 -1
- package/dist/react/context.d.ts.map +1 -1
- package/dist/react/context.test.d.ts +2 -0
- package/dist/react/context.test.d.ts.map +1 -0
- package/dist/react/signals.d.ts +19 -0
- package/dist/react/signals.d.ts.map +1 -1
- package/dist/react/useContextSubscription.test.d.ts +2 -0
- package/dist/react/useContextSubscription.test.d.ts.map +1 -0
- package/dist/react.js +40 -8
- 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 +4 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/login/adapter.d.ts +33 -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 +10 -0
- package/dist/services/order/adapter.d.ts.map +1 -0
- package/dist/services/order/index.d.ts +4 -0
- package/dist/services/order/index.d.ts.map +1 -0
- package/dist/services/order/orderService.d.ts +6 -0
- package/dist/services/order/orderService.d.ts.map +1 -0
- package/dist/services/order/orderService.test.d.ts +2 -0
- package/dist/services/order/orderService.test.d.ts.map +1 -0
- package/dist/services/{orders → order}/types.d.ts +3 -3
- package/dist/services/order/types.d.ts.map +1 -0
- package/dist/services/product/adapter.d.ts +27 -2
- package/dist/services/product/adapter.d.ts.map +1 -1
- package/dist/services/product/adapter.test.d.ts +2 -0
- package/dist/services/product/adapter.test.d.ts.map +1 -0
- package/dist/services/product/index.d.ts +1 -1
- package/dist/services/product/index.d.ts.map +1 -1
- package/dist/services/product/productService.d.ts.map +1 -1
- package/dist/services/product/types.d.ts +68 -0
- package/dist/services/product/types.d.ts.map +1 -1
- package/dist/services/schedules/adapter.d.ts +94 -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/apiSchedulesAdapter.test.d.ts +2 -0
- package/dist/services/schedules/apiSchedulesAdapter.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 +290 -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 +77 -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 +220 -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/shared/context.d.ts +10 -3
- package/dist/shared/context.d.ts.map +1 -1
- package/dist/shared/touchSignals.d.ts +17 -0
- package/dist/shared/touchSignals.d.ts.map +1 -0
- package/dist/shared/touchSignals.test.d.ts +2 -0
- package/dist/shared/touchSignals.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/context.d.ts +1 -0
- package/dist/vue/context.d.ts.map +1 -1
- package/dist/vue.js +28 -15
- 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 +6 -5
- package/dist/web-components-runtime.js +3 -2
- package/package.json +1 -1
- package/dist/css-runtime-ClH0MEYr.js +0 -1570
- package/dist/devtools/instrumentation.d.ts +0 -47
- package/dist/devtools/instrumentation.d.ts.map +0 -1
- package/dist/index-JSv6UH_o.js +0 -169
- package/dist/services/orders/adapter.d.ts +0 -8
- package/dist/services/orders/adapter.d.ts.map +0 -1
- package/dist/services/orders/index.d.ts +0 -4
- package/dist/services/orders/index.d.ts.map +0 -1
- package/dist/services/orders/ordersService.d.ts +0 -6
- package/dist/services/orders/ordersService.d.ts.map +0 -1
- package/dist/services/orders/ordersService.test.d.ts +0 -2
- package/dist/services/orders/ordersService.test.d.ts.map +0 -1
- package/dist/services/orders/types.d.ts.map +0 -1
package/dist/core.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
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, f as d, m as w, n as defineBlock, h as createBlockInstance, j as getDefinedBlocks, p as registerBlock, E, u, s as subscribeContext } from "./block-BjJztStr.js";
|
|
5
|
+
import { B, q, t, S, v, _, x, c, y, g, z, A, D, F, G, i, H, b, I, o, J, k, K, l, L, w as w2 } from "./block-BjJztStr.js";
|
|
6
|
+
import { G as GRID_V1_ENGINE_ID, d as clampRect, e as applyFillExpansion, f as compactItems, g as findFreeRect, D as DEFAULT_GRID } from "./css-runtime-B7tZrBIp.js";
|
|
7
|
+
import { h, i as i2, L as L2, P, a, T, b as b2, j, k as k2, l as l2, m, n, o as o2, p, q as q2, s, t as t2, u as u2, v as v2, c as c2, w as w3, r } from "./css-runtime-B7tZrBIp.js";
|
|
6
8
|
import { reportBlockWarning, reportBlockError } from "./observability.js";
|
|
7
|
-
import { a as createEditorBridge,
|
|
8
|
-
import { p } from "./project-gridstack-
|
|
9
|
+
import { a as createEditorBridge, E as createSelectBlockMessage } from "./bridge-Mz0CqG6E.js";
|
|
10
|
+
import { p as p2 } from "./project-gridstack-PxN_DIfP.js";
|
|
9
11
|
import { clearLayoutModeAdapter, getLayoutModeAdapter, registerLayoutModeAdapter } from "./layout-mode-adapter-BBd4JRQd.js";
|
|
10
12
|
function createState() {
|
|
11
13
|
const discountCode = d(null);
|
|
@@ -75,6 +77,111 @@ function createState() {
|
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
const StateContext = createContext("state");
|
|
80
|
+
const OVERLAY_WRAPPER_SCHEMA = {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
props: {
|
|
84
|
+
type: "object",
|
|
85
|
+
properties: {
|
|
86
|
+
layout: {
|
|
87
|
+
type: "string",
|
|
88
|
+
enum: ["modal", "sidebar"]
|
|
89
|
+
},
|
|
90
|
+
canAbort: {
|
|
91
|
+
type: "boolean"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
required: ["layout"],
|
|
95
|
+
additionalProperties: false
|
|
96
|
+
},
|
|
97
|
+
slots: {
|
|
98
|
+
type: "object",
|
|
99
|
+
properties: {
|
|
100
|
+
content: {}
|
|
101
|
+
},
|
|
102
|
+
additionalProperties: false
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
required: ["props", "slots"],
|
|
106
|
+
additionalProperties: false
|
|
107
|
+
};
|
|
108
|
+
function defineOverlayWrapper(opts) {
|
|
109
|
+
const layout = opts.layout ?? "sidebar";
|
|
110
|
+
const canAbort = opts.canAbort ?? true;
|
|
111
|
+
return defineBlock(opts.name, {
|
|
112
|
+
displayName: opts.title,
|
|
113
|
+
group: "internal",
|
|
114
|
+
schema: OVERLAY_WRAPPER_SCHEMA,
|
|
115
|
+
initialValue: () => ({
|
|
116
|
+
props: { layout, canAbort },
|
|
117
|
+
slots: {
|
|
118
|
+
content: [createBlockInstance(opts.content)]
|
|
119
|
+
}
|
|
120
|
+
}),
|
|
121
|
+
renderer: () => null
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const OVERLAY_PREFIX$1 = "overlay:";
|
|
125
|
+
function callInitialValue(block) {
|
|
126
|
+
if (typeof block.initialValue !== "function") return block.initialValue;
|
|
127
|
+
try {
|
|
128
|
+
return block.initialValue();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`[overlays] initialValue() threw for "${block.name}"; its content block will not be hidden from the palette.`,
|
|
132
|
+
error
|
|
133
|
+
);
|
|
134
|
+
return void 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function buildOverlayRegistry(blocks) {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
const registry = {};
|
|
140
|
+
for (const block of blocks) {
|
|
141
|
+
if (!block.name.startsWith(OVERLAY_PREFIX$1)) continue;
|
|
142
|
+
const id = block.name.slice(OVERLAY_PREFIX$1.length);
|
|
143
|
+
const initial = callInitialValue(block);
|
|
144
|
+
const first = (_b = (_a = initial == null ? void 0 : initial.slots) == null ? void 0 : _a.content) == null ? void 0 : _b[0];
|
|
145
|
+
const contentBlock = typeof (first == null ? void 0 : first.type) === "string" ? first.type : "";
|
|
146
|
+
registry[id] = {
|
|
147
|
+
block: block.name,
|
|
148
|
+
title: block.displayName ?? id,
|
|
149
|
+
contentBlock
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return registry;
|
|
153
|
+
}
|
|
154
|
+
const OVERLAY_PREFIX = "overlay:";
|
|
155
|
+
const OverlayServiceContext = createContext("services/overlay");
|
|
156
|
+
function createOverlayService(themeState, options = {}) {
|
|
157
|
+
const getBlocks = options.getBlocks ?? getDefinedBlocks;
|
|
158
|
+
const current = w(() => {
|
|
159
|
+
const root = themeState.overlay.blocks.value[0];
|
|
160
|
+
const type = root == null ? void 0 : root.type;
|
|
161
|
+
if (typeof type !== "string" || !type.startsWith(OVERLAY_PREFIX)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const props = root.props ?? {};
|
|
165
|
+
return {
|
|
166
|
+
name: type,
|
|
167
|
+
// Defaults mirror defineOverlayWrapper's seeding in overlay-wrapper.ts — keep in sync.
|
|
168
|
+
layout: props.layout ?? "sidebar",
|
|
169
|
+
canAbort: Boolean(props.canAbort ?? true)
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
getEditableOverlays() {
|
|
174
|
+
return buildOverlayRegistry(getBlocks());
|
|
175
|
+
},
|
|
176
|
+
open(name, params) {
|
|
177
|
+
void themeState.resolve("overlay", name, params);
|
|
178
|
+
},
|
|
179
|
+
close() {
|
|
180
|
+
themeState.overlay.blocks.value = [];
|
|
181
|
+
},
|
|
182
|
+
current
|
|
183
|
+
};
|
|
184
|
+
}
|
|
78
185
|
function isLocalhostOrigin(origin) {
|
|
79
186
|
try {
|
|
80
187
|
const url = new URL(origin);
|
|
@@ -159,13 +266,43 @@ async function loadBlockRegistry(options = {}) {
|
|
|
159
266
|
function createCustomerService(options) {
|
|
160
267
|
const current = d(null);
|
|
161
268
|
const accountOrdersUrl = d(options.getAccountOrdersUrl());
|
|
162
|
-
|
|
269
|
+
return {
|
|
163
270
|
current,
|
|
164
|
-
accountOrdersUrl
|
|
271
|
+
accountOrdersUrl,
|
|
272
|
+
async getCurrent() {
|
|
273
|
+
const result = await options.adapter.getCurrent();
|
|
274
|
+
if (result._tag === "Success") current.value = result.data;
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
165
277
|
};
|
|
166
|
-
return service;
|
|
167
278
|
}
|
|
168
279
|
const CustomerServiceContext = createContext("services/customer");
|
|
280
|
+
function createMockCustomerAdapter() {
|
|
281
|
+
const customer = { id: "customer-1", name: "Customer 1" };
|
|
282
|
+
return {
|
|
283
|
+
async getCurrent() {
|
|
284
|
+
return { _tag: "Success", data: customer };
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function createApiCustomerAdapter(fetcher) {
|
|
289
|
+
return {
|
|
290
|
+
async getCurrent() {
|
|
291
|
+
try {
|
|
292
|
+
const res = await fetcher("/customer/info");
|
|
293
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
294
|
+
const data = await res.json();
|
|
295
|
+
const name = [data.firstName, data.lastName].filter(Boolean).join(" ") || data.email || "";
|
|
296
|
+
return {
|
|
297
|
+
_tag: "Success",
|
|
298
|
+
data: { id: data.id, name, email: data.email }
|
|
299
|
+
};
|
|
300
|
+
} catch (e) {
|
|
301
|
+
return { _tag: "Failure", error: e };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
}
|
|
169
306
|
const PARAMS = {
|
|
170
307
|
DELETGATED_TOKEN: "delegated-token",
|
|
171
308
|
REDIRECT_TO: "redirect-to",
|
|
@@ -179,9 +316,126 @@ const PARAMS = {
|
|
|
179
316
|
function uuid() {
|
|
180
317
|
return ("10000000-1000-4000-8000" + -1e11).replace(
|
|
181
318
|
/[018]/g,
|
|
182
|
-
(
|
|
319
|
+
(c3) => (c3 ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c3 / 4).toString(16)
|
|
183
320
|
);
|
|
184
321
|
}
|
|
322
|
+
function createMockLoginAdapter(opts = {}) {
|
|
323
|
+
const appUrl = opts.appUrl ?? "https://admin.juo.io";
|
|
324
|
+
return {
|
|
325
|
+
async passwordlessStart() {
|
|
326
|
+
return { _tag: "Success", data: void 0 };
|
|
327
|
+
},
|
|
328
|
+
async authenticate() {
|
|
329
|
+
return {
|
|
330
|
+
_tag: "Success",
|
|
331
|
+
data: {
|
|
332
|
+
access_token: "mock-access-token",
|
|
333
|
+
refresh_token: "mock-refresh-token",
|
|
334
|
+
expires_in: 3600
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
},
|
|
338
|
+
getAuthorizeUrl({ connection, state, shop }) {
|
|
339
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
340
|
+
url.searchParams.set("connection", connection);
|
|
341
|
+
url.searchParams.set("state", state);
|
|
342
|
+
url.searchParams.set("shop", shop);
|
|
343
|
+
return url.toString();
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function createApiLoginAdapter(fetcher, opts) {
|
|
348
|
+
const { shopDomain, appUrl } = opts;
|
|
349
|
+
async function callAuthEndpoint(path, body, lang) {
|
|
350
|
+
const url = lang ? `${path}?lang=${lang}` : path;
|
|
351
|
+
const res = await fetcher(url, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: {
|
|
354
|
+
Accept: "application/json",
|
|
355
|
+
"Content-Type": "application/json",
|
|
356
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
357
|
+
},
|
|
358
|
+
body: JSON.stringify(body)
|
|
359
|
+
});
|
|
360
|
+
if (res.status === 403) {
|
|
361
|
+
const data = await res.json();
|
|
362
|
+
if ("error_description" in data) {
|
|
363
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
364
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
365
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
366
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
367
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
368
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
369
|
+
}
|
|
370
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
371
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
372
|
+
connection: data.connection
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (!res.ok) {
|
|
377
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
378
|
+
}
|
|
379
|
+
return { _tag: "Success", data: void 0 };
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
async passwordlessStart({ email, lang }) {
|
|
383
|
+
try {
|
|
384
|
+
await callAuthEndpoint(
|
|
385
|
+
"/auth/customer/passwordless/start",
|
|
386
|
+
{ email },
|
|
387
|
+
lang
|
|
388
|
+
);
|
|
389
|
+
return { _tag: "Success", data: void 0 };
|
|
390
|
+
} catch (e) {
|
|
391
|
+
return { _tag: "Failure", error: e };
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
async authenticate(params) {
|
|
395
|
+
try {
|
|
396
|
+
const res = await fetcher("/auth/customer/token", {
|
|
397
|
+
method: "POST",
|
|
398
|
+
headers: {
|
|
399
|
+
Accept: "application/json",
|
|
400
|
+
"Content-Type": "application/json",
|
|
401
|
+
"X-Shopify-Shop-Domain": shopDomain
|
|
402
|
+
},
|
|
403
|
+
body: JSON.stringify(params)
|
|
404
|
+
});
|
|
405
|
+
if (res.status === 403) {
|
|
406
|
+
const data = await res.json();
|
|
407
|
+
if ("error_description" in data) {
|
|
408
|
+
if (data.error_description === "Wrong email or verification code.") {
|
|
409
|
+
throw new AuthenticationError(AuthenticationErrorReason.IncorrectCode);
|
|
410
|
+
} else if (data.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
411
|
+
throw new AuthenticationError(AuthenticationErrorReason.TooManyTries);
|
|
412
|
+
} else if (data.error_description === "The verification code has expired. Please try to login again.") {
|
|
413
|
+
throw new AuthenticationError(AuthenticationErrorReason.Expired);
|
|
414
|
+
}
|
|
415
|
+
} else if ("code" in data && data.code === "AUTH_ERROR") {
|
|
416
|
+
throw new AuthenticationError(AuthenticationErrorReason.NoEmail, {
|
|
417
|
+
connection: data.connection
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!res.ok) {
|
|
422
|
+
throw new Error(`Unexpected response: ${res.statusText}`);
|
|
423
|
+
}
|
|
424
|
+
return { _tag: "Success", data: await res.json() };
|
|
425
|
+
} catch (e) {
|
|
426
|
+
return { _tag: "Failure", error: e };
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
getAuthorizeUrl({ connection, state, shop }) {
|
|
430
|
+
const url = new URL(`${appUrl}/auth/customer/authorize`);
|
|
431
|
+
url.searchParams.set("connection", connection);
|
|
432
|
+
url.searchParams.set("state", state);
|
|
433
|
+
url.searchParams.set("shop", shop);
|
|
434
|
+
return url.toString();
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const __vite_import_meta_env__ = {};
|
|
185
439
|
var AuthenticationErrorReason = /* @__PURE__ */ ((AuthenticationErrorReason2) => {
|
|
186
440
|
AuthenticationErrorReason2["Expired"] = "expired";
|
|
187
441
|
AuthenticationErrorReason2["IncorrectCode"] = "wrong-code";
|
|
@@ -200,6 +454,9 @@ class AuthenticationError extends Error {
|
|
|
200
454
|
this.data = data ?? {};
|
|
201
455
|
}
|
|
202
456
|
}
|
|
457
|
+
const STORAGE_DELEGATED_TOKEN = "delegated-token";
|
|
458
|
+
const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
|
|
459
|
+
const STORAGE_STATE_KEY = "state";
|
|
203
460
|
function storage(key, value) {
|
|
204
461
|
const storageKey = `auth:${key}`;
|
|
205
462
|
try {
|
|
@@ -215,83 +472,33 @@ function storage(key, value) {
|
|
|
215
472
|
}
|
|
216
473
|
return null;
|
|
217
474
|
}
|
|
218
|
-
const STORAGE_DELEGATED_TOKEN = "delegated-token";
|
|
219
|
-
const STORAGE_REFRESH_TOKEN_KEY = "refresh-token";
|
|
220
|
-
const STORAGE_STATE_KEY = "state";
|
|
221
|
-
function createAuthFunctions(basePath, shop) {
|
|
222
|
-
async function start(email, lang) {
|
|
223
|
-
const response = await fetch(
|
|
224
|
-
`${basePath}/auth/customer/passwordless/start?lang=${lang}`,
|
|
225
|
-
{
|
|
226
|
-
method: "POST",
|
|
227
|
-
headers: {
|
|
228
|
-
Accept: "application/json",
|
|
229
|
-
"Content-Type": "application/json",
|
|
230
|
-
"X-Shopify-Shop-Domain": shop
|
|
231
|
-
},
|
|
232
|
-
body: JSON.stringify({ email })
|
|
233
|
-
}
|
|
234
|
-
);
|
|
235
|
-
if (response.status !== 200) {
|
|
236
|
-
throw new Error(`Unexpected response: ${response.statusText}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
async function authenticate(data) {
|
|
240
|
-
const response = await fetch(`${basePath}/auth/customer/token`, {
|
|
241
|
-
method: "POST",
|
|
242
|
-
headers: {
|
|
243
|
-
Accept: "application/json",
|
|
244
|
-
"Content-Type": "application/json",
|
|
245
|
-
"X-Shopify-Shop-Domain": shop
|
|
246
|
-
},
|
|
247
|
-
body: JSON.stringify(data)
|
|
248
|
-
});
|
|
249
|
-
if (response.status === 403) {
|
|
250
|
-
const data2 = await response.json();
|
|
251
|
-
if ("error_description" in data2) {
|
|
252
|
-
if (data2.error_description === "Wrong email or verification code.") {
|
|
253
|
-
throw new AuthenticationError(
|
|
254
|
-
"wrong-code"
|
|
255
|
-
/* IncorrectCode */
|
|
256
|
-
);
|
|
257
|
-
} else if (data2.error_description === "You've reached the maximum number of attempts. Please try to login again.") {
|
|
258
|
-
throw new AuthenticationError(
|
|
259
|
-
"too-many-tries"
|
|
260
|
-
/* TooManyTries */
|
|
261
|
-
);
|
|
262
|
-
} else if (data2.error_description === "The verification code has expired. Please try to login again.") {
|
|
263
|
-
throw new AuthenticationError(
|
|
264
|
-
"expired"
|
|
265
|
-
/* Expired */
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
} else if ("code" in data2) {
|
|
269
|
-
if (data2.code === "AUTH_ERROR") {
|
|
270
|
-
throw new AuthenticationError("no-email", {
|
|
271
|
-
connection: data2.connection
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (response.status !== 200) {
|
|
277
|
-
throw new Error(`Unexpected response: ${response.statusText}`);
|
|
278
|
-
}
|
|
279
|
-
return await response.json();
|
|
280
|
-
}
|
|
281
|
-
return { start, authenticate };
|
|
282
|
-
}
|
|
283
475
|
function nonce(len = 10) {
|
|
284
476
|
return uuid().replace(/-/g, "").slice(0, len);
|
|
285
477
|
}
|
|
286
|
-
function
|
|
287
|
-
const
|
|
478
|
+
function loginService(deps) {
|
|
479
|
+
const apiBase = deps.shop.rootUrl.replace(/\/$/, "") + "/api";
|
|
480
|
+
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";
|
|
481
|
+
const fetcher = (path, init) => fetch(`${apiBase}${path}`, init);
|
|
482
|
+
const adapter = createApiLoginAdapter(fetcher, {
|
|
483
|
+
shopDomain: deps.shop.domain,
|
|
484
|
+
appUrl
|
|
485
|
+
});
|
|
486
|
+
return createLoginService({
|
|
487
|
+
adapter,
|
|
488
|
+
router: deps.router,
|
|
489
|
+
formatError: deps.formatError,
|
|
490
|
+
shop: {
|
|
491
|
+
domain: deps.shop.domain,
|
|
492
|
+
locale: w(() => deps.getLocale())
|
|
493
|
+
},
|
|
494
|
+
onLoginSuccess: deps.onLoginSuccess
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
function createLoginService(init) {
|
|
498
|
+
const { adapter, formatError, router, shop, onLoginSuccess } = init;
|
|
288
499
|
const accessToken = d(null);
|
|
289
|
-
const delegatedToken = d(
|
|
290
|
-
|
|
291
|
-
);
|
|
292
|
-
const refreshToken = d(
|
|
293
|
-
storage(STORAGE_REFRESH_TOKEN_KEY)
|
|
294
|
-
);
|
|
500
|
+
const delegatedToken = d(storage(STORAGE_DELEGATED_TOKEN));
|
|
501
|
+
const refreshToken = d(storage(STORAGE_REFRESH_TOKEN_KEY));
|
|
295
502
|
const expires = d(null);
|
|
296
503
|
const state = d(storage(STORAGE_STATE_KEY));
|
|
297
504
|
E(() => {
|
|
@@ -303,67 +510,28 @@ function createAuthService(apiUrl, shopDomain) {
|
|
|
303
510
|
E(() => {
|
|
304
511
|
storage(STORAGE_STATE_KEY, state.value);
|
|
305
512
|
});
|
|
306
|
-
function
|
|
513
|
+
function storeTokens(data) {
|
|
307
514
|
const { access_token, refresh_token, expires_in } = data;
|
|
308
515
|
accessToken.value = access_token;
|
|
309
516
|
refreshToken.value = refresh_token;
|
|
310
517
|
expires.value = new Date(Date.now() + expires_in);
|
|
311
518
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
getDelegatedToken() {
|
|
321
|
-
return delegatedToken.value;
|
|
322
|
-
},
|
|
323
|
-
setDelegatedToken(token) {
|
|
324
|
-
delegatedToken.value = token;
|
|
325
|
-
},
|
|
326
|
-
async getToken() {
|
|
327
|
-
if (refreshToken.value == null) {
|
|
328
|
-
return null;
|
|
329
|
-
}
|
|
330
|
-
if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
|
|
331
|
-
accessToken.value = new Promise(
|
|
332
|
-
(resolve, reject) => authAuthenticate({ refreshToken: refreshToken.value }).then((data) => data).then((data) => {
|
|
333
|
-
store(data);
|
|
334
|
-
resolve(data.access_token);
|
|
519
|
+
async function getToken() {
|
|
520
|
+
if (refreshToken.value == null) return null;
|
|
521
|
+
if (accessToken.value == null || typeof accessToken.value === "string" && (expires.value == null || expires.value < /* @__PURE__ */ new Date())) {
|
|
522
|
+
accessToken.value = new Promise(
|
|
523
|
+
(resolve, reject) => adapter.authenticate({ refreshToken: refreshToken.value }).then((result) => {
|
|
524
|
+
if (result._tag === "Failure") {
|
|
525
|
+
reject(result.error);
|
|
335
526
|
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
refreshToken.value = null;
|
|
345
|
-
expires.value = null;
|
|
346
|
-
},
|
|
347
|
-
isAuthenticated: w(
|
|
348
|
-
() => delegatedToken.value != null || refreshToken.value != null
|
|
349
|
-
)
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
function loginService(deps) {
|
|
353
|
-
return createLoginService({
|
|
354
|
-
apiUrl: `${deps.shop.rootUrl.replace(/\/$/, "")}/api`,
|
|
355
|
-
router: deps.router,
|
|
356
|
-
formatError: deps.formatError,
|
|
357
|
-
shop: {
|
|
358
|
-
domain: deps.shop.domain,
|
|
359
|
-
locale: w(() => deps.getLocale())
|
|
360
|
-
},
|
|
361
|
-
onLoginSuccess: deps.onLoginSuccess
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
function createLoginService(init) {
|
|
365
|
-
const { formatError, router, shop, onLoginSuccess } = init;
|
|
366
|
-
const auth = createAuthService(init.apiUrl.replace(/\/$/, ""), shop.domain);
|
|
527
|
+
}
|
|
528
|
+
storeTokens(result.data);
|
|
529
|
+
resolve(result.data.access_token);
|
|
530
|
+
}).catch(reject)
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
return Promise.resolve(accessToken.value);
|
|
534
|
+
}
|
|
367
535
|
const userEmail = d(null);
|
|
368
536
|
async function onSuccess(query) {
|
|
369
537
|
await (onLoginSuccess == null ? void 0 : onLoginSuccess());
|
|
@@ -376,13 +544,9 @@ function createLoginService(init) {
|
|
|
376
544
|
queryParams[key] = value;
|
|
377
545
|
}
|
|
378
546
|
});
|
|
379
|
-
router.push({
|
|
380
|
-
path: decodeURIComponent(redirect),
|
|
381
|
-
query: queryParams
|
|
382
|
-
});
|
|
547
|
+
router.push({ path: decodeURIComponent(redirect), query: queryParams });
|
|
383
548
|
} else {
|
|
384
|
-
|
|
385
|
-
router.replace(decodeURIComponent(redirect));
|
|
549
|
+
router.replace(decodeURIComponent(query.get(PARAMS.REDIRECT_TO)));
|
|
386
550
|
}
|
|
387
551
|
} else {
|
|
388
552
|
router.replace("/");
|
|
@@ -394,16 +558,17 @@ function createLoginService(init) {
|
|
|
394
558
|
async start(email) {
|
|
395
559
|
try {
|
|
396
560
|
userEmail.value = email;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
561
|
+
const lang = shop.locale instanceof u ? shop.locale.value : shop.locale;
|
|
562
|
+
const result = await adapter.passwordlessStart({
|
|
563
|
+
email: userEmail.value,
|
|
564
|
+
lang
|
|
565
|
+
});
|
|
566
|
+
if (result._tag === "Failure") {
|
|
567
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
568
|
+
}
|
|
401
569
|
return { _tag: "Success", data: void 0 };
|
|
402
570
|
} catch (error) {
|
|
403
|
-
return {
|
|
404
|
-
_tag: "Failure",
|
|
405
|
-
error: formatError(error)
|
|
406
|
-
};
|
|
571
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
407
572
|
}
|
|
408
573
|
},
|
|
409
574
|
async verify(email, code) {
|
|
@@ -414,63 +579,57 @@ function createLoginService(init) {
|
|
|
414
579
|
};
|
|
415
580
|
}
|
|
416
581
|
try {
|
|
417
|
-
const result = await
|
|
582
|
+
const result = await adapter.authenticate({
|
|
418
583
|
email: userEmail.value,
|
|
419
584
|
code
|
|
420
585
|
});
|
|
586
|
+
if (result._tag === "Failure") {
|
|
587
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
588
|
+
}
|
|
589
|
+
storeTokens(result.data);
|
|
421
590
|
await onSuccess();
|
|
422
|
-
return { _tag: "Success", data:
|
|
591
|
+
return { _tag: "Success", data: void 0 };
|
|
423
592
|
} catch (error) {
|
|
424
|
-
return {
|
|
425
|
-
_tag: "Failure",
|
|
426
|
-
error: formatError(error)
|
|
427
|
-
};
|
|
593
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
428
594
|
}
|
|
429
595
|
}
|
|
430
596
|
}
|
|
431
597
|
},
|
|
432
598
|
social: {
|
|
433
599
|
async start(provider) {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
url.searchParams.append("shop", shop2);
|
|
443
|
-
return url.toString();
|
|
444
|
-
}
|
|
445
|
-
window.location.href = socialUrl(
|
|
446
|
-
provider === "Facebook" ? "facebook" : "google-oauth2",
|
|
447
|
-
shop.domain
|
|
448
|
-
);
|
|
449
|
-
return {
|
|
450
|
-
_tag: "Success",
|
|
451
|
-
data: void 0
|
|
452
|
-
};
|
|
600
|
+
state.value = nonce();
|
|
601
|
+
const connection = provider === "Facebook" ? "facebook" : "google-oauth2";
|
|
602
|
+
window.location.href = adapter.getAuthorizeUrl({
|
|
603
|
+
connection,
|
|
604
|
+
state: state.value ?? "",
|
|
605
|
+
shop: shop.domain
|
|
606
|
+
});
|
|
607
|
+
return { _tag: "Success", data: void 0 };
|
|
453
608
|
},
|
|
454
609
|
async verify(code) {
|
|
455
610
|
try {
|
|
456
|
-
const result = await
|
|
611
|
+
const result = await adapter.authenticate({ code });
|
|
612
|
+
if (result._tag === "Failure") {
|
|
613
|
+
return { _tag: "Failure", error: formatError(result.error) };
|
|
614
|
+
}
|
|
615
|
+
storeTokens(result.data);
|
|
457
616
|
await onSuccess();
|
|
458
|
-
return { _tag: "Success", data:
|
|
617
|
+
return { _tag: "Success", data: void 0 };
|
|
459
618
|
} catch (error) {
|
|
460
|
-
return {
|
|
461
|
-
_tag: "Failure",
|
|
462
|
-
error: formatError(error)
|
|
463
|
-
};
|
|
619
|
+
return { _tag: "Failure", error: formatError(error) };
|
|
464
620
|
}
|
|
465
621
|
}
|
|
466
622
|
},
|
|
467
623
|
async logout() {
|
|
468
|
-
|
|
624
|
+
accessToken.value = null;
|
|
625
|
+
delegatedToken.value = null;
|
|
626
|
+
refreshToken.value = null;
|
|
627
|
+
expires.value = null;
|
|
469
628
|
return { _tag: "Success", data: void 0 };
|
|
470
629
|
},
|
|
471
630
|
async handleQueryParams(query) {
|
|
472
631
|
if (query.has(PARAMS.DELETGATED_TOKEN)) {
|
|
473
|
-
|
|
632
|
+
delegatedToken.value = query.get(PARAMS.DELETGATED_TOKEN);
|
|
474
633
|
await onSuccess(query);
|
|
475
634
|
return { _tag: "Success", data: void 0 };
|
|
476
635
|
}
|
|
@@ -482,16 +641,13 @@ function createLoginService(init) {
|
|
|
482
641
|
}
|
|
483
642
|
const code = query.get(PARAMS.CODE);
|
|
484
643
|
if (code != null) {
|
|
485
|
-
if (query.get(PARAMS.STATE) !==
|
|
486
|
-
return {
|
|
487
|
-
_tag: "Failure",
|
|
488
|
-
error: formatError("Invalid state")
|
|
489
|
-
};
|
|
644
|
+
if (query.get(PARAMS.STATE) !== state.value) {
|
|
645
|
+
return { _tag: "Failure", error: formatError("Invalid state") };
|
|
490
646
|
} else {
|
|
491
647
|
return service.social.verify(code);
|
|
492
648
|
}
|
|
493
649
|
}
|
|
494
|
-
if (query.get(PARAMS.REFRESH) === "1" && await
|
|
650
|
+
if (query.get(PARAMS.REFRESH) === "1" && await getToken() != null) {
|
|
495
651
|
await onSuccess();
|
|
496
652
|
return { _tag: "Success", data: void 0 };
|
|
497
653
|
}
|
|
@@ -508,13 +664,25 @@ function createLoginService(init) {
|
|
|
508
664
|
}
|
|
509
665
|
return { _tag: "Success", data: void 0 };
|
|
510
666
|
},
|
|
511
|
-
isAuthenticated:
|
|
512
|
-
|
|
667
|
+
isAuthenticated: w(
|
|
668
|
+
() => delegatedToken.value != null || refreshToken.value != null
|
|
669
|
+
),
|
|
670
|
+
getToken,
|
|
671
|
+
async getAuthHeaders() {
|
|
672
|
+
if (delegatedToken.value) {
|
|
673
|
+
return { "X-Delegated-Token": delegatedToken.value };
|
|
674
|
+
}
|
|
675
|
+
const token = await getToken();
|
|
676
|
+
if (token) {
|
|
677
|
+
return { Authorization: `Bearer ${token}` };
|
|
678
|
+
}
|
|
679
|
+
return {};
|
|
680
|
+
}
|
|
513
681
|
};
|
|
514
682
|
return service;
|
|
515
683
|
}
|
|
516
684
|
const LoginServiceContext = createContext("services/login");
|
|
517
|
-
function
|
|
685
|
+
function createMockOrderAdapter() {
|
|
518
686
|
const mockOrders = [
|
|
519
687
|
{
|
|
520
688
|
id: "10001",
|
|
@@ -575,7 +743,56 @@ function createMockOrdersAdapter() {
|
|
|
575
743
|
}
|
|
576
744
|
};
|
|
577
745
|
}
|
|
578
|
-
function
|
|
746
|
+
function mapOrderLine(item) {
|
|
747
|
+
var _a;
|
|
748
|
+
return {
|
|
749
|
+
id: item.id,
|
|
750
|
+
title: item.title,
|
|
751
|
+
quantity: item.quantity,
|
|
752
|
+
variantImage: ((_a = item.variantImage) == null ? void 0 : _a.url) ? { url: item.variantImage.url, altText: item.variantImage.altText ?? void 0 } : null,
|
|
753
|
+
customAttributes: item.customAttributes
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function mapOrder(order) {
|
|
757
|
+
return {
|
|
758
|
+
id: order.id,
|
|
759
|
+
createdAt: order.createdAt,
|
|
760
|
+
placedAt: order.placedAt,
|
|
761
|
+
currencyCode: order.currencyCode,
|
|
762
|
+
totalPrice: order.totalPrice,
|
|
763
|
+
financialStatus: order.financialStatus,
|
|
764
|
+
fulfillmentStatus: order.fulfillmentStatus,
|
|
765
|
+
lines: order.items.map(mapOrderLine),
|
|
766
|
+
customAttributes: order.customAttributes
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function createApiOrderAdapter(fetcher) {
|
|
770
|
+
return {
|
|
771
|
+
async getOrders(params) {
|
|
772
|
+
const query = new URLSearchParams();
|
|
773
|
+
if ((params == null ? void 0 : params.limit) != null) query.set("limit", String(params.limit));
|
|
774
|
+
if ((params == null ? void 0 : params.after) != null) query.set("after", params.after);
|
|
775
|
+
if ((params == null ? void 0 : params.before) != null) query.set("before", params.before);
|
|
776
|
+
if ((params == null ? void 0 : params.query) != null) query.set("query", params.query);
|
|
777
|
+
if ((params == null ? void 0 : params.includeOneTime) != null) query.set("includeOneTime", String(params.includeOneTime));
|
|
778
|
+
if ((params == null ? void 0 : params.sort) != null) query.set("sort", params.sort);
|
|
779
|
+
const qs = query.toString();
|
|
780
|
+
const res = await fetcher(`/customer/v1/orders${qs ? `?${qs}` : ""}`);
|
|
781
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
782
|
+
const body = await res.json();
|
|
783
|
+
return {
|
|
784
|
+
items: body.data.map(mapOrder),
|
|
785
|
+
pageInfo: {
|
|
786
|
+
hasNextPage: body.hasNextPage,
|
|
787
|
+
hasPrevPage: body.hasPrevPage,
|
|
788
|
+
startCursor: body.startCursor,
|
|
789
|
+
endCursor: body.endCursor
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function createOrderService(adapter) {
|
|
579
796
|
return {
|
|
580
797
|
async getOrders(params) {
|
|
581
798
|
try {
|
|
@@ -587,14 +804,14 @@ function createOrdersService(adapter) {
|
|
|
587
804
|
}
|
|
588
805
|
};
|
|
589
806
|
}
|
|
590
|
-
const
|
|
807
|
+
const OrderServiceContext = createContext("services/order");
|
|
591
808
|
function createMockProductAdapter() {
|
|
592
809
|
const products = [
|
|
593
810
|
{
|
|
594
811
|
id: "sample-1",
|
|
595
812
|
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
596
813
|
featuredImage: {
|
|
597
|
-
url: "https://
|
|
814
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
598
815
|
},
|
|
599
816
|
hasOnlyDefaultVariant: true,
|
|
600
817
|
variants: [
|
|
@@ -602,7 +819,7 @@ function createMockProductAdapter() {
|
|
|
602
819
|
id: "sample-1-1",
|
|
603
820
|
title: "150 ml bottle",
|
|
604
821
|
image: {
|
|
605
|
-
url: "https://
|
|
822
|
+
url: "https://placehold.co/400x400?text=Product+1"
|
|
606
823
|
},
|
|
607
824
|
price: {
|
|
608
825
|
amount: "12.49",
|
|
@@ -628,7 +845,7 @@ function createMockProductAdapter() {
|
|
|
628
845
|
id: "sample-2",
|
|
629
846
|
title: "More COLLAGEN",
|
|
630
847
|
featuredImage: {
|
|
631
|
-
url: "https://
|
|
848
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
632
849
|
},
|
|
633
850
|
hasOnlyDefaultVariant: true,
|
|
634
851
|
variants: [
|
|
@@ -636,7 +853,7 @@ function createMockProductAdapter() {
|
|
|
636
853
|
id: "sample-2-1",
|
|
637
854
|
title: "blackcurrant",
|
|
638
855
|
image: {
|
|
639
|
-
url: "https://
|
|
856
|
+
url: "https://placehold.co/400x400?text=Product+2"
|
|
640
857
|
},
|
|
641
858
|
price: {
|
|
642
859
|
amount: "12.49",
|
|
@@ -657,7 +874,7 @@ function createMockProductAdapter() {
|
|
|
657
874
|
id: "sample-3",
|
|
658
875
|
title: "Gentle deodorant for underarms and bust",
|
|
659
876
|
featuredImage: {
|
|
660
|
-
url: "https://
|
|
877
|
+
url: "https://placehold.co/400x400?text=Product+3"
|
|
661
878
|
},
|
|
662
879
|
hasOnlyDefaultVariant: false,
|
|
663
880
|
variants: [
|
|
@@ -665,7 +882,7 @@ function createMockProductAdapter() {
|
|
|
665
882
|
id: "sample-3-1",
|
|
666
883
|
title: "Green tea and jasmine",
|
|
667
884
|
image: {
|
|
668
|
-
url: "https://
|
|
885
|
+
url: "https://placehold.co/400x400?text=Product+3a"
|
|
669
886
|
},
|
|
670
887
|
price: {
|
|
671
888
|
amount: "6.7",
|
|
@@ -683,7 +900,7 @@ function createMockProductAdapter() {
|
|
|
683
900
|
id: "sample-3-2",
|
|
684
901
|
title: "Lavender and mint",
|
|
685
902
|
image: {
|
|
686
|
-
url: "https://
|
|
903
|
+
url: "https://placehold.co/400x400?text=Product+3b"
|
|
687
904
|
},
|
|
688
905
|
price: {
|
|
689
906
|
amount: "7.5",
|
|
@@ -715,6 +932,48 @@ function createMockProductAdapter() {
|
|
|
715
932
|
}
|
|
716
933
|
};
|
|
717
934
|
}
|
|
935
|
+
function toAvailableProduct(product) {
|
|
936
|
+
return {
|
|
937
|
+
id: product.id,
|
|
938
|
+
title: product.title,
|
|
939
|
+
description: `Sample description for ${product.title}.`,
|
|
940
|
+
featuredImage: product.featuredImage ?? void 0,
|
|
941
|
+
hasOnlyDefaultVariant: product.hasOnlyDefaultVariant,
|
|
942
|
+
variants: product.variants.map((variant) => {
|
|
943
|
+
var _a, _b, _c;
|
|
944
|
+
const base = Number.parseFloat(variant.price.amount);
|
|
945
|
+
const sub = (_b = (_a = variant.purchaseOptions) == null ? void 0 : _a[0]) == null ? void 0 : _b.price.amount;
|
|
946
|
+
const current = sub == null ? base : Number.parseFloat(sub);
|
|
947
|
+
return {
|
|
948
|
+
id: variant.id,
|
|
949
|
+
availableForSale: true,
|
|
950
|
+
title: variant.title,
|
|
951
|
+
currentPrice: { amount: current, currencyCode: variant.price.currencyCode },
|
|
952
|
+
basePrice: { amount: base, currencyCode: variant.price.currencyCode },
|
|
953
|
+
image: variant.image ?? void 0,
|
|
954
|
+
recurring: (((_c = variant.purchaseOptions) == null ? void 0 : _c.length) ?? 0) > 0
|
|
955
|
+
};
|
|
956
|
+
})
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const sampleBox = {
|
|
960
|
+
id: "sample-box-1",
|
|
961
|
+
title: "Build Your Own Box",
|
|
962
|
+
minProductsCount: 3,
|
|
963
|
+
parentProduct: "sample-1",
|
|
964
|
+
tiers: [
|
|
965
|
+
{ value: 5, threshold: 3 },
|
|
966
|
+
{ value: 10, threshold: 5 }
|
|
967
|
+
],
|
|
968
|
+
variants: products.map((product) => ({
|
|
969
|
+
id: product.id,
|
|
970
|
+
variants: product.variants.map((variant) => ({ id: variant.id }))
|
|
971
|
+
})),
|
|
972
|
+
freeVariants: [],
|
|
973
|
+
thresholdType: "LINES_COUNT",
|
|
974
|
+
type: "PERCENTAGE",
|
|
975
|
+
restrictQuantityToTiers: false
|
|
976
|
+
};
|
|
718
977
|
return {
|
|
719
978
|
async getById(id) {
|
|
720
979
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -728,6 +987,27 @@ function createMockProductAdapter() {
|
|
|
728
987
|
);
|
|
729
988
|
return product == null ? null : processProduct(product);
|
|
730
989
|
},
|
|
990
|
+
async getProductsByIds({ ids }) {
|
|
991
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
992
|
+
return products.filter((product) => ids.includes(product.id)).map(({ variants: _variants, ...product }) => product);
|
|
993
|
+
},
|
|
994
|
+
async getBoxVariants({ ids }) {
|
|
995
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
996
|
+
return products.filter((product) => ids.includes(product.id)).map(toAvailableProduct);
|
|
997
|
+
},
|
|
998
|
+
async getAvailableProducts({ q: q3 }) {
|
|
999
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1000
|
+
const queryLower = q3 == null ? void 0 : q3.toLowerCase();
|
|
1001
|
+
return products.filter(
|
|
1002
|
+
(product) => !queryLower || product.title.toLowerCase().includes(queryLower) || product.variants.some(
|
|
1003
|
+
(variant) => variant.title.toLowerCase().includes(queryLower)
|
|
1004
|
+
)
|
|
1005
|
+
).map(toAvailableProduct);
|
|
1006
|
+
},
|
|
1007
|
+
async getBox({ id }) {
|
|
1008
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1009
|
+
return id === sampleBox.id ? sampleBox : null;
|
|
1010
|
+
},
|
|
731
1011
|
async search(opts) {
|
|
732
1012
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
733
1013
|
let filteredProducts = [...products];
|
|
@@ -768,23 +1048,206 @@ function createMockProductAdapter() {
|
|
|
768
1048
|
}
|
|
769
1049
|
};
|
|
770
1050
|
}
|
|
771
|
-
|
|
772
|
-
|
|
1051
|
+
function mapVariant(variant, currencyCode) {
|
|
1052
|
+
var _a;
|
|
773
1053
|
return {
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
},
|
|
777
|
-
|
|
778
|
-
|
|
1054
|
+
id: variant.id,
|
|
1055
|
+
title: variant.title,
|
|
1056
|
+
image: variant.image == null ? null : { url: variant.image.url, altText: variant.image.altText ?? void 0 },
|
|
1057
|
+
price: { amount: variant.price.toFixed(2), currencyCode },
|
|
1058
|
+
...((_a = variant.purchaseOptions) == null ? void 0 : _a.length) ? {
|
|
1059
|
+
purchaseOptions: variant.purchaseOptions.map((opt) => ({
|
|
1060
|
+
name: opt.name,
|
|
1061
|
+
price: { amount: opt.price.toFixed(2), currencyCode },
|
|
1062
|
+
interval: opt.interval
|
|
1063
|
+
}))
|
|
1064
|
+
} : {}
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
function mapProduct(product, currencyCode) {
|
|
1068
|
+
return {
|
|
1069
|
+
id: product.id,
|
|
1070
|
+
title: product.title,
|
|
1071
|
+
featuredImage: product.image == null ? null : { url: product.image.url, altText: product.image.altText ?? void 0 },
|
|
1072
|
+
hasOnlyDefaultVariant: product.variants.length <= 1,
|
|
1073
|
+
variants: {
|
|
1074
|
+
items: product.variants.map((v3) => mapVariant(v3, currencyCode)),
|
|
1075
|
+
pageInfo: {
|
|
1076
|
+
hasNextPage: false,
|
|
1077
|
+
hasPrevPage: false,
|
|
1078
|
+
startCursor: null,
|
|
1079
|
+
endCursor: null
|
|
1080
|
+
}
|
|
779
1081
|
},
|
|
780
|
-
|
|
781
|
-
return adapter.search(opts);
|
|
782
|
-
}
|
|
1082
|
+
collections: product.collections
|
|
783
1083
|
};
|
|
784
1084
|
}
|
|
785
|
-
function
|
|
786
|
-
const
|
|
787
|
-
|
|
1085
|
+
function createApiProductAdapter(fetcher, opts = { currencyCode: "USD" }) {
|
|
1086
|
+
const { currencyCode } = opts;
|
|
1087
|
+
async function fetchProducts(params) {
|
|
1088
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1089
|
+
const res = await fetcher(`/customer/v1/products${query}`);
|
|
1090
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
1091
|
+
return res.json();
|
|
1092
|
+
}
|
|
1093
|
+
return {
|
|
1094
|
+
async getById(id) {
|
|
1095
|
+
try {
|
|
1096
|
+
const params = new URLSearchParams({ query: `id:"${id}"`, limit: "1" });
|
|
1097
|
+
const body = await fetchProducts(params);
|
|
1098
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1099
|
+
} catch {
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
1103
|
+
async getByVariantId(variantId) {
|
|
1104
|
+
try {
|
|
1105
|
+
const params = new URLSearchParams({
|
|
1106
|
+
query: `variants.id:"${variantId}"`,
|
|
1107
|
+
limit: "1"
|
|
1108
|
+
});
|
|
1109
|
+
const body = await fetchProducts(params);
|
|
1110
|
+
return body.data.length > 0 ? mapProduct(body.data[0], currencyCode) : null;
|
|
1111
|
+
} catch {
|
|
1112
|
+
return null;
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
async search(opts2) {
|
|
1116
|
+
try {
|
|
1117
|
+
const params = new URLSearchParams();
|
|
1118
|
+
if (opts2 == null ? void 0 : opts2.query) {
|
|
1119
|
+
params.set("query", `"${opts2.query.trim().replace(/"/g, '\\"')}"`);
|
|
1120
|
+
}
|
|
1121
|
+
if (opts2 == null ? void 0 : opts2.pagination) {
|
|
1122
|
+
if ("after" in opts2.pagination)
|
|
1123
|
+
params.set("after", opts2.pagination.after);
|
|
1124
|
+
else if ("before" in opts2.pagination)
|
|
1125
|
+
params.set("before", opts2.pagination.before);
|
|
1126
|
+
}
|
|
1127
|
+
const body = await fetchProducts(params);
|
|
1128
|
+
return {
|
|
1129
|
+
items: body.data.map((p3) => mapProduct(p3, currencyCode)),
|
|
1130
|
+
pageInfo: {
|
|
1131
|
+
hasNextPage: body.hasNextPage,
|
|
1132
|
+
hasPrevPage: body.hasPrevPage,
|
|
1133
|
+
startCursor: body.startCursor,
|
|
1134
|
+
endCursor: body.endCursor
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
} catch {
|
|
1138
|
+
return {
|
|
1139
|
+
items: [],
|
|
1140
|
+
pageInfo: {
|
|
1141
|
+
hasNextPage: false,
|
|
1142
|
+
hasPrevPage: false,
|
|
1143
|
+
startCursor: null,
|
|
1144
|
+
endCursor: null
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
},
|
|
1149
|
+
async getProductsByIds({ ids }) {
|
|
1150
|
+
const params = new URLSearchParams();
|
|
1151
|
+
for (const id of ids) params.append("ids", id);
|
|
1152
|
+
const res = await fetcher(`/customer/products/by-ids?${params.toString()}`);
|
|
1153
|
+
if (!res.ok) return [];
|
|
1154
|
+
const data = await res.json();
|
|
1155
|
+
return (data ?? []).map((p3) => ({
|
|
1156
|
+
id: p3.id,
|
|
1157
|
+
title: p3.title ?? "",
|
|
1158
|
+
featuredImage: p3.featuredImage ? { url: p3.featuredImage.url, altText: p3.featuredImage.altText } : null,
|
|
1159
|
+
hasOnlyDefaultVariant: true,
|
|
1160
|
+
collections: []
|
|
1161
|
+
}));
|
|
1162
|
+
},
|
|
1163
|
+
async getBoxVariants({ ids, subscriptionId }) {
|
|
1164
|
+
const params = new URLSearchParams();
|
|
1165
|
+
for (const id of ids) params.append("ids", id);
|
|
1166
|
+
params.set("subscriptionId", subscriptionId);
|
|
1167
|
+
const res = await fetcher(`/customer/products/variants?${params.toString()}`);
|
|
1168
|
+
if (!res.ok) return [];
|
|
1169
|
+
const data = await res.json();
|
|
1170
|
+
return (data ?? []).map((p3) => {
|
|
1171
|
+
var _a;
|
|
1172
|
+
return {
|
|
1173
|
+
id: p3.id,
|
|
1174
|
+
title: p3.title,
|
|
1175
|
+
description: p3.description ?? "",
|
|
1176
|
+
featuredImage: p3.featuredImage,
|
|
1177
|
+
hasOnlyDefaultVariant: p3.hasOnlyDefaultVariant ?? (((_a = p3.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1178
|
+
variants: (p3.variants ?? []).map((v3) => ({
|
|
1179
|
+
id: v3.id,
|
|
1180
|
+
availableForSale: v3.availableForSale,
|
|
1181
|
+
title: v3.title,
|
|
1182
|
+
currentPrice: v3.currentPrice,
|
|
1183
|
+
basePrice: v3.basePrice,
|
|
1184
|
+
image: v3.image,
|
|
1185
|
+
recurring: v3.recurring
|
|
1186
|
+
}))
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
1189
|
+
},
|
|
1190
|
+
async getAvailableProducts({ subscriptionId, lineId, q: q3, mode, action, pagination }) {
|
|
1191
|
+
const params = new URLSearchParams();
|
|
1192
|
+
params.set("action", action);
|
|
1193
|
+
if (q3) params.set("q", q3);
|
|
1194
|
+
if (mode) params.set("mode", mode);
|
|
1195
|
+
if (pagination && "after" in pagination) params.set("after", pagination.after);
|
|
1196
|
+
if (pagination && "before" in pagination) params.set("before", pagination.before);
|
|
1197
|
+
const res = await fetcher(
|
|
1198
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}/available-products?${params.toString()}`
|
|
1199
|
+
);
|
|
1200
|
+
if (!res.ok) return [];
|
|
1201
|
+
const data = await res.json();
|
|
1202
|
+
return (data ?? []).map((p3) => {
|
|
1203
|
+
var _a;
|
|
1204
|
+
return {
|
|
1205
|
+
id: p3.id,
|
|
1206
|
+
title: p3.title,
|
|
1207
|
+
description: p3.description ?? "",
|
|
1208
|
+
featuredImage: p3.featuredImage,
|
|
1209
|
+
hasOnlyDefaultVariant: p3.hasOnlyDefaultVariant ?? (((_a = p3.variants) == null ? void 0 : _a.length) ?? 0) <= 1,
|
|
1210
|
+
variants: (p3.variants ?? []).map((v3) => ({
|
|
1211
|
+
id: v3.id,
|
|
1212
|
+
availableForSale: v3.availableForSale,
|
|
1213
|
+
title: v3.title,
|
|
1214
|
+
currentPrice: v3.currentPrice,
|
|
1215
|
+
basePrice: v3.basePrice,
|
|
1216
|
+
image: v3.image,
|
|
1217
|
+
recurring: v3.recurring
|
|
1218
|
+
}))
|
|
1219
|
+
};
|
|
1220
|
+
});
|
|
1221
|
+
},
|
|
1222
|
+
async getBox({ id }) {
|
|
1223
|
+
const res = await fetcher(`/customer/boxes/${id}`);
|
|
1224
|
+
if (!res.ok) return null;
|
|
1225
|
+
const data = await res.json();
|
|
1226
|
+
return { id, ...data };
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const ProductServiceContext = createContext("services/product");
|
|
1231
|
+
function createProductService(adapter) {
|
|
1232
|
+
return {
|
|
1233
|
+
async getById(id) {
|
|
1234
|
+
return adapter.getById(id);
|
|
1235
|
+
},
|
|
1236
|
+
async getByVariantId(variantId) {
|
|
1237
|
+
return adapter.getByVariantId(variantId);
|
|
1238
|
+
},
|
|
1239
|
+
async search(opts) {
|
|
1240
|
+
return adapter.search(opts);
|
|
1241
|
+
},
|
|
1242
|
+
getProductsByIds: (ids) => adapter.getProductsByIds({ ids }),
|
|
1243
|
+
getBoxVariants: (ids, subscriptionId) => adapter.getBoxVariants({ ids, subscriptionId }),
|
|
1244
|
+
getAvailableProducts: (params) => adapter.getAvailableProducts(params),
|
|
1245
|
+
getBox: (id) => adapter.getBox({ id })
|
|
1246
|
+
};
|
|
1247
|
+
}
|
|
1248
|
+
function routeNameTableFromPages(pages) {
|
|
1249
|
+
const table = {};
|
|
1250
|
+
for (const page of pages) {
|
|
788
1251
|
const names = page.routeNames;
|
|
789
1252
|
if (names.length === 0) continue;
|
|
790
1253
|
for (const name of names) {
|
|
@@ -796,7 +1259,7 @@ function routeNameTableFromPages(pages) {
|
|
|
796
1259
|
function createRouterService(options) {
|
|
797
1260
|
const { pages, pushImplementation } = options;
|
|
798
1261
|
const routeNameToPagePath = routeNameTableFromPages(pages);
|
|
799
|
-
const pathSet = new Set(pages.map((
|
|
1262
|
+
const pathSet = new Set(pages.map((p3) => p3.path));
|
|
800
1263
|
const navigationSubscribers = [];
|
|
801
1264
|
function notifySubscribers(logicalPath) {
|
|
802
1265
|
for (const cb of navigationSubscribers) cb({ path: logicalPath });
|
|
@@ -833,7 +1296,85 @@ function createMockRouterService() {
|
|
|
833
1296
|
}
|
|
834
1297
|
}).routerService;
|
|
835
1298
|
}
|
|
1299
|
+
const DISCOUNT_REJECTION_REASONS = [
|
|
1300
|
+
"INVALID_CODE",
|
|
1301
|
+
"INACTIVE",
|
|
1302
|
+
"NOT_APPLICABLE_TO_CUSTOMER",
|
|
1303
|
+
"ALREADY_ADDED",
|
|
1304
|
+
"ALREADY_USED",
|
|
1305
|
+
"NOT_APPLICABLE_TO_PRODUCTS",
|
|
1306
|
+
"NOT_APPLICABLE_TO_SUBSCRIPTION",
|
|
1307
|
+
"INVALID_COMBINATION",
|
|
1308
|
+
"QUANTITY_REQUIREMENT_NOT_MET"
|
|
1309
|
+
];
|
|
1310
|
+
function toAttributeArray(next) {
|
|
1311
|
+
return Object.entries(next).map(([key, value]) => ({ key, value }));
|
|
1312
|
+
}
|
|
836
1313
|
function createMockSubscriptionAdapter(options = {}) {
|
|
1314
|
+
const richBase = (id, itemId, renewalId) => ({
|
|
1315
|
+
id,
|
|
1316
|
+
customerId: "customer_1",
|
|
1317
|
+
items: [
|
|
1318
|
+
{
|
|
1319
|
+
id: itemId,
|
|
1320
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1321
|
+
// sample-3 "Gentle deodorant", variant sample-3-1 "Green tea and jasmine",
|
|
1322
|
+
// recurring price 5.36 EUR (10% off → 4.82 with SAVE10).
|
|
1323
|
+
productId: "sample-3",
|
|
1324
|
+
variantId: "sample-3-1",
|
|
1325
|
+
quantity: 2,
|
|
1326
|
+
price: { amount: "5.36", currencyCode: "EUR" },
|
|
1327
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1328
|
+
isUpsell: false,
|
|
1329
|
+
isVirtual: false,
|
|
1330
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1331
|
+
currentPrice: { amount: "5.36", currencyCode: "EUR" },
|
|
1332
|
+
lineDiscountedPrice: { amount: "4.82", currencyCode: "EUR" },
|
|
1333
|
+
variantAvailableForSale: true,
|
|
1334
|
+
title: "Gentle deodorant for underarms and bust",
|
|
1335
|
+
variantTitle: "Green tea and jasmine",
|
|
1336
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+3a" },
|
|
1337
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1338
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" }
|
|
1339
|
+
}
|
|
1340
|
+
],
|
|
1341
|
+
discounts: [
|
|
1342
|
+
{
|
|
1343
|
+
id: "discount_1",
|
|
1344
|
+
type: "percentage",
|
|
1345
|
+
value: 10,
|
|
1346
|
+
code: "SAVE10",
|
|
1347
|
+
description: "10% off subscription"
|
|
1348
|
+
}
|
|
1349
|
+
],
|
|
1350
|
+
status: "active",
|
|
1351
|
+
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1352
|
+
frequency: "1 MONTH",
|
|
1353
|
+
createdAt: "2024-10-01T00:00:00Z",
|
|
1354
|
+
updatedAt: "2024-10-14T00:00:00Z",
|
|
1355
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1356
|
+
deliveryPolicy: {
|
|
1357
|
+
intervalCount: 1,
|
|
1358
|
+
interval: "MONTH",
|
|
1359
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1360
|
+
},
|
|
1361
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1362
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1363
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1364
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1365
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1366
|
+
customerPaymentMethod: {
|
|
1367
|
+
id: "pm_1",
|
|
1368
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1369
|
+
active: true,
|
|
1370
|
+
isRecurring: true
|
|
1371
|
+
},
|
|
1372
|
+
requiredBillingCyclesLeft: 3,
|
|
1373
|
+
renewal: { id: renewalId, isInActiveProcessState: false },
|
|
1374
|
+
lastPaymentStatus: "succeeded",
|
|
1375
|
+
pausePeriod: null,
|
|
1376
|
+
isEditable: true
|
|
1377
|
+
});
|
|
837
1378
|
const mockSubscriptions = [
|
|
838
1379
|
{
|
|
839
1380
|
id: "sub_1",
|
|
@@ -841,14 +1382,27 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
841
1382
|
items: [
|
|
842
1383
|
{
|
|
843
1384
|
id: "item_1",
|
|
844
|
-
|
|
845
|
-
|
|
1385
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1386
|
+
// sample-1 "Hydrating Gel Cleanser" (default variant), recurring price
|
|
1387
|
+
// 9.99 EUR (10% off → 8.99 with SAVE10).
|
|
1388
|
+
productId: "sample-1",
|
|
1389
|
+
variantId: "sample-1-1",
|
|
846
1390
|
quantity: 2,
|
|
847
|
-
price: { amount: "
|
|
1391
|
+
price: { amount: "9.99", currencyCode: "EUR" },
|
|
848
1392
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-15"),
|
|
1393
|
+
isUpsell: false,
|
|
1394
|
+
isVirtual: false,
|
|
1395
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1396
|
+
currentPrice: { amount: "9.99", currencyCode: "EUR" },
|
|
1397
|
+
lineDiscountedPrice: { amount: "8.99", currencyCode: "EUR" },
|
|
1398
|
+
variantAvailableForSale: true,
|
|
1399
|
+
title: "Hydrating Gel Cleanser for Face and Eye Area",
|
|
1400
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+1" },
|
|
1401
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
1402
|
+
deliveryPolicy: { intervalCount: 1, interval: "MONTH" },
|
|
849
1403
|
customAttributes: [
|
|
850
|
-
{ key: "
|
|
851
|
-
{ key: "
|
|
1404
|
+
{ key: "skin_type", value: "combination" },
|
|
1405
|
+
{ key: "fragrance", value: "fragrance_free" }
|
|
852
1406
|
]
|
|
853
1407
|
}
|
|
854
1408
|
],
|
|
@@ -866,9 +1420,31 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
866
1420
|
frequency: "1 MONTH",
|
|
867
1421
|
createdAt: "2024-10-01T00:00:00Z",
|
|
868
1422
|
updatedAt: "2024-10-14T00:00:00Z",
|
|
1423
|
+
billingPolicy: { intervalCount: 1, interval: "MONTH", minCycles: 6 },
|
|
1424
|
+
deliveryPolicy: {
|
|
1425
|
+
intervalCount: 1,
|
|
1426
|
+
interval: "MONTH",
|
|
1427
|
+
anchors: [{ day: 15, type: "MONTHDAY" }]
|
|
1428
|
+
},
|
|
1429
|
+
nextBillingDate: "2025-11-15T00:00:00Z",
|
|
1430
|
+
nextBillingAttempt: { date: "2025-11-15T00:00:00Z", attempt: 1, ready: true },
|
|
1431
|
+
lastBillingAttempt: { date: "2025-10-15T00:00:00Z", ready: true },
|
|
1432
|
+
nextFulfillment: "2025-11-17T00:00:00Z",
|
|
1433
|
+
estimatedOrderDate: "2025-11-13T00:00:00Z",
|
|
1434
|
+
customerPaymentMethod: {
|
|
1435
|
+
id: "pm_1",
|
|
1436
|
+
instrument: { type: "credit_card", brand: "visa", maskedNumber: "************4242" },
|
|
1437
|
+
active: true,
|
|
1438
|
+
isRecurring: true
|
|
1439
|
+
},
|
|
1440
|
+
requiredBillingCyclesLeft: 3,
|
|
1441
|
+
renewal: { id: "ren_1", isInActiveProcessState: false },
|
|
1442
|
+
lastPaymentStatus: "succeeded",
|
|
1443
|
+
pausePeriod: null,
|
|
1444
|
+
isEditable: true,
|
|
869
1445
|
customAttributes: [
|
|
870
1446
|
{ key: "subscription_tier", value: "premium" },
|
|
871
|
-
{ key: "gift_message", value: "Enjoy your
|
|
1447
|
+
{ key: "gift_message", value: "Enjoy your skincare routine!" }
|
|
872
1448
|
]
|
|
873
1449
|
},
|
|
874
1450
|
{
|
|
@@ -877,11 +1453,17 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
877
1453
|
items: [
|
|
878
1454
|
{
|
|
879
1455
|
id: "item_2",
|
|
880
|
-
|
|
881
|
-
|
|
1456
|
+
// Aligned with the mock product catalog (createMockProductAdapter):
|
|
1457
|
+
// sample-2 "More COLLAGEN" (default variant), recurring price 10.99 EUR.
|
|
1458
|
+
productId: "sample-2",
|
|
1459
|
+
variantId: "sample-2-1",
|
|
882
1460
|
quantity: 1,
|
|
883
|
-
price: { amount: "
|
|
1461
|
+
price: { amount: "10.99", currencyCode: "EUR" },
|
|
884
1462
|
nextDeliveryDate: /* @__PURE__ */ new Date("2025-11-20"),
|
|
1463
|
+
title: "More COLLAGEN",
|
|
1464
|
+
variantImage: { url: "https://placehold.co/400x400?text=Product+2" },
|
|
1465
|
+
billingPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
1466
|
+
deliveryPolicy: { intervalCount: 2, interval: "WEEK" },
|
|
885
1467
|
customAttributes: []
|
|
886
1468
|
}
|
|
887
1469
|
],
|
|
@@ -892,17 +1474,66 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
892
1474
|
createdAt: "2024-09-15T00:00:00Z",
|
|
893
1475
|
updatedAt: "2024-10-10T00:00:00Z",
|
|
894
1476
|
customAttributes: []
|
|
1477
|
+
},
|
|
1478
|
+
// Per-status fixtures: groundwork for the deferred editor preview picker
|
|
1479
|
+
// (docs/superpowers/specs/2026-06-17-renewalsummary-parity-and-mock-groundwork-design.md §9).
|
|
1480
|
+
// Not consumed yet — the editor still seeds the active sub_1 via getDefaultSubscriptionId().
|
|
1481
|
+
{
|
|
1482
|
+
...richBase("sub_paused", "item_paused", "ren_paused"),
|
|
1483
|
+
status: "paused",
|
|
1484
|
+
pausePeriod: { start: "2025-11-01T00:00:00Z", end: "2026-01-01T00:00:00Z" }
|
|
1485
|
+
},
|
|
1486
|
+
{
|
|
1487
|
+
...richBase("sub_canceled", "item_canceled", "ren_canceled"),
|
|
1488
|
+
status: "canceled",
|
|
1489
|
+
pausePeriod: null
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
...richBase("sub_failed", "item_failed", "ren_failed"),
|
|
1493
|
+
status: "failed",
|
|
1494
|
+
lastPaymentStatus: "failed",
|
|
1495
|
+
pausePeriod: null
|
|
1496
|
+
},
|
|
1497
|
+
{
|
|
1498
|
+
...richBase("sub_expired", "item_expired", "ren_expired"),
|
|
1499
|
+
status: "expired",
|
|
1500
|
+
pausePeriod: null
|
|
1501
|
+
}
|
|
1502
|
+
];
|
|
1503
|
+
const mockUpsellProducts = [
|
|
1504
|
+
{
|
|
1505
|
+
id: "upsell-1",
|
|
1506
|
+
variantId: "upsell-v-1",
|
|
1507
|
+
title: "Retinol Night Cream",
|
|
1508
|
+
imageUrl: "https://images.unsplash.com/photo-1617897903246-719242758050?w=200",
|
|
1509
|
+
price: { amount: "38.00", currencyCode: "USD" },
|
|
1510
|
+
description: "Anti-aging night treatment"
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
id: "upsell-2",
|
|
1514
|
+
variantId: "upsell-v-2",
|
|
1515
|
+
title: "SPF 50 Day Shield",
|
|
1516
|
+
imageUrl: "https://images.unsplash.com/photo-1556228578-8c89e6adf883?w=200",
|
|
1517
|
+
price: { amount: "24.00", currencyCode: "USD" },
|
|
1518
|
+
description: "Lightweight daily sun protection"
|
|
895
1519
|
}
|
|
896
1520
|
];
|
|
897
1521
|
return {
|
|
898
1522
|
async getById(id) {
|
|
899
1523
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
900
|
-
const subscription = mockSubscriptions.find((sub) => sub.id === id)
|
|
901
|
-
return {
|
|
1524
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === id);
|
|
1525
|
+
return {
|
|
1526
|
+
_tag: "Success",
|
|
1527
|
+
data: subscription ? {
|
|
1528
|
+
...subscription,
|
|
1529
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1530
|
+
discounts: subscription.discounts.map((d2) => ({ ...d2 }))
|
|
1531
|
+
} : null
|
|
1532
|
+
};
|
|
902
1533
|
},
|
|
903
1534
|
async search(options2 = {}) {
|
|
904
1535
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
905
|
-
const pageSize =
|
|
1536
|
+
const pageSize = 2;
|
|
906
1537
|
const allItems = mockSubscriptions;
|
|
907
1538
|
let startIndex = 0;
|
|
908
1539
|
let endIndex = pageSize;
|
|
@@ -918,29 +1549,108 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
918
1549
|
}
|
|
919
1550
|
}
|
|
920
1551
|
const pageItems = allItems.slice(startIndex, endIndex);
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
items:
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1552
|
+
return {
|
|
1553
|
+
_tag: "Success",
|
|
1554
|
+
data: {
|
|
1555
|
+
items: pageItems.map((sub) => ({
|
|
1556
|
+
...sub,
|
|
1557
|
+
items: sub.items.map((item) => ({ ...item })),
|
|
1558
|
+
discounts: sub.discounts.map((discount) => ({ ...discount }))
|
|
1559
|
+
})),
|
|
1560
|
+
pageInfo: {
|
|
1561
|
+
hasNextPage: endIndex < allItems.length,
|
|
1562
|
+
hasPrevPage: startIndex > 0,
|
|
1563
|
+
startCursor: pageItems.length > 0 ? startIndex.toString() : null,
|
|
1564
|
+
endCursor: pageItems.length > 0 ? (endIndex - 1).toString() : null
|
|
1565
|
+
}
|
|
932
1566
|
}
|
|
933
1567
|
};
|
|
934
|
-
return { _tag: "Success", data: paginatedList };
|
|
935
1568
|
},
|
|
936
|
-
async addItem() {
|
|
1569
|
+
async addItem({ subscriptionId, item }) {
|
|
1570
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1571
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1572
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1573
|
+
if (!subscription) {
|
|
1574
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1575
|
+
}
|
|
1576
|
+
const currencyCode = ((_a = subscription.items[0]) == null ? void 0 : _a.price.currencyCode) ?? "USD";
|
|
1577
|
+
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;
|
|
1578
|
+
const variant = (product == null ? void 0 : product.variants.items.find((v3) => v3.id === item.variantId)) ?? (product == null ? void 0 : product.variants.items[0]);
|
|
1579
|
+
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);
|
|
1580
|
+
subscription.items.push({
|
|
1581
|
+
id: `item_${Date.now()}_${subscription.items.length}`,
|
|
1582
|
+
productId: (product == null ? void 0 : product.id) ?? "",
|
|
1583
|
+
variantId: item.variantId,
|
|
1584
|
+
quantity: item.quantity,
|
|
1585
|
+
price: linePrice ? { ...linePrice } : { amount: "0.00", currencyCode },
|
|
1586
|
+
...linePrice ? { currentPrice: { ...linePrice } } : {},
|
|
1587
|
+
title: (product == null ? void 0 : product.title) ?? `Product ${item.variantId}`,
|
|
1588
|
+
...variant && product && !product.hasOnlyDefaultVariant ? { variantTitle: variant.title } : {},
|
|
1589
|
+
...(variant == null ? void 0 : variant.image) ? { variantImage: variant.image } : {},
|
|
1590
|
+
variantAvailableForSale: true,
|
|
1591
|
+
billingPolicy: ((_f = subscription.items[0]) == null ? void 0 : _f.billingPolicy) ?? subscription.billingPolicy ?? null,
|
|
1592
|
+
deliveryPolicy: ((_g = subscription.items[0]) == null ? void 0 : _g.deliveryPolicy) ?? subscription.deliveryPolicy ?? null
|
|
1593
|
+
});
|
|
1594
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1595
|
+
return { _tag: "Success", data: void 0 };
|
|
1596
|
+
},
|
|
1597
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
1598
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1599
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1600
|
+
if (!subscription) {
|
|
1601
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1602
|
+
}
|
|
1603
|
+
subscription.items = subscription.items.filter((i3) => i3.id !== itemId);
|
|
1604
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1605
|
+
return { _tag: "Success", data: void 0 };
|
|
1606
|
+
},
|
|
1607
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
1608
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1609
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1610
|
+
if (!subscription) {
|
|
1611
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1612
|
+
}
|
|
1613
|
+
const item = subscription.items.find((i3) => i3.id === lineId);
|
|
1614
|
+
if (!item) {
|
|
1615
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1616
|
+
}
|
|
1617
|
+
if (update.variantId !== void 0) item.variantId = update.variantId;
|
|
1618
|
+
if (update.quantity !== void 0) item.quantity = update.quantity;
|
|
1619
|
+
if (update.billingPolicy !== void 0) item.billingPolicy = update.billingPolicy;
|
|
1620
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1621
|
+
return { _tag: "Success", data: void 0 };
|
|
1622
|
+
},
|
|
1623
|
+
async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
|
|
1624
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1625
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1626
|
+
if (!subscription) {
|
|
1627
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1628
|
+
}
|
|
1629
|
+
const item = subscription.items.find((i3) => i3.id === lineId);
|
|
1630
|
+
if (!item) {
|
|
1631
|
+
return { _tag: "Failure", error: "Line item not found" };
|
|
1632
|
+
}
|
|
1633
|
+
item.customAttributes = toAttributeArray(customAttributes);
|
|
1634
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1635
|
+
return { _tag: "Success", data: void 0 };
|
|
1636
|
+
},
|
|
1637
|
+
async updateAttributes({ subscriptionId, customAttributes }) {
|
|
937
1638
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1639
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === subscriptionId);
|
|
1640
|
+
if (!subscription) {
|
|
1641
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1642
|
+
}
|
|
1643
|
+
subscription.customAttributes = toAttributeArray(customAttributes);
|
|
1644
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
938
1645
|
return { _tag: "Success", data: void 0 };
|
|
939
1646
|
},
|
|
940
|
-
async
|
|
1647
|
+
async skipLineItem() {
|
|
941
1648
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
942
1649
|
return { _tag: "Success", data: void 0 };
|
|
943
1650
|
},
|
|
1651
|
+
async updateBoxItems() {
|
|
1652
|
+
return { _tag: "Success", data: void 0 };
|
|
1653
|
+
},
|
|
944
1654
|
async addDiscount() {
|
|
945
1655
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
946
1656
|
const behavior = options.addDiscountBehavior ?? { kind: "success" };
|
|
@@ -969,24 +1679,29 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
969
1679
|
(sub) => sub.id === params.subscriptionId
|
|
970
1680
|
);
|
|
971
1681
|
if (!subscription) {
|
|
972
|
-
return {
|
|
973
|
-
_tag: "Failure",
|
|
974
|
-
error: "Subscription not found"
|
|
975
|
-
};
|
|
1682
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
976
1683
|
}
|
|
977
1684
|
subscription.nextDeliveryDate = new Date(params.newDate);
|
|
978
1685
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
979
|
-
subscription
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1686
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1687
|
+
},
|
|
1688
|
+
async updateCustomAttributes(params) {
|
|
1689
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1690
|
+
const subscription = mockSubscriptions.find(
|
|
1691
|
+
(sub) => sub.id === params.subscriptionId
|
|
1692
|
+
);
|
|
1693
|
+
if (!subscription) {
|
|
1694
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1695
|
+
}
|
|
1696
|
+
const preserved = (subscription.customAttributes ?? []).filter(
|
|
1697
|
+
(attr) => attr.key.startsWith("_")
|
|
1698
|
+
);
|
|
1699
|
+
const next = Object.entries(params.customAttributes).map(
|
|
1700
|
+
([key, value]) => ({ key, value })
|
|
1701
|
+
);
|
|
1702
|
+
subscription.customAttributes = [...preserved, ...next];
|
|
1703
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1704
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
990
1705
|
},
|
|
991
1706
|
async resume(params) {
|
|
992
1707
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -994,21 +1709,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
994
1709
|
(sub) => sub.id === params.subscriptionId
|
|
995
1710
|
);
|
|
996
1711
|
if (!subscription) {
|
|
997
|
-
return {
|
|
998
|
-
_tag: "Failure",
|
|
999
|
-
error: "Subscription not found"
|
|
1000
|
-
};
|
|
1712
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1001
1713
|
}
|
|
1002
1714
|
subscription.status = "active";
|
|
1003
1715
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1004
|
-
return {
|
|
1005
|
-
_tag: "Success",
|
|
1006
|
-
data: {
|
|
1007
|
-
...subscription,
|
|
1008
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1009
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1010
|
-
}
|
|
1011
|
-
};
|
|
1716
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1012
1717
|
},
|
|
1013
1718
|
async pause(params) {
|
|
1014
1719
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1016,21 +1721,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1016
1721
|
(sub) => sub.id === params.subscriptionId
|
|
1017
1722
|
);
|
|
1018
1723
|
if (!subscription) {
|
|
1019
|
-
return {
|
|
1020
|
-
_tag: "Failure",
|
|
1021
|
-
error: "Subscription not found"
|
|
1022
|
-
};
|
|
1724
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1023
1725
|
}
|
|
1024
1726
|
subscription.status = "paused";
|
|
1025
1727
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1026
|
-
return {
|
|
1027
|
-
_tag: "Success",
|
|
1028
|
-
data: {
|
|
1029
|
-
...subscription,
|
|
1030
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1031
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1032
|
-
}
|
|
1033
|
-
};
|
|
1728
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1034
1729
|
},
|
|
1035
1730
|
async reactivate(params) {
|
|
1036
1731
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1038,21 +1733,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1038
1733
|
(sub) => sub.id === params.subscriptionId
|
|
1039
1734
|
);
|
|
1040
1735
|
if (!subscription) {
|
|
1041
|
-
return {
|
|
1042
|
-
_tag: "Failure",
|
|
1043
|
-
error: "Subscription not found"
|
|
1044
|
-
};
|
|
1736
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1045
1737
|
}
|
|
1046
1738
|
subscription.status = "active";
|
|
1047
1739
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1048
|
-
return {
|
|
1049
|
-
_tag: "Success",
|
|
1050
|
-
data: {
|
|
1051
|
-
...subscription,
|
|
1052
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1053
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1054
|
-
}
|
|
1055
|
-
};
|
|
1740
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1056
1741
|
},
|
|
1057
1742
|
async cancel(params) {
|
|
1058
1743
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1060,21 +1745,11 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1060
1745
|
(sub) => sub.id === params.subscriptionId
|
|
1061
1746
|
);
|
|
1062
1747
|
if (!subscription) {
|
|
1063
|
-
return {
|
|
1064
|
-
_tag: "Failure",
|
|
1065
|
-
error: "Subscription not found"
|
|
1066
|
-
};
|
|
1748
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1067
1749
|
}
|
|
1068
1750
|
subscription.status = "canceled";
|
|
1069
1751
|
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1070
|
-
return {
|
|
1071
|
-
_tag: "Success",
|
|
1072
|
-
data: {
|
|
1073
|
-
...subscription,
|
|
1074
|
-
items: subscription.items.map((item) => ({ ...item })),
|
|
1075
|
-
discounts: subscription.discounts.map((discount) => ({ ...discount }))
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1752
|
+
return { _tag: "Success", data: { ...subscription } };
|
|
1078
1753
|
},
|
|
1079
1754
|
async getAvailablePlans() {
|
|
1080
1755
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
@@ -1102,70 +1777,1410 @@ function createMockSubscriptionAdapter(options = {}) {
|
|
|
1102
1777
|
]
|
|
1103
1778
|
};
|
|
1104
1779
|
},
|
|
1780
|
+
async getLastOrder(params) {
|
|
1781
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
1782
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1783
|
+
if (!subscription) return { _tag: "Success", data: null };
|
|
1784
|
+
return {
|
|
1785
|
+
_tag: "Success",
|
|
1786
|
+
data: {
|
|
1787
|
+
id: `mock_last_order_${params.subscriptionId}`,
|
|
1788
|
+
createdAt: "2026-04-22T12:00:00Z"
|
|
1789
|
+
}
|
|
1790
|
+
};
|
|
1791
|
+
},
|
|
1792
|
+
async renewNow(_params) {
|
|
1793
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1794
|
+
return { _tag: "Success", data: { result: "success" } };
|
|
1795
|
+
},
|
|
1796
|
+
async changeFrequency(params) {
|
|
1797
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1798
|
+
const subscription = mockSubscriptions.find((sub) => sub.id === params.subscriptionId);
|
|
1799
|
+
if (!subscription) {
|
|
1800
|
+
return { _tag: "Failure", error: "Subscription not found" };
|
|
1801
|
+
}
|
|
1802
|
+
if (typeof params.plan === "string") {
|
|
1803
|
+
subscription.frequency = `1 ${params.plan}`;
|
|
1804
|
+
} else {
|
|
1805
|
+
subscription.frequency = `${params.plan.intervalCount} ${params.plan.interval}`;
|
|
1806
|
+
if (subscription.billingPolicy) {
|
|
1807
|
+
subscription.billingPolicy = { ...subscription.billingPolicy, ...params.plan };
|
|
1808
|
+
}
|
|
1809
|
+
if (subscription.deliveryPolicy) {
|
|
1810
|
+
subscription.deliveryPolicy = { ...subscription.deliveryPolicy, ...params.plan };
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
subscription.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1814
|
+
return {
|
|
1815
|
+
_tag: "Success",
|
|
1816
|
+
data: {
|
|
1817
|
+
...subscription,
|
|
1818
|
+
items: subscription.items.map((item) => ({ ...item })),
|
|
1819
|
+
discounts: subscription.discounts.map((d2) => ({ ...d2 }))
|
|
1820
|
+
}
|
|
1821
|
+
};
|
|
1822
|
+
},
|
|
1105
1823
|
async getDefaultSubscriptionId() {
|
|
1106
1824
|
var _a;
|
|
1107
1825
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1108
1826
|
return { _tag: "Success", data: ((_a = mockSubscriptions[0]) == null ? void 0 : _a.id) ?? null };
|
|
1827
|
+
},
|
|
1828
|
+
async getUpsellProducts() {
|
|
1829
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1830
|
+
return { _tag: "Success", data: [...mockUpsellProducts] };
|
|
1831
|
+
},
|
|
1832
|
+
async addUpsellProduct() {
|
|
1833
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
1834
|
+
return { _tag: "Success", data: void 0 };
|
|
1109
1835
|
}
|
|
1110
1836
|
};
|
|
1111
1837
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const subscriptionIndex = segments.findIndex(
|
|
1120
|
-
(segment) => segment === "subscriptions" || segment === "subscription"
|
|
1121
|
-
);
|
|
1122
|
-
if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
|
|
1123
|
-
return segments[subscriptionIndex + 1];
|
|
1838
|
+
function normalizeInstrumentType(type) {
|
|
1839
|
+
return type.replace(/-/g, "_");
|
|
1840
|
+
}
|
|
1841
|
+
function mapPaymentMethod(rawPm) {
|
|
1842
|
+
if (!rawPm) return null;
|
|
1843
|
+
if (typeof rawPm === "string") {
|
|
1844
|
+
return { id: rawPm };
|
|
1124
1845
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1846
|
+
return {
|
|
1847
|
+
id: rawPm.id ?? "",
|
|
1848
|
+
...rawPm.instrument != null ? {
|
|
1849
|
+
instrument: {
|
|
1850
|
+
...rawPm.instrument,
|
|
1851
|
+
type: normalizeInstrumentType(rawPm.instrument.type)
|
|
1852
|
+
}
|
|
1853
|
+
} : {},
|
|
1854
|
+
...rawPm.active != null ? { active: rawPm.active } : {},
|
|
1855
|
+
...rawPm.isRecurring != null ? { isRecurring: rawPm.isRecurring } : {}
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
function mapSubscription(raw) {
|
|
1859
|
+
var _a, _b;
|
|
1860
|
+
const status = raw.status === "merged" ? "canceled" : raw.status;
|
|
1861
|
+
const deliveryPolicy = raw.deliveryPolicy ?? ((_b = (_a = raw.items) == null ? void 0 : _a[0]) == null ? void 0 : _b.deliveryPolicy);
|
|
1862
|
+
const frequency = deliveryPolicy ? `${deliveryPolicy.intervalCount} ${deliveryPolicy.interval}` : "";
|
|
1863
|
+
return {
|
|
1864
|
+
id: raw.id,
|
|
1865
|
+
customerId: "",
|
|
1866
|
+
serial: raw.serial,
|
|
1867
|
+
items: (raw.items ?? []).map(
|
|
1868
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1869
|
+
(item) => ({
|
|
1870
|
+
id: item.id,
|
|
1871
|
+
productId: item.productId ?? item.product ?? "",
|
|
1872
|
+
variantId: item.variantId ?? item.variant ?? "",
|
|
1873
|
+
quantity: item.quantity,
|
|
1874
|
+
price: item.price ?? {
|
|
1875
|
+
amount: String(item.totalPrice ?? 0),
|
|
1876
|
+
currencyCode: raw.currencyCode ?? ""
|
|
1877
|
+
},
|
|
1878
|
+
nextDeliveryDate: item.nextDeliveryDate ? new Date(item.nextDeliveryDate) : null,
|
|
1879
|
+
billingPolicy: item.billingPolicy ?? raw.billingPolicy ?? null,
|
|
1880
|
+
deliveryPolicy: item.deliveryPolicy ?? raw.deliveryPolicy ?? null,
|
|
1881
|
+
// Map human-readable titles from the v1 SubscriptionItemResource
|
|
1882
|
+
title: item.title,
|
|
1883
|
+
variantTitle: item.subtitle ?? void 0,
|
|
1884
|
+
// recurringCycleLimit === 1 means the item charges exactly once (one-time upsell)
|
|
1885
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
1886
|
+
customAttributes: Array.isArray(item.customAttributes) ? item.customAttributes : []
|
|
1887
|
+
})
|
|
1888
|
+
),
|
|
1889
|
+
discounts: (raw.discounts ?? []).map(
|
|
1890
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1891
|
+
(d2) => {
|
|
1892
|
+
var _a2, _b2, _c;
|
|
1893
|
+
return {
|
|
1894
|
+
id: d2.id,
|
|
1895
|
+
type: ((_a2 = d2.value) == null ? void 0 : _a2.type) === "fixed-amount" || d2.type === "fixed_amount" ? "fixed_amount" : "percentage",
|
|
1896
|
+
value: ((_b2 = d2.value) == null ? void 0 : _b2.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? d2.value ?? 0,
|
|
1897
|
+
code: d2.title ?? d2.code ?? "",
|
|
1898
|
+
description: d2.description ?? ""
|
|
1899
|
+
};
|
|
1900
|
+
}
|
|
1901
|
+
),
|
|
1902
|
+
status,
|
|
1903
|
+
nextDeliveryDate: raw.nextBillingDate ? new Date(raw.nextBillingDate) : raw.nextDeliveryDate ? new Date(raw.nextDeliveryDate) : null,
|
|
1904
|
+
// Map nextBillingDate as the raw ISO string for ProductSelect skip() and other consumers
|
|
1905
|
+
nextBillingDate: raw.nextBillingDate ?? null,
|
|
1906
|
+
// Map customerPaymentMethod — expanded to an object (id + instrument) via the
|
|
1907
|
+
// adapter's ?expand=paymentMethod request; mapPaymentMethod tolerates a bare id too.
|
|
1908
|
+
customerPaymentMethod: mapPaymentMethod(raw.paymentMethod),
|
|
1909
|
+
frequency,
|
|
1910
|
+
createdAt: raw.createdAt,
|
|
1911
|
+
updatedAt: raw.updatedAt,
|
|
1912
|
+
billingPolicy: raw.billingPolicy ?? null,
|
|
1913
|
+
deliveryPolicy: raw.deliveryPolicy ?? null,
|
|
1914
|
+
customAttributes: Array.isArray(raw.customAttributes) ? raw.customAttributes : []
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
async function readErrorBody(res) {
|
|
1918
|
+
try {
|
|
1919
|
+
const body = await res.json();
|
|
1920
|
+
return body != null && typeof body === "object" ? body : {};
|
|
1921
|
+
} catch {
|
|
1922
|
+
return {};
|
|
1129
1923
|
}
|
|
1130
|
-
return null;
|
|
1131
1924
|
}
|
|
1132
|
-
function
|
|
1925
|
+
function createApiSubscriptionAdapter(fetcher) {
|
|
1926
|
+
const adapter = {
|
|
1927
|
+
async search(options) {
|
|
1928
|
+
var _a;
|
|
1929
|
+
try {
|
|
1930
|
+
const params = new URLSearchParams();
|
|
1931
|
+
if ((_a = options == null ? void 0 : options.filters) == null ? void 0 : _a.length) {
|
|
1932
|
+
const groups = options.filters.filter((f) => Object.keys(f).length > 0).map((f) => Object.entries(f).filter(([, v3]) => Boolean(v3))).filter((entries) => entries.length > 0).map(
|
|
1933
|
+
(entries) => "(" + entries.map(([k3, v3]) => `${k3}:'${v3}'`).join(" AND ") + ")"
|
|
1934
|
+
);
|
|
1935
|
+
if (groups.length) params.set("query", groups.join(" OR "));
|
|
1936
|
+
}
|
|
1937
|
+
if (options == null ? void 0 : options.includeCanceledItems) {
|
|
1938
|
+
params.set("includeCanceledItems", "true");
|
|
1939
|
+
}
|
|
1940
|
+
params.set("expand", "paymentMethod");
|
|
1941
|
+
if (options == null ? void 0 : options.pagination) {
|
|
1942
|
+
if ("after" in options.pagination)
|
|
1943
|
+
params.set("after", options.pagination.after);
|
|
1944
|
+
else if ("before" in options.pagination)
|
|
1945
|
+
params.set("before", options.pagination.before);
|
|
1946
|
+
}
|
|
1947
|
+
const query = params.toString() ? `?${params.toString()}` : "";
|
|
1948
|
+
const res = await fetcher(`/customer/v1/subscriptions${query}`);
|
|
1949
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1950
|
+
const body = await res.json();
|
|
1951
|
+
return {
|
|
1952
|
+
_tag: "Success",
|
|
1953
|
+
data: {
|
|
1954
|
+
items: body.data.map(mapSubscription),
|
|
1955
|
+
pageInfo: {
|
|
1956
|
+
hasNextPage: body.hasNextPage,
|
|
1957
|
+
hasPrevPage: body.hasPrevPage,
|
|
1958
|
+
startCursor: body.startCursor,
|
|
1959
|
+
endCursor: body.endCursor
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1963
|
+
} catch (e) {
|
|
1964
|
+
return { _tag: "Failure", error: e };
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
async getById(id) {
|
|
1968
|
+
try {
|
|
1969
|
+
const res = await fetcher(
|
|
1970
|
+
`/customer/v1/subscriptions?query=id:"${id}"&limit=1&expand=paymentMethod`
|
|
1971
|
+
);
|
|
1972
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1973
|
+
const body = await res.json();
|
|
1974
|
+
const raw = body.data[0];
|
|
1975
|
+
return { _tag: "Success", data: raw ? mapSubscription(raw) : null };
|
|
1976
|
+
} catch (e) {
|
|
1977
|
+
return { _tag: "Failure", error: e };
|
|
1978
|
+
}
|
|
1979
|
+
},
|
|
1980
|
+
async addItem({ subscriptionId, item }) {
|
|
1981
|
+
try {
|
|
1982
|
+
const res = await fetcher(
|
|
1983
|
+
`/customer/v1/subscriptions/${subscriptionId}/items`,
|
|
1984
|
+
{
|
|
1985
|
+
method: "POST",
|
|
1986
|
+
headers: { "Content-Type": "application/json" },
|
|
1987
|
+
body: JSON.stringify({ variant: item.variantId, quantity: item.quantity })
|
|
1988
|
+
}
|
|
1989
|
+
);
|
|
1990
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
1991
|
+
return { _tag: "Success", data: void 0 };
|
|
1992
|
+
} catch (e) {
|
|
1993
|
+
return { _tag: "Failure", error: e };
|
|
1994
|
+
}
|
|
1995
|
+
},
|
|
1996
|
+
async removeItem({ subscriptionId, itemId }) {
|
|
1997
|
+
try {
|
|
1998
|
+
const res = await fetcher(
|
|
1999
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${itemId}`,
|
|
2000
|
+
{ method: "DELETE" }
|
|
2001
|
+
);
|
|
2002
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2003
|
+
return { _tag: "Success", data: void 0 };
|
|
2004
|
+
} catch (e) {
|
|
2005
|
+
return { _tag: "Failure", error: e };
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
async updateItem({ subscriptionId, lineId, update }) {
|
|
2009
|
+
try {
|
|
2010
|
+
const body = {
|
|
2011
|
+
...update.variantId !== void 0 ? { productVariantId: update.variantId } : {},
|
|
2012
|
+
...update.quantity !== void 0 ? { quantity: update.quantity } : {},
|
|
2013
|
+
...update.billingPolicy !== void 0 ? { billingPolicy: update.billingPolicy } : {},
|
|
2014
|
+
...update.shippingMethodHandle !== void 0 ? { shippingMethodHandle: update.shippingMethodHandle } : {}
|
|
2015
|
+
};
|
|
2016
|
+
const res = await fetcher(
|
|
2017
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
2018
|
+
{
|
|
2019
|
+
method: "PUT",
|
|
2020
|
+
headers: { "Content-Type": "application/json" },
|
|
2021
|
+
body: JSON.stringify(body)
|
|
2022
|
+
}
|
|
2023
|
+
);
|
|
2024
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2025
|
+
return { _tag: "Success", data: void 0 };
|
|
2026
|
+
} catch (e) {
|
|
2027
|
+
return { _tag: "Failure", error: e };
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
async updateItemAttributes({ subscriptionId, lineId, customAttributes }) {
|
|
2031
|
+
try {
|
|
2032
|
+
const res = await fetcher(
|
|
2033
|
+
`/customer/v1/subscriptions/${subscriptionId}/items/${lineId}`,
|
|
2034
|
+
{
|
|
2035
|
+
method: "PATCH",
|
|
2036
|
+
headers: { "Content-Type": "application/json" },
|
|
2037
|
+
body: JSON.stringify({ customAttributes })
|
|
2038
|
+
}
|
|
2039
|
+
);
|
|
2040
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2041
|
+
return { _tag: "Success", data: void 0 };
|
|
2042
|
+
} catch (e) {
|
|
2043
|
+
return { _tag: "Failure", error: e };
|
|
2044
|
+
}
|
|
2045
|
+
},
|
|
2046
|
+
async updateAttributes({ subscriptionId, customAttributes }) {
|
|
2047
|
+
try {
|
|
2048
|
+
const res = await fetcher(
|
|
2049
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2050
|
+
{
|
|
2051
|
+
method: "PATCH",
|
|
2052
|
+
headers: { "Content-Type": "application/json" },
|
|
2053
|
+
body: JSON.stringify({ customAttributes })
|
|
2054
|
+
}
|
|
2055
|
+
);
|
|
2056
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2057
|
+
return { _tag: "Success", data: void 0 };
|
|
2058
|
+
} catch (e) {
|
|
2059
|
+
return { _tag: "Failure", error: e };
|
|
2060
|
+
}
|
|
2061
|
+
},
|
|
2062
|
+
async skipLineItem({ subscriptionId, lineId, nextBillingDate }) {
|
|
2063
|
+
try {
|
|
2064
|
+
const res = await fetcher(
|
|
2065
|
+
`/customer/subscriptions/${subscriptionId}/lines/${lineId}`,
|
|
2066
|
+
{
|
|
2067
|
+
method: "PUT",
|
|
2068
|
+
headers: { "Content-Type": "application/json" },
|
|
2069
|
+
body: JSON.stringify({ nextBillingDate })
|
|
2070
|
+
}
|
|
2071
|
+
);
|
|
2072
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2073
|
+
return { _tag: "Success", data: void 0 };
|
|
2074
|
+
} catch (e) {
|
|
2075
|
+
return { _tag: "Failure", error: e };
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
async updateBoxItems({ subscriptionId, parentId, items, shippingMethodHandle }) {
|
|
2079
|
+
try {
|
|
2080
|
+
const res = await fetcher(
|
|
2081
|
+
`/customer/subscriptions/${subscriptionId}/update-box-products`,
|
|
2082
|
+
{
|
|
2083
|
+
method: "POST",
|
|
2084
|
+
headers: { "Content-Type": "application/json" },
|
|
2085
|
+
body: JSON.stringify({ parentId, items, shippingMethodHandle })
|
|
2086
|
+
}
|
|
2087
|
+
);
|
|
2088
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2089
|
+
return { _tag: "Success", data: void 0 };
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
return { _tag: "Failure", error: e };
|
|
2092
|
+
}
|
|
2093
|
+
},
|
|
2094
|
+
async addDiscount({ subscriptionId, code }) {
|
|
2095
|
+
try {
|
|
2096
|
+
const res = await fetcher(
|
|
2097
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts`,
|
|
2098
|
+
{
|
|
2099
|
+
method: "POST",
|
|
2100
|
+
headers: { "Content-Type": "application/json" },
|
|
2101
|
+
body: JSON.stringify({ code })
|
|
2102
|
+
}
|
|
2103
|
+
);
|
|
2104
|
+
if (!res.ok) {
|
|
2105
|
+
const body = await readErrorBody(res);
|
|
2106
|
+
if (res.status === 400 && body.code === "DISCOUNT_CODE_REJECTED") {
|
|
2107
|
+
const raw = body.rejectionReason;
|
|
2108
|
+
const reason = typeof raw === "string" && DISCOUNT_REJECTION_REASONS.includes(raw) ? raw : "INVALID_CODE";
|
|
2109
|
+
return { _tag: "Failure", error: { kind: "REJECTED", reason } };
|
|
2110
|
+
}
|
|
2111
|
+
if (res.status === 404) {
|
|
2112
|
+
return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
|
|
2113
|
+
}
|
|
2114
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
|
|
2115
|
+
}
|
|
2116
|
+
return { _tag: "Success", data: void 0 };
|
|
2117
|
+
} catch (e) {
|
|
2118
|
+
return {
|
|
2119
|
+
_tag: "Failure",
|
|
2120
|
+
error: { kind: "NETWORK", message: e instanceof Error ? e.message : void 0 }
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
},
|
|
2124
|
+
async removeDiscount({ subscriptionId, discountId }) {
|
|
2125
|
+
try {
|
|
2126
|
+
const res = await fetcher(
|
|
2127
|
+
`/customer/v1/subscriptions/${subscriptionId}/discounts/${discountId}`,
|
|
2128
|
+
{ method: "DELETE" }
|
|
2129
|
+
);
|
|
2130
|
+
if (!res.ok) {
|
|
2131
|
+
if (res.status === 404) {
|
|
2132
|
+
const body = await readErrorBody(res);
|
|
2133
|
+
const message = typeof body.message === "string" ? body.message : "";
|
|
2134
|
+
if (message.toLowerCase().includes("discount")) {
|
|
2135
|
+
return { _tag: "Failure", error: { kind: "DISCOUNT_NOT_FOUND" } };
|
|
2136
|
+
}
|
|
2137
|
+
return { _tag: "Failure", error: { kind: "SUBSCRIPTION_NOT_FOUND" } };
|
|
2138
|
+
}
|
|
2139
|
+
return { _tag: "Failure", error: { kind: "UNKNOWN", message: `HTTP ${res.status}` } };
|
|
2140
|
+
}
|
|
2141
|
+
return { _tag: "Success", data: void 0 };
|
|
2142
|
+
} catch (e) {
|
|
2143
|
+
return {
|
|
2144
|
+
_tag: "Failure",
|
|
2145
|
+
error: { kind: "NETWORK", message: e instanceof Error ? e.message : void 0 }
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
},
|
|
2149
|
+
// Uses PATCH /customer/v1/subscriptions/{id} — the dedicated /reschedule route does not exist.
|
|
2150
|
+
async reschedule({ subscriptionId, newDate }) {
|
|
2151
|
+
try {
|
|
2152
|
+
const res = await fetcher(
|
|
2153
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2154
|
+
{
|
|
2155
|
+
method: "PATCH",
|
|
2156
|
+
headers: { "Content-Type": "application/json" },
|
|
2157
|
+
body: JSON.stringify({ nextBillingDate: newDate })
|
|
2158
|
+
}
|
|
2159
|
+
);
|
|
2160
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2161
|
+
const data = await res.json();
|
|
2162
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
return { _tag: "Failure", error: e };
|
|
2165
|
+
}
|
|
2166
|
+
},
|
|
2167
|
+
// PATCH /customer/v1/subscriptions/{id} with the full customer-visible set.
|
|
2168
|
+
async updateCustomAttributes({ subscriptionId, customAttributes }) {
|
|
2169
|
+
try {
|
|
2170
|
+
const res = await fetcher(
|
|
2171
|
+
`/customer/v1/subscriptions/${subscriptionId}`,
|
|
2172
|
+
{
|
|
2173
|
+
method: "PATCH",
|
|
2174
|
+
headers: { "Content-Type": "application/json" },
|
|
2175
|
+
body: JSON.stringify({ customAttributes })
|
|
2176
|
+
}
|
|
2177
|
+
);
|
|
2178
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2179
|
+
const data = await res.json();
|
|
2180
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2181
|
+
} catch (e) {
|
|
2182
|
+
return { _tag: "Failure", error: e };
|
|
2183
|
+
}
|
|
2184
|
+
},
|
|
2185
|
+
async resume({ subscriptionId }) {
|
|
2186
|
+
try {
|
|
2187
|
+
const res = await fetcher(
|
|
2188
|
+
`/customer/v1/subscriptions/${subscriptionId}/resume`,
|
|
2189
|
+
{ method: "POST" }
|
|
2190
|
+
);
|
|
2191
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2192
|
+
const data = await res.json();
|
|
2193
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2194
|
+
} catch (e) {
|
|
2195
|
+
return { _tag: "Failure", error: e };
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
async pause({ subscriptionId }) {
|
|
2199
|
+
try {
|
|
2200
|
+
const res = await fetcher(
|
|
2201
|
+
`/customer/v1/subscriptions/${subscriptionId}/pause`,
|
|
2202
|
+
{ method: "POST" }
|
|
2203
|
+
);
|
|
2204
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2205
|
+
const data = await res.json();
|
|
2206
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2207
|
+
} catch (e) {
|
|
2208
|
+
return { _tag: "Failure", error: e };
|
|
2209
|
+
}
|
|
2210
|
+
},
|
|
2211
|
+
async reactivate({ subscriptionId }) {
|
|
2212
|
+
try {
|
|
2213
|
+
const res = await fetcher(
|
|
2214
|
+
`/customer/v1/subscriptions/${subscriptionId}/reactivate`,
|
|
2215
|
+
{ method: "POST" }
|
|
2216
|
+
);
|
|
2217
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2218
|
+
const data = await res.json();
|
|
2219
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2220
|
+
} catch (e) {
|
|
2221
|
+
return { _tag: "Failure", error: e };
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
async cancel({ subscriptionId, reason }) {
|
|
2225
|
+
try {
|
|
2226
|
+
const res = await fetcher(
|
|
2227
|
+
`/customer/v1/subscriptions/${subscriptionId}/cancel`,
|
|
2228
|
+
{
|
|
2229
|
+
method: "POST",
|
|
2230
|
+
headers: { "Content-Type": "application/json" },
|
|
2231
|
+
body: JSON.stringify({ cancellationReason: reason })
|
|
2232
|
+
}
|
|
2233
|
+
);
|
|
2234
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2235
|
+
const data = await res.json();
|
|
2236
|
+
return { _tag: "Success", data: mapSubscription(data) };
|
|
2237
|
+
} catch (e) {
|
|
2238
|
+
return { _tag: "Failure", error: e };
|
|
2239
|
+
}
|
|
2240
|
+
},
|
|
2241
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/plans exists yet.
|
|
2242
|
+
async getAvailablePlans({ subscriptionId }) {
|
|
2243
|
+
try {
|
|
2244
|
+
const res = await fetcher(
|
|
2245
|
+
`/customer/subscriptions/${subscriptionId}/plans`
|
|
2246
|
+
);
|
|
2247
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2248
|
+
const data = await res.json();
|
|
2249
|
+
return { _tag: "Success", data };
|
|
2250
|
+
} catch (e) {
|
|
2251
|
+
return { _tag: "Failure", error: e };
|
|
2252
|
+
}
|
|
2253
|
+
},
|
|
2254
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2255
|
+
async getDefaultSubscriptionId() {
|
|
2256
|
+
try {
|
|
2257
|
+
const res = await fetcher(
|
|
2258
|
+
`/customer/subscriptions/one-by-next-billing-date`
|
|
2259
|
+
);
|
|
2260
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2261
|
+
const data = await res.json();
|
|
2262
|
+
return { _tag: "Success", data: (data == null ? void 0 : data.id) ?? null };
|
|
2263
|
+
} catch (e) {
|
|
2264
|
+
return { _tag: "Failure", error: e };
|
|
2265
|
+
}
|
|
2266
|
+
},
|
|
2267
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells exists yet.
|
|
2268
|
+
async getUpsellProducts({ subscriptionId }) {
|
|
2269
|
+
try {
|
|
2270
|
+
const res = await fetcher(
|
|
2271
|
+
`/customer/subscriptions/${subscriptionId}/upsell-products`
|
|
2272
|
+
);
|
|
2273
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2274
|
+
const data = await res.json();
|
|
2275
|
+
const products = data.map((p3) => {
|
|
2276
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
2277
|
+
return {
|
|
2278
|
+
id: p3.id,
|
|
2279
|
+
variantId: ((_b = (_a = p3.variants) == null ? void 0 : _a[0]) == null ? void 0 : _b.id) ?? "",
|
|
2280
|
+
title: p3.title ?? "",
|
|
2281
|
+
imageUrl: ((_c = p3.featuredImage) == null ? void 0 : _c.url) ?? void 0,
|
|
2282
|
+
price: {
|
|
2283
|
+
amount: String(((_f = (_e = (_d = p3.variants) == null ? void 0 : _d[0]) == null ? void 0 : _e.currentPrice) == null ? void 0 : _f.amount) ?? 0),
|
|
2284
|
+
currencyCode: ((_i = (_h = (_g = p3.variants) == null ? void 0 : _g[0]) == null ? void 0 : _h.currentPrice) == null ? void 0 : _i.currencyCode) ?? ""
|
|
2285
|
+
},
|
|
2286
|
+
// recurring comes from the upsell rule on variants[0]; present in PreparedProductSchema
|
|
2287
|
+
recurring: (_k = (_j = p3.variants) == null ? void 0 : _j[0]) == null ? void 0 : _k.recurring
|
|
2288
|
+
};
|
|
2289
|
+
});
|
|
2290
|
+
return { _tag: "Success", data: products };
|
|
2291
|
+
} catch (e) {
|
|
2292
|
+
return { _tag: "Failure", error: e };
|
|
2293
|
+
}
|
|
2294
|
+
},
|
|
2295
|
+
// Legacy endpoint — no /customer/v1/subscriptions/{id}/upsells POST exists yet.
|
|
2296
|
+
async addUpsellProduct({ subscriptionId, variantId, quantity, oneTime }) {
|
|
2297
|
+
try {
|
|
2298
|
+
const res = await fetcher(
|
|
2299
|
+
`/customer/subscriptions/${subscriptionId}/upsell`,
|
|
2300
|
+
{
|
|
2301
|
+
method: "POST",
|
|
2302
|
+
headers: { "Content-Type": "application/json" },
|
|
2303
|
+
body: JSON.stringify({
|
|
2304
|
+
productVariantId: variantId,
|
|
2305
|
+
quantity,
|
|
2306
|
+
...oneTime ? { renewalLimit: 1 } : {}
|
|
2307
|
+
})
|
|
2308
|
+
}
|
|
2309
|
+
);
|
|
2310
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2311
|
+
return { _tag: "Success", data: void 0 };
|
|
2312
|
+
} catch (e) {
|
|
2313
|
+
return { _tag: "Failure", error: e };
|
|
2314
|
+
}
|
|
2315
|
+
},
|
|
2316
|
+
// Legacy endpoint — no /customer/v1 equivalent exists yet.
|
|
2317
|
+
async getLastOrder({ subscriptionId }) {
|
|
2318
|
+
try {
|
|
2319
|
+
const res = await fetcher(
|
|
2320
|
+
`/customer/subscriptions/${subscriptionId}/last-order`
|
|
2321
|
+
);
|
|
2322
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2323
|
+
const data = await res.json();
|
|
2324
|
+
if (!data) return { _tag: "Success", data: null };
|
|
2325
|
+
return {
|
|
2326
|
+
_tag: "Success",
|
|
2327
|
+
data: { id: data.id, createdAt: data.createdAt }
|
|
2328
|
+
};
|
|
2329
|
+
} catch (e) {
|
|
2330
|
+
return { _tag: "Failure", error: e };
|
|
2331
|
+
}
|
|
2332
|
+
},
|
|
2333
|
+
// Legacy endpoints, split by stage (see SubscriptionService.renewNow docs):
|
|
2334
|
+
// retry → POST /customer/subscriptions/{id}/retry
|
|
2335
|
+
// renewalId → POST /customer/renewals/{renewalId}/renew
|
|
2336
|
+
// default → POST /customer/subscriptions/{id}/order-now
|
|
2337
|
+
async renewNow({ subscriptionId, renewalId, retry }) {
|
|
2338
|
+
try {
|
|
2339
|
+
const url = retry ? `/customer/subscriptions/${subscriptionId}/retry` : renewalId ? `/customer/renewals/${renewalId}/renew` : `/customer/subscriptions/${subscriptionId}/order-now`;
|
|
2340
|
+
const res = await fetcher(url, { method: "POST" });
|
|
2341
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2342
|
+
const body = res.status === 204 ? null : await res.json();
|
|
2343
|
+
return {
|
|
2344
|
+
_tag: "Success",
|
|
2345
|
+
data: {
|
|
2346
|
+
nextActionUrl: (body == null ? void 0 : body.nextActionUrl) ?? void 0,
|
|
2347
|
+
result: (body == null ? void 0 : body.result) ?? "success"
|
|
2348
|
+
}
|
|
2349
|
+
};
|
|
2350
|
+
} catch (e) {
|
|
2351
|
+
return { _tag: "Failure", error: e };
|
|
2352
|
+
}
|
|
2353
|
+
},
|
|
2354
|
+
// Legacy endpoint — POST /plan returns 204; re-fetch to return the updated subscription.
|
|
2355
|
+
async changeFrequency({ subscriptionId, plan }) {
|
|
2356
|
+
try {
|
|
2357
|
+
const res = await fetcher(
|
|
2358
|
+
`/customer/subscriptions/${subscriptionId}/plan`,
|
|
2359
|
+
{
|
|
2360
|
+
method: "POST",
|
|
2361
|
+
headers: { "Content-Type": "application/json" },
|
|
2362
|
+
body: JSON.stringify({ plan })
|
|
2363
|
+
}
|
|
2364
|
+
);
|
|
2365
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2366
|
+
const refreshed = await adapter.getById(subscriptionId);
|
|
2367
|
+
if (refreshed._tag === "Failure") return refreshed;
|
|
2368
|
+
if (refreshed.data) return { _tag: "Success", data: refreshed.data };
|
|
2369
|
+
return {
|
|
2370
|
+
_tag: "Failure",
|
|
2371
|
+
error: "Subscription not found after frequency change"
|
|
2372
|
+
};
|
|
2373
|
+
} catch (e) {
|
|
2374
|
+
return { _tag: "Failure", error: e };
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
};
|
|
2378
|
+
return adapter;
|
|
2379
|
+
}
|
|
2380
|
+
function expandableId(field) {
|
|
2381
|
+
if (typeof field === "string") return field;
|
|
2382
|
+
if (field != null && typeof field === "object" && "id" in field) {
|
|
2383
|
+
const id = field.id;
|
|
2384
|
+
return typeof id === "string" ? id : void 0;
|
|
2385
|
+
}
|
|
2386
|
+
return void 0;
|
|
2387
|
+
}
|
|
2388
|
+
function mapScheduleOrder(order) {
|
|
2389
|
+
return {
|
|
2390
|
+
id: order.id,
|
|
2391
|
+
customerId: order.customerId,
|
|
2392
|
+
subscriptionId: order.subscriptionId,
|
|
2393
|
+
date: order.date,
|
|
2394
|
+
skipped: order.skipped ?? false,
|
|
2395
|
+
currencyCode: order.currencyCode,
|
|
2396
|
+
subtotal: order.subtotal,
|
|
2397
|
+
deliveryPrice: order.deliveryPrice,
|
|
2398
|
+
cycleIndex: order.cycleIndex,
|
|
2399
|
+
deliveryDate: order.deliveryDate ?? order.date,
|
|
2400
|
+
status: order.status ?? (order.skipped ? "skipped" : "scheduled"),
|
|
2401
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2402
|
+
items: (order.items ?? []).map((item) => {
|
|
2403
|
+
var _a;
|
|
2404
|
+
return {
|
|
2405
|
+
id: item.id,
|
|
2406
|
+
title: item.title,
|
|
2407
|
+
subtitle: item.subtitle ?? null,
|
|
2408
|
+
imageUrl: item.imageUrl ?? ((_a = item.variantImage) == null ? void 0 : _a.url) ?? void 0,
|
|
2409
|
+
variantId: expandableId(item.variant) ?? "",
|
|
2410
|
+
quantity: item.quantity,
|
|
2411
|
+
price: item.price,
|
|
2412
|
+
totalPrice: item.totalPrice,
|
|
2413
|
+
skipped: item.skipped ?? false,
|
|
2414
|
+
variantImage: item.variantImage != null ? { url: item.variantImage.url ?? "", altText: item.variantImage.altText ?? void 0 } : null,
|
|
2415
|
+
variantAvailableForSale: item.variantAvailableForSale ?? void 0,
|
|
2416
|
+
subscriptionId: expandableId(item.subscription) ?? "",
|
|
2417
|
+
subscriptionLineId: expandableId(item.subscriptionItem) ?? "",
|
|
2418
|
+
productId: expandableId(item.product),
|
|
2419
|
+
parentProductId: item.parentProductId ?? null,
|
|
2420
|
+
parentLineId: item.parentLineId ?? null,
|
|
2421
|
+
isVirtual: item.isVirtual ?? void 0,
|
|
2422
|
+
isUpsell: item.isUpsell ?? void 0,
|
|
2423
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
2424
|
+
customAttributes: item.customAttributes ?? [],
|
|
2425
|
+
sellingPlanId: item.sellingPlanId ?? null,
|
|
2426
|
+
sellingPlanName: item.sellingPlanName ?? null,
|
|
2427
|
+
nextBillingDate: item.nextBillingDate ?? null,
|
|
2428
|
+
billingPolicy: item.billingPolicy ?? null,
|
|
2429
|
+
subscriptionStatus: item.subscriptionStatus ?? null,
|
|
2430
|
+
subscriptionIsEditable: item.subscriptionIsEditable ?? null,
|
|
2431
|
+
requiredBillingCyclesLeft: item.requiredBillingCyclesLeft ?? null
|
|
2432
|
+
};
|
|
2433
|
+
}),
|
|
2434
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2435
|
+
discounts: (order.discounts ?? []).map((d2) => {
|
|
2436
|
+
var _a, _b, _c;
|
|
2437
|
+
return {
|
|
2438
|
+
id: d2.id,
|
|
2439
|
+
title: d2.title ?? void 0,
|
|
2440
|
+
type: ((_a = d2.value) == null ? void 0 : _a.type) === "fixed-amount" ? "fixed_amount" : "percentage",
|
|
2441
|
+
value: ((_b = d2.value) == null ? void 0 : _b.amount) ?? ((_c = d2.value) == null ? void 0 : _c.percentage) ?? 0
|
|
2442
|
+
};
|
|
2443
|
+
})
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
function toIsoOrNull(value) {
|
|
2447
|
+
const date = new Date(value);
|
|
2448
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
2449
|
+
}
|
|
2450
|
+
function buildMatcher(params) {
|
|
2451
|
+
const { cycle, date } = params;
|
|
2452
|
+
let iso;
|
|
2453
|
+
if (date != null) {
|
|
2454
|
+
const parsed = toIsoOrNull(date);
|
|
2455
|
+
if (parsed == null) return { ok: false, error: "date must be a valid date" };
|
|
2456
|
+
iso = parsed;
|
|
2457
|
+
}
|
|
2458
|
+
if (cycle != null && iso != null) {
|
|
2459
|
+
return { ok: true, matcher: { type: "CYCLE_AND_DATE", input: { cycle, date: iso } } };
|
|
2460
|
+
}
|
|
2461
|
+
if (cycle != null) return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
|
|
2462
|
+
if (iso != null) return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
|
|
2463
|
+
return { ok: false, error: "Either cycle or date is required" };
|
|
2464
|
+
}
|
|
2465
|
+
function buildSkipItemMatcher(params) {
|
|
2466
|
+
const { cycle, date } = params;
|
|
2467
|
+
if (cycle != null) {
|
|
2468
|
+
return { ok: true, matcher: { type: "CYCLE", input: { cycle } } };
|
|
2469
|
+
}
|
|
2470
|
+
if (date != null) {
|
|
2471
|
+
const iso = toIsoOrNull(date);
|
|
2472
|
+
if (iso == null) return { ok: false, error: "date must be a valid date" };
|
|
2473
|
+
return { ok: true, matcher: { type: "DATE", input: { date: iso } } };
|
|
2474
|
+
}
|
|
2475
|
+
return { ok: false, error: "Either cycle or date is required" };
|
|
2476
|
+
}
|
|
2477
|
+
const DELIVERY_PRICE = 5;
|
|
2478
|
+
const DELIVERY_OFFSET_DAYS = 2;
|
|
2479
|
+
function toPrice(amount) {
|
|
2480
|
+
const n2 = Number.parseFloat(amount ?? "0");
|
|
2481
|
+
return Number.isFinite(n2) ? n2 : 0;
|
|
2482
|
+
}
|
|
2483
|
+
function addInterval(date, interval, units) {
|
|
2484
|
+
const d2 = new Date(date);
|
|
2485
|
+
switch (interval) {
|
|
2486
|
+
case "DAY":
|
|
2487
|
+
d2.setDate(d2.getDate() + units);
|
|
2488
|
+
break;
|
|
2489
|
+
case "WEEK":
|
|
2490
|
+
d2.setDate(d2.getDate() + units * 7);
|
|
2491
|
+
break;
|
|
2492
|
+
case "YEAR":
|
|
2493
|
+
d2.setFullYear(d2.getFullYear() + units);
|
|
2494
|
+
break;
|
|
2495
|
+
default:
|
|
2496
|
+
d2.setMonth(d2.getMonth() + units);
|
|
2497
|
+
break;
|
|
2498
|
+
}
|
|
2499
|
+
return d2;
|
|
2500
|
+
}
|
|
2501
|
+
function mapItem(sub, item, cycle) {
|
|
2502
|
+
var _a, _b;
|
|
2503
|
+
const price = toPrice(((_a = item.currentPrice) == null ? void 0 : _a.amount) ?? item.price.amount);
|
|
2504
|
+
return {
|
|
2505
|
+
id: `${sub.id}_cycle_${cycle}_${item.id}`,
|
|
2506
|
+
title: item.title ?? `Product ${item.variantId}`,
|
|
2507
|
+
subtitle: item.variantTitle ?? null,
|
|
2508
|
+
imageUrl: (_b = item.variantImage) == null ? void 0 : _b.url,
|
|
2509
|
+
variantId: item.variantId,
|
|
2510
|
+
quantity: item.quantity,
|
|
2511
|
+
price,
|
|
2512
|
+
totalPrice: price * item.quantity,
|
|
2513
|
+
skipped: false,
|
|
2514
|
+
variantImage: item.variantImage ?? null,
|
|
2515
|
+
variantAvailableForSale: item.variantAvailableForSale ?? true,
|
|
2516
|
+
subscriptionId: sub.id,
|
|
2517
|
+
subscriptionLineId: item.id,
|
|
2518
|
+
productId: item.productId,
|
|
2519
|
+
parentProductId: item.parentProductId ?? null,
|
|
2520
|
+
parentLineId: item.parentLineId ?? null,
|
|
2521
|
+
isVirtual: item.isVirtual ?? false,
|
|
2522
|
+
isUpsell: item.isUpsell ?? false,
|
|
2523
|
+
recurringCycleLimit: item.recurringCycleLimit ?? null,
|
|
2524
|
+
customAttributes: item.customAttributes ?? []
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
function projectUpcomingOrders(sub, count) {
|
|
2528
|
+
var _a, _b;
|
|
2529
|
+
if (sub.status !== "active") return [];
|
|
2530
|
+
const billing = sub.billingPolicy ?? ((_a = sub.items[0]) == null ? void 0 : _a.billingPolicy) ?? { interval: "MONTH", intervalCount: 1 };
|
|
2531
|
+
const baseStr = sub.nextBillingDate ?? (sub.nextDeliveryDate ? sub.nextDeliveryDate.toISOString() : null);
|
|
2532
|
+
const base = baseStr ? new Date(baseStr) : /* @__PURE__ */ new Date();
|
|
2533
|
+
const currencyCode = ((_b = sub.items[0]) == null ? void 0 : _b.price.currencyCode) ?? "USD";
|
|
2534
|
+
const orders = [];
|
|
2535
|
+
for (let cycle = 0; cycle < count; cycle++) {
|
|
2536
|
+
const billingDate = addInterval(
|
|
2537
|
+
base,
|
|
2538
|
+
billing.interval,
|
|
2539
|
+
cycle * billing.intervalCount
|
|
2540
|
+
);
|
|
2541
|
+
const deliveryDate = addInterval(billingDate, "DAY", DELIVERY_OFFSET_DAYS);
|
|
2542
|
+
const items = sub.items.map((item) => mapItem(sub, item, cycle));
|
|
2543
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2544
|
+
orders.push({
|
|
2545
|
+
id: `${sub.id}_cycle_${cycle}`,
|
|
2546
|
+
customerId: sub.customerId,
|
|
2547
|
+
subscriptionId: sub.id,
|
|
2548
|
+
date: billingDate.toISOString(),
|
|
2549
|
+
skipped: false,
|
|
2550
|
+
currencyCode,
|
|
2551
|
+
subtotal,
|
|
2552
|
+
deliveryPrice: DELIVERY_PRICE,
|
|
2553
|
+
cycleIndex: cycle,
|
|
2554
|
+
deliveryDate: deliveryDate.toISOString(),
|
|
2555
|
+
status: "scheduled",
|
|
2556
|
+
items,
|
|
2557
|
+
discounts: sub.discounts.map((d2) => ({
|
|
2558
|
+
id: d2.id,
|
|
2559
|
+
title: d2.code,
|
|
2560
|
+
type: d2.type,
|
|
2561
|
+
value: d2.value
|
|
2562
|
+
}))
|
|
2563
|
+
});
|
|
2564
|
+
}
|
|
2565
|
+
return orders;
|
|
2566
|
+
}
|
|
2567
|
+
function applyAdjustments(orders, stored) {
|
|
2568
|
+
return orders.map((order) => {
|
|
2569
|
+
let next = order;
|
|
2570
|
+
for (const { adjustment, subscriptionId, cycle } of stored) {
|
|
2571
|
+
if (subscriptionId !== order.subscriptionId) continue;
|
|
2572
|
+
if (cycle !== order.cycleIndex) continue;
|
|
2573
|
+
const { action } = adjustment;
|
|
2574
|
+
if (action.type === "SKIP_ORDER") {
|
|
2575
|
+
next = { ...next, skipped: true, status: "skipped" };
|
|
2576
|
+
} else if (action.type === "CHANGE_DATE") {
|
|
2577
|
+
next = {
|
|
2578
|
+
...next,
|
|
2579
|
+
date: action.input.newDate,
|
|
2580
|
+
deliveryDate: action.input.newDate
|
|
2581
|
+
};
|
|
2582
|
+
} else if (action.type === "SKIP_ITEM") {
|
|
2583
|
+
const lineIds = new Set(action.input.lineIds);
|
|
2584
|
+
const items = next.items.map(
|
|
2585
|
+
(item) => lineIds.has(item.subscriptionLineId) ? { ...item, skipped: true, totalPrice: 0 } : item
|
|
2586
|
+
);
|
|
2587
|
+
const subtotal = items.reduce((sum, it) => sum + it.totalPrice, 0);
|
|
2588
|
+
next = { ...next, items, subtotal };
|
|
2589
|
+
if (items.length > 0 && items.every((i3) => i3.skipped)) {
|
|
2590
|
+
next = { ...next, skipped: true, status: "skipped", deliveryPrice: 0 };
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
return next;
|
|
2595
|
+
});
|
|
2596
|
+
}
|
|
2597
|
+
function createMockSchedulesAdapter(subscriptions = createMockSubscriptionAdapter()) {
|
|
2598
|
+
const RESOLVE_DEPTH = 12;
|
|
2599
|
+
let stored = [];
|
|
2600
|
+
let idSeq = 0;
|
|
2601
|
+
async function subscriptionsToProject(subscriptionId) {
|
|
2602
|
+
if (subscriptionId) {
|
|
2603
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2604
|
+
return res._tag === "Success" && res.data ? [res.data] : [];
|
|
2605
|
+
}
|
|
2606
|
+
const all = [];
|
|
2607
|
+
let pagination;
|
|
2608
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2609
|
+
const res = await subscriptions.search(pagination ? { pagination } : {});
|
|
2610
|
+
if (res._tag !== "Success") break;
|
|
2611
|
+
all.push(...res.data.items);
|
|
2612
|
+
const { hasNextPage, endCursor } = res.data.pageInfo;
|
|
2613
|
+
if (!hasNextPage || !endCursor) break;
|
|
2614
|
+
pagination = { after: endCursor };
|
|
2615
|
+
}
|
|
2616
|
+
return all;
|
|
2617
|
+
}
|
|
2618
|
+
function resolveCycle(orders, opts) {
|
|
2619
|
+
if (opts.cycle != null) return opts.cycle;
|
|
2620
|
+
if (opts.date) {
|
|
2621
|
+
const iso = toIsoOrNull(opts.date);
|
|
2622
|
+
const target = iso == null ? void 0 : iso.split("T")[0];
|
|
2623
|
+
const match = target ? orders.find(
|
|
2624
|
+
(o3) => o3.deliveryDate.split("T")[0] === target || o3.date.split("T")[0] === target
|
|
2625
|
+
) : void 0;
|
|
2626
|
+
return match ? match.cycleIndex : null;
|
|
2627
|
+
}
|
|
2628
|
+
const firstScheduled = orders.find((o3) => o3.status === "scheduled");
|
|
2629
|
+
return (firstScheduled == null ? void 0 : firstScheduled.cycleIndex) ?? null;
|
|
2630
|
+
}
|
|
2631
|
+
async function recordAdjustment(subscriptionId, opts, buildAction, matcherOverride) {
|
|
2632
|
+
const res = await subscriptions.getById(subscriptionId);
|
|
2633
|
+
const sub = res._tag === "Success" ? res.data : null;
|
|
2634
|
+
if (!sub) return { _tag: "Failure", error: "No scheduled order found" };
|
|
2635
|
+
const orders = applyAdjustments(
|
|
2636
|
+
projectUpcomingOrders(sub, RESOLVE_DEPTH),
|
|
2637
|
+
stored
|
|
2638
|
+
);
|
|
2639
|
+
const cycle = resolveCycle(orders, opts);
|
|
2640
|
+
if (cycle == null) {
|
|
2641
|
+
return { _tag: "Failure", error: "No scheduled order found" };
|
|
2642
|
+
}
|
|
2643
|
+
const built = buildMatcher({ cycle, date: opts.date });
|
|
2644
|
+
const matcher = matcherOverride ?? (built.ok ? built.matcher : { type: "CYCLE", input: { cycle } });
|
|
2645
|
+
stored = [
|
|
2646
|
+
...stored,
|
|
2647
|
+
{
|
|
2648
|
+
adjustment: { id: `adj-${idSeq++}`, matcher, action: buildAction() },
|
|
2649
|
+
subscriptionId,
|
|
2650
|
+
cycle
|
|
2651
|
+
}
|
|
2652
|
+
];
|
|
2653
|
+
return { _tag: "Success", data: void 0 };
|
|
2654
|
+
}
|
|
2655
|
+
return {
|
|
2656
|
+
async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
|
|
2657
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2658
|
+
const subs = await subscriptionsToProject(subscriptionId);
|
|
2659
|
+
const skipCount = new Set(
|
|
2660
|
+
stored.filter(
|
|
2661
|
+
(s2) => s2.adjustment.action.type === "SKIP_ORDER" || s2.adjustment.action.type === "SKIP_ITEM"
|
|
2662
|
+
).map((s2) => `${s2.subscriptionId}:${s2.cycle}`)
|
|
2663
|
+
).size;
|
|
2664
|
+
const depth = excludeSkipped ? count + skipCount : count;
|
|
2665
|
+
const orders = subs.flatMap(
|
|
2666
|
+
(sub) => applyAdjustments(projectUpcomingOrders(sub, depth), stored)
|
|
2667
|
+
).filter((o3) => excludeSkipped ? !o3.skipped : true).map(
|
|
2668
|
+
(o3) => (
|
|
2669
|
+
// Mirror backend removeSkippedItems: the billable view also strips
|
|
2670
|
+
// skipped lines from surviving (partially-skipped) orders. No
|
|
2671
|
+
// recompute needed — skipped lines already carry totalPrice 0 (R4).
|
|
2672
|
+
excludeSkipped && o3.items.some((i3) => i3.skipped) ? { ...o3, items: o3.items.filter((i3) => !i3.skipped) } : o3
|
|
2673
|
+
)
|
|
2674
|
+
).sort((a2, b3) => a2.date.localeCompare(b3.date));
|
|
2675
|
+
return { _tag: "Success", data: orders.slice(0, count) };
|
|
2676
|
+
},
|
|
2677
|
+
async getAdjustments() {
|
|
2678
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2679
|
+
return { _tag: "Success", data: stored.map((s2) => s2.adjustment) };
|
|
2680
|
+
},
|
|
2681
|
+
async skipOrder({ subscriptionId, cycle, date }) {
|
|
2682
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2683
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2684
|
+
type: "SKIP_ORDER",
|
|
2685
|
+
input: { reason: "" }
|
|
2686
|
+
}));
|
|
2687
|
+
},
|
|
2688
|
+
async skipItem({ subscriptionId, lineId, cycle, date, reason }) {
|
|
2689
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2690
|
+
const built = buildSkipItemMatcher({ cycle, date });
|
|
2691
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2692
|
+
return recordAdjustment(
|
|
2693
|
+
subscriptionId,
|
|
2694
|
+
{ cycle, date },
|
|
2695
|
+
() => ({
|
|
2696
|
+
type: "SKIP_ITEM",
|
|
2697
|
+
input: { lineIds: [lineId], reason: reason ?? "" }
|
|
2698
|
+
}),
|
|
2699
|
+
built.matcher
|
|
2700
|
+
);
|
|
2701
|
+
},
|
|
2702
|
+
async changeDateAdjustment({ subscriptionId, newDate, cycle, currentDate }) {
|
|
2703
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2704
|
+
const newIso = toIsoOrNull(newDate);
|
|
2705
|
+
if (newIso == null) {
|
|
2706
|
+
return { _tag: "Failure", error: "newDate must be a valid date" };
|
|
2707
|
+
}
|
|
2708
|
+
return recordAdjustment(
|
|
2709
|
+
subscriptionId,
|
|
2710
|
+
{ cycle, date: currentDate },
|
|
2711
|
+
() => ({
|
|
2712
|
+
type: "CHANGE_DATE",
|
|
2713
|
+
input: { newDate: newIso }
|
|
2714
|
+
})
|
|
2715
|
+
);
|
|
2716
|
+
},
|
|
2717
|
+
async updateShippingAdjustment({ subscriptionId, address, cycle, date }) {
|
|
2718
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2719
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2720
|
+
type: "UPDATE_SHIPPING",
|
|
2721
|
+
input: { address }
|
|
2722
|
+
}));
|
|
2723
|
+
},
|
|
2724
|
+
async updatePaymentMethodAdjustment({
|
|
2725
|
+
subscriptionId,
|
|
2726
|
+
paymentMethodId,
|
|
2727
|
+
cycle,
|
|
2728
|
+
date
|
|
2729
|
+
}) {
|
|
2730
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2731
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2732
|
+
type: "UPDATE_PAYMENT_METHOD",
|
|
2733
|
+
input: { paymentMethodId }
|
|
2734
|
+
}));
|
|
2735
|
+
},
|
|
2736
|
+
async updateProductsAdjustment({ subscriptionId, lines, cycle, date }) {
|
|
2737
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2738
|
+
return recordAdjustment(subscriptionId, { cycle, date }, () => ({
|
|
2739
|
+
type: "UPDATE_PRODUCTS",
|
|
2740
|
+
input: { lines }
|
|
2741
|
+
}));
|
|
2742
|
+
},
|
|
2743
|
+
async applyDiscountAdjustment({ subscriptionId, discountCode, cycle }) {
|
|
2744
|
+
await new Promise((r2) => setTimeout(r2, 300));
|
|
2745
|
+
return recordAdjustment(subscriptionId, { cycle }, () => ({
|
|
2746
|
+
type: "APPLY_DISCOUNT",
|
|
2747
|
+
input: { discountSource: "code", discountCode }
|
|
2748
|
+
}));
|
|
2749
|
+
},
|
|
2750
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2751
|
+
await new Promise((r2) => setTimeout(r2, 200));
|
|
2752
|
+
stored = stored.filter((s2) => s2.adjustment.id !== adjustmentId);
|
|
2753
|
+
return { _tag: "Success", data: void 0 };
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
function createApiSchedulesAdapter(fetcher) {
|
|
2758
|
+
async function postAdjustment(matcher, action) {
|
|
2759
|
+
try {
|
|
2760
|
+
const res = await fetcher(`/customer/v1/schedules/adjustments`, {
|
|
2761
|
+
method: "POST",
|
|
2762
|
+
headers: { "Content-Type": "application/json" },
|
|
2763
|
+
body: JSON.stringify({ matcher, action })
|
|
2764
|
+
});
|
|
2765
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2766
|
+
return { _tag: "Success", data: void 0 };
|
|
2767
|
+
} catch (e) {
|
|
2768
|
+
return { _tag: "Failure", error: e };
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
return {
|
|
2772
|
+
async getUpcomingOrders({ count, subscriptionId, excludeSkipped = true }) {
|
|
2773
|
+
try {
|
|
2774
|
+
const params = new URLSearchParams({ count: String(count) });
|
|
2775
|
+
params.set("excludeSkipped", String(excludeSkipped));
|
|
2776
|
+
if (subscriptionId) params.set("query", `id:"${subscriptionId}"`);
|
|
2777
|
+
const res = await fetcher(`/customer/v1/schedules/?${params.toString()}`);
|
|
2778
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2779
|
+
const data = await res.json();
|
|
2780
|
+
return { _tag: "Success", data: (data.orders ?? []).map(mapScheduleOrder) };
|
|
2781
|
+
} catch (e) {
|
|
2782
|
+
return { _tag: "Failure", error: e };
|
|
2783
|
+
}
|
|
2784
|
+
},
|
|
2785
|
+
async getAdjustments() {
|
|
2786
|
+
try {
|
|
2787
|
+
const all = [];
|
|
2788
|
+
let after;
|
|
2789
|
+
for (let guard = 0; guard < 50; guard++) {
|
|
2790
|
+
const params = new URLSearchParams({ limit: "100" });
|
|
2791
|
+
if (after) params.set("after", after);
|
|
2792
|
+
const res = await fetcher(
|
|
2793
|
+
`/customer/v1/schedules/adjustments?${params.toString()}`
|
|
2794
|
+
);
|
|
2795
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2796
|
+
const body = await res.json();
|
|
2797
|
+
all.push(
|
|
2798
|
+
...body.data.map((a2) => ({
|
|
2799
|
+
id: a2.id,
|
|
2800
|
+
matcher: a2.matcher,
|
|
2801
|
+
action: a2.action
|
|
2802
|
+
}))
|
|
2803
|
+
);
|
|
2804
|
+
if (!body.hasNextPage) return { _tag: "Success", data: all };
|
|
2805
|
+
if (!body.endCursor || body.endCursor === after) {
|
|
2806
|
+
return {
|
|
2807
|
+
_tag: "Failure",
|
|
2808
|
+
error: "Adjustments pagination cursor did not advance"
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2811
|
+
after = body.endCursor;
|
|
2812
|
+
}
|
|
2813
|
+
return {
|
|
2814
|
+
_tag: "Failure",
|
|
2815
|
+
error: "Adjustments pagination exceeded 50 pages"
|
|
2816
|
+
};
|
|
2817
|
+
} catch (e) {
|
|
2818
|
+
return { _tag: "Failure", error: e };
|
|
2819
|
+
}
|
|
2820
|
+
},
|
|
2821
|
+
async skipOrder({ cycle, date }) {
|
|
2822
|
+
const built = buildMatcher({ cycle, date });
|
|
2823
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2824
|
+
return postAdjustment(built.matcher, {
|
|
2825
|
+
type: "SKIP_ORDER",
|
|
2826
|
+
input: { reason: "" }
|
|
2827
|
+
});
|
|
2828
|
+
},
|
|
2829
|
+
async skipItem({ lineId, cycle, date, reason }) {
|
|
2830
|
+
const built = buildSkipItemMatcher({ cycle, date });
|
|
2831
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2832
|
+
return postAdjustment(built.matcher, {
|
|
2833
|
+
type: "SKIP_ITEM",
|
|
2834
|
+
input: { lineIds: [lineId], reason: reason ?? "" }
|
|
2835
|
+
});
|
|
2836
|
+
},
|
|
2837
|
+
async changeDateAdjustment({ newDate, cycle }) {
|
|
2838
|
+
if (cycle == null) {
|
|
2839
|
+
return {
|
|
2840
|
+
_tag: "Failure",
|
|
2841
|
+
error: "cycle is required for changeDateAdjustment"
|
|
2842
|
+
};
|
|
2843
|
+
}
|
|
2844
|
+
const newIso = toIsoOrNull(newDate);
|
|
2845
|
+
if (newIso == null) {
|
|
2846
|
+
return { _tag: "Failure", error: "newDate must be a valid date" };
|
|
2847
|
+
}
|
|
2848
|
+
return postAdjustment(
|
|
2849
|
+
{ type: "CYCLE", input: { cycle } },
|
|
2850
|
+
{ type: "CHANGE_DATE", input: { newDate: newIso } }
|
|
2851
|
+
);
|
|
2852
|
+
},
|
|
2853
|
+
async updateShippingAdjustment({ address, cycle, date }) {
|
|
2854
|
+
const built = buildMatcher({ cycle, date });
|
|
2855
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2856
|
+
return postAdjustment(built.matcher, {
|
|
2857
|
+
type: "UPDATE_SHIPPING",
|
|
2858
|
+
input: { address }
|
|
2859
|
+
});
|
|
2860
|
+
},
|
|
2861
|
+
async updatePaymentMethodAdjustment({ paymentMethodId, cycle, date }) {
|
|
2862
|
+
const built = buildMatcher({ cycle, date });
|
|
2863
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2864
|
+
return postAdjustment(built.matcher, {
|
|
2865
|
+
type: "UPDATE_PAYMENT_METHOD",
|
|
2866
|
+
input: { paymentMethodId }
|
|
2867
|
+
});
|
|
2868
|
+
},
|
|
2869
|
+
async updateProductsAdjustment({ lines, cycle, date }) {
|
|
2870
|
+
const built = buildMatcher({ cycle, date });
|
|
2871
|
+
if (!built.ok) return { _tag: "Failure", error: built.error };
|
|
2872
|
+
return postAdjustment(built.matcher, {
|
|
2873
|
+
type: "UPDATE_PRODUCTS",
|
|
2874
|
+
input: { lines }
|
|
2875
|
+
});
|
|
2876
|
+
},
|
|
2877
|
+
async applyDiscountAdjustment({ discountCode, cycle }) {
|
|
2878
|
+
return postAdjustment(
|
|
2879
|
+
{ type: "CYCLE", input: { cycle } },
|
|
2880
|
+
{
|
|
2881
|
+
type: "APPLY_DISCOUNT",
|
|
2882
|
+
input: { discountSource: "code", discountCode }
|
|
2883
|
+
}
|
|
2884
|
+
);
|
|
2885
|
+
},
|
|
2886
|
+
async removeAdjustment({ adjustmentId }) {
|
|
2887
|
+
try {
|
|
2888
|
+
const res = await fetcher(
|
|
2889
|
+
`/customer/v1/schedules/adjustments/${adjustmentId}`,
|
|
2890
|
+
{ method: "DELETE" }
|
|
2891
|
+
);
|
|
2892
|
+
if (!res.ok) return { _tag: "Failure", error: `HTTP ${res.status}` };
|
|
2893
|
+
return { _tag: "Success", data: void 0 };
|
|
2894
|
+
} catch (e) {
|
|
2895
|
+
return { _tag: "Failure", error: e };
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
};
|
|
2899
|
+
}
|
|
2900
|
+
const SchedulesServiceContext = createContext("services/schedules");
|
|
2901
|
+
function isSameCalendarDay(a2, b3) {
|
|
2902
|
+
const da = new Date(a2);
|
|
2903
|
+
const db = new Date(b3);
|
|
2904
|
+
if (Number.isNaN(da.getTime()) || Number.isNaN(db.getTime())) return false;
|
|
2905
|
+
return da.toISOString().slice(0, 10) === db.toISOString().slice(0, 10);
|
|
2906
|
+
}
|
|
2907
|
+
function matcherArms(matcher, target) {
|
|
2908
|
+
const cycleArm = (matcher.type === "CYCLE" || matcher.type === "CYCLE_AND_DATE") && matcher.input.cycle === target.cycle;
|
|
2909
|
+
const dateArm = (matcher.type === "DATE" || matcher.type === "CYCLE_AND_DATE") && isSameCalendarDay(matcher.input.date, target.date);
|
|
2910
|
+
return { cycleArm, dateArm };
|
|
2911
|
+
}
|
|
2912
|
+
function matcherAffects(matcher, target) {
|
|
2913
|
+
const { cycleArm, dateArm } = matcherArms(matcher, target);
|
|
2914
|
+
return cycleArm || dateArm;
|
|
2915
|
+
}
|
|
2916
|
+
function matcherExactlyTargets(matcher, target) {
|
|
2917
|
+
const { cycleArm, dateArm } = matcherArms(matcher, target);
|
|
2918
|
+
if (matcher.type === "CYCLE") return cycleArm;
|
|
2919
|
+
if (matcher.type === "DATE") return dateArm;
|
|
2920
|
+
return cycleArm && dateArm;
|
|
2921
|
+
}
|
|
2922
|
+
function createSchedulesService(adapter, opts) {
|
|
1133
2923
|
const current = d(null);
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
2924
|
+
const currentLoading = d(false);
|
|
2925
|
+
const hasPendingBilling = (opts == null ? void 0 : opts.hasPendingBilling) ?? d(false);
|
|
2926
|
+
const schedulesChangedAt = d(0);
|
|
2927
|
+
let reloadToken = 0;
|
|
2928
|
+
let currentSubscriptionId;
|
|
2929
|
+
async function getUpcomingOrder() {
|
|
2930
|
+
const result = await adapter.getUpcomingOrders({ count: 1 });
|
|
2931
|
+
if (result._tag === "Failure") return result;
|
|
2932
|
+
return { _tag: "Success", data: result.data[0] ?? null };
|
|
2933
|
+
}
|
|
2934
|
+
async function getUpcomingOrders(count, opts2) {
|
|
2935
|
+
return adapter.getUpcomingOrders({ count, ...opts2 });
|
|
2936
|
+
}
|
|
2937
|
+
async function reloadCurrent(subscriptionId) {
|
|
2938
|
+
var _a;
|
|
2939
|
+
const scopeChanged = currentSubscriptionId !== subscriptionId;
|
|
2940
|
+
currentSubscriptionId = subscriptionId;
|
|
2941
|
+
const token = ++reloadToken;
|
|
2942
|
+
currentLoading.value = true;
|
|
2943
|
+
const count = Math.max(((_a = current.value) == null ? void 0 : _a.length) ?? 0, 3);
|
|
2944
|
+
if (scopeChanged) current.value = null;
|
|
2945
|
+
const result = await adapter.getUpcomingOrders({
|
|
2946
|
+
count,
|
|
2947
|
+
subscriptionId
|
|
2948
|
+
});
|
|
2949
|
+
if (token !== reloadToken) return;
|
|
2950
|
+
currentLoading.value = false;
|
|
2951
|
+
if (result._tag === "Success") {
|
|
1139
2952
|
current.value = result.data;
|
|
1140
2953
|
}
|
|
1141
2954
|
}
|
|
1142
|
-
async function
|
|
1143
|
-
await
|
|
1144
|
-
|
|
2955
|
+
async function notifyChanged(subscriptionId) {
|
|
2956
|
+
if (subscriptionId) await reloadCurrent(subscriptionId);
|
|
2957
|
+
schedulesChangedAt.value++;
|
|
1145
2958
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
2959
|
+
return {
|
|
2960
|
+
current,
|
|
2961
|
+
currentLoading,
|
|
2962
|
+
hasPendingBilling,
|
|
2963
|
+
schedulesChangedAt,
|
|
2964
|
+
getUpcomingOrder,
|
|
2965
|
+
getUpcomingOrders,
|
|
2966
|
+
async getAdjustments() {
|
|
2967
|
+
return adapter.getAdjustments();
|
|
2968
|
+
},
|
|
2969
|
+
async skipOrder(params) {
|
|
2970
|
+
const result = await adapter.skipOrder(params);
|
|
2971
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2972
|
+
return result;
|
|
2973
|
+
},
|
|
2974
|
+
async skipItem(params) {
|
|
2975
|
+
const result = await adapter.skipItem(params);
|
|
2976
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
2977
|
+
return result;
|
|
2978
|
+
},
|
|
2979
|
+
async unskipItem({ subscriptionId, lineId, cycle, date }) {
|
|
2980
|
+
const adjustments = await adapter.getAdjustments();
|
|
2981
|
+
if (adjustments._tag === "Failure") return adjustments;
|
|
2982
|
+
const covering = adjustments.data.filter(
|
|
2983
|
+
(a2) => a2.action.type === "SKIP_ITEM" && a2.action.input.lineIds.includes(lineId) && matcherAffects(a2.matcher, { cycle, date })
|
|
2984
|
+
);
|
|
2985
|
+
if (covering.length === 0) {
|
|
2986
|
+
return { _tag: "Failure", error: "No skip found for this item" };
|
|
2987
|
+
}
|
|
2988
|
+
if (covering.some((a2) => a2.action.input.lineIds.length > 1)) {
|
|
2989
|
+
return {
|
|
2990
|
+
_tag: "Failure",
|
|
2991
|
+
error: "This item was skipped together with other items and can't be unskipped individually"
|
|
2992
|
+
};
|
|
2993
|
+
}
|
|
2994
|
+
if (covering.some((a2) => !matcherExactlyTargets(a2.matcher, { cycle, date }))) {
|
|
2995
|
+
return {
|
|
2996
|
+
_tag: "Failure",
|
|
2997
|
+
error: "This skip also affects another order and can't be removed from here"
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
let deleted = 0;
|
|
3001
|
+
for (const a2 of covering) {
|
|
3002
|
+
const result = await adapter.removeAdjustment({ adjustmentId: a2.id });
|
|
3003
|
+
if (result._tag === "Failure") {
|
|
3004
|
+
if (deleted > 0) await notifyChanged(subscriptionId);
|
|
3005
|
+
return result;
|
|
3006
|
+
}
|
|
3007
|
+
deleted++;
|
|
3008
|
+
}
|
|
3009
|
+
await notifyChanged(subscriptionId);
|
|
3010
|
+
return { _tag: "Success", data: void 0 };
|
|
3011
|
+
},
|
|
3012
|
+
async changeDateAdjustment(params) {
|
|
3013
|
+
const result = await adapter.changeDateAdjustment(params);
|
|
3014
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
3015
|
+
return result;
|
|
3016
|
+
},
|
|
3017
|
+
async updateShippingAdjustment(params) {
|
|
3018
|
+
const result = await adapter.updateShippingAdjustment(params);
|
|
3019
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
3020
|
+
return result;
|
|
3021
|
+
},
|
|
3022
|
+
async updatePaymentMethodAdjustment(params) {
|
|
3023
|
+
const result = await adapter.updatePaymentMethodAdjustment(params);
|
|
3024
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
3025
|
+
return result;
|
|
3026
|
+
},
|
|
3027
|
+
async updateProductsAdjustment(params) {
|
|
3028
|
+
const result = await adapter.updateProductsAdjustment(params);
|
|
3029
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
3030
|
+
return result;
|
|
3031
|
+
},
|
|
3032
|
+
async applyDiscountAdjustment(params) {
|
|
3033
|
+
const result = await adapter.applyDiscountAdjustment(params);
|
|
3034
|
+
if (result._tag === "Success") await notifyChanged(params.subscriptionId);
|
|
3035
|
+
return result;
|
|
3036
|
+
},
|
|
3037
|
+
async removeAdjustment(params) {
|
|
3038
|
+
var _a, _b, _c;
|
|
3039
|
+
const result = await adapter.removeAdjustment(params);
|
|
3040
|
+
if (result._tag === "Success") {
|
|
3041
|
+
const order = (_a = current.value) == null ? void 0 : _a[0];
|
|
3042
|
+
const subscriptionId = currentSubscriptionId ?? (order == null ? void 0 : order.subscriptionId) ?? ((_c = (_b = order == null ? void 0 : order.items) == null ? void 0 : _b[0]) == null ? void 0 : _c.subscriptionId);
|
|
3043
|
+
await notifyChanged(subscriptionId);
|
|
3044
|
+
}
|
|
3045
|
+
return result;
|
|
3046
|
+
},
|
|
3047
|
+
reloadCurrent
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
const SettingsServiceContext = createContext("services/settings");
|
|
3051
|
+
function createSettingsService(initial = {}) {
|
|
3052
|
+
const current = d(initial);
|
|
3053
|
+
return {
|
|
3054
|
+
current,
|
|
3055
|
+
get(key) {
|
|
3056
|
+
return this.current.value[key];
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
function createShopService(options) {
|
|
3061
|
+
const contactEmail = d(options.getContactEmail());
|
|
3062
|
+
const service = { contactEmail };
|
|
3063
|
+
return service;
|
|
3064
|
+
}
|
|
3065
|
+
const ShopServiceContext = createContext("services/shop");
|
|
3066
|
+
const SubscriptionServiceContext = createContext("services/subscription");
|
|
3067
|
+
function getCurrentSubscriptionId() {
|
|
3068
|
+
if (typeof window === "undefined") {
|
|
3069
|
+
return null;
|
|
3070
|
+
}
|
|
3071
|
+
const pathname = window.location.pathname;
|
|
3072
|
+
const segments = pathname.split("/").filter(Boolean);
|
|
3073
|
+
const subscriptionIndex = segments.findIndex(
|
|
3074
|
+
(segment) => segment === "subscriptions" || segment === "subscription"
|
|
3075
|
+
);
|
|
3076
|
+
if (subscriptionIndex !== -1 && segments[subscriptionIndex + 1]) {
|
|
3077
|
+
return segments[subscriptionIndex + 1];
|
|
3078
|
+
}
|
|
3079
|
+
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3080
|
+
const lastSegment = segments[segments.length - 1];
|
|
3081
|
+
if (lastSegment && uuidPattern.test(lastSegment)) {
|
|
3082
|
+
return lastSegment;
|
|
3083
|
+
}
|
|
3084
|
+
return null;
|
|
3085
|
+
}
|
|
3086
|
+
function createSubscriptionService(adapter) {
|
|
3087
|
+
const current = d(null);
|
|
3088
|
+
const hasPendingBilling = w(
|
|
3089
|
+
() => {
|
|
3090
|
+
var _a, _b, _c, _d;
|
|
3091
|
+
return ((_b = (_a = current.value) == null ? void 0 : _a.lastBillingAttempt) == null ? void 0 : _b.ready) === false || ((_d = (_c = current.value) == null ? void 0 : _c.renewal) == null ? void 0 : _d.isInActiveProcessState) === true;
|
|
3092
|
+
}
|
|
3093
|
+
);
|
|
3094
|
+
const discountsChangedAt = d(0);
|
|
3095
|
+
const itemsChangedAt = d(0);
|
|
3096
|
+
const lastOrder = d(null);
|
|
3097
|
+
async function refreshAfterDiscountChange() {
|
|
3098
|
+
await reload();
|
|
3099
|
+
discountsChangedAt.value = discountsChangedAt.value + 1;
|
|
3100
|
+
}
|
|
3101
|
+
async function refreshAfterItemChange() {
|
|
3102
|
+
await reload();
|
|
3103
|
+
itemsChangedAt.value = itemsChangedAt.value + 1;
|
|
1149
3104
|
}
|
|
1150
3105
|
function subscriptionIdFromCurrent() {
|
|
1151
3106
|
var _a;
|
|
1152
3107
|
return ((_a = current.value) == null ? void 0 : _a.id) ?? null;
|
|
1153
3108
|
}
|
|
3109
|
+
async function reload() {
|
|
3110
|
+
var _a, _b;
|
|
3111
|
+
const id = (_a = current.value) == null ? void 0 : _a.id;
|
|
3112
|
+
if (!id) return;
|
|
3113
|
+
const [subResult, lastOrderResult] = await Promise.all([
|
|
3114
|
+
adapter.getById(id),
|
|
3115
|
+
adapter.getLastOrder({ subscriptionId: id })
|
|
3116
|
+
]);
|
|
3117
|
+
if (((_b = current.value) == null ? void 0 : _b.id) !== id) return;
|
|
3118
|
+
if (subResult._tag === "Success") current.value = subResult.data;
|
|
3119
|
+
if (lastOrderResult._tag === "Success") lastOrder.value = lastOrderResult.data;
|
|
3120
|
+
}
|
|
1154
3121
|
const service = {
|
|
3122
|
+
current,
|
|
3123
|
+
hasPendingBilling,
|
|
3124
|
+
discountsChangedAt,
|
|
3125
|
+
itemsChangedAt,
|
|
3126
|
+
lastOrder,
|
|
1155
3127
|
search: adapter.search,
|
|
3128
|
+
// Prefer the adapter's rich cross-subscription list (legacy endpoint, active-first).
|
|
3129
|
+
// Fall back to a single `search` page for adapters that don't implement it
|
|
3130
|
+
// (e.g. the editor mock) so the cross-subscription views still render.
|
|
3131
|
+
listAll: adapter.listAll ? adapter.listAll.bind(adapter) : async () => {
|
|
3132
|
+
const result = await adapter.search();
|
|
3133
|
+
if (result._tag !== "Success") return result;
|
|
3134
|
+
return { _tag: "Success", data: result.data.items };
|
|
3135
|
+
},
|
|
1156
3136
|
getById: adapter.getById,
|
|
1157
3137
|
getCurrentSubscriptionId,
|
|
1158
3138
|
addItem: async (subscriptionId, item) => {
|
|
1159
3139
|
const result = await adapter.addItem({ subscriptionId, item });
|
|
1160
3140
|
if (result._tag === "Success") {
|
|
1161
|
-
await refreshAfterItemChange(
|
|
3141
|
+
await refreshAfterItemChange();
|
|
1162
3142
|
}
|
|
1163
3143
|
return result;
|
|
1164
3144
|
},
|
|
1165
3145
|
removeItem: async (subscriptionId, itemId) => {
|
|
1166
3146
|
const result = await adapter.removeItem({ subscriptionId, itemId });
|
|
1167
3147
|
if (result._tag === "Success") {
|
|
1168
|
-
await refreshAfterItemChange(
|
|
3148
|
+
await refreshAfterItemChange();
|
|
3149
|
+
}
|
|
3150
|
+
return result;
|
|
3151
|
+
},
|
|
3152
|
+
updateItem: async (subscriptionId, lineId, update) => {
|
|
3153
|
+
const result = await adapter.updateItem({ subscriptionId, lineId, update });
|
|
3154
|
+
if (result._tag === "Success") {
|
|
3155
|
+
await refreshAfterItemChange();
|
|
3156
|
+
}
|
|
3157
|
+
return result;
|
|
3158
|
+
},
|
|
3159
|
+
updateItemAttributes: async (subscriptionId, lineId, customAttributes) => {
|
|
3160
|
+
const result = await adapter.updateItemAttributes({ subscriptionId, lineId, customAttributes });
|
|
3161
|
+
if (result._tag === "Success") {
|
|
3162
|
+
await refreshAfterItemChange();
|
|
3163
|
+
}
|
|
3164
|
+
return result;
|
|
3165
|
+
},
|
|
3166
|
+
updateAttributes: async (subscriptionId, customAttributes) => {
|
|
3167
|
+
const result = await adapter.updateAttributes({ subscriptionId, customAttributes });
|
|
3168
|
+
if (result._tag === "Success") {
|
|
3169
|
+
await reload();
|
|
3170
|
+
}
|
|
3171
|
+
return result;
|
|
3172
|
+
},
|
|
3173
|
+
skipLineItem: async (subscriptionId, lineId, nextBillingDate) => {
|
|
3174
|
+
const result = await adapter.skipLineItem({ subscriptionId, lineId, nextBillingDate });
|
|
3175
|
+
if (result._tag === "Success") {
|
|
3176
|
+
await refreshAfterItemChange();
|
|
3177
|
+
}
|
|
3178
|
+
return result;
|
|
3179
|
+
},
|
|
3180
|
+
updateBoxItems: async (subscriptionId, params) => {
|
|
3181
|
+
const result = await adapter.updateBoxItems({ subscriptionId, ...params });
|
|
3182
|
+
if (result._tag === "Success") {
|
|
3183
|
+
await refreshAfterItemChange();
|
|
1169
3184
|
}
|
|
1170
3185
|
return result;
|
|
1171
3186
|
},
|
|
@@ -1179,7 +3194,7 @@ function createSubscriptionService(adapter) {
|
|
|
1179
3194
|
}
|
|
1180
3195
|
const result = await adapter.addDiscount({ subscriptionId: id, code });
|
|
1181
3196
|
if (result._tag === "Success") {
|
|
1182
|
-
await refreshAfterDiscountChange(
|
|
3197
|
+
await refreshAfterDiscountChange();
|
|
1183
3198
|
}
|
|
1184
3199
|
return result;
|
|
1185
3200
|
},
|
|
@@ -1196,38 +3211,30 @@ function createSubscriptionService(adapter) {
|
|
|
1196
3211
|
discountId
|
|
1197
3212
|
});
|
|
1198
3213
|
if (result._tag === "Success") {
|
|
1199
|
-
await refreshAfterDiscountChange(
|
|
3214
|
+
await refreshAfterDiscountChange();
|
|
1200
3215
|
}
|
|
1201
3216
|
return result;
|
|
1202
3217
|
},
|
|
1203
3218
|
reschedule: (subscriptionId, newDate) => adapter.reschedule({ subscriptionId, newDate }),
|
|
3219
|
+
updateCustomAttributes: (subscriptionId, customAttributes) => adapter.updateCustomAttributes({ subscriptionId, customAttributes }),
|
|
1204
3220
|
resume: (subscriptionId) => adapter.resume({ subscriptionId }),
|
|
1205
3221
|
pause: (subscriptionId) => adapter.pause({ subscriptionId }),
|
|
1206
3222
|
reactivate: (subscriptionId) => adapter.reactivate({ subscriptionId }),
|
|
1207
3223
|
cancel: (subscriptionId, reason) => adapter.cancel({ subscriptionId, reason }),
|
|
3224
|
+
renewNow: (params) => adapter.renewNow(params),
|
|
3225
|
+
changeFrequency: (subscriptionId, plan) => adapter.changeFrequency({ subscriptionId, plan }),
|
|
3226
|
+
reload,
|
|
1208
3227
|
getAvailablePlans: (subscriptionId) => adapter.getAvailablePlans({ subscriptionId }),
|
|
1209
3228
|
getDefaultSubscriptionId: () => adapter.getDefaultSubscriptionId(),
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
itemsChangedAt: itemsChangedAtSignal
|
|
3229
|
+
getUpsellProducts: (params) => adapter.getUpsellProducts ? adapter.getUpsellProducts(params) : Promise.resolve({ _tag: "Success", data: [] }),
|
|
3230
|
+
addUpsellProduct: (params) => adapter.addUpsellProduct ? adapter.addUpsellProduct(params) : Promise.resolve({ _tag: "Success", data: void 0 })
|
|
1213
3231
|
};
|
|
1214
3232
|
return service;
|
|
1215
3233
|
}
|
|
1216
|
-
const DISCOUNT_REJECTION_REASONS = [
|
|
1217
|
-
"INVALID_CODE",
|
|
1218
|
-
"INACTIVE",
|
|
1219
|
-
"NOT_APPLICABLE_TO_CUSTOMER",
|
|
1220
|
-
"ALREADY_ADDED",
|
|
1221
|
-
"ALREADY_USED",
|
|
1222
|
-
"NOT_APPLICABLE_TO_PRODUCTS",
|
|
1223
|
-
"NOT_APPLICABLE_TO_SUBSCRIPTION",
|
|
1224
|
-
"INVALID_COMBINATION",
|
|
1225
|
-
"QUANTITY_REQUIREMENT_NOT_MET"
|
|
1226
|
-
];
|
|
1227
3234
|
function getDefaultBlockForAction(actionType, actionConfig, blockId) {
|
|
1228
3235
|
const blockName = `action:${actionType}`;
|
|
1229
3236
|
const definedBlocks = getDefinedBlocks();
|
|
1230
|
-
const exists = definedBlocks.some((
|
|
3237
|
+
const exists = definedBlocks.some((b3) => b3.name === blockName);
|
|
1231
3238
|
if (!exists) {
|
|
1232
3239
|
throw new Error(
|
|
1233
3240
|
`No default block registered for action type "${actionType}". Expected a block named "${blockName}" to be registered in blocks-extensions.`
|
|
@@ -1237,7 +3244,7 @@ function getDefaultBlockForAction(actionType, actionConfig, blockId) {
|
|
|
1237
3244
|
}
|
|
1238
3245
|
function hasDefaultBlockForAction(actionType) {
|
|
1239
3246
|
const blockName = `action:${actionType}`;
|
|
1240
|
-
return getDefinedBlocks().some((
|
|
3247
|
+
return getDefinedBlocks().some((b3) => b3.name === blockName);
|
|
1241
3248
|
}
|
|
1242
3249
|
const WorkflowServiceContext = createContext("services/workflow");
|
|
1243
3250
|
const RUN_SCOPED_RESET = {
|
|
@@ -1375,7 +3382,7 @@ function createWorkflowService(deps) {
|
|
|
1375
3382
|
outcome: null
|
|
1376
3383
|
});
|
|
1377
3384
|
if (editor.isEditorMode) {
|
|
1378
|
-
const displayStepId = isInitialResponse && previewStepId && ((_e = data.interactiveSteps) == null ? void 0 : _e.some((
|
|
3385
|
+
const displayStepId = isInitialResponse && previewStepId && ((_e = data.interactiveSteps) == null ? void 0 : _e.some((s2) => s2.stepId === previewStepId)) ? previewStepId : currentStepId;
|
|
1379
3386
|
const overlayBlocks = deps.themeState.overlay.blocks.value;
|
|
1380
3387
|
const rootBlock = overlayBlocks[0];
|
|
1381
3388
|
const stepSlot = displayStepId ? (_f = rootBlock == null ? void 0 : rootBlock.slots) == null ? void 0 : _f[displayStepId] : null;
|
|
@@ -1470,7 +3477,7 @@ function createWorkflowService(deps) {
|
|
|
1470
3477
|
const presets = {};
|
|
1471
3478
|
const baseLabelFor = (step) => {
|
|
1472
3479
|
var _a;
|
|
1473
|
-
return ((_a = getDefinedBlocks().find((
|
|
3480
|
+
return ((_a = getDefinedBlocks().find((b3) => b3.name === `action:${step.actionType}`)) == null ? void 0 : _a.displayName) ?? step.stepId;
|
|
1474
3481
|
};
|
|
1475
3482
|
const totalCounts = /* @__PURE__ */ new Map();
|
|
1476
3483
|
for (const step of allSteps) {
|
|
@@ -2065,9 +4072,9 @@ const gridV1Engine = {
|
|
|
2065
4072
|
case "resize": {
|
|
2066
4073
|
const current = layout.items[cmd.blockId];
|
|
2067
4074
|
if (current) {
|
|
2068
|
-
const
|
|
2069
|
-
const
|
|
2070
|
-
layout.items[cmd.blockId] = { ...current, w:
|
|
4075
|
+
const w4 = Math.max(1, Math.min(cmd.size.w, ctx2.cols - current.c));
|
|
4076
|
+
const h2 = Math.max(1, cmd.size.h);
|
|
4077
|
+
layout.items[cmd.blockId] = { ...current, w: w4, h: h2 };
|
|
2071
4078
|
}
|
|
2072
4079
|
break;
|
|
2073
4080
|
}
|
|
@@ -2134,7 +4141,7 @@ function insertRectRelative(doc, args) {
|
|
|
2134
4141
|
if (!layout) continue;
|
|
2135
4142
|
const target = layout.items[args.targetId];
|
|
2136
4143
|
if (!target) continue;
|
|
2137
|
-
const cols = ((_a = doc.breakpoints.find((
|
|
4144
|
+
const cols = ((_a = doc.breakpoints.find((b3) => b3.id === breakpointId)) == null ? void 0 : _a.cols) ?? ((_b = layout.grid) == null ? void 0 : _b.cols) ?? DEFAULT_GRID.cols;
|
|
2138
4145
|
const insertRow = args.position === "before" ? target.r : target.r + target.h;
|
|
2139
4146
|
for (const [id, rect] of Object.entries(layout.items)) {
|
|
2140
4147
|
if (id === args.blockId) continue;
|
|
@@ -2185,105 +4192,128 @@ export {
|
|
|
2185
4192
|
AuthenticationError,
|
|
2186
4193
|
AuthenticationErrorReason,
|
|
2187
4194
|
B as BlockWrapperEvent,
|
|
2188
|
-
|
|
2189
|
-
|
|
4195
|
+
q as ContextProvideEvent,
|
|
4196
|
+
t as ContextRequestEvent,
|
|
2190
4197
|
CustomerServiceContext,
|
|
2191
|
-
|
|
2192
|
-
|
|
4198
|
+
h as DEFAULT_BREAKPOINTS,
|
|
4199
|
+
i2 as DEFAULT_GLOBAL_STYLES,
|
|
2193
4200
|
DEFAULT_GRID,
|
|
2194
4201
|
DEFAULT_NEW_BLOCK_HEIGHT,
|
|
2195
4202
|
DISCOUNT_REJECTION_REASONS,
|
|
2196
4203
|
GRID_V1_ENGINE_ID,
|
|
2197
|
-
|
|
4204
|
+
L2 as LAYOUT_SCOPE_ATTR,
|
|
2198
4205
|
LoginServiceContext,
|
|
2199
|
-
|
|
4206
|
+
OrderServiceContext,
|
|
4207
|
+
OverlayServiceContext,
|
|
2200
4208
|
P as PAGE_ROOT_SLOT,
|
|
2201
4209
|
ProductServiceContext,
|
|
2202
4210
|
RouterServiceContext,
|
|
4211
|
+
SchedulesServiceContext,
|
|
2203
4212
|
S as SelectedBlockContext,
|
|
4213
|
+
SettingsServiceContext,
|
|
4214
|
+
ShopServiceContext,
|
|
2204
4215
|
u as Signal,
|
|
2205
4216
|
StateContext,
|
|
2206
4217
|
SubscriptionServiceContext,
|
|
2207
|
-
|
|
4218
|
+
a as ThemeStateContext,
|
|
2208
4219
|
T as TranslationContext,
|
|
2209
4220
|
WorkflowServiceContext,
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
4221
|
+
v as __recordDevtoolsInstance,
|
|
4222
|
+
_ as __recordDevtoolsPropSetter,
|
|
4223
|
+
x as attachEmptyTracking,
|
|
4224
|
+
c as backfillInstanceDefaults,
|
|
4225
|
+
b2 as buildGridLayout,
|
|
4226
|
+
j as buildLayoutCss,
|
|
2214
4227
|
canvasMode,
|
|
2215
4228
|
clampRect,
|
|
2216
|
-
|
|
4229
|
+
k2 as classifyRowDividers,
|
|
2217
4230
|
clearAllSurfaceContexts,
|
|
2218
4231
|
clearLayoutModeAdapter,
|
|
2219
4232
|
compactItems,
|
|
2220
|
-
|
|
4233
|
+
l2 as compactLayoutDocument,
|
|
2221
4234
|
w as computed,
|
|
4235
|
+
createApiCustomerAdapter,
|
|
4236
|
+
createApiLoginAdapter,
|
|
4237
|
+
createApiOrderAdapter,
|
|
4238
|
+
createApiProductAdapter,
|
|
4239
|
+
createApiSchedulesAdapter,
|
|
4240
|
+
createApiSubscriptionAdapter,
|
|
2222
4241
|
createBlockInstance,
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
4242
|
+
y as createBlockInstanceFromJSON,
|
|
4243
|
+
g as createBlockInstanceFromObject,
|
|
4244
|
+
z as createCeRenderer,
|
|
2226
4245
|
createContext,
|
|
2227
4246
|
createCustomerService,
|
|
2228
|
-
|
|
2229
|
-
|
|
4247
|
+
m as createDefaultLayoutDocument,
|
|
4248
|
+
createLoginService,
|
|
4249
|
+
createMockCustomerAdapter,
|
|
4250
|
+
createMockLoginAdapter,
|
|
4251
|
+
createMockOrderAdapter,
|
|
2230
4252
|
createMockProductAdapter,
|
|
2231
4253
|
createMockRouterService,
|
|
4254
|
+
createMockSchedulesAdapter,
|
|
2232
4255
|
createMockSubscriptionAdapter,
|
|
2233
4256
|
createMockWorkflowApiAdapter,
|
|
2234
|
-
|
|
4257
|
+
createOrderService,
|
|
4258
|
+
createOverlayService,
|
|
2235
4259
|
createProductService,
|
|
2236
4260
|
createRouterService,
|
|
2237
|
-
|
|
4261
|
+
createSchedulesService,
|
|
4262
|
+
A as createSelectedBlock,
|
|
4263
|
+
createSettingsService,
|
|
4264
|
+
createShopService,
|
|
2238
4265
|
createState,
|
|
2239
4266
|
createSubscriptionService,
|
|
2240
|
-
|
|
2241
|
-
|
|
4267
|
+
n as createThemeState,
|
|
4268
|
+
o2 as createTranslationService,
|
|
2242
4269
|
createWorkflowApiAdapter,
|
|
2243
4270
|
createWorkflowService,
|
|
2244
4271
|
defineBlock,
|
|
2245
|
-
|
|
2246
|
-
|
|
4272
|
+
D as defineBlockRuntime,
|
|
4273
|
+
defineOverlayWrapper,
|
|
4274
|
+
p as deriveLayout,
|
|
2247
4275
|
E as effect,
|
|
2248
4276
|
emitLayoutCommand,
|
|
2249
4277
|
emitLayoutSetRects,
|
|
2250
|
-
|
|
4278
|
+
q2 as expandFillLayoutDocument,
|
|
2251
4279
|
findFreeRect,
|
|
2252
4280
|
getActiveSurfaceContext,
|
|
2253
4281
|
getDefaultBlockForAction,
|
|
2254
4282
|
getDefinedBlocks,
|
|
4283
|
+
F as getDevtoolsBlockState,
|
|
2255
4284
|
getLayoutModeAdapter,
|
|
2256
4285
|
gridV1Engine,
|
|
2257
4286
|
hasDefaultBlockForAction,
|
|
2258
|
-
|
|
2259
|
-
|
|
4287
|
+
G as hasRenderedContent,
|
|
4288
|
+
s as indexOverrides,
|
|
2260
4289
|
i as injectContext,
|
|
2261
4290
|
insertRectRelative,
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
4291
|
+
H as isEditorPreview,
|
|
4292
|
+
b as isStubDefinition,
|
|
4293
|
+
t2 as layoutScopeSelector,
|
|
4294
|
+
I as layoutSystemEnabled,
|
|
2266
4295
|
loadBlockRegistry,
|
|
2267
4296
|
loginService,
|
|
2268
4297
|
o as onRegisterBlock,
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
4298
|
+
u2 as projectToCss,
|
|
4299
|
+
p2 as projectToGridStack,
|
|
4300
|
+
J as provideContext,
|
|
4301
|
+
v2 as readingOrder,
|
|
4302
|
+
c2 as reconcileLayout,
|
|
4303
|
+
w3 as rectsOverlap,
|
|
2275
4304
|
registerBlock,
|
|
2276
4305
|
registerLayoutModeAdapter,
|
|
2277
4306
|
resetBreakpoint,
|
|
2278
4307
|
r as resolveLayout,
|
|
2279
|
-
|
|
4308
|
+
k as serializeBlocks,
|
|
2280
4309
|
setActiveSurfaceContext,
|
|
4310
|
+
K as setDevtoolsBlockProp,
|
|
2281
4311
|
setLayoutCommandDispatcher,
|
|
2282
4312
|
setLayoutCommandEmitter,
|
|
2283
|
-
|
|
4313
|
+
l as setLayoutSystemEnabled,
|
|
2284
4314
|
d as signal,
|
|
2285
4315
|
subscribeContext,
|
|
2286
|
-
|
|
4316
|
+
L as untracked,
|
|
2287
4317
|
waitForDevBlocksUrl,
|
|
2288
4318
|
w2 as withWrapper
|
|
2289
4319
|
};
|