@nevios/storefront-kit 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/index.cjs +1088 -122
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +207 -8
- package/dist/index.d.ts +207 -8
- package/dist/index.js +1081 -122
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React27 = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
5
|
var storefrontJs = require('@nevios/storefront-js');
|
|
6
6
|
|
|
@@ -22,15 +22,15 @@ function _interopNamespace(e) {
|
|
|
22
22
|
return Object.freeze(n);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
var
|
|
25
|
+
var React27__namespace = /*#__PURE__*/_interopNamespace(React27);
|
|
26
26
|
|
|
27
27
|
// src/provider.tsx
|
|
28
|
-
var StorefrontContext =
|
|
28
|
+
var StorefrontContext = React27__namespace.createContext(null);
|
|
29
29
|
function NeviosProvider({ client, children }) {
|
|
30
30
|
return /* @__PURE__ */ jsxRuntime.jsx(StorefrontContext.Provider, { value: client, children });
|
|
31
31
|
}
|
|
32
32
|
function useStorefront() {
|
|
33
|
-
const client =
|
|
33
|
+
const client = React27__namespace.useContext(StorefrontContext);
|
|
34
34
|
if (!client) {
|
|
35
35
|
throw new Error(
|
|
36
36
|
"useStorefront must be used inside <NeviosProvider>. Wrap your app root with it."
|
|
@@ -39,7 +39,7 @@ function useStorefront() {
|
|
|
39
39
|
return client;
|
|
40
40
|
}
|
|
41
41
|
function useStorefrontOptional() {
|
|
42
|
-
return
|
|
42
|
+
return React27__namespace.useContext(StorefrontContext);
|
|
43
43
|
}
|
|
44
44
|
var DEFAULTS = {
|
|
45
45
|
brand: "#111111",
|
|
@@ -75,7 +75,7 @@ function ThemeProvider({
|
|
|
75
75
|
as: Tag = "div"
|
|
76
76
|
}) {
|
|
77
77
|
const style = themeToCssVars(theme);
|
|
78
|
-
return
|
|
78
|
+
return React27__namespace.createElement(
|
|
79
79
|
Tag,
|
|
80
80
|
{ "data-nevios-theme": "", style },
|
|
81
81
|
children
|
|
@@ -84,6 +84,16 @@ function ThemeProvider({
|
|
|
84
84
|
|
|
85
85
|
// src/i18n/en.ts
|
|
86
86
|
var en = {
|
|
87
|
+
"subscriptions.purchaseOption": "Purchase option",
|
|
88
|
+
"subscriptions.empty": "You have no subscriptions yet.",
|
|
89
|
+
"subscriptions.status.active": "Active",
|
|
90
|
+
"subscriptions.status.paused": "Paused",
|
|
91
|
+
"subscriptions.status.pending": "Awaiting payment",
|
|
92
|
+
"subscriptions.status.cancelled": "Cancelled",
|
|
93
|
+
"subscriptions.title": "Subscription",
|
|
94
|
+
"subscriptions.consent": "I agree to the recurring price, delivery schedule and terms shown above.",
|
|
95
|
+
"subscriptions.eachDelivery": "For each delivery",
|
|
96
|
+
"subscriptions.once": "One-time purchase",
|
|
87
97
|
// common
|
|
88
98
|
"common.free": "Free",
|
|
89
99
|
"common.close": "Close",
|
|
@@ -238,6 +248,45 @@ var en = {
|
|
|
238
248
|
"error.generic.title": "Something went wrong",
|
|
239
249
|
"error.generic.body": "Please try again in a moment.",
|
|
240
250
|
"error.home": "Back to home",
|
|
251
|
+
"subscriptions.mixPrice": "Fixed box price; shipping is separate",
|
|
252
|
+
"subscriptions.locked.inactive": "This subscription is not active",
|
|
253
|
+
"subscriptions.locked.cutoff": "The edit cutoff has passed",
|
|
254
|
+
"subscriptions.locked.in_flight": "This shipment or payment is already being processed",
|
|
255
|
+
"subscriptions.quantity": "Quantity",
|
|
256
|
+
"subscriptions.back": "Back",
|
|
257
|
+
"subscriptions.loading": "Loading subscription\u2026",
|
|
258
|
+
"subscriptions.loadingProducts": "Loading products\u2026",
|
|
259
|
+
"subscriptions.chargeDate": "Scheduled charge",
|
|
260
|
+
"subscriptions.noticeHeld": "The charge is waiting for a confirmed advance notice.",
|
|
261
|
+
"subscriptions.nextDelivery": "Next shipment",
|
|
262
|
+
"subscriptions.onlyNext": "Only in this shipment",
|
|
263
|
+
"subscriptions.toCharge": "Amount to charge",
|
|
264
|
+
"subscriptions.noticeStatus": "Renewal notice",
|
|
265
|
+
"subscriptions.notice.pending": "Pending",
|
|
266
|
+
"subscriptions.notice.queued": "Queued",
|
|
267
|
+
"subscriptions.notice.sent": "Sent",
|
|
268
|
+
"subscriptions.notice.failed": "Needs attention",
|
|
269
|
+
"subscriptions.notice.superseded": "Updated notice pending",
|
|
270
|
+
"subscriptions.editUntil": "Changes available until",
|
|
271
|
+
"subscriptions.changeInterval": "Change delivery frequency",
|
|
272
|
+
"subscriptions.addNext": "Add to next shipment",
|
|
273
|
+
"subscriptions.changeMix": "Change my mix",
|
|
274
|
+
"subscriptions.skip": "Skip next shipment",
|
|
275
|
+
"subscriptions.skipWarning": "This shipment and its one-time additions will be skipped. Following shipments keep their regular contents.",
|
|
276
|
+
"subscriptions.futureOnly": "Applies to future shipments. Existing orders stay unchanged.",
|
|
277
|
+
"subscriptions.nextUnchanged": "Next date",
|
|
278
|
+
"subscriptions.reviewTotal": "Reviewed amount",
|
|
279
|
+
"subscriptions.acceptChange": "I agree to the displayed contents, schedule and price.",
|
|
280
|
+
"subscriptions.reloadBeforeRetry": "Reload before another attempt",
|
|
281
|
+
"subscriptions.confirmChange": "Confirm change",
|
|
282
|
+
"subscriptions.reviewChange": "Review change",
|
|
283
|
+
"subscriptions.unit.week": "Every {count} week(s)",
|
|
284
|
+
"subscriptions.unit.month": "Every {count} month(s)",
|
|
285
|
+
"subscriptions.unit.year": "Every {count} year(s)",
|
|
286
|
+
"subscriptions.searchProducts": "Search products",
|
|
287
|
+
"subscriptions.addProduct": "Add product",
|
|
288
|
+
"subscriptions.mixCount": "{count} selected \xB7 choose {min}\u2013{max} units",
|
|
289
|
+
"subscriptions.addMix": "Add mix to cart",
|
|
241
290
|
// account
|
|
242
291
|
"account.title": "Your account",
|
|
243
292
|
"account.signIn": "Sign in",
|
|
@@ -300,6 +349,16 @@ var en = {
|
|
|
300
349
|
|
|
301
350
|
// src/i18n/cs.ts
|
|
302
351
|
var cs = {
|
|
352
|
+
"subscriptions.purchaseOption": "Zp\u016Fsob n\xE1kupu",
|
|
353
|
+
"subscriptions.empty": "Zat\xEDm nem\xE1te \u017E\xE1dn\xE9 p\u0159edplatn\xE9.",
|
|
354
|
+
"subscriptions.status.active": "Aktivn\xED",
|
|
355
|
+
"subscriptions.status.paused": "Pozastaveno",
|
|
356
|
+
"subscriptions.status.pending": "\u010Cek\xE1 na platbu",
|
|
357
|
+
"subscriptions.status.cancelled": "Zru\u0161eno",
|
|
358
|
+
"subscriptions.title": "P\u0159edplatn\xE9",
|
|
359
|
+
"subscriptions.consent": "Souhlas\xEDm s uvedenou opakovanou cenou, pl\xE1nem doru\u010Den\xED a podm\xEDnkami.",
|
|
360
|
+
"subscriptions.eachDelivery": "Za ka\u017Ed\xE9 doru\u010Den\xED",
|
|
361
|
+
"subscriptions.once": "Jednor\xE1zov\xFD n\xE1kup",
|
|
303
362
|
"common.free": "Zdarma",
|
|
304
363
|
"common.close": "Zav\u0159\xEDt",
|
|
305
364
|
"common.loadingShort": "\u2026",
|
|
@@ -434,6 +493,45 @@ var cs = {
|
|
|
434
493
|
"error.generic.title": "N\u011Bco se pokazilo",
|
|
435
494
|
"error.generic.body": "Zkuste to pros\xEDm za chv\xEDli znovu.",
|
|
436
495
|
"error.home": "Zp\u011Bt na \xFAvod",
|
|
496
|
+
"subscriptions.mixPrice": "Pevn\xE1 cena mixu; doprava se po\u010D\xEDt\xE1 zvl\xE1\u0161\u0165",
|
|
497
|
+
"subscriptions.locked.inactive": "P\u0159edplatn\xE9 nen\xED aktivn\xED.",
|
|
498
|
+
"subscriptions.locked.cutoff": "Term\xEDn pro \xFApravy t\xE9to z\xE1silky ji\u017E uplynul.",
|
|
499
|
+
"subscriptions.locked.in_flight": "Z\xE1silka nebo jej\xED platba se ji\u017E zpracov\xE1v\xE1.",
|
|
500
|
+
"subscriptions.quantity": "Mno\u017Estv\xED",
|
|
501
|
+
"subscriptions.back": "Zp\u011Bt",
|
|
502
|
+
"subscriptions.loading": "Na\u010D\xEDt\xE1n\xED p\u0159edplatn\xE9ho\u2026",
|
|
503
|
+
"subscriptions.loadingProducts": "Na\u010D\xEDt\xE1n\xED produkt\u016F\u2026",
|
|
504
|
+
"subscriptions.chargeDate": "Pl\xE1novan\xE9 str\u017Een\xED",
|
|
505
|
+
"subscriptions.noticeHeld": "Platba \u010Dek\xE1 na potvrzen\xE9 odesl\xE1n\xED upozorn\u011Bn\xED a sjednan\xFD p\u0159edstih.",
|
|
506
|
+
"subscriptions.nextDelivery": "P\u0159\xED\u0161t\xED z\xE1silka",
|
|
507
|
+
"subscriptions.onlyNext": "Jen v t\xE9to z\xE1silce",
|
|
508
|
+
"subscriptions.toCharge": "\u010C\xE1stka ke str\u017Een\xED",
|
|
509
|
+
"subscriptions.noticeStatus": "Upozorn\u011Bn\xED p\u0159ed platbou",
|
|
510
|
+
"subscriptions.notice.pending": "\u010Cek\xE1 na odesl\xE1n\xED",
|
|
511
|
+
"subscriptions.notice.queued": "Ve front\u011B",
|
|
512
|
+
"subscriptions.notice.sent": "Odesl\xE1no",
|
|
513
|
+
"subscriptions.notice.failed": "Vy\u017Eaduje pozornost",
|
|
514
|
+
"subscriptions.notice.superseded": "\u010Cek\xE1 na aktualizovan\xE9 upozorn\u011Bn\xED",
|
|
515
|
+
"subscriptions.editUntil": "\xDApravy mo\u017En\xE9 do",
|
|
516
|
+
"subscriptions.changeInterval": "Zm\u011Bnit interval doru\u010Den\xED",
|
|
517
|
+
"subscriptions.addNext": "P\u0159idat k p\u0159\xED\u0161t\xED z\xE1silce",
|
|
518
|
+
"subscriptions.changeMix": "Zm\u011Bnit vlastn\xED mix",
|
|
519
|
+
"subscriptions.skip": "P\u0159esko\u010Dit p\u0159\xED\u0161t\xED z\xE1silku",
|
|
520
|
+
"subscriptions.skipWarning": "Tato z\xE1silka se p\u0159esko\u010D\xED v\u010Detn\u011B jednor\xE1zov\u011B p\u0159idan\xFDch produkt\u016F. Dal\u0161\xED z\xE1silky budou m\xEDt b\u011B\u017En\xFD obsah p\u0159edplatn\xE9ho.",
|
|
521
|
+
"subscriptions.futureOnly": "Plat\xED pro budouc\xED z\xE1silky. Existuj\xEDc\xED objedn\xE1vky se nezm\u011Bn\xED.",
|
|
522
|
+
"subscriptions.nextUnchanged": "Nejbli\u017E\u0161\xED term\xEDn",
|
|
523
|
+
"subscriptions.reviewTotal": "Potvrzovan\xE1 \u010D\xE1stka",
|
|
524
|
+
"subscriptions.acceptChange": "Souhlas\xEDm se zobrazen\xFDm obsahem, term\xEDny a cenou.",
|
|
525
|
+
"subscriptions.reloadBeforeRetry": "Obnovit p\u0159ed dal\u0161\xEDm pokusem",
|
|
526
|
+
"subscriptions.confirmChange": "Potvrdit zm\u011Bnu",
|
|
527
|
+
"subscriptions.reviewChange": "Zkontrolovat zm\u011Bnu",
|
|
528
|
+
"subscriptions.unit.week": "Ka\u017Ed\xFDch {count} t\xFDdn\u016F",
|
|
529
|
+
"subscriptions.unit.month": "Ka\u017Ed\xFDch {count} m\u011Bs\xEDc\u016F",
|
|
530
|
+
"subscriptions.unit.year": "Ka\u017Ed\xFDch {count} let",
|
|
531
|
+
"subscriptions.searchProducts": "Hledat produkty",
|
|
532
|
+
"subscriptions.addProduct": "P\u0159idat produkt",
|
|
533
|
+
"subscriptions.mixCount": "Vybr\xE1no {count} \xB7 zvolte {min}\u2013{max} kus\u016F",
|
|
534
|
+
"subscriptions.addMix": "P\u0159idat mix do ko\u0161\xEDku",
|
|
437
535
|
"account.title": "V\xE1\u0161 \xFA\u010Det",
|
|
438
536
|
"account.signIn": "P\u0159ihl\xE1sit se",
|
|
439
537
|
"account.signInBlurb": "Zadejte e-mail a po\u0161leme v\xE1m p\u0159ihla\u0161ovac\xED odkaz.",
|
|
@@ -541,13 +639,13 @@ function makeI18n(locale, messages) {
|
|
|
541
639
|
return { locale, messages, t };
|
|
542
640
|
}
|
|
543
641
|
var DEFAULT_I18N = makeI18n("en", en);
|
|
544
|
-
var I18nContext =
|
|
642
|
+
var I18nContext = React27__namespace.createContext(null);
|
|
545
643
|
function I18nProvider({
|
|
546
644
|
locale,
|
|
547
645
|
messages,
|
|
548
646
|
children
|
|
549
647
|
}) {
|
|
550
|
-
const value =
|
|
648
|
+
const value = React27__namespace.useMemo(() => {
|
|
551
649
|
const lang = langOf(locale);
|
|
552
650
|
if (locale && !(lang in BUNDLED) && !messages) {
|
|
553
651
|
devWarn(
|
|
@@ -561,7 +659,7 @@ function I18nProvider({
|
|
|
561
659
|
return /* @__PURE__ */ jsxRuntime.jsx(I18nContext.Provider, { value, children });
|
|
562
660
|
}
|
|
563
661
|
function useI18n() {
|
|
564
|
-
return
|
|
662
|
+
return React27__namespace.useContext(I18nContext) ?? DEFAULT_I18N;
|
|
565
663
|
}
|
|
566
664
|
function useT() {
|
|
567
665
|
return useI18n().t;
|
|
@@ -685,7 +783,7 @@ function ProductCard({ product, href, locale }) {
|
|
|
685
783
|
var MAIN_WIDTHS = [600, 800, 1e3];
|
|
686
784
|
var THUMB_WIDTHS = [56, 112];
|
|
687
785
|
function ProductGallery({ media, alt }) {
|
|
688
|
-
const [active, setActive] =
|
|
786
|
+
const [active, setActive] = React27__namespace.useState(0);
|
|
689
787
|
const imageBase = useStorefrontOptional()?.imageBase;
|
|
690
788
|
if (media.length === 0) {
|
|
691
789
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -773,10 +871,10 @@ function matchVariant(product, selected) {
|
|
|
773
871
|
) ?? null;
|
|
774
872
|
}
|
|
775
873
|
function VariantPicker({ product, onChange }) {
|
|
776
|
-
const [selected, setSelected] =
|
|
874
|
+
const [selected, setSelected] = React27__namespace.useState(
|
|
777
875
|
() => product.variants[0]?.options ?? []
|
|
778
876
|
);
|
|
779
|
-
|
|
877
|
+
React27__namespace.useEffect(() => {
|
|
780
878
|
onChange?.(matchVariant(product, selected));
|
|
781
879
|
}, [product, selected]);
|
|
782
880
|
if (product.options.length === 0) return null;
|
|
@@ -817,8 +915,8 @@ function BackInStockForm({
|
|
|
817
915
|
}) {
|
|
818
916
|
const t = useT();
|
|
819
917
|
const client = useStorefront();
|
|
820
|
-
const [email, setEmail] =
|
|
821
|
-
const [state, setState] =
|
|
918
|
+
const [email, setEmail] = React27__namespace.useState(defaultEmail);
|
|
919
|
+
const [state, setState] = React27__namespace.useState("idle");
|
|
822
920
|
if (!variant || variant.availability?.status !== "out_of_stock") return null;
|
|
823
921
|
async function submit(e) {
|
|
824
922
|
e.preventDefault();
|
|
@@ -918,7 +1016,7 @@ function ProductDetailPage({
|
|
|
918
1016
|
locale,
|
|
919
1017
|
addToCart
|
|
920
1018
|
}) {
|
|
921
|
-
const [variant, setVariant] =
|
|
1019
|
+
const [variant, setVariant] = React27__namespace.useState(
|
|
922
1020
|
product.variants[0] ?? null
|
|
923
1021
|
);
|
|
924
1022
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1150,12 +1248,28 @@ function TagFilter({ tags, selected = [], onChange }) {
|
|
|
1150
1248
|
}) })
|
|
1151
1249
|
] });
|
|
1152
1250
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1251
|
+
function PurchaseOptions({ plans, value, onChange, disabled, locale }) {
|
|
1252
|
+
const t = useT();
|
|
1253
|
+
const loc = useFormatLocale(locale);
|
|
1254
|
+
const name = React27__namespace.useId();
|
|
1255
|
+
const options = [{ id: null, name: t("subscriptions.once"), price: void 0 }, ...plans];
|
|
1256
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled, style: { border: 0, padding: 0, margin: 0, display: "grid", gap: "0.5rem" }, children: [
|
|
1257
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { style: { marginBottom: "0.5rem", fontWeight: 500 }, children: t("subscriptions.purchaseOption") }),
|
|
1258
|
+
options.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", gap: "0.6rem", alignItems: "center", padding: "0.75rem", border: `1px solid ${value === option.id ? "var(--nv-fg)" : "var(--nv-border)"}`, borderRadius: "var(--nv-radius)", cursor: "pointer" }, children: [
|
|
1259
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name, checked: value === option.id, onChange: () => onChange(option.id) }),
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: option.name }),
|
|
1261
|
+
option.price ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatCents(option.price.amount_cents, option.price.currency, loc) }) : null
|
|
1262
|
+
] }, option.id ?? "once"))
|
|
1263
|
+
] });
|
|
1264
|
+
}
|
|
1265
|
+
var CartContext = React27__namespace.createContext(null);
|
|
1266
|
+
function CartProvider({
|
|
1267
|
+
children
|
|
1268
|
+
}) {
|
|
1155
1269
|
const client = useStorefront();
|
|
1156
|
-
const [cart, setCart] =
|
|
1157
|
-
const [loading, setLoading] =
|
|
1158
|
-
const run =
|
|
1270
|
+
const [cart, setCart] = React27__namespace.useState(null);
|
|
1271
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
1272
|
+
const run = React27__namespace.useCallback(
|
|
1159
1273
|
async (op) => {
|
|
1160
1274
|
setLoading(true);
|
|
1161
1275
|
try {
|
|
@@ -1168,7 +1282,7 @@ function CartProvider({ children }) {
|
|
|
1168
1282
|
},
|
|
1169
1283
|
[]
|
|
1170
1284
|
);
|
|
1171
|
-
const refresh =
|
|
1285
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
1172
1286
|
setLoading(true);
|
|
1173
1287
|
try {
|
|
1174
1288
|
setCart(await client.cart.get());
|
|
@@ -1176,16 +1290,22 @@ function CartProvider({ children }) {
|
|
|
1176
1290
|
setLoading(false);
|
|
1177
1291
|
}
|
|
1178
1292
|
}, [client]);
|
|
1179
|
-
|
|
1293
|
+
React27__namespace.useEffect(() => {
|
|
1180
1294
|
void refresh();
|
|
1181
1295
|
}, [refresh]);
|
|
1182
|
-
const value =
|
|
1296
|
+
const value = React27__namespace.useMemo(
|
|
1183
1297
|
() => ({
|
|
1184
1298
|
cart,
|
|
1185
1299
|
loading,
|
|
1186
1300
|
itemCount: cart?.totals.item_count ?? 0,
|
|
1187
1301
|
addLine: (input) => run(() => client.cart.addLine(input)),
|
|
1302
|
+
setSubscriptionMix: (planId, lines, expectedLineIds) => run(
|
|
1303
|
+
() => client.cart.setSubscriptionMix(planId, lines, expectedLineIds, {
|
|
1304
|
+
expectedRevision: cart?.subscription?.revision
|
|
1305
|
+
})
|
|
1306
|
+
),
|
|
1188
1307
|
updateLine: (lineId, quantity) => run(() => client.cart.updateLine(lineId, quantity)),
|
|
1308
|
+
clearSubscription: () => run(() => client.cart.clearSubscription()),
|
|
1189
1309
|
removeLine: (lineId) => run(() => client.cart.removeLine(lineId)),
|
|
1190
1310
|
refresh
|
|
1191
1311
|
}),
|
|
@@ -1194,7 +1314,7 @@ function CartProvider({ children }) {
|
|
|
1194
1314
|
return /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value, children });
|
|
1195
1315
|
}
|
|
1196
1316
|
function useCart() {
|
|
1197
|
-
const ctx =
|
|
1317
|
+
const ctx = React27__namespace.useContext(CartContext);
|
|
1198
1318
|
if (!ctx) {
|
|
1199
1319
|
throw new Error("useCart must be used inside <CartProvider>.");
|
|
1200
1320
|
}
|
|
@@ -1203,13 +1323,14 @@ function useCart() {
|
|
|
1203
1323
|
function AddToCart({
|
|
1204
1324
|
variant,
|
|
1205
1325
|
quantity = 1,
|
|
1326
|
+
subscriptionPlanId,
|
|
1206
1327
|
children,
|
|
1207
1328
|
onAdded
|
|
1208
1329
|
}) {
|
|
1209
1330
|
const t = useT();
|
|
1210
1331
|
const { addLine } = useCart();
|
|
1211
|
-
const [busy, setBusy] =
|
|
1212
|
-
const [error, setError] =
|
|
1332
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
1333
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
1213
1334
|
const disabled = variant == null || busy;
|
|
1214
1335
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
1215
1336
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1222,7 +1343,7 @@ function AddToCart({
|
|
|
1222
1343
|
setBusy(true);
|
|
1223
1344
|
setError(null);
|
|
1224
1345
|
try {
|
|
1225
|
-
await addLine({ variant_id: variant.id, quantity });
|
|
1346
|
+
await addLine({ variant_id: variant.id, quantity, ...subscriptionPlanId ? { subscription_plan_id: subscriptionPlanId } : {} });
|
|
1226
1347
|
onAdded?.();
|
|
1227
1348
|
} catch (err) {
|
|
1228
1349
|
const code = err?.code;
|
|
@@ -1251,7 +1372,7 @@ function AddToCart({
|
|
|
1251
1372
|
function CartLineItem({ line, locale }) {
|
|
1252
1373
|
const t = useT();
|
|
1253
1374
|
const loc = useFormatLocale(locale);
|
|
1254
|
-
const { updateLine, removeLine } = useCart();
|
|
1375
|
+
const { cart, updateLine, removeLine } = useCart();
|
|
1255
1376
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.85rem", alignItems: "flex-start", padding: "0.75rem 0" }, children: [
|
|
1256
1377
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1257
1378
|
"div",
|
|
@@ -1280,6 +1401,7 @@ function CartLineItem({ line, locale }) {
|
|
|
1280
1401
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1281
1402
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 500, lineHeight: 1.3 }, children: line.title }),
|
|
1282
1403
|
line.variant_title ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: line.variant_title }) : null,
|
|
1404
|
+
line.subscription_plan_id ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: cart?.subscription?.plan?.name ?? t("subscriptions.title") }) : null,
|
|
1283
1405
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", marginTop: "0.5rem" }, children: [
|
|
1284
1406
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1285
1407
|
"div",
|
|
@@ -1358,7 +1480,7 @@ function CartSummary({
|
|
|
1358
1480
|
const { cart } = useCart();
|
|
1359
1481
|
if (cart == null) return null;
|
|
1360
1482
|
const t = cart.totals;
|
|
1361
|
-
const empty = t.item_count === 0;
|
|
1483
|
+
const empty = t.item_count === 0 || cart.subscription?.available === false;
|
|
1362
1484
|
const cta = checkoutLabel ?? tr("cart.checkout");
|
|
1363
1485
|
const ctaStyle = {
|
|
1364
1486
|
display: "block",
|
|
@@ -1379,6 +1501,7 @@ function CartSummary({
|
|
|
1379
1501
|
t.tax_cents > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, loc), muted: true }) : null,
|
|
1380
1502
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "var(--nv-border)" } }),
|
|
1381
1503
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, loc), strong: true }),
|
|
1504
|
+
cart.subscription?.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { margin: 0 }, children: cart.subscription.error.message }) : null,
|
|
1382
1505
|
checkoutHref && !empty ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: checkoutHref, style: ctaStyle, children: cta }) : /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: empty, onClick: onCheckout, style: ctaStyle, children: cta })
|
|
1383
1506
|
] });
|
|
1384
1507
|
}
|
|
@@ -1485,15 +1608,15 @@ function CartDrawer({
|
|
|
1485
1608
|
}
|
|
1486
1609
|
);
|
|
1487
1610
|
}
|
|
1488
|
-
var CheckoutContext =
|
|
1611
|
+
var CheckoutContext = React27__namespace.createContext(null);
|
|
1489
1612
|
function CheckoutProvider({
|
|
1490
1613
|
children
|
|
1491
1614
|
}) {
|
|
1492
1615
|
const client = useStorefront();
|
|
1493
|
-
const [checkout, setCheckout] =
|
|
1494
|
-
const [loading, setLoading] =
|
|
1495
|
-
const [error, setError] =
|
|
1496
|
-
const run =
|
|
1616
|
+
const [checkout, setCheckout] = React27__namespace.useState(null);
|
|
1617
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
1618
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
1619
|
+
const run = React27__namespace.useCallback(
|
|
1497
1620
|
async (op, store) => {
|
|
1498
1621
|
setLoading(true);
|
|
1499
1622
|
try {
|
|
@@ -1511,23 +1634,36 @@ function CheckoutProvider({
|
|
|
1511
1634
|
},
|
|
1512
1635
|
[]
|
|
1513
1636
|
);
|
|
1514
|
-
const refresh =
|
|
1637
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
1515
1638
|
setLoading(true);
|
|
1516
1639
|
try {
|
|
1517
1640
|
setCheckout(await client.checkout.get());
|
|
1518
1641
|
setError(null);
|
|
1642
|
+
} catch (err) {
|
|
1643
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1519
1644
|
} finally {
|
|
1520
1645
|
setLoading(false);
|
|
1521
1646
|
}
|
|
1522
1647
|
}, [client]);
|
|
1523
|
-
|
|
1648
|
+
React27__namespace.useEffect(() => {
|
|
1524
1649
|
void refresh();
|
|
1525
1650
|
}, [refresh]);
|
|
1526
|
-
const pay =
|
|
1651
|
+
const pay = React27__namespace.useCallback(
|
|
1527
1652
|
async (opts = {}) => {
|
|
1528
1653
|
const co = checkout;
|
|
1529
1654
|
if (!co) throw new Error("pay() called before a checkout exists");
|
|
1530
1655
|
const idempotencyKey = opts.idempotencyKey ?? co.token;
|
|
1656
|
+
if (co.subscription) {
|
|
1657
|
+
if (co.subscription.subscription_id) {
|
|
1658
|
+
const payment = await run(() => client.checkout.subscriptionPayment(), () => null);
|
|
1659
|
+
setCheckout(await client.checkout.get());
|
|
1660
|
+
return { kind: "subscription_payment", payment };
|
|
1661
|
+
}
|
|
1662
|
+
if (!opts.subscription) throw new Error("Accept the current subscription terms before paying");
|
|
1663
|
+
const subscription = await run(() => client.checkout.enrollSubscription(opts.subscription), () => null);
|
|
1664
|
+
setCheckout(await client.checkout.get());
|
|
1665
|
+
return { kind: "subscription", subscription };
|
|
1666
|
+
}
|
|
1531
1667
|
if (co.payment_category === "gateway") {
|
|
1532
1668
|
const start = await run(
|
|
1533
1669
|
() => client.checkout.startPayment({ return_url: opts.returnUrl, cancel_url: opts.cancelUrl }),
|
|
@@ -1546,11 +1682,18 @@ function CheckoutProvider({
|
|
|
1546
1682
|
},
|
|
1547
1683
|
[checkout, client, run]
|
|
1548
1684
|
);
|
|
1549
|
-
const value =
|
|
1685
|
+
const value = React27__namespace.useMemo(
|
|
1550
1686
|
() => ({
|
|
1551
1687
|
checkout,
|
|
1552
1688
|
loading,
|
|
1553
1689
|
error,
|
|
1690
|
+
selectSubscription: (selection) => run(async () => {
|
|
1691
|
+
const quote = await client.checkout.selectSubscription(selection);
|
|
1692
|
+
setCheckout(await client.checkout.get());
|
|
1693
|
+
return quote;
|
|
1694
|
+
}, () => null),
|
|
1695
|
+
clearSubscription: () => run(() => client.checkout.clearSubscription(), (r) => r),
|
|
1696
|
+
reviewSubscription: () => run(() => client.checkout.reviewSubscription(), () => null),
|
|
1554
1697
|
create: (cartToken, input) => run(() => client.checkout.create(cartToken, input), (r) => r),
|
|
1555
1698
|
setAddress: (input) => run(() => client.checkout.setAddress(input), (r) => r),
|
|
1556
1699
|
shippingQuotes: () => run(() => client.checkout.shippingQuotes(), () => null),
|
|
@@ -1571,7 +1714,7 @@ function CheckoutProvider({
|
|
|
1571
1714
|
return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value, children });
|
|
1572
1715
|
}
|
|
1573
1716
|
function useCheckout() {
|
|
1574
|
-
const ctx =
|
|
1717
|
+
const ctx = React27__namespace.useContext(CheckoutContext);
|
|
1575
1718
|
if (!ctx) {
|
|
1576
1719
|
throw new Error("useCheckout must be used inside <CheckoutProvider>.");
|
|
1577
1720
|
}
|
|
@@ -1730,11 +1873,11 @@ function AddressForm({
|
|
|
1730
1873
|
}) {
|
|
1731
1874
|
const t = useT();
|
|
1732
1875
|
const { checkout, setAddress, loading } = useCheckout();
|
|
1733
|
-
const [form, setForm] =
|
|
1876
|
+
const [form, setForm] = React27__namespace.useState(
|
|
1734
1877
|
() => seed(checkout?.email ?? null, checkout?.shipping_address ?? null)
|
|
1735
1878
|
);
|
|
1736
|
-
const touched =
|
|
1737
|
-
|
|
1879
|
+
const touched = React27__namespace.useRef(/* @__PURE__ */ new Set());
|
|
1880
|
+
React27__namespace.useEffect(() => {
|
|
1738
1881
|
const prefill = checkout?.customer_prefill;
|
|
1739
1882
|
const addresses = prefill?.addresses.filter((a) => !a.is_pickup);
|
|
1740
1883
|
const saved = addresses?.find((a) => a.is_default) ?? addresses?.[0];
|
|
@@ -1799,11 +1942,11 @@ function ShippingPicker({
|
|
|
1799
1942
|
const loc = useFormatLocale(locale);
|
|
1800
1943
|
const { checkout, shippingQuotes, setShippingMethod, loading } = useCheckout();
|
|
1801
1944
|
const heading = title ?? t("checkout.shippingMethod");
|
|
1802
|
-
const [quotes, setQuotes] =
|
|
1803
|
-
const [selected, setSelected] =
|
|
1804
|
-
const [fetching, setFetching] =
|
|
1945
|
+
const [quotes, setQuotes] = React27__namespace.useState(null);
|
|
1946
|
+
const [selected, setSelected] = React27__namespace.useState(null);
|
|
1947
|
+
const [fetching, setFetching] = React27__namespace.useState(true);
|
|
1805
1948
|
const addressKey = checkout?.shipping_address ? `${checkout.shipping_address.country_code}:${checkout.shipping_address.postal_code}` : null;
|
|
1806
|
-
|
|
1949
|
+
React27__namespace.useEffect(() => {
|
|
1807
1950
|
let live = true;
|
|
1808
1951
|
setFetching(true);
|
|
1809
1952
|
shippingQuotes().then((qs) => {
|
|
@@ -1878,20 +2021,45 @@ function PaymentMethodPicker({
|
|
|
1878
2021
|
}
|
|
1879
2022
|
);
|
|
1880
2023
|
}
|
|
2024
|
+
function SubscriptionTerms({ quote, checked, onChange, locale }) {
|
|
2025
|
+
const t = useT();
|
|
2026
|
+
const loc = useFormatLocale(locale);
|
|
2027
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { style: { margin: 0, padding: "1rem", border: "1px solid var(--nv-border)", borderRadius: "var(--nv-radius)" }, children: [
|
|
2028
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: t("subscriptions.title") }),
|
|
2029
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0 0 0.5rem", fontWeight: 500 }, children: [
|
|
2030
|
+
t("subscriptions.eachDelivery"),
|
|
2031
|
+
": ",
|
|
2032
|
+
formatCents(quote.recurring_total_cents, quote.currency, loc)
|
|
2033
|
+
] }),
|
|
2034
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 0.75rem", whiteSpace: "pre-wrap" }, children: quote.terms_text }),
|
|
2035
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "flex-start", gap: "0.6rem", cursor: "pointer" }, children: [
|
|
2036
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked, onChange: (event) => onChange(event.target.checked) }),
|
|
2037
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t("subscriptions.consent") })
|
|
2038
|
+
] })
|
|
2039
|
+
] });
|
|
2040
|
+
}
|
|
1881
2041
|
function PaymentSection({
|
|
1882
2042
|
title,
|
|
1883
2043
|
returnUrl,
|
|
1884
2044
|
cancelUrl,
|
|
1885
2045
|
onOrderPlaced,
|
|
1886
|
-
locale
|
|
2046
|
+
locale,
|
|
2047
|
+
onSubscriptionPayment
|
|
1887
2048
|
}) {
|
|
1888
2049
|
const t = useT();
|
|
1889
2050
|
const heading = title ?? t("checkout.payment");
|
|
1890
|
-
const { checkout, paymentMethods, setPaymentMethod, pay, loading } = useCheckout();
|
|
1891
|
-
const [methods, setMethods] =
|
|
1892
|
-
const [fetching, setFetching] =
|
|
2051
|
+
const { checkout, paymentMethods, setPaymentMethod, pay, loading, refresh } = useCheckout();
|
|
2052
|
+
const [methods, setMethods] = React27__namespace.useState(null);
|
|
2053
|
+
const [fetching, setFetching] = React27__namespace.useState(true);
|
|
1893
2054
|
const selected = checkout?.payment_method ?? null;
|
|
1894
|
-
|
|
2055
|
+
const [acceptedDigest, setAcceptedDigest] = React27__namespace.useState(null);
|
|
2056
|
+
const [actionBusy, setActionBusy] = React27__namespace.useState(false);
|
|
2057
|
+
const quote = checkout?.subscription?.quote;
|
|
2058
|
+
const needsConsent = Boolean(checkout?.subscription?.requires_consent);
|
|
2059
|
+
const clickInFlight = React27__namespace.useRef(false);
|
|
2060
|
+
const [actionError, setActionError] = React27__namespace.useState(null);
|
|
2061
|
+
const accepted = Boolean(quote && acceptedDigest === quote.quote_digest);
|
|
2062
|
+
React27__namespace.useEffect(() => {
|
|
1895
2063
|
let live = true;
|
|
1896
2064
|
setFetching(true);
|
|
1897
2065
|
paymentMethods().then((ms) => {
|
|
@@ -1904,12 +2072,30 @@ function PaymentSection({
|
|
|
1904
2072
|
return () => {
|
|
1905
2073
|
live = false;
|
|
1906
2074
|
};
|
|
1907
|
-
}, []);
|
|
2075
|
+
}, [checkout?.token, checkout?.shipping_method_id, checkout?.subscription?.selection.plan_id]);
|
|
1908
2076
|
const selectedMethod = methods?.find((m) => m.method === selected) ?? null;
|
|
1909
2077
|
const isGateway = selectedMethod?.category === "gateway";
|
|
1910
2078
|
async function placeOrder() {
|
|
1911
|
-
|
|
1912
|
-
|
|
2079
|
+
if (clickInFlight.current) return;
|
|
2080
|
+
clickInFlight.current = true;
|
|
2081
|
+
setActionBusy(true);
|
|
2082
|
+
setActionError(null);
|
|
2083
|
+
try {
|
|
2084
|
+
const result = await pay({
|
|
2085
|
+
returnUrl,
|
|
2086
|
+
cancelUrl,
|
|
2087
|
+
...needsConsent && quote && accepted ? { subscription: { quote, consent: true } } : {}
|
|
2088
|
+
});
|
|
2089
|
+
if (result.kind === "subscription") await onSubscriptionPayment?.(result.subscription.payment);
|
|
2090
|
+
if (result.kind === "subscription_payment") await onSubscriptionPayment?.(result.payment);
|
|
2091
|
+
if (result.kind === "subscription" || result.kind === "subscription_payment") await refresh();
|
|
2092
|
+
if (result.kind === "order") onOrderPlaced?.(result.checkout);
|
|
2093
|
+
} catch (err) {
|
|
2094
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
2095
|
+
} finally {
|
|
2096
|
+
clickInFlight.current = false;
|
|
2097
|
+
setActionBusy(false);
|
|
2098
|
+
}
|
|
1913
2099
|
}
|
|
1914
2100
|
if (fetching && methods == null) {
|
|
1915
2101
|
return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: heading, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("checkout.loadingPayment") }) });
|
|
@@ -1929,9 +2115,12 @@ function PaymentSection({
|
|
|
1929
2115
|
label: heading
|
|
1930
2116
|
}
|
|
1931
2117
|
),
|
|
2118
|
+
quote && needsConsent ? /* @__PURE__ */ jsxRuntime.jsx(SubscriptionTerms, { locale, quote, checked: accepted, onChange: (value) => setAcceptedDigest(value && quote ? quote.quote_digest : null) }) : null,
|
|
2119
|
+
checkout?.subscription?.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: checkout.subscription.error.message }) : null,
|
|
2120
|
+
actionError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: actionError }) : null,
|
|
1932
2121
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1933
|
-
/* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: placeOrder, busy: loading, disabled: selected == null, children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
|
|
1934
|
-
isGateway ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
2122
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: placeOrder, busy: loading || actionBusy, disabled: selected == null || Boolean(checkout?.subscription) && (needsConsent && (!accepted || !quote) || !onSubscriptionPayment || !checkout?.subscription?.available), children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
|
|
2123
|
+
isGateway && !checkout?.subscription ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
1935
2124
|
] })
|
|
1936
2125
|
] });
|
|
1937
2126
|
}
|
|
@@ -2019,6 +2208,7 @@ function reachedStep(checkout) {
|
|
|
2019
2208
|
}
|
|
2020
2209
|
function CheckoutFlow({
|
|
2021
2210
|
cartToken,
|
|
2211
|
+
onSubscriptionPayment,
|
|
2022
2212
|
onOrderPlaced,
|
|
2023
2213
|
emptyState,
|
|
2024
2214
|
renderConfirmation,
|
|
@@ -2028,19 +2218,19 @@ function CheckoutFlow({
|
|
|
2028
2218
|
}) {
|
|
2029
2219
|
const t = useT();
|
|
2030
2220
|
const { checkout, loading, create } = useCheckout();
|
|
2031
|
-
const [step, setStep] =
|
|
2032
|
-
const [bootstrapped, setBootstrapped] =
|
|
2033
|
-
|
|
2221
|
+
const [step, setStep] = React27__namespace.useState("address");
|
|
2222
|
+
const [bootstrapped, setBootstrapped] = React27__namespace.useState(false);
|
|
2223
|
+
React27__namespace.useEffect(() => {
|
|
2034
2224
|
if (checkout || !cartToken || bootstrapped) return;
|
|
2035
2225
|
setBootstrapped(true);
|
|
2036
2226
|
void create(cartToken).catch(() => {
|
|
2037
2227
|
});
|
|
2038
2228
|
}, [checkout, cartToken, bootstrapped, create]);
|
|
2039
2229
|
const serverStep = reachedStep(checkout);
|
|
2040
|
-
|
|
2230
|
+
React27__namespace.useEffect(() => {
|
|
2041
2231
|
setStep((cur) => rank(serverStep) > rank(cur) ? serverStep : cur);
|
|
2042
2232
|
}, [serverStep]);
|
|
2043
|
-
if (checkout?.order_id) {
|
|
2233
|
+
if (checkout?.order_id && (!checkout.subscription || checkout.phase === "success")) {
|
|
2044
2234
|
return /* @__PURE__ */ jsxRuntime.jsx(Shell, { summary: /* @__PURE__ */ jsxRuntime.jsx(OrderSummary, { locale }), children: renderConfirmation ? renderConfirmation(checkout) : /* @__PURE__ */ jsxRuntime.jsx(DefaultConfirmation, { checkout }) });
|
|
2045
2235
|
}
|
|
2046
2236
|
if (!checkout) {
|
|
@@ -2054,6 +2244,7 @@ function CheckoutFlow({
|
|
|
2054
2244
|
step === "address" ? /* @__PURE__ */ jsxRuntime.jsx(AddressForm, { onComplete: () => setStep("shipping") }) : step === "shipping" ? /* @__PURE__ */ jsxRuntime.jsx(ShippingPicker, { onComplete: () => setStep("payment"), locale }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2055
2245
|
PaymentSection,
|
|
2056
2246
|
{
|
|
2247
|
+
onSubscriptionPayment,
|
|
2057
2248
|
returnUrl,
|
|
2058
2249
|
cancelUrl,
|
|
2059
2250
|
onOrderPlaced,
|
|
@@ -2158,10 +2349,10 @@ function DefaultConfirmation({ checkout }) {
|
|
|
2158
2349
|
}
|
|
2159
2350
|
function useOrderStatus(statusToken, initial) {
|
|
2160
2351
|
const client = useStorefront();
|
|
2161
|
-
const [order, setOrder] =
|
|
2162
|
-
const [loading, setLoading] =
|
|
2163
|
-
const [error, setError] =
|
|
2164
|
-
const load =
|
|
2352
|
+
const [order, setOrder] = React27__namespace.useState(initial ?? null);
|
|
2353
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
2354
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
2355
|
+
const load = React27__namespace.useCallback(async () => {
|
|
2165
2356
|
if (!statusToken) return;
|
|
2166
2357
|
setLoading(true);
|
|
2167
2358
|
try {
|
|
@@ -2173,7 +2364,7 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2173
2364
|
setLoading(false);
|
|
2174
2365
|
}
|
|
2175
2366
|
}, [client, statusToken]);
|
|
2176
|
-
|
|
2367
|
+
React27__namespace.useEffect(() => {
|
|
2177
2368
|
if (initial) return;
|
|
2178
2369
|
void load();
|
|
2179
2370
|
}, [load, initial]);
|
|
@@ -2181,12 +2372,12 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2181
2372
|
}
|
|
2182
2373
|
function usePayment(token, initial) {
|
|
2183
2374
|
const client = useStorefront();
|
|
2184
|
-
const [payment, setPayment] =
|
|
2185
|
-
const [methods, setMethods] =
|
|
2186
|
-
const [loading, setLoading] =
|
|
2187
|
-
const [busy, setBusy] =
|
|
2188
|
-
const [error, setError] =
|
|
2189
|
-
const load =
|
|
2375
|
+
const [payment, setPayment] = React27__namespace.useState(initial ?? null);
|
|
2376
|
+
const [methods, setMethods] = React27__namespace.useState([]);
|
|
2377
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
2378
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
2379
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
2380
|
+
const load = React27__namespace.useCallback(async () => {
|
|
2190
2381
|
if (!token) return;
|
|
2191
2382
|
setLoading(true);
|
|
2192
2383
|
try {
|
|
@@ -2203,7 +2394,7 @@ function usePayment(token, initial) {
|
|
|
2203
2394
|
setLoading(false);
|
|
2204
2395
|
}
|
|
2205
2396
|
}, [client, token]);
|
|
2206
|
-
|
|
2397
|
+
React27__namespace.useEffect(() => {
|
|
2207
2398
|
if (!token) return;
|
|
2208
2399
|
if (initial && initial.token === token) {
|
|
2209
2400
|
setPayment(initial);
|
|
@@ -2212,7 +2403,7 @@ function usePayment(token, initial) {
|
|
|
2212
2403
|
}
|
|
2213
2404
|
void load();
|
|
2214
2405
|
}, [token, load]);
|
|
2215
|
-
const selectMethod =
|
|
2406
|
+
const selectMethod = React27__namespace.useCallback(
|
|
2216
2407
|
async (method) => {
|
|
2217
2408
|
if (!token) return null;
|
|
2218
2409
|
setBusy(true);
|
|
@@ -2230,7 +2421,7 @@ function usePayment(token, initial) {
|
|
|
2230
2421
|
},
|
|
2231
2422
|
[client, token]
|
|
2232
2423
|
);
|
|
2233
|
-
const startPayment =
|
|
2424
|
+
const startPayment = React27__namespace.useCallback(
|
|
2234
2425
|
async (input) => {
|
|
2235
2426
|
if (!token) return null;
|
|
2236
2427
|
setBusy(true);
|
|
@@ -2667,8 +2858,8 @@ function Row4({
|
|
|
2667
2858
|
}
|
|
2668
2859
|
function CopyRow({ label, value }) {
|
|
2669
2860
|
const t = useT();
|
|
2670
|
-
const [copied, setCopied] =
|
|
2671
|
-
|
|
2861
|
+
const [copied, setCopied] = React27__namespace.useState(false);
|
|
2862
|
+
React27__namespace.useEffect(() => {
|
|
2672
2863
|
if (!copied) return;
|
|
2673
2864
|
const id = setTimeout(() => setCopied(false), 1800);
|
|
2674
2865
|
return () => clearTimeout(id);
|
|
@@ -2752,12 +2943,12 @@ function PaymentPage({
|
|
|
2752
2943
|
const t = useT();
|
|
2753
2944
|
const loc = useFormatLocale(locale);
|
|
2754
2945
|
const { payment, methods, loading, busy, error, reload, selectMethod, startPayment } = usePayment(token, initial);
|
|
2755
|
-
const [verifying, setVerifying] =
|
|
2756
|
-
|
|
2946
|
+
const [verifying, setVerifying] = React27__namespace.useState(false);
|
|
2947
|
+
React27__namespace.useEffect(() => {
|
|
2757
2948
|
if (token) setVerifying(consumeHandoff(token));
|
|
2758
2949
|
}, [token]);
|
|
2759
2950
|
const settled = payment != null && !payment.can_pay && payment.blocked_reason === "already_paid";
|
|
2760
|
-
|
|
2951
|
+
React27__namespace.useEffect(() => {
|
|
2761
2952
|
if (!verifying || !token || settled) return;
|
|
2762
2953
|
let attempts = 0;
|
|
2763
2954
|
const id = setInterval(() => {
|
|
@@ -2772,7 +2963,7 @@ function PaymentPage({
|
|
|
2772
2963
|
}, POLL_MS);
|
|
2773
2964
|
return () => clearInterval(id);
|
|
2774
2965
|
}, [verifying, token, settled, reload]);
|
|
2775
|
-
|
|
2966
|
+
React27__namespace.useEffect(() => {
|
|
2776
2967
|
if (!settled || !token || !payment) return;
|
|
2777
2968
|
setVerifying(false);
|
|
2778
2969
|
forgetHandoff(token);
|
|
@@ -2853,13 +3044,13 @@ function Amount({ label, value }) {
|
|
|
2853
3044
|
function Link({ href, children }) {
|
|
2854
3045
|
return /* @__PURE__ */ jsxRuntime.jsx("a", { href, style: { color: "var(--nv-brand)", fontSize: "0.875rem" }, children });
|
|
2855
3046
|
}
|
|
2856
|
-
var AccountContext =
|
|
3047
|
+
var AccountContext = React27__namespace.createContext(null);
|
|
2857
3048
|
function AccountProvider({ children }) {
|
|
2858
3049
|
const client = useStorefront();
|
|
2859
|
-
const [customer, setCustomer] =
|
|
2860
|
-
const [loading, setLoading] =
|
|
2861
|
-
const [error, setError] =
|
|
2862
|
-
const refresh =
|
|
3050
|
+
const [customer, setCustomer] = React27__namespace.useState(null);
|
|
3051
|
+
const [loading, setLoading] = React27__namespace.useState(true);
|
|
3052
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3053
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
2863
3054
|
setLoading(true);
|
|
2864
3055
|
setError(false);
|
|
2865
3056
|
try {
|
|
@@ -2870,10 +3061,10 @@ function AccountProvider({ children }) {
|
|
|
2870
3061
|
setLoading(false);
|
|
2871
3062
|
}
|
|
2872
3063
|
}, [client]);
|
|
2873
|
-
|
|
3064
|
+
React27__namespace.useEffect(() => {
|
|
2874
3065
|
void refresh();
|
|
2875
3066
|
}, [refresh]);
|
|
2876
|
-
const value =
|
|
3067
|
+
const value = React27__namespace.useMemo(
|
|
2877
3068
|
() => ({
|
|
2878
3069
|
customer,
|
|
2879
3070
|
signedIn: customer != null,
|
|
@@ -2896,7 +3087,7 @@ function AccountProvider({ children }) {
|
|
|
2896
3087
|
return /* @__PURE__ */ jsxRuntime.jsx(AccountContext.Provider, { value, children });
|
|
2897
3088
|
}
|
|
2898
3089
|
function useAccount() {
|
|
2899
|
-
const ctx =
|
|
3090
|
+
const ctx = React27__namespace.useContext(AccountContext);
|
|
2900
3091
|
if (!ctx) {
|
|
2901
3092
|
throw new Error("useAccount must be used inside <AccountProvider>.");
|
|
2902
3093
|
}
|
|
@@ -2904,9 +3095,9 @@ function useAccount() {
|
|
|
2904
3095
|
}
|
|
2905
3096
|
function useMagicLinkVerify(token) {
|
|
2906
3097
|
const { verify } = useAccount();
|
|
2907
|
-
const [status, setStatus] =
|
|
2908
|
-
const done =
|
|
2909
|
-
|
|
3098
|
+
const [status, setStatus] = React27__namespace.useState("idle");
|
|
3099
|
+
const done = React27__namespace.useRef(false);
|
|
3100
|
+
React27__namespace.useEffect(() => {
|
|
2910
3101
|
if (!token || done.current) return;
|
|
2911
3102
|
done.current = true;
|
|
2912
3103
|
setStatus("verifying");
|
|
@@ -2917,11 +3108,11 @@ function useMagicLinkVerify(token) {
|
|
|
2917
3108
|
function LoginForm({ verifyPath = "/account/verify", onSent }) {
|
|
2918
3109
|
const t = useT();
|
|
2919
3110
|
const { requestMagicLink } = useAccount();
|
|
2920
|
-
const [email, setEmail] =
|
|
2921
|
-
const [busy, setBusy] =
|
|
2922
|
-
const [sent, setSent] =
|
|
2923
|
-
const [error, setError] =
|
|
2924
|
-
const [devLink, setDevLink] =
|
|
3111
|
+
const [email, setEmail] = React27__namespace.useState("");
|
|
3112
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3113
|
+
const [sent, setSent] = React27__namespace.useState(false);
|
|
3114
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3115
|
+
const [devLink, setDevLink] = React27__namespace.useState(null);
|
|
2925
3116
|
async function submit(e) {
|
|
2926
3117
|
e.preventDefault();
|
|
2927
3118
|
setBusy(true);
|
|
@@ -3033,14 +3224,14 @@ function OrderHistory({
|
|
|
3033
3224
|
const loc = useFormatLocale(locale);
|
|
3034
3225
|
const { signedIn } = useAccount();
|
|
3035
3226
|
const client = useStorefront();
|
|
3036
|
-
const [orders, setOrders] =
|
|
3037
|
-
const [error, setError] =
|
|
3038
|
-
const [nextCursor, setNextCursor] =
|
|
3039
|
-
const [pageLoading, setPageLoading] =
|
|
3040
|
-
const [reload, setReload] =
|
|
3041
|
-
const [reordering, setReordering] =
|
|
3042
|
-
const [notice, setNotice] =
|
|
3043
|
-
|
|
3227
|
+
const [orders, setOrders] = React27__namespace.useState(null);
|
|
3228
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3229
|
+
const [nextCursor, setNextCursor] = React27__namespace.useState(null);
|
|
3230
|
+
const [pageLoading, setPageLoading] = React27__namespace.useState(false);
|
|
3231
|
+
const [reload, setReload] = React27__namespace.useState(0);
|
|
3232
|
+
const [reordering, setReordering] = React27__namespace.useState(null);
|
|
3233
|
+
const [notice, setNotice] = React27__namespace.useState(null);
|
|
3234
|
+
React27__namespace.useEffect(() => {
|
|
3044
3235
|
let live = true;
|
|
3045
3236
|
if (!signedIn) {
|
|
3046
3237
|
setOrders([]);
|
|
@@ -3151,7 +3342,7 @@ function OrderHistory({
|
|
|
3151
3342
|
}
|
|
3152
3343
|
function AddressForm2({ initial, onSubmit, onCancel, busy }) {
|
|
3153
3344
|
const t = useT();
|
|
3154
|
-
const [form, setForm] =
|
|
3345
|
+
const [form, setForm] = React27__namespace.useState(() => ({
|
|
3155
3346
|
first_name: initial?.first_name ?? "",
|
|
3156
3347
|
last_name: initial?.last_name ?? "",
|
|
3157
3348
|
company: initial?.company ?? "",
|
|
@@ -3249,17 +3440,17 @@ function AddressBook() {
|
|
|
3249
3440
|
const t = useT();
|
|
3250
3441
|
const { signedIn } = useAccount();
|
|
3251
3442
|
const client = useStorefront();
|
|
3252
|
-
const [addresses, setAddresses] =
|
|
3253
|
-
const [mode, setMode] =
|
|
3254
|
-
const [busy, setBusy] =
|
|
3255
|
-
const load =
|
|
3443
|
+
const [addresses, setAddresses] = React27__namespace.useState(null);
|
|
3444
|
+
const [mode, setMode] = React27__namespace.useState({ kind: "list" });
|
|
3445
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3446
|
+
const load = React27__namespace.useCallback(() => {
|
|
3256
3447
|
if (!signedIn) {
|
|
3257
3448
|
setAddresses([]);
|
|
3258
3449
|
return;
|
|
3259
3450
|
}
|
|
3260
3451
|
client.account.addresses().then(setAddresses).catch(() => setAddresses([]));
|
|
3261
3452
|
}, [signedIn, client]);
|
|
3262
|
-
|
|
3453
|
+
React27__namespace.useEffect(() => {
|
|
3263
3454
|
load();
|
|
3264
3455
|
}, [load]);
|
|
3265
3456
|
async function run(fn) {
|
|
@@ -3313,10 +3504,10 @@ function ProfileEditor() {
|
|
|
3313
3504
|
const t = useT();
|
|
3314
3505
|
const client = useStorefront();
|
|
3315
3506
|
const { customer, refresh } = useAccount();
|
|
3316
|
-
const [editing, setEditing] =
|
|
3317
|
-
const [busy, setBusy] =
|
|
3318
|
-
const [error, setError] =
|
|
3319
|
-
const [form, setForm] =
|
|
3507
|
+
const [editing, setEditing] = React27__namespace.useState(false);
|
|
3508
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3509
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3510
|
+
const [form, setForm] = React27__namespace.useState({});
|
|
3320
3511
|
if (!customer) return null;
|
|
3321
3512
|
function startEdit() {
|
|
3322
3513
|
setForm({
|
|
@@ -3397,13 +3588,13 @@ function AccountSecurity() {
|
|
|
3397
3588
|
const client = useStorefront();
|
|
3398
3589
|
const account = useAccount();
|
|
3399
3590
|
const t = useT();
|
|
3400
|
-
const [sessions, setSessions] =
|
|
3401
|
-
const [email, setEmail] =
|
|
3402
|
-
const [busy, setBusy] =
|
|
3403
|
-
const [notice, setNotice] =
|
|
3404
|
-
const [confirmClose, setConfirmClose] =
|
|
3405
|
-
const [retry, setRetry] =
|
|
3406
|
-
|
|
3591
|
+
const [sessions, setSessions] = React27__namespace.useState(null);
|
|
3592
|
+
const [email, setEmail] = React27__namespace.useState("");
|
|
3593
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3594
|
+
const [notice, setNotice] = React27__namespace.useState(null);
|
|
3595
|
+
const [confirmClose, setConfirmClose] = React27__namespace.useState(false);
|
|
3596
|
+
const [retry, setRetry] = React27__namespace.useState(0);
|
|
3597
|
+
React27__namespace.useEffect(() => {
|
|
3407
3598
|
let live = true;
|
|
3408
3599
|
if (account.signedIn) client.account.sessions().then((s) => {
|
|
3409
3600
|
if (live) setSessions(s);
|
|
@@ -3466,6 +3657,662 @@ function AccountSecurity() {
|
|
|
3466
3657
|
] }) : /* @__PURE__ */ jsxRuntime.jsx(Button, { disabled: busy, onClick: () => setConfirmClose(true), children: t("account.closeAccount") })
|
|
3467
3658
|
] });
|
|
3468
3659
|
}
|
|
3660
|
+
function useCustomerSubscriptions() {
|
|
3661
|
+
const client = useStorefront();
|
|
3662
|
+
const { customer } = useAccount();
|
|
3663
|
+
const [dataOwner, setDataOwner] = React27__namespace.useState(
|
|
3664
|
+
void 0
|
|
3665
|
+
);
|
|
3666
|
+
const [subscriptions, setSubscriptions] = React27__namespace.useState([]);
|
|
3667
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
3668
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
3669
|
+
const generation = React27__namespace.useRef(0);
|
|
3670
|
+
const owner = customer?.id;
|
|
3671
|
+
const api = client.account.subscriptions;
|
|
3672
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
3673
|
+
const current = ++generation.current;
|
|
3674
|
+
setError(null);
|
|
3675
|
+
if (!owner) {
|
|
3676
|
+
setSubscriptions([]);
|
|
3677
|
+
setLoading(false);
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3680
|
+
setLoading(true);
|
|
3681
|
+
try {
|
|
3682
|
+
const rows = await api.list();
|
|
3683
|
+
if (current === generation.current) {
|
|
3684
|
+
setSubscriptions(rows);
|
|
3685
|
+
setDataOwner(owner);
|
|
3686
|
+
setError(null);
|
|
3687
|
+
}
|
|
3688
|
+
} catch (err) {
|
|
3689
|
+
if (current === generation.current)
|
|
3690
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3691
|
+
} finally {
|
|
3692
|
+
if (current === generation.current) setLoading(false);
|
|
3693
|
+
}
|
|
3694
|
+
}, [owner, api]);
|
|
3695
|
+
React27__namespace.useEffect(() => {
|
|
3696
|
+
setSubscriptions([]);
|
|
3697
|
+
void refresh();
|
|
3698
|
+
return () => {
|
|
3699
|
+
generation.current++;
|
|
3700
|
+
};
|
|
3701
|
+
}, [refresh]);
|
|
3702
|
+
return {
|
|
3703
|
+
subscriptions: dataOwner === owner ? subscriptions : [],
|
|
3704
|
+
loading,
|
|
3705
|
+
error,
|
|
3706
|
+
refresh,
|
|
3707
|
+
api
|
|
3708
|
+
};
|
|
3709
|
+
}
|
|
3710
|
+
function useCustomerSubscription(id) {
|
|
3711
|
+
const client = useStorefront();
|
|
3712
|
+
const { customer } = useAccount();
|
|
3713
|
+
const [readRevision, setReadRevision] = React27__namespace.useState(0);
|
|
3714
|
+
const [dataKey, setDataKey] = React27__namespace.useState("");
|
|
3715
|
+
const [subscription, setSubscription] = React27__namespace.useState(null);
|
|
3716
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
3717
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
3718
|
+
const generation = React27__namespace.useRef(0);
|
|
3719
|
+
const owner = customer?.id;
|
|
3720
|
+
const api = client.account.subscriptions;
|
|
3721
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
3722
|
+
const current = ++generation.current;
|
|
3723
|
+
setError(null);
|
|
3724
|
+
if (!owner || !id) {
|
|
3725
|
+
setSubscription(null);
|
|
3726
|
+
setLoading(false);
|
|
3727
|
+
return;
|
|
3728
|
+
}
|
|
3729
|
+
setLoading(true);
|
|
3730
|
+
try {
|
|
3731
|
+
const row = await api.management(id);
|
|
3732
|
+
if (current === generation.current) {
|
|
3733
|
+
setSubscription(row);
|
|
3734
|
+
setReadRevision((n) => n + 1);
|
|
3735
|
+
setDataKey(`${owner}:${id}`);
|
|
3736
|
+
setError(null);
|
|
3737
|
+
}
|
|
3738
|
+
} catch (err) {
|
|
3739
|
+
if (current === generation.current)
|
|
3740
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3741
|
+
} finally {
|
|
3742
|
+
if (current === generation.current) setLoading(false);
|
|
3743
|
+
}
|
|
3744
|
+
}, [owner, id, api]);
|
|
3745
|
+
React27__namespace.useEffect(() => {
|
|
3746
|
+
setSubscription(null);
|
|
3747
|
+
void refresh();
|
|
3748
|
+
return () => {
|
|
3749
|
+
generation.current++;
|
|
3750
|
+
};
|
|
3751
|
+
}, [refresh]);
|
|
3752
|
+
return {
|
|
3753
|
+
subscription: dataKey === `${owner}:${id}` ? subscription : null,
|
|
3754
|
+
loading,
|
|
3755
|
+
error,
|
|
3756
|
+
refresh,
|
|
3757
|
+
api,
|
|
3758
|
+
readRevision
|
|
3759
|
+
};
|
|
3760
|
+
}
|
|
3761
|
+
function SubscriptionList({ href, locale }) {
|
|
3762
|
+
const t = useT();
|
|
3763
|
+
const { subscriptions, loading, error } = useCustomerSubscriptions();
|
|
3764
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { style: { fontFamily: "var(--nv-font)", color: "var(--nv-fg)" }, children: [
|
|
3765
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.125rem", fontWeight: 500 }, children: t("subscriptions.title") }),
|
|
3766
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("checkout.loading") }) : error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error.message }) : subscriptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("subscriptions.empty") }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: subscriptions.map((sub) => /* @__PURE__ */ jsxRuntime.jsx("li", { style: { borderBottom: "1px solid var(--nv-border)", padding: "0.8rem 0" }, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: href(sub.id), style: { color: "inherit", display: "flex", justifyContent: "space-between", gap: "1rem", flexWrap: "wrap" }, children: [
|
|
3767
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3768
|
+
"#",
|
|
3769
|
+
sub.id
|
|
3770
|
+
] }),
|
|
3771
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`subscriptions.status.${sub.status}`) }),
|
|
3772
|
+
sub.status === "active" && sub.next_at ? /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: sub.next_at, children: new Date(sub.next_at).toLocaleDateString(locale) }) : null
|
|
3773
|
+
] }) }, sub.id)) })
|
|
3774
|
+
] });
|
|
3775
|
+
}
|
|
3776
|
+
var subscriptionPanel = {
|
|
3777
|
+
fontFamily: "var(--nv-font)",
|
|
3778
|
+
color: "var(--nv-fg)",
|
|
3779
|
+
background: "var(--nv-bg)",
|
|
3780
|
+
border: "1px solid var(--nv-border)",
|
|
3781
|
+
borderRadius: "var(--nv-radius)",
|
|
3782
|
+
padding: "1rem"
|
|
3783
|
+
};
|
|
3784
|
+
function SubscriptionButton({
|
|
3785
|
+
primary = false,
|
|
3786
|
+
style,
|
|
3787
|
+
...props
|
|
3788
|
+
}) {
|
|
3789
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3790
|
+
"button",
|
|
3791
|
+
{
|
|
3792
|
+
type: "button",
|
|
3793
|
+
...props,
|
|
3794
|
+
style: {
|
|
3795
|
+
font: "inherit",
|
|
3796
|
+
fontWeight: 500,
|
|
3797
|
+
padding: ".5rem .85rem",
|
|
3798
|
+
borderRadius: "var(--nv-radius)",
|
|
3799
|
+
border: "1px solid var(--nv-border)",
|
|
3800
|
+
background: primary ? "var(--nv-brand)" : "var(--nv-bg)",
|
|
3801
|
+
color: primary ? "var(--nv-brand-foreground)" : "var(--nv-fg)",
|
|
3802
|
+
cursor: props.disabled ? "default" : "pointer",
|
|
3803
|
+
opacity: props.disabled ? 0.55 : 1,
|
|
3804
|
+
...style
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
function ProductSelection({
|
|
3810
|
+
load,
|
|
3811
|
+
value,
|
|
3812
|
+
onChange,
|
|
3813
|
+
initialItems = [],
|
|
3814
|
+
locale,
|
|
3815
|
+
maxQuantity = 99
|
|
3816
|
+
}) {
|
|
3817
|
+
const t = useT(), loc = useFormatLocale(locale), [query, setQuery] = React27__namespace.useState(""), [page, setPage] = React27__namespace.useState(0), [items, setItems] = React27__namespace.useState([]), [more, setMore] = React27__namespace.useState(false), [busy, setBusy] = React27__namespace.useState(false), [error, setError] = React27__namespace.useState("");
|
|
3818
|
+
const known = React27__namespace.useRef({});
|
|
3819
|
+
for (const item of initialItems) known.current[item.variant_id] = item;
|
|
3820
|
+
React27__namespace.useEffect(() => {
|
|
3821
|
+
const controller = new AbortController();
|
|
3822
|
+
setBusy(true);
|
|
3823
|
+
setError("");
|
|
3824
|
+
const timer = setTimeout(() => {
|
|
3825
|
+
void load(query, page * 50, controller.signal).then((result) => {
|
|
3826
|
+
if (controller.signal.aborted) return;
|
|
3827
|
+
for (const item of result.items)
|
|
3828
|
+
known.current[item.variant_id] = item;
|
|
3829
|
+
setItems((old) => page ? [...old, ...result.items] : result.items);
|
|
3830
|
+
setMore(result.more);
|
|
3831
|
+
}).catch((err) => {
|
|
3832
|
+
if (!controller.signal.aborted)
|
|
3833
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3834
|
+
}).finally(() => {
|
|
3835
|
+
if (!controller.signal.aborted) setBusy(false);
|
|
3836
|
+
});
|
|
3837
|
+
}, 180);
|
|
3838
|
+
return () => {
|
|
3839
|
+
clearTimeout(timer);
|
|
3840
|
+
controller.abort();
|
|
3841
|
+
};
|
|
3842
|
+
}, [load, query, page]);
|
|
3843
|
+
const selected = Object.keys(value).filter((id) => value[id] > 0).map((id) => known.current[id]).filter(Boolean);
|
|
3844
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "grid", gap: ".75rem" }, children: [
|
|
3845
|
+
selected.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3846
|
+
"label",
|
|
3847
|
+
{
|
|
3848
|
+
style: {
|
|
3849
|
+
display: "flex",
|
|
3850
|
+
justifyContent: "space-between",
|
|
3851
|
+
gap: "1rem",
|
|
3852
|
+
alignItems: "center"
|
|
3853
|
+
},
|
|
3854
|
+
children: [
|
|
3855
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3856
|
+
p.title,
|
|
3857
|
+
" ",
|
|
3858
|
+
p.variant_title,
|
|
3859
|
+
/* @__PURE__ */ jsxRuntime.jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: p.sku })
|
|
3860
|
+
] }),
|
|
3861
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3862
|
+
"input",
|
|
3863
|
+
{
|
|
3864
|
+
"aria-label": `${t("subscriptions.quantity")}: ${p.title}`,
|
|
3865
|
+
type: "number",
|
|
3866
|
+
min: 0,
|
|
3867
|
+
max: maxQuantity,
|
|
3868
|
+
value: value[p.variant_id] ?? 0,
|
|
3869
|
+
onChange: (e) => onChange({ ...value, [p.variant_id]: Number(e.target.value) }),
|
|
3870
|
+
style: {
|
|
3871
|
+
font: "inherit",
|
|
3872
|
+
width: "4.5rem",
|
|
3873
|
+
padding: ".5rem",
|
|
3874
|
+
border: "1px solid var(--nv-border)",
|
|
3875
|
+
borderRadius: "var(--nv-radius)",
|
|
3876
|
+
color: "var(--nv-fg)",
|
|
3877
|
+
background: "var(--nv-bg)"
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
)
|
|
3881
|
+
]
|
|
3882
|
+
},
|
|
3883
|
+
p.variant_id
|
|
3884
|
+
)),
|
|
3885
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3886
|
+
"input",
|
|
3887
|
+
{
|
|
3888
|
+
"aria-label": t("subscriptions.searchProducts"),
|
|
3889
|
+
placeholder: t("subscriptions.searchProducts"),
|
|
3890
|
+
value: query,
|
|
3891
|
+
onChange: (e) => {
|
|
3892
|
+
setQuery(e.target.value);
|
|
3893
|
+
setPage(0);
|
|
3894
|
+
setItems([]);
|
|
3895
|
+
},
|
|
3896
|
+
style: {
|
|
3897
|
+
font: "inherit",
|
|
3898
|
+
padding: ".65rem",
|
|
3899
|
+
border: "1px solid var(--nv-border)",
|
|
3900
|
+
borderRadius: "var(--nv-radius)",
|
|
3901
|
+
background: "var(--nv-bg)",
|
|
3902
|
+
color: "var(--nv-fg)"
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
),
|
|
3906
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3907
|
+
"ul",
|
|
3908
|
+
{
|
|
3909
|
+
style: {
|
|
3910
|
+
listStyle: "none",
|
|
3911
|
+
padding: 0,
|
|
3912
|
+
margin: 0,
|
|
3913
|
+
maxHeight: "18rem",
|
|
3914
|
+
overflowY: "auto"
|
|
3915
|
+
},
|
|
3916
|
+
children: items.filter((p) => !value[p.variant_id]).map((p) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3917
|
+
"li",
|
|
3918
|
+
{
|
|
3919
|
+
style: {
|
|
3920
|
+
display: "flex",
|
|
3921
|
+
alignItems: "center",
|
|
3922
|
+
justifyContent: "space-between",
|
|
3923
|
+
gap: "1rem",
|
|
3924
|
+
padding: ".5rem 0",
|
|
3925
|
+
borderBottom: "1px solid var(--nv-border)"
|
|
3926
|
+
},
|
|
3927
|
+
children: [
|
|
3928
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3929
|
+
p.title,
|
|
3930
|
+
" ",
|
|
3931
|
+
p.variant_title,
|
|
3932
|
+
/* @__PURE__ */ jsxRuntime.jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: formatCents(p.unit_gross, p.currency, loc) })
|
|
3933
|
+
] }),
|
|
3934
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3935
|
+
SubscriptionButton,
|
|
3936
|
+
{
|
|
3937
|
+
"aria-label": `${t("subscriptions.addProduct")}: ${p.title}`,
|
|
3938
|
+
onClick: () => onChange({ ...value, [p.variant_id]: 1 }),
|
|
3939
|
+
children: "+"
|
|
3940
|
+
}
|
|
3941
|
+
)
|
|
3942
|
+
]
|
|
3943
|
+
},
|
|
3944
|
+
p.variant_id
|
|
3945
|
+
))
|
|
3946
|
+
}
|
|
3947
|
+
),
|
|
3948
|
+
busy ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t("subscriptions.loadingProducts") }) : more ? /* @__PURE__ */ jsxRuntime.jsx(SubscriptionButton, { onClick: () => setPage((p) => p + 1), children: t("account.loadMore") }) : null,
|
|
3949
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error })
|
|
3950
|
+
] });
|
|
3951
|
+
}
|
|
3952
|
+
function SubscriptionManager({
|
|
3953
|
+
subscriptionId,
|
|
3954
|
+
locale
|
|
3955
|
+
}) {
|
|
3956
|
+
const state = useCustomerSubscription(subscriptionId), { customer } = useAccount(), t = useT();
|
|
3957
|
+
if (!customer) return null;
|
|
3958
|
+
if (state.error)
|
|
3959
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("p", { role: "alert", children: [
|
|
3960
|
+
state.error.message,
|
|
3961
|
+
" ",
|
|
3962
|
+
/* @__PURE__ */ jsxRuntime.jsx(SubscriptionButton, { onClick: () => void state.refresh(), children: t("account.retry") })
|
|
3963
|
+
] });
|
|
3964
|
+
if (!state.subscription)
|
|
3965
|
+
return /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t("subscriptions.loading") });
|
|
3966
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3967
|
+
Manager,
|
|
3968
|
+
{
|
|
3969
|
+
state,
|
|
3970
|
+
locale
|
|
3971
|
+
},
|
|
3972
|
+
`${customer.id}:${subscriptionId}:${state.subscription.version}:${state.readRevision}`
|
|
3973
|
+
);
|
|
3974
|
+
}
|
|
3975
|
+
function Manager({
|
|
3976
|
+
state,
|
|
3977
|
+
locale
|
|
3978
|
+
}) {
|
|
3979
|
+
const { subscription: s, api, refresh } = state, t = useT(), loc = useFormatLocale(locale);
|
|
3980
|
+
const [mode, setMode] = React27__namespace.useState(null);
|
|
3981
|
+
const [option, setOption] = React27__namespace.useState("");
|
|
3982
|
+
const [quantities, setQuantities] = React27__namespace.useState(
|
|
3983
|
+
{}
|
|
3984
|
+
);
|
|
3985
|
+
const [quote, setQuote] = React27__namespace.useState(null);
|
|
3986
|
+
const [busy, setBusy] = React27__namespace.useState(false), [locked, setLocked] = React27__namespace.useState(false), [error, setError] = React27__namespace.useState("");
|
|
3987
|
+
const [consent, setConsent] = React27__namespace.useState(false);
|
|
3988
|
+
const upcoming = s?.upcoming, heading = React27__namespace.useRef(null);
|
|
3989
|
+
const loader = React27__namespace.useCallback(
|
|
3990
|
+
(q, offset, signal) => api.products(s.id, {
|
|
3991
|
+
q,
|
|
3992
|
+
offset,
|
|
3993
|
+
signal,
|
|
3994
|
+
scope: mode === "mix" ? "mix" : "one_time"
|
|
3995
|
+
}),
|
|
3996
|
+
[api, s?.id, mode]
|
|
3997
|
+
);
|
|
3998
|
+
React27__namespace.useEffect(() => {
|
|
3999
|
+
if (mode) heading.current?.focus();
|
|
4000
|
+
}, [mode]);
|
|
4001
|
+
if (!s || !upcoming) return null;
|
|
4002
|
+
if (s.status !== "active")
|
|
4003
|
+
return /* @__PURE__ */ jsxRuntime.jsx("section", { style: subscriptionPanel, children: /* @__PURE__ */ jsxRuntime.jsx("h2", { children: t(`subscriptions.status.${s.status}`) }) });
|
|
4004
|
+
const money = (n) => formatCents(n, s.currency, loc);
|
|
4005
|
+
const scope = mode === "mix" ? "recurring" : "one_time";
|
|
4006
|
+
const selected = Object.entries(quantities).filter(([, quantity]) => quantity > 0).map(([variant_id, quantity]) => ({ variant_id, quantity }));
|
|
4007
|
+
const changes = mode === "cadence" ? { cadence_option_id: option } : { lines: selected };
|
|
4008
|
+
const amount = quote ? "recurring_total_cents" in quote ? quote.recurring_total_cents : quote.after.total_cents : upcoming.total_cents;
|
|
4009
|
+
const enter = (next) => {
|
|
4010
|
+
setMode(next);
|
|
4011
|
+
setQuote(null);
|
|
4012
|
+
setConsent(false);
|
|
4013
|
+
setError("");
|
|
4014
|
+
setOption("");
|
|
4015
|
+
setQuantities(
|
|
4016
|
+
Object.fromEntries(
|
|
4017
|
+
upcoming.lines.filter(
|
|
4018
|
+
(li) => li.scope === (next === "mix" ? "recurring" : "one_time")
|
|
4019
|
+
).map((li) => [li.variant_id, li.quantity])
|
|
4020
|
+
)
|
|
4021
|
+
);
|
|
4022
|
+
};
|
|
4023
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4024
|
+
"section",
|
|
4025
|
+
{
|
|
4026
|
+
style: { ...subscriptionPanel, display: "grid", gap: "1rem" },
|
|
4027
|
+
"aria-busy": busy,
|
|
4028
|
+
children: [
|
|
4029
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { children: [
|
|
4030
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontWeight: 500 }, children: t("subscriptions.nextDelivery") }),
|
|
4031
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4032
|
+
t("subscriptions.chargeDate"),
|
|
4033
|
+
":",
|
|
4034
|
+
" ",
|
|
4035
|
+
/* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: upcoming.due_at, children: new Date(upcoming.due_at).toLocaleString(loc) })
|
|
4036
|
+
] })
|
|
4037
|
+
] }),
|
|
4038
|
+
/* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: upcoming.lines.map((li) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4039
|
+
"li",
|
|
4040
|
+
{
|
|
4041
|
+
style: {
|
|
4042
|
+
display: "flex",
|
|
4043
|
+
justifyContent: "space-between",
|
|
4044
|
+
gap: "1rem",
|
|
4045
|
+
padding: ".65rem 0",
|
|
4046
|
+
borderBottom: "1px solid var(--nv-border)"
|
|
4047
|
+
},
|
|
4048
|
+
children: [
|
|
4049
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
4050
|
+
li.title ?? li.sku,
|
|
4051
|
+
" \xD7 ",
|
|
4052
|
+
li.quantity,
|
|
4053
|
+
li.scope === "one_time" ? /* @__PURE__ */ jsxRuntime.jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: t("subscriptions.onlyNext") }) : null
|
|
4054
|
+
] }),
|
|
4055
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: money(li.unit_gross * li.quantity - (li.discount_cents ?? 0)) })
|
|
4056
|
+
]
|
|
4057
|
+
},
|
|
4058
|
+
li.line_id
|
|
4059
|
+
)) }),
|
|
4060
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
4061
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t("totals.shipping") }),
|
|
4062
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: money(upcoming.shipping_cents) })
|
|
4063
|
+
] }),
|
|
4064
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4065
|
+
"div",
|
|
4066
|
+
{
|
|
4067
|
+
style: {
|
|
4068
|
+
display: "flex",
|
|
4069
|
+
justifyContent: "space-between",
|
|
4070
|
+
fontWeight: 500
|
|
4071
|
+
},
|
|
4072
|
+
children: [
|
|
4073
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t("subscriptions.toCharge") }),
|
|
4074
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: money(upcoming.total_cents) })
|
|
4075
|
+
]
|
|
4076
|
+
}
|
|
4077
|
+
),
|
|
4078
|
+
upcoming.capabilities.reminder?.enabled ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
|
|
4079
|
+
t("subscriptions.noticeStatus"),
|
|
4080
|
+
":",
|
|
4081
|
+
" ",
|
|
4082
|
+
t(`subscriptions.notice.${upcoming.reminder?.status ?? "pending"}`)
|
|
4083
|
+
] }) : null,
|
|
4084
|
+
upcoming.charge_held_for_notice && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t("subscriptions.noticeHeld") }),
|
|
4085
|
+
!upcoming.editable ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t(
|
|
4086
|
+
`subscriptions.locked.${upcoming.locked_reason_code ?? "in_flight"}`
|
|
4087
|
+
) }) : /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
|
|
4088
|
+
t("subscriptions.editUntil"),
|
|
4089
|
+
":",
|
|
4090
|
+
" ",
|
|
4091
|
+
new Date(upcoming.edit_cutoff_at).toLocaleString(loc)
|
|
4092
|
+
] }),
|
|
4093
|
+
!mode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
|
|
4094
|
+
!!upcoming.capabilities.cadence_options?.length && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4095
|
+
SubscriptionButton,
|
|
4096
|
+
{
|
|
4097
|
+
disabled: !upcoming.editable,
|
|
4098
|
+
onClick: () => enter("cadence"),
|
|
4099
|
+
children: t("subscriptions.changeInterval")
|
|
4100
|
+
}
|
|
4101
|
+
),
|
|
4102
|
+
upcoming.capabilities.one_time_addons && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4103
|
+
SubscriptionButton,
|
|
4104
|
+
{
|
|
4105
|
+
disabled: !upcoming.editable,
|
|
4106
|
+
onClick: () => enter("addons"),
|
|
4107
|
+
children: t("subscriptions.addNext")
|
|
4108
|
+
}
|
|
4109
|
+
),
|
|
4110
|
+
upcoming.capabilities.mix && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4111
|
+
SubscriptionButton,
|
|
4112
|
+
{
|
|
4113
|
+
disabled: !upcoming.editable,
|
|
4114
|
+
onClick: () => enter("mix"),
|
|
4115
|
+
children: t("subscriptions.changeMix")
|
|
4116
|
+
}
|
|
4117
|
+
),
|
|
4118
|
+
s.allowed_actions.skip && /* @__PURE__ */ jsxRuntime.jsx(SubscriptionButton, { onClick: () => enter("skip"), children: t("subscriptions.skip") })
|
|
4119
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4120
|
+
"form",
|
|
4121
|
+
{
|
|
4122
|
+
noValidate: true,
|
|
4123
|
+
onSubmit: async (event) => {
|
|
4124
|
+
event.preventDefault();
|
|
4125
|
+
if (busy || locked) return;
|
|
4126
|
+
setBusy(true);
|
|
4127
|
+
setError("");
|
|
4128
|
+
try {
|
|
4129
|
+
if (mode === "skip") {
|
|
4130
|
+
if (!consent) return;
|
|
4131
|
+
try {
|
|
4132
|
+
await api.skipNext(s.id, s.version);
|
|
4133
|
+
await refresh();
|
|
4134
|
+
} catch (err) {
|
|
4135
|
+
setLocked(true);
|
|
4136
|
+
throw err;
|
|
4137
|
+
}
|
|
4138
|
+
} else if (!quote) {
|
|
4139
|
+
const q = mode === "addons" ? await api.previewNextItems(s.id, { addons: selected }) : await api.previewChanges(s.id, changes);
|
|
4140
|
+
setQuote(q);
|
|
4141
|
+
setConsent(false);
|
|
4142
|
+
} else {
|
|
4143
|
+
if (!consent) return;
|
|
4144
|
+
try {
|
|
4145
|
+
if (mode === "addons")
|
|
4146
|
+
await api.commitNextItems(
|
|
4147
|
+
s.id,
|
|
4148
|
+
{ addons: selected },
|
|
4149
|
+
quote,
|
|
4150
|
+
"Customer explicitly accepted the displayed next-delivery contents and total"
|
|
4151
|
+
);
|
|
4152
|
+
else
|
|
4153
|
+
await api.commitChanges(
|
|
4154
|
+
s.id,
|
|
4155
|
+
changes,
|
|
4156
|
+
quote,
|
|
4157
|
+
"Customer explicitly accepted the displayed future terms, dates and price"
|
|
4158
|
+
);
|
|
4159
|
+
await refresh();
|
|
4160
|
+
} catch (err) {
|
|
4161
|
+
setLocked(true);
|
|
4162
|
+
throw err;
|
|
4163
|
+
}
|
|
4164
|
+
}
|
|
4165
|
+
} catch (err) {
|
|
4166
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4167
|
+
} finally {
|
|
4168
|
+
setBusy(false);
|
|
4169
|
+
}
|
|
4170
|
+
},
|
|
4171
|
+
style: {
|
|
4172
|
+
borderTop: "1px solid var(--nv-border)",
|
|
4173
|
+
paddingTop: "1rem",
|
|
4174
|
+
display: "grid",
|
|
4175
|
+
gap: "1rem"
|
|
4176
|
+
},
|
|
4177
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4178
|
+
"fieldset",
|
|
4179
|
+
{
|
|
4180
|
+
disabled: busy,
|
|
4181
|
+
style: {
|
|
4182
|
+
border: 0,
|
|
4183
|
+
margin: 0,
|
|
4184
|
+
padding: 0,
|
|
4185
|
+
display: "grid",
|
|
4186
|
+
gap: "1rem",
|
|
4187
|
+
minWidth: 0
|
|
4188
|
+
},
|
|
4189
|
+
children: [
|
|
4190
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4191
|
+
"h3",
|
|
4192
|
+
{
|
|
4193
|
+
ref: heading,
|
|
4194
|
+
tabIndex: -1,
|
|
4195
|
+
style: { margin: 0, fontWeight: 500 },
|
|
4196
|
+
children: t(
|
|
4197
|
+
mode === "cadence" ? "subscriptions.changeInterval" : mode === "mix" ? "subscriptions.changeMix" : mode === "skip" ? "subscriptions.skip" : "subscriptions.addNext"
|
|
4198
|
+
)
|
|
4199
|
+
}
|
|
4200
|
+
),
|
|
4201
|
+
mode === "skip" ? /* @__PURE__ */ jsxRuntime.jsx("p", { children: t("subscriptions.skipWarning") }) : quote ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "status", children: [
|
|
4202
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: t(
|
|
4203
|
+
mode === "addons" ? "subscriptions.onlyNext" : "subscriptions.futureOnly"
|
|
4204
|
+
) }),
|
|
4205
|
+
mode === "cadence" ? /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4206
|
+
t("subscriptions.nextUnchanged"),
|
|
4207
|
+
":",
|
|
4208
|
+
" ",
|
|
4209
|
+
new Date(
|
|
4210
|
+
String(quote.after.next_at)
|
|
4211
|
+
).toLocaleString(loc)
|
|
4212
|
+
] }) : null,
|
|
4213
|
+
"upcoming_dates" in quote && quote.upcoming_dates?.map((date) => /* @__PURE__ */ jsxRuntime.jsx("p", { children: new Date(date).toLocaleString(loc) }, date)),
|
|
4214
|
+
mode === "addons" && "addons" in quote.after && quote.after.addons.map(
|
|
4215
|
+
(li) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4216
|
+
li.title ?? li.sku ?? li.variant_id,
|
|
4217
|
+
" \xD7 ",
|
|
4218
|
+
li.quantity
|
|
4219
|
+
] }, li.variant_id)
|
|
4220
|
+
),
|
|
4221
|
+
"display_items" in quote && quote.display_items?.map((li) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4222
|
+
li.title,
|
|
4223
|
+
" \xD7 ",
|
|
4224
|
+
li.quantity
|
|
4225
|
+
] }, li.variant_id)),
|
|
4226
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
4227
|
+
t("subscriptions.reviewTotal"),
|
|
4228
|
+
": ",
|
|
4229
|
+
money(amount)
|
|
4230
|
+
] })
|
|
4231
|
+
] }) : mode === "cadence" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4232
|
+
"div",
|
|
4233
|
+
{
|
|
4234
|
+
role: "group",
|
|
4235
|
+
"aria-label": t("subscriptions.changeInterval"),
|
|
4236
|
+
style: { display: "flex", gap: ".5rem", flexWrap: "wrap" },
|
|
4237
|
+
children: upcoming.capabilities.cadence_options?.map((o) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
4238
|
+
SubscriptionButton,
|
|
4239
|
+
{
|
|
4240
|
+
"aria-pressed": option === o.id,
|
|
4241
|
+
onClick: () => setOption(o.id),
|
|
4242
|
+
children: t(`subscriptions.unit.${o.unit}`, { count: o.count })
|
|
4243
|
+
},
|
|
4244
|
+
o.id
|
|
4245
|
+
))
|
|
4246
|
+
}
|
|
4247
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4248
|
+
ProductSelection,
|
|
4249
|
+
{
|
|
4250
|
+
maxQuantity: mode === "mix" ? upcoming.capabilities.mix?.per_sku_max ?? 99 : 99,
|
|
4251
|
+
load: loader,
|
|
4252
|
+
value: quantities,
|
|
4253
|
+
onChange: setQuantities,
|
|
4254
|
+
initialItems: upcoming.lines.filter((li) => li.scope === scope).map((li) => ({
|
|
4255
|
+
...li,
|
|
4256
|
+
title: li.title ?? li.sku ?? li.variant_id,
|
|
4257
|
+
currency: s.currency,
|
|
4258
|
+
variant_title: li.variant_title ?? null,
|
|
4259
|
+
sku: li.sku ?? null
|
|
4260
|
+
})),
|
|
4261
|
+
locale: loc
|
|
4262
|
+
}
|
|
4263
|
+
),
|
|
4264
|
+
(quote || mode === "skip") && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4265
|
+
"label",
|
|
4266
|
+
{
|
|
4267
|
+
style: {
|
|
4268
|
+
display: "flex",
|
|
4269
|
+
gap: ".5rem",
|
|
4270
|
+
alignItems: "flex-start"
|
|
4271
|
+
},
|
|
4272
|
+
children: [
|
|
4273
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4274
|
+
"input",
|
|
4275
|
+
{
|
|
4276
|
+
type: "checkbox",
|
|
4277
|
+
checked: consent,
|
|
4278
|
+
onChange: (e) => setConsent(e.target.checked)
|
|
4279
|
+
}
|
|
4280
|
+
),
|
|
4281
|
+
t("subscriptions.acceptChange")
|
|
4282
|
+
]
|
|
4283
|
+
}
|
|
4284
|
+
),
|
|
4285
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error }),
|
|
4286
|
+
locked ? /* @__PURE__ */ jsxRuntime.jsx(SubscriptionButton, { onClick: () => void refresh(), children: t("subscriptions.reloadBeforeRetry") }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
|
|
4287
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4288
|
+
SubscriptionButton,
|
|
4289
|
+
{
|
|
4290
|
+
disabled: busy,
|
|
4291
|
+
onClick: () => quote ? (setQuote(null), setConsent(false)) : enter(null),
|
|
4292
|
+
children: t(quote ? "subscriptions.back" : "account.cancel")
|
|
4293
|
+
}
|
|
4294
|
+
),
|
|
4295
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4296
|
+
SubscriptionButton,
|
|
4297
|
+
{
|
|
4298
|
+
type: "submit",
|
|
4299
|
+
primary: true,
|
|
4300
|
+
disabled: busy || (quote || mode === "skip" ? !consent : mode === "cadence" ? !option : mode === "mix" ? !selected.length : false),
|
|
4301
|
+
children: busy ? t("subscriptions.loading") : t(
|
|
4302
|
+
quote || mode === "skip" ? "subscriptions.confirmChange" : "subscriptions.reviewChange"
|
|
4303
|
+
)
|
|
4304
|
+
}
|
|
4305
|
+
)
|
|
4306
|
+
] })
|
|
4307
|
+
]
|
|
4308
|
+
}
|
|
4309
|
+
)
|
|
4310
|
+
}
|
|
4311
|
+
)
|
|
4312
|
+
]
|
|
4313
|
+
}
|
|
4314
|
+
);
|
|
4315
|
+
}
|
|
3469
4316
|
function serializeJsonLd(data) {
|
|
3470
4317
|
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
3471
4318
|
}
|
|
@@ -3569,6 +4416,118 @@ function OrganizationJsonLd({
|
|
|
3569
4416
|
});
|
|
3570
4417
|
return /* @__PURE__ */ jsxRuntime.jsx(JsonLd, { data });
|
|
3571
4418
|
}
|
|
4419
|
+
function Mix({
|
|
4420
|
+
plan,
|
|
4421
|
+
marketId,
|
|
4422
|
+
locale,
|
|
4423
|
+
onAdded
|
|
4424
|
+
}) {
|
|
4425
|
+
const client = useStorefront(), cart = useCart(), t = useT(), [value, setValue] = React27__namespace.useState({}), [busy, setBusy] = React27__namespace.useState(false), [error, setError] = React27__namespace.useState(""), [locked, setLocked] = React27__namespace.useState(false);
|
|
4426
|
+
const load = React27__namespace.useCallback(
|
|
4427
|
+
(q, offset, signal) => client.subscriptions.mixProducts(plan.id, {
|
|
4428
|
+
q,
|
|
4429
|
+
offset,
|
|
4430
|
+
marketId,
|
|
4431
|
+
signal
|
|
4432
|
+
}),
|
|
4433
|
+
[client, plan.id, marketId]
|
|
4434
|
+
);
|
|
4435
|
+
const loc = useFormatLocale(locale);
|
|
4436
|
+
const mix = plan.capabilities?.mix;
|
|
4437
|
+
if (!mix) return null;
|
|
4438
|
+
const units = Object.values(value).reduce((a, b) => a + b, 0), lines = Object.entries(value).filter(([, q]) => q > 0).map(([variant_id, quantity]) => ({ variant_id, quantity }));
|
|
4439
|
+
const valid = units >= mix.min_units && units <= mix.max_units && lines.every(
|
|
4440
|
+
(li) => Number.isInteger(li.quantity) && li.quantity <= (mix.per_sku_max ?? 99)
|
|
4441
|
+
) && (mix.groups ?? []).every((g) => {
|
|
4442
|
+
const n = lines.filter((li) => g.variant_ids.includes(li.variant_id)).reduce((a, li) => a + li.quantity, 0);
|
|
4443
|
+
return n >= g.min_units && n <= g.max_units;
|
|
4444
|
+
});
|
|
4445
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { style: { ...subscriptionPanel, display: "grid", gap: "1rem" }, children: [
|
|
4446
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontWeight: 500, margin: 0 }, children: plan.name }),
|
|
4447
|
+
plan.price && /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4448
|
+
formatCents(plan.price.amount_cents, plan.price.currency, loc),
|
|
4449
|
+
" \xB7",
|
|
4450
|
+
" ",
|
|
4451
|
+
t("subscriptions.mixPrice")
|
|
4452
|
+
] }),
|
|
4453
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: t("subscriptions.mixCount", {
|
|
4454
|
+
count: units,
|
|
4455
|
+
min: mix.min_units,
|
|
4456
|
+
max: mix.max_units
|
|
4457
|
+
}) }),
|
|
4458
|
+
mix.groups?.map((g) => /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
4459
|
+
g.name,
|
|
4460
|
+
": ",
|
|
4461
|
+
g.min_units,
|
|
4462
|
+
"\u2013",
|
|
4463
|
+
g.max_units
|
|
4464
|
+
] }, g.name)),
|
|
4465
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4466
|
+
"fieldset",
|
|
4467
|
+
{
|
|
4468
|
+
disabled: busy || locked,
|
|
4469
|
+
style: { border: 0, padding: 0, margin: 0, minWidth: 0 },
|
|
4470
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4471
|
+
ProductSelection,
|
|
4472
|
+
{
|
|
4473
|
+
maxQuantity: mix.per_sku_max ?? 99,
|
|
4474
|
+
load,
|
|
4475
|
+
value,
|
|
4476
|
+
onChange: (v) => {
|
|
4477
|
+
setValue(v);
|
|
4478
|
+
setError("");
|
|
4479
|
+
},
|
|
4480
|
+
locale
|
|
4481
|
+
}
|
|
4482
|
+
)
|
|
4483
|
+
}
|
|
4484
|
+
),
|
|
4485
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: error }),
|
|
4486
|
+
locked ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4487
|
+
SubscriptionButton,
|
|
4488
|
+
{
|
|
4489
|
+
onClick: async () => {
|
|
4490
|
+
await cart.refresh();
|
|
4491
|
+
setLocked(false);
|
|
4492
|
+
},
|
|
4493
|
+
children: t("subscriptions.reloadBeforeRetry")
|
|
4494
|
+
}
|
|
4495
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
4496
|
+
SubscriptionButton,
|
|
4497
|
+
{
|
|
4498
|
+
primary: true,
|
|
4499
|
+
disabled: !valid || busy || cart.loading,
|
|
4500
|
+
onClick: async () => {
|
|
4501
|
+
setBusy(true);
|
|
4502
|
+
setError("");
|
|
4503
|
+
try {
|
|
4504
|
+
await cart.setSubscriptionMix(
|
|
4505
|
+
plan.id,
|
|
4506
|
+
lines,
|
|
4507
|
+
cart.cart?.line_items.filter((li) => li.subscription_plan_id).map((li) => li.id) ?? []
|
|
4508
|
+
);
|
|
4509
|
+
onAdded?.();
|
|
4510
|
+
} catch (err) {
|
|
4511
|
+
setLocked(true);
|
|
4512
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4513
|
+
} finally {
|
|
4514
|
+
setBusy(false);
|
|
4515
|
+
}
|
|
4516
|
+
},
|
|
4517
|
+
children: t("subscriptions.addMix")
|
|
4518
|
+
}
|
|
4519
|
+
)
|
|
4520
|
+
] });
|
|
4521
|
+
}
|
|
4522
|
+
function SubscriptionMix(props) {
|
|
4523
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4524
|
+
Mix,
|
|
4525
|
+
{
|
|
4526
|
+
...props
|
|
4527
|
+
},
|
|
4528
|
+
`${props.plan.id}:${props.plan.version}:${props.marketId ?? ""}`
|
|
4529
|
+
);
|
|
4530
|
+
}
|
|
3572
4531
|
|
|
3573
4532
|
exports.AccountDashboard = AccountDashboard;
|
|
3574
4533
|
exports.AccountProvider = AccountProvider;
|
|
@@ -3609,10 +4568,15 @@ exports.ProductDetailPage = ProductDetailPage;
|
|
|
3609
4568
|
exports.ProductGallery = ProductGallery;
|
|
3610
4569
|
exports.ProductJsonLd = ProductJsonLd;
|
|
3611
4570
|
exports.ProfileEditor = ProfileEditor;
|
|
4571
|
+
exports.PurchaseOptions = PurchaseOptions;
|
|
3612
4572
|
exports.ShippingPicker = ShippingPicker;
|
|
3613
4573
|
exports.SortSelect = SortSelect;
|
|
3614
4574
|
exports.StatusBanner = StatusBanner;
|
|
3615
4575
|
exports.StockBadge = StockBadge;
|
|
4576
|
+
exports.SubscriptionList = SubscriptionList;
|
|
4577
|
+
exports.SubscriptionManager = SubscriptionManager;
|
|
4578
|
+
exports.SubscriptionMix = SubscriptionMix;
|
|
4579
|
+
exports.SubscriptionTerms = SubscriptionTerms;
|
|
3616
4580
|
exports.TagFilter = TagFilter;
|
|
3617
4581
|
exports.ThemeProvider = ThemeProvider;
|
|
3618
4582
|
exports.TransferInstructions = TransferInstructions;
|
|
@@ -3630,6 +4594,8 @@ exports.themeToCssVars = themeToCssVars;
|
|
|
3630
4594
|
exports.useAccount = useAccount;
|
|
3631
4595
|
exports.useCart = useCart;
|
|
3632
4596
|
exports.useCheckout = useCheckout;
|
|
4597
|
+
exports.useCustomerSubscription = useCustomerSubscription;
|
|
4598
|
+
exports.useCustomerSubscriptions = useCustomerSubscriptions;
|
|
3633
4599
|
exports.useI18n = useI18n;
|
|
3634
4600
|
exports.useMagicLinkVerify = useMagicLinkVerify;
|
|
3635
4601
|
exports.useOrderStatus = useOrderStatus;
|