@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.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as React27 from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { imageSrcSet, StorefrontError } from '@nevios/storefront-js';
|
|
4
4
|
|
|
5
5
|
// src/provider.tsx
|
|
6
|
-
var StorefrontContext =
|
|
6
|
+
var StorefrontContext = React27.createContext(null);
|
|
7
7
|
function NeviosProvider({ client, children }) {
|
|
8
8
|
return /* @__PURE__ */ jsx(StorefrontContext.Provider, { value: client, children });
|
|
9
9
|
}
|
|
10
10
|
function useStorefront() {
|
|
11
|
-
const client =
|
|
11
|
+
const client = React27.useContext(StorefrontContext);
|
|
12
12
|
if (!client) {
|
|
13
13
|
throw new Error(
|
|
14
14
|
"useStorefront must be used inside <NeviosProvider>. Wrap your app root with it."
|
|
@@ -17,7 +17,7 @@ function useStorefront() {
|
|
|
17
17
|
return client;
|
|
18
18
|
}
|
|
19
19
|
function useStorefrontOptional() {
|
|
20
|
-
return
|
|
20
|
+
return React27.useContext(StorefrontContext);
|
|
21
21
|
}
|
|
22
22
|
var DEFAULTS = {
|
|
23
23
|
brand: "#111111",
|
|
@@ -53,7 +53,7 @@ function ThemeProvider({
|
|
|
53
53
|
as: Tag = "div"
|
|
54
54
|
}) {
|
|
55
55
|
const style = themeToCssVars(theme);
|
|
56
|
-
return
|
|
56
|
+
return React27.createElement(
|
|
57
57
|
Tag,
|
|
58
58
|
{ "data-nevios-theme": "", style },
|
|
59
59
|
children
|
|
@@ -62,6 +62,16 @@ function ThemeProvider({
|
|
|
62
62
|
|
|
63
63
|
// src/i18n/en.ts
|
|
64
64
|
var en = {
|
|
65
|
+
"subscriptions.purchaseOption": "Purchase option",
|
|
66
|
+
"subscriptions.empty": "You have no subscriptions yet.",
|
|
67
|
+
"subscriptions.status.active": "Active",
|
|
68
|
+
"subscriptions.status.paused": "Paused",
|
|
69
|
+
"subscriptions.status.pending": "Awaiting payment",
|
|
70
|
+
"subscriptions.status.cancelled": "Cancelled",
|
|
71
|
+
"subscriptions.title": "Subscription",
|
|
72
|
+
"subscriptions.consent": "I agree to the recurring price, delivery schedule and terms shown above.",
|
|
73
|
+
"subscriptions.eachDelivery": "For each delivery",
|
|
74
|
+
"subscriptions.once": "One-time purchase",
|
|
65
75
|
// common
|
|
66
76
|
"common.free": "Free",
|
|
67
77
|
"common.close": "Close",
|
|
@@ -216,6 +226,45 @@ var en = {
|
|
|
216
226
|
"error.generic.title": "Something went wrong",
|
|
217
227
|
"error.generic.body": "Please try again in a moment.",
|
|
218
228
|
"error.home": "Back to home",
|
|
229
|
+
"subscriptions.mixPrice": "Fixed box price; shipping is separate",
|
|
230
|
+
"subscriptions.locked.inactive": "This subscription is not active",
|
|
231
|
+
"subscriptions.locked.cutoff": "The edit cutoff has passed",
|
|
232
|
+
"subscriptions.locked.in_flight": "This shipment or payment is already being processed",
|
|
233
|
+
"subscriptions.quantity": "Quantity",
|
|
234
|
+
"subscriptions.back": "Back",
|
|
235
|
+
"subscriptions.loading": "Loading subscription\u2026",
|
|
236
|
+
"subscriptions.loadingProducts": "Loading products\u2026",
|
|
237
|
+
"subscriptions.chargeDate": "Scheduled charge",
|
|
238
|
+
"subscriptions.noticeHeld": "The charge is waiting for a confirmed advance notice.",
|
|
239
|
+
"subscriptions.nextDelivery": "Next shipment",
|
|
240
|
+
"subscriptions.onlyNext": "Only in this shipment",
|
|
241
|
+
"subscriptions.toCharge": "Amount to charge",
|
|
242
|
+
"subscriptions.noticeStatus": "Renewal notice",
|
|
243
|
+
"subscriptions.notice.pending": "Pending",
|
|
244
|
+
"subscriptions.notice.queued": "Queued",
|
|
245
|
+
"subscriptions.notice.sent": "Sent",
|
|
246
|
+
"subscriptions.notice.failed": "Needs attention",
|
|
247
|
+
"subscriptions.notice.superseded": "Updated notice pending",
|
|
248
|
+
"subscriptions.editUntil": "Changes available until",
|
|
249
|
+
"subscriptions.changeInterval": "Change delivery frequency",
|
|
250
|
+
"subscriptions.addNext": "Add to next shipment",
|
|
251
|
+
"subscriptions.changeMix": "Change my mix",
|
|
252
|
+
"subscriptions.skip": "Skip next shipment",
|
|
253
|
+
"subscriptions.skipWarning": "This shipment and its one-time additions will be skipped. Following shipments keep their regular contents.",
|
|
254
|
+
"subscriptions.futureOnly": "Applies to future shipments. Existing orders stay unchanged.",
|
|
255
|
+
"subscriptions.nextUnchanged": "Next date",
|
|
256
|
+
"subscriptions.reviewTotal": "Reviewed amount",
|
|
257
|
+
"subscriptions.acceptChange": "I agree to the displayed contents, schedule and price.",
|
|
258
|
+
"subscriptions.reloadBeforeRetry": "Reload before another attempt",
|
|
259
|
+
"subscriptions.confirmChange": "Confirm change",
|
|
260
|
+
"subscriptions.reviewChange": "Review change",
|
|
261
|
+
"subscriptions.unit.week": "Every {count} week(s)",
|
|
262
|
+
"subscriptions.unit.month": "Every {count} month(s)",
|
|
263
|
+
"subscriptions.unit.year": "Every {count} year(s)",
|
|
264
|
+
"subscriptions.searchProducts": "Search products",
|
|
265
|
+
"subscriptions.addProduct": "Add product",
|
|
266
|
+
"subscriptions.mixCount": "{count} selected \xB7 choose {min}\u2013{max} units",
|
|
267
|
+
"subscriptions.addMix": "Add mix to cart",
|
|
219
268
|
// account
|
|
220
269
|
"account.title": "Your account",
|
|
221
270
|
"account.signIn": "Sign in",
|
|
@@ -278,6 +327,16 @@ var en = {
|
|
|
278
327
|
|
|
279
328
|
// src/i18n/cs.ts
|
|
280
329
|
var cs = {
|
|
330
|
+
"subscriptions.purchaseOption": "Zp\u016Fsob n\xE1kupu",
|
|
331
|
+
"subscriptions.empty": "Zat\xEDm nem\xE1te \u017E\xE1dn\xE9 p\u0159edplatn\xE9.",
|
|
332
|
+
"subscriptions.status.active": "Aktivn\xED",
|
|
333
|
+
"subscriptions.status.paused": "Pozastaveno",
|
|
334
|
+
"subscriptions.status.pending": "\u010Cek\xE1 na platbu",
|
|
335
|
+
"subscriptions.status.cancelled": "Zru\u0161eno",
|
|
336
|
+
"subscriptions.title": "P\u0159edplatn\xE9",
|
|
337
|
+
"subscriptions.consent": "Souhlas\xEDm s uvedenou opakovanou cenou, pl\xE1nem doru\u010Den\xED a podm\xEDnkami.",
|
|
338
|
+
"subscriptions.eachDelivery": "Za ka\u017Ed\xE9 doru\u010Den\xED",
|
|
339
|
+
"subscriptions.once": "Jednor\xE1zov\xFD n\xE1kup",
|
|
281
340
|
"common.free": "Zdarma",
|
|
282
341
|
"common.close": "Zav\u0159\xEDt",
|
|
283
342
|
"common.loadingShort": "\u2026",
|
|
@@ -412,6 +471,45 @@ var cs = {
|
|
|
412
471
|
"error.generic.title": "N\u011Bco se pokazilo",
|
|
413
472
|
"error.generic.body": "Zkuste to pros\xEDm za chv\xEDli znovu.",
|
|
414
473
|
"error.home": "Zp\u011Bt na \xFAvod",
|
|
474
|
+
"subscriptions.mixPrice": "Pevn\xE1 cena mixu; doprava se po\u010D\xEDt\xE1 zvl\xE1\u0161\u0165",
|
|
475
|
+
"subscriptions.locked.inactive": "P\u0159edplatn\xE9 nen\xED aktivn\xED.",
|
|
476
|
+
"subscriptions.locked.cutoff": "Term\xEDn pro \xFApravy t\xE9to z\xE1silky ji\u017E uplynul.",
|
|
477
|
+
"subscriptions.locked.in_flight": "Z\xE1silka nebo jej\xED platba se ji\u017E zpracov\xE1v\xE1.",
|
|
478
|
+
"subscriptions.quantity": "Mno\u017Estv\xED",
|
|
479
|
+
"subscriptions.back": "Zp\u011Bt",
|
|
480
|
+
"subscriptions.loading": "Na\u010D\xEDt\xE1n\xED p\u0159edplatn\xE9ho\u2026",
|
|
481
|
+
"subscriptions.loadingProducts": "Na\u010D\xEDt\xE1n\xED produkt\u016F\u2026",
|
|
482
|
+
"subscriptions.chargeDate": "Pl\xE1novan\xE9 str\u017Een\xED",
|
|
483
|
+
"subscriptions.noticeHeld": "Platba \u010Dek\xE1 na potvrzen\xE9 odesl\xE1n\xED upozorn\u011Bn\xED a sjednan\xFD p\u0159edstih.",
|
|
484
|
+
"subscriptions.nextDelivery": "P\u0159\xED\u0161t\xED z\xE1silka",
|
|
485
|
+
"subscriptions.onlyNext": "Jen v t\xE9to z\xE1silce",
|
|
486
|
+
"subscriptions.toCharge": "\u010C\xE1stka ke str\u017Een\xED",
|
|
487
|
+
"subscriptions.noticeStatus": "Upozorn\u011Bn\xED p\u0159ed platbou",
|
|
488
|
+
"subscriptions.notice.pending": "\u010Cek\xE1 na odesl\xE1n\xED",
|
|
489
|
+
"subscriptions.notice.queued": "Ve front\u011B",
|
|
490
|
+
"subscriptions.notice.sent": "Odesl\xE1no",
|
|
491
|
+
"subscriptions.notice.failed": "Vy\u017Eaduje pozornost",
|
|
492
|
+
"subscriptions.notice.superseded": "\u010Cek\xE1 na aktualizovan\xE9 upozorn\u011Bn\xED",
|
|
493
|
+
"subscriptions.editUntil": "\xDApravy mo\u017En\xE9 do",
|
|
494
|
+
"subscriptions.changeInterval": "Zm\u011Bnit interval doru\u010Den\xED",
|
|
495
|
+
"subscriptions.addNext": "P\u0159idat k p\u0159\xED\u0161t\xED z\xE1silce",
|
|
496
|
+
"subscriptions.changeMix": "Zm\u011Bnit vlastn\xED mix",
|
|
497
|
+
"subscriptions.skip": "P\u0159esko\u010Dit p\u0159\xED\u0161t\xED z\xE1silku",
|
|
498
|
+
"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.",
|
|
499
|
+
"subscriptions.futureOnly": "Plat\xED pro budouc\xED z\xE1silky. Existuj\xEDc\xED objedn\xE1vky se nezm\u011Bn\xED.",
|
|
500
|
+
"subscriptions.nextUnchanged": "Nejbli\u017E\u0161\xED term\xEDn",
|
|
501
|
+
"subscriptions.reviewTotal": "Potvrzovan\xE1 \u010D\xE1stka",
|
|
502
|
+
"subscriptions.acceptChange": "Souhlas\xEDm se zobrazen\xFDm obsahem, term\xEDny a cenou.",
|
|
503
|
+
"subscriptions.reloadBeforeRetry": "Obnovit p\u0159ed dal\u0161\xEDm pokusem",
|
|
504
|
+
"subscriptions.confirmChange": "Potvrdit zm\u011Bnu",
|
|
505
|
+
"subscriptions.reviewChange": "Zkontrolovat zm\u011Bnu",
|
|
506
|
+
"subscriptions.unit.week": "Ka\u017Ed\xFDch {count} t\xFDdn\u016F",
|
|
507
|
+
"subscriptions.unit.month": "Ka\u017Ed\xFDch {count} m\u011Bs\xEDc\u016F",
|
|
508
|
+
"subscriptions.unit.year": "Ka\u017Ed\xFDch {count} let",
|
|
509
|
+
"subscriptions.searchProducts": "Hledat produkty",
|
|
510
|
+
"subscriptions.addProduct": "P\u0159idat produkt",
|
|
511
|
+
"subscriptions.mixCount": "Vybr\xE1no {count} \xB7 zvolte {min}\u2013{max} kus\u016F",
|
|
512
|
+
"subscriptions.addMix": "P\u0159idat mix do ko\u0161\xEDku",
|
|
415
513
|
"account.title": "V\xE1\u0161 \xFA\u010Det",
|
|
416
514
|
"account.signIn": "P\u0159ihl\xE1sit se",
|
|
417
515
|
"account.signInBlurb": "Zadejte e-mail a po\u0161leme v\xE1m p\u0159ihla\u0161ovac\xED odkaz.",
|
|
@@ -519,13 +617,13 @@ function makeI18n(locale, messages) {
|
|
|
519
617
|
return { locale, messages, t };
|
|
520
618
|
}
|
|
521
619
|
var DEFAULT_I18N = makeI18n("en", en);
|
|
522
|
-
var I18nContext =
|
|
620
|
+
var I18nContext = React27.createContext(null);
|
|
523
621
|
function I18nProvider({
|
|
524
622
|
locale,
|
|
525
623
|
messages,
|
|
526
624
|
children
|
|
527
625
|
}) {
|
|
528
|
-
const value =
|
|
626
|
+
const value = React27.useMemo(() => {
|
|
529
627
|
const lang = langOf(locale);
|
|
530
628
|
if (locale && !(lang in BUNDLED) && !messages) {
|
|
531
629
|
devWarn(
|
|
@@ -539,7 +637,7 @@ function I18nProvider({
|
|
|
539
637
|
return /* @__PURE__ */ jsx(I18nContext.Provider, { value, children });
|
|
540
638
|
}
|
|
541
639
|
function useI18n() {
|
|
542
|
-
return
|
|
640
|
+
return React27.useContext(I18nContext) ?? DEFAULT_I18N;
|
|
543
641
|
}
|
|
544
642
|
function useT() {
|
|
545
643
|
return useI18n().t;
|
|
@@ -663,7 +761,7 @@ function ProductCard({ product, href, locale }) {
|
|
|
663
761
|
var MAIN_WIDTHS = [600, 800, 1e3];
|
|
664
762
|
var THUMB_WIDTHS = [56, 112];
|
|
665
763
|
function ProductGallery({ media, alt }) {
|
|
666
|
-
const [active, setActive] =
|
|
764
|
+
const [active, setActive] = React27.useState(0);
|
|
667
765
|
const imageBase = useStorefrontOptional()?.imageBase;
|
|
668
766
|
if (media.length === 0) {
|
|
669
767
|
return /* @__PURE__ */ jsx(
|
|
@@ -751,10 +849,10 @@ function matchVariant(product, selected) {
|
|
|
751
849
|
) ?? null;
|
|
752
850
|
}
|
|
753
851
|
function VariantPicker({ product, onChange }) {
|
|
754
|
-
const [selected, setSelected] =
|
|
852
|
+
const [selected, setSelected] = React27.useState(
|
|
755
853
|
() => product.variants[0]?.options ?? []
|
|
756
854
|
);
|
|
757
|
-
|
|
855
|
+
React27.useEffect(() => {
|
|
758
856
|
onChange?.(matchVariant(product, selected));
|
|
759
857
|
}, [product, selected]);
|
|
760
858
|
if (product.options.length === 0) return null;
|
|
@@ -795,8 +893,8 @@ function BackInStockForm({
|
|
|
795
893
|
}) {
|
|
796
894
|
const t = useT();
|
|
797
895
|
const client = useStorefront();
|
|
798
|
-
const [email, setEmail] =
|
|
799
|
-
const [state, setState] =
|
|
896
|
+
const [email, setEmail] = React27.useState(defaultEmail);
|
|
897
|
+
const [state, setState] = React27.useState("idle");
|
|
800
898
|
if (!variant || variant.availability?.status !== "out_of_stock") return null;
|
|
801
899
|
async function submit(e) {
|
|
802
900
|
e.preventDefault();
|
|
@@ -896,7 +994,7 @@ function ProductDetailPage({
|
|
|
896
994
|
locale,
|
|
897
995
|
addToCart
|
|
898
996
|
}) {
|
|
899
|
-
const [variant, setVariant] =
|
|
997
|
+
const [variant, setVariant] = React27.useState(
|
|
900
998
|
product.variants[0] ?? null
|
|
901
999
|
);
|
|
902
1000
|
return /* @__PURE__ */ jsxs(
|
|
@@ -1128,12 +1226,28 @@ function TagFilter({ tags, selected = [], onChange }) {
|
|
|
1128
1226
|
}) })
|
|
1129
1227
|
] });
|
|
1130
1228
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1229
|
+
function PurchaseOptions({ plans, value, onChange, disabled, locale }) {
|
|
1230
|
+
const t = useT();
|
|
1231
|
+
const loc = useFormatLocale(locale);
|
|
1232
|
+
const name = React27.useId();
|
|
1233
|
+
const options = [{ id: null, name: t("subscriptions.once"), price: void 0 }, ...plans];
|
|
1234
|
+
return /* @__PURE__ */ jsxs("fieldset", { disabled, style: { border: 0, padding: 0, margin: 0, display: "grid", gap: "0.5rem" }, children: [
|
|
1235
|
+
/* @__PURE__ */ jsx("legend", { style: { marginBottom: "0.5rem", fontWeight: 500 }, children: t("subscriptions.purchaseOption") }),
|
|
1236
|
+
options.map((option) => /* @__PURE__ */ 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: [
|
|
1237
|
+
/* @__PURE__ */ jsx("input", { type: "radio", name, checked: value === option.id, onChange: () => onChange(option.id) }),
|
|
1238
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: option.name }),
|
|
1239
|
+
option.price ? /* @__PURE__ */ jsx("span", { children: formatCents(option.price.amount_cents, option.price.currency, loc) }) : null
|
|
1240
|
+
] }, option.id ?? "once"))
|
|
1241
|
+
] });
|
|
1242
|
+
}
|
|
1243
|
+
var CartContext = React27.createContext(null);
|
|
1244
|
+
function CartProvider({
|
|
1245
|
+
children
|
|
1246
|
+
}) {
|
|
1133
1247
|
const client = useStorefront();
|
|
1134
|
-
const [cart, setCart] =
|
|
1135
|
-
const [loading, setLoading] =
|
|
1136
|
-
const run =
|
|
1248
|
+
const [cart, setCart] = React27.useState(null);
|
|
1249
|
+
const [loading, setLoading] = React27.useState(false);
|
|
1250
|
+
const run = React27.useCallback(
|
|
1137
1251
|
async (op) => {
|
|
1138
1252
|
setLoading(true);
|
|
1139
1253
|
try {
|
|
@@ -1146,7 +1260,7 @@ function CartProvider({ children }) {
|
|
|
1146
1260
|
},
|
|
1147
1261
|
[]
|
|
1148
1262
|
);
|
|
1149
|
-
const refresh =
|
|
1263
|
+
const refresh = React27.useCallback(async () => {
|
|
1150
1264
|
setLoading(true);
|
|
1151
1265
|
try {
|
|
1152
1266
|
setCart(await client.cart.get());
|
|
@@ -1154,16 +1268,22 @@ function CartProvider({ children }) {
|
|
|
1154
1268
|
setLoading(false);
|
|
1155
1269
|
}
|
|
1156
1270
|
}, [client]);
|
|
1157
|
-
|
|
1271
|
+
React27.useEffect(() => {
|
|
1158
1272
|
void refresh();
|
|
1159
1273
|
}, [refresh]);
|
|
1160
|
-
const value =
|
|
1274
|
+
const value = React27.useMemo(
|
|
1161
1275
|
() => ({
|
|
1162
1276
|
cart,
|
|
1163
1277
|
loading,
|
|
1164
1278
|
itemCount: cart?.totals.item_count ?? 0,
|
|
1165
1279
|
addLine: (input) => run(() => client.cart.addLine(input)),
|
|
1280
|
+
setSubscriptionMix: (planId, lines, expectedLineIds) => run(
|
|
1281
|
+
() => client.cart.setSubscriptionMix(planId, lines, expectedLineIds, {
|
|
1282
|
+
expectedRevision: cart?.subscription?.revision
|
|
1283
|
+
})
|
|
1284
|
+
),
|
|
1166
1285
|
updateLine: (lineId, quantity) => run(() => client.cart.updateLine(lineId, quantity)),
|
|
1286
|
+
clearSubscription: () => run(() => client.cart.clearSubscription()),
|
|
1167
1287
|
removeLine: (lineId) => run(() => client.cart.removeLine(lineId)),
|
|
1168
1288
|
refresh
|
|
1169
1289
|
}),
|
|
@@ -1172,7 +1292,7 @@ function CartProvider({ children }) {
|
|
|
1172
1292
|
return /* @__PURE__ */ jsx(CartContext.Provider, { value, children });
|
|
1173
1293
|
}
|
|
1174
1294
|
function useCart() {
|
|
1175
|
-
const ctx =
|
|
1295
|
+
const ctx = React27.useContext(CartContext);
|
|
1176
1296
|
if (!ctx) {
|
|
1177
1297
|
throw new Error("useCart must be used inside <CartProvider>.");
|
|
1178
1298
|
}
|
|
@@ -1181,13 +1301,14 @@ function useCart() {
|
|
|
1181
1301
|
function AddToCart({
|
|
1182
1302
|
variant,
|
|
1183
1303
|
quantity = 1,
|
|
1304
|
+
subscriptionPlanId,
|
|
1184
1305
|
children,
|
|
1185
1306
|
onAdded
|
|
1186
1307
|
}) {
|
|
1187
1308
|
const t = useT();
|
|
1188
1309
|
const { addLine } = useCart();
|
|
1189
|
-
const [busy, setBusy] =
|
|
1190
|
-
const [error, setError] =
|
|
1310
|
+
const [busy, setBusy] = React27.useState(false);
|
|
1311
|
+
const [error, setError] = React27.useState(null);
|
|
1191
1312
|
const disabled = variant == null || busy;
|
|
1192
1313
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
1193
1314
|
/* @__PURE__ */ jsx(
|
|
@@ -1200,7 +1321,7 @@ function AddToCart({
|
|
|
1200
1321
|
setBusy(true);
|
|
1201
1322
|
setError(null);
|
|
1202
1323
|
try {
|
|
1203
|
-
await addLine({ variant_id: variant.id, quantity });
|
|
1324
|
+
await addLine({ variant_id: variant.id, quantity, ...subscriptionPlanId ? { subscription_plan_id: subscriptionPlanId } : {} });
|
|
1204
1325
|
onAdded?.();
|
|
1205
1326
|
} catch (err) {
|
|
1206
1327
|
const code = err?.code;
|
|
@@ -1229,7 +1350,7 @@ function AddToCart({
|
|
|
1229
1350
|
function CartLineItem({ line, locale }) {
|
|
1230
1351
|
const t = useT();
|
|
1231
1352
|
const loc = useFormatLocale(locale);
|
|
1232
|
-
const { updateLine, removeLine } = useCart();
|
|
1353
|
+
const { cart, updateLine, removeLine } = useCart();
|
|
1233
1354
|
return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.85rem", alignItems: "flex-start", padding: "0.75rem 0" }, children: [
|
|
1234
1355
|
/* @__PURE__ */ jsx(
|
|
1235
1356
|
"div",
|
|
@@ -1258,6 +1379,7 @@ function CartLineItem({ line, locale }) {
|
|
|
1258
1379
|
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1259
1380
|
/* @__PURE__ */ jsx("div", { style: { fontWeight: 500, lineHeight: 1.3 }, children: line.title }),
|
|
1260
1381
|
line.variant_title ? /* @__PURE__ */ jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: line.variant_title }) : null,
|
|
1382
|
+
line.subscription_plan_id ? /* @__PURE__ */ jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: cart?.subscription?.plan?.name ?? t("subscriptions.title") }) : null,
|
|
1261
1383
|
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", marginTop: "0.5rem" }, children: [
|
|
1262
1384
|
/* @__PURE__ */ jsxs(
|
|
1263
1385
|
"div",
|
|
@@ -1336,7 +1458,7 @@ function CartSummary({
|
|
|
1336
1458
|
const { cart } = useCart();
|
|
1337
1459
|
if (cart == null) return null;
|
|
1338
1460
|
const t = cart.totals;
|
|
1339
|
-
const empty = t.item_count === 0;
|
|
1461
|
+
const empty = t.item_count === 0 || cart.subscription?.available === false;
|
|
1340
1462
|
const cta = checkoutLabel ?? tr("cart.checkout");
|
|
1341
1463
|
const ctaStyle = {
|
|
1342
1464
|
display: "block",
|
|
@@ -1357,6 +1479,7 @@ function CartSummary({
|
|
|
1357
1479
|
t.tax_cents > 0 ? /* @__PURE__ */ jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, loc), muted: true }) : null,
|
|
1358
1480
|
/* @__PURE__ */ jsx("div", { style: { height: 1, background: "var(--nv-border)" } }),
|
|
1359
1481
|
/* @__PURE__ */ jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, loc), strong: true }),
|
|
1482
|
+
cart.subscription?.error ? /* @__PURE__ */ jsx("p", { role: "alert", style: { margin: 0 }, children: cart.subscription.error.message }) : null,
|
|
1360
1483
|
checkoutHref && !empty ? /* @__PURE__ */ jsx("a", { href: checkoutHref, style: ctaStyle, children: cta }) : /* @__PURE__ */ jsx("button", { type: "button", disabled: empty, onClick: onCheckout, style: ctaStyle, children: cta })
|
|
1361
1484
|
] });
|
|
1362
1485
|
}
|
|
@@ -1463,15 +1586,15 @@ function CartDrawer({
|
|
|
1463
1586
|
}
|
|
1464
1587
|
);
|
|
1465
1588
|
}
|
|
1466
|
-
var CheckoutContext =
|
|
1589
|
+
var CheckoutContext = React27.createContext(null);
|
|
1467
1590
|
function CheckoutProvider({
|
|
1468
1591
|
children
|
|
1469
1592
|
}) {
|
|
1470
1593
|
const client = useStorefront();
|
|
1471
|
-
const [checkout, setCheckout] =
|
|
1472
|
-
const [loading, setLoading] =
|
|
1473
|
-
const [error, setError] =
|
|
1474
|
-
const run =
|
|
1594
|
+
const [checkout, setCheckout] = React27.useState(null);
|
|
1595
|
+
const [loading, setLoading] = React27.useState(false);
|
|
1596
|
+
const [error, setError] = React27.useState(null);
|
|
1597
|
+
const run = React27.useCallback(
|
|
1475
1598
|
async (op, store) => {
|
|
1476
1599
|
setLoading(true);
|
|
1477
1600
|
try {
|
|
@@ -1489,23 +1612,36 @@ function CheckoutProvider({
|
|
|
1489
1612
|
},
|
|
1490
1613
|
[]
|
|
1491
1614
|
);
|
|
1492
|
-
const refresh =
|
|
1615
|
+
const refresh = React27.useCallback(async () => {
|
|
1493
1616
|
setLoading(true);
|
|
1494
1617
|
try {
|
|
1495
1618
|
setCheckout(await client.checkout.get());
|
|
1496
1619
|
setError(null);
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1497
1622
|
} finally {
|
|
1498
1623
|
setLoading(false);
|
|
1499
1624
|
}
|
|
1500
1625
|
}, [client]);
|
|
1501
|
-
|
|
1626
|
+
React27.useEffect(() => {
|
|
1502
1627
|
void refresh();
|
|
1503
1628
|
}, [refresh]);
|
|
1504
|
-
const pay =
|
|
1629
|
+
const pay = React27.useCallback(
|
|
1505
1630
|
async (opts = {}) => {
|
|
1506
1631
|
const co = checkout;
|
|
1507
1632
|
if (!co) throw new Error("pay() called before a checkout exists");
|
|
1508
1633
|
const idempotencyKey = opts.idempotencyKey ?? co.token;
|
|
1634
|
+
if (co.subscription) {
|
|
1635
|
+
if (co.subscription.subscription_id) {
|
|
1636
|
+
const payment = await run(() => client.checkout.subscriptionPayment(), () => null);
|
|
1637
|
+
setCheckout(await client.checkout.get());
|
|
1638
|
+
return { kind: "subscription_payment", payment };
|
|
1639
|
+
}
|
|
1640
|
+
if (!opts.subscription) throw new Error("Accept the current subscription terms before paying");
|
|
1641
|
+
const subscription = await run(() => client.checkout.enrollSubscription(opts.subscription), () => null);
|
|
1642
|
+
setCheckout(await client.checkout.get());
|
|
1643
|
+
return { kind: "subscription", subscription };
|
|
1644
|
+
}
|
|
1509
1645
|
if (co.payment_category === "gateway") {
|
|
1510
1646
|
const start = await run(
|
|
1511
1647
|
() => client.checkout.startPayment({ return_url: opts.returnUrl, cancel_url: opts.cancelUrl }),
|
|
@@ -1524,11 +1660,18 @@ function CheckoutProvider({
|
|
|
1524
1660
|
},
|
|
1525
1661
|
[checkout, client, run]
|
|
1526
1662
|
);
|
|
1527
|
-
const value =
|
|
1663
|
+
const value = React27.useMemo(
|
|
1528
1664
|
() => ({
|
|
1529
1665
|
checkout,
|
|
1530
1666
|
loading,
|
|
1531
1667
|
error,
|
|
1668
|
+
selectSubscription: (selection) => run(async () => {
|
|
1669
|
+
const quote = await client.checkout.selectSubscription(selection);
|
|
1670
|
+
setCheckout(await client.checkout.get());
|
|
1671
|
+
return quote;
|
|
1672
|
+
}, () => null),
|
|
1673
|
+
clearSubscription: () => run(() => client.checkout.clearSubscription(), (r) => r),
|
|
1674
|
+
reviewSubscription: () => run(() => client.checkout.reviewSubscription(), () => null),
|
|
1532
1675
|
create: (cartToken, input) => run(() => client.checkout.create(cartToken, input), (r) => r),
|
|
1533
1676
|
setAddress: (input) => run(() => client.checkout.setAddress(input), (r) => r),
|
|
1534
1677
|
shippingQuotes: () => run(() => client.checkout.shippingQuotes(), () => null),
|
|
@@ -1549,7 +1692,7 @@ function CheckoutProvider({
|
|
|
1549
1692
|
return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value, children });
|
|
1550
1693
|
}
|
|
1551
1694
|
function useCheckout() {
|
|
1552
|
-
const ctx =
|
|
1695
|
+
const ctx = React27.useContext(CheckoutContext);
|
|
1553
1696
|
if (!ctx) {
|
|
1554
1697
|
throw new Error("useCheckout must be used inside <CheckoutProvider>.");
|
|
1555
1698
|
}
|
|
@@ -1708,11 +1851,11 @@ function AddressForm({
|
|
|
1708
1851
|
}) {
|
|
1709
1852
|
const t = useT();
|
|
1710
1853
|
const { checkout, setAddress, loading } = useCheckout();
|
|
1711
|
-
const [form, setForm] =
|
|
1854
|
+
const [form, setForm] = React27.useState(
|
|
1712
1855
|
() => seed(checkout?.email ?? null, checkout?.shipping_address ?? null)
|
|
1713
1856
|
);
|
|
1714
|
-
const touched =
|
|
1715
|
-
|
|
1857
|
+
const touched = React27.useRef(/* @__PURE__ */ new Set());
|
|
1858
|
+
React27.useEffect(() => {
|
|
1716
1859
|
const prefill = checkout?.customer_prefill;
|
|
1717
1860
|
const addresses = prefill?.addresses.filter((a) => !a.is_pickup);
|
|
1718
1861
|
const saved = addresses?.find((a) => a.is_default) ?? addresses?.[0];
|
|
@@ -1777,11 +1920,11 @@ function ShippingPicker({
|
|
|
1777
1920
|
const loc = useFormatLocale(locale);
|
|
1778
1921
|
const { checkout, shippingQuotes, setShippingMethod, loading } = useCheckout();
|
|
1779
1922
|
const heading = title ?? t("checkout.shippingMethod");
|
|
1780
|
-
const [quotes, setQuotes] =
|
|
1781
|
-
const [selected, setSelected] =
|
|
1782
|
-
const [fetching, setFetching] =
|
|
1923
|
+
const [quotes, setQuotes] = React27.useState(null);
|
|
1924
|
+
const [selected, setSelected] = React27.useState(null);
|
|
1925
|
+
const [fetching, setFetching] = React27.useState(true);
|
|
1783
1926
|
const addressKey = checkout?.shipping_address ? `${checkout.shipping_address.country_code}:${checkout.shipping_address.postal_code}` : null;
|
|
1784
|
-
|
|
1927
|
+
React27.useEffect(() => {
|
|
1785
1928
|
let live = true;
|
|
1786
1929
|
setFetching(true);
|
|
1787
1930
|
shippingQuotes().then((qs) => {
|
|
@@ -1856,20 +1999,45 @@ function PaymentMethodPicker({
|
|
|
1856
1999
|
}
|
|
1857
2000
|
);
|
|
1858
2001
|
}
|
|
2002
|
+
function SubscriptionTerms({ quote, checked, onChange, locale }) {
|
|
2003
|
+
const t = useT();
|
|
2004
|
+
const loc = useFormatLocale(locale);
|
|
2005
|
+
return /* @__PURE__ */ jsxs("fieldset", { style: { margin: 0, padding: "1rem", border: "1px solid var(--nv-border)", borderRadius: "var(--nv-radius)" }, children: [
|
|
2006
|
+
/* @__PURE__ */ jsx("legend", { children: t("subscriptions.title") }),
|
|
2007
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "0 0 0.5rem", fontWeight: 500 }, children: [
|
|
2008
|
+
t("subscriptions.eachDelivery"),
|
|
2009
|
+
": ",
|
|
2010
|
+
formatCents(quote.recurring_total_cents, quote.currency, loc)
|
|
2011
|
+
] }),
|
|
2012
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 0.75rem", whiteSpace: "pre-wrap" }, children: quote.terms_text }),
|
|
2013
|
+
/* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "flex-start", gap: "0.6rem", cursor: "pointer" }, children: [
|
|
2014
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked, onChange: (event) => onChange(event.target.checked) }),
|
|
2015
|
+
/* @__PURE__ */ jsx("span", { children: t("subscriptions.consent") })
|
|
2016
|
+
] })
|
|
2017
|
+
] });
|
|
2018
|
+
}
|
|
1859
2019
|
function PaymentSection({
|
|
1860
2020
|
title,
|
|
1861
2021
|
returnUrl,
|
|
1862
2022
|
cancelUrl,
|
|
1863
2023
|
onOrderPlaced,
|
|
1864
|
-
locale
|
|
2024
|
+
locale,
|
|
2025
|
+
onSubscriptionPayment
|
|
1865
2026
|
}) {
|
|
1866
2027
|
const t = useT();
|
|
1867
2028
|
const heading = title ?? t("checkout.payment");
|
|
1868
|
-
const { checkout, paymentMethods, setPaymentMethod, pay, loading } = useCheckout();
|
|
1869
|
-
const [methods, setMethods] =
|
|
1870
|
-
const [fetching, setFetching] =
|
|
2029
|
+
const { checkout, paymentMethods, setPaymentMethod, pay, loading, refresh } = useCheckout();
|
|
2030
|
+
const [methods, setMethods] = React27.useState(null);
|
|
2031
|
+
const [fetching, setFetching] = React27.useState(true);
|
|
1871
2032
|
const selected = checkout?.payment_method ?? null;
|
|
1872
|
-
|
|
2033
|
+
const [acceptedDigest, setAcceptedDigest] = React27.useState(null);
|
|
2034
|
+
const [actionBusy, setActionBusy] = React27.useState(false);
|
|
2035
|
+
const quote = checkout?.subscription?.quote;
|
|
2036
|
+
const needsConsent = Boolean(checkout?.subscription?.requires_consent);
|
|
2037
|
+
const clickInFlight = React27.useRef(false);
|
|
2038
|
+
const [actionError, setActionError] = React27.useState(null);
|
|
2039
|
+
const accepted = Boolean(quote && acceptedDigest === quote.quote_digest);
|
|
2040
|
+
React27.useEffect(() => {
|
|
1873
2041
|
let live = true;
|
|
1874
2042
|
setFetching(true);
|
|
1875
2043
|
paymentMethods().then((ms) => {
|
|
@@ -1882,12 +2050,30 @@ function PaymentSection({
|
|
|
1882
2050
|
return () => {
|
|
1883
2051
|
live = false;
|
|
1884
2052
|
};
|
|
1885
|
-
}, []);
|
|
2053
|
+
}, [checkout?.token, checkout?.shipping_method_id, checkout?.subscription?.selection.plan_id]);
|
|
1886
2054
|
const selectedMethod = methods?.find((m) => m.method === selected) ?? null;
|
|
1887
2055
|
const isGateway = selectedMethod?.category === "gateway";
|
|
1888
2056
|
async function placeOrder() {
|
|
1889
|
-
|
|
1890
|
-
|
|
2057
|
+
if (clickInFlight.current) return;
|
|
2058
|
+
clickInFlight.current = true;
|
|
2059
|
+
setActionBusy(true);
|
|
2060
|
+
setActionError(null);
|
|
2061
|
+
try {
|
|
2062
|
+
const result = await pay({
|
|
2063
|
+
returnUrl,
|
|
2064
|
+
cancelUrl,
|
|
2065
|
+
...needsConsent && quote && accepted ? { subscription: { quote, consent: true } } : {}
|
|
2066
|
+
});
|
|
2067
|
+
if (result.kind === "subscription") await onSubscriptionPayment?.(result.subscription.payment);
|
|
2068
|
+
if (result.kind === "subscription_payment") await onSubscriptionPayment?.(result.payment);
|
|
2069
|
+
if (result.kind === "subscription" || result.kind === "subscription_payment") await refresh();
|
|
2070
|
+
if (result.kind === "order") onOrderPlaced?.(result.checkout);
|
|
2071
|
+
} catch (err) {
|
|
2072
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
2073
|
+
} finally {
|
|
2074
|
+
clickInFlight.current = false;
|
|
2075
|
+
setActionBusy(false);
|
|
2076
|
+
}
|
|
1891
2077
|
}
|
|
1892
2078
|
if (fetching && methods == null) {
|
|
1893
2079
|
return /* @__PURE__ */ jsx(Section, { title: heading, children: /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("checkout.loadingPayment") }) });
|
|
@@ -1907,9 +2093,12 @@ function PaymentSection({
|
|
|
1907
2093
|
label: heading
|
|
1908
2094
|
}
|
|
1909
2095
|
),
|
|
2096
|
+
quote && needsConsent ? /* @__PURE__ */ jsx(SubscriptionTerms, { locale, quote, checked: accepted, onChange: (value) => setAcceptedDigest(value && quote ? quote.quote_digest : null) }) : null,
|
|
2097
|
+
checkout?.subscription?.error ? /* @__PURE__ */ jsx("p", { role: "alert", children: checkout.subscription.error.message }) : null,
|
|
2098
|
+
actionError ? /* @__PURE__ */ jsx("p", { role: "alert", children: actionError }) : null,
|
|
1910
2099
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1911
|
-
/* @__PURE__ */ jsx(Button, { onClick: placeOrder, busy: loading, disabled: selected == null, children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
|
|
1912
|
-
isGateway ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
2100
|
+
/* @__PURE__ */ 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") }),
|
|
2101
|
+
isGateway && !checkout?.subscription ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
1913
2102
|
] })
|
|
1914
2103
|
] });
|
|
1915
2104
|
}
|
|
@@ -1997,6 +2186,7 @@ function reachedStep(checkout) {
|
|
|
1997
2186
|
}
|
|
1998
2187
|
function CheckoutFlow({
|
|
1999
2188
|
cartToken,
|
|
2189
|
+
onSubscriptionPayment,
|
|
2000
2190
|
onOrderPlaced,
|
|
2001
2191
|
emptyState,
|
|
2002
2192
|
renderConfirmation,
|
|
@@ -2006,19 +2196,19 @@ function CheckoutFlow({
|
|
|
2006
2196
|
}) {
|
|
2007
2197
|
const t = useT();
|
|
2008
2198
|
const { checkout, loading, create } = useCheckout();
|
|
2009
|
-
const [step, setStep] =
|
|
2010
|
-
const [bootstrapped, setBootstrapped] =
|
|
2011
|
-
|
|
2199
|
+
const [step, setStep] = React27.useState("address");
|
|
2200
|
+
const [bootstrapped, setBootstrapped] = React27.useState(false);
|
|
2201
|
+
React27.useEffect(() => {
|
|
2012
2202
|
if (checkout || !cartToken || bootstrapped) return;
|
|
2013
2203
|
setBootstrapped(true);
|
|
2014
2204
|
void create(cartToken).catch(() => {
|
|
2015
2205
|
});
|
|
2016
2206
|
}, [checkout, cartToken, bootstrapped, create]);
|
|
2017
2207
|
const serverStep = reachedStep(checkout);
|
|
2018
|
-
|
|
2208
|
+
React27.useEffect(() => {
|
|
2019
2209
|
setStep((cur) => rank(serverStep) > rank(cur) ? serverStep : cur);
|
|
2020
2210
|
}, [serverStep]);
|
|
2021
|
-
if (checkout?.order_id) {
|
|
2211
|
+
if (checkout?.order_id && (!checkout.subscription || checkout.phase === "success")) {
|
|
2022
2212
|
return /* @__PURE__ */ jsx(Shell, { summary: /* @__PURE__ */ jsx(OrderSummary, { locale }), children: renderConfirmation ? renderConfirmation(checkout) : /* @__PURE__ */ jsx(DefaultConfirmation, { checkout }) });
|
|
2023
2213
|
}
|
|
2024
2214
|
if (!checkout) {
|
|
@@ -2032,6 +2222,7 @@ function CheckoutFlow({
|
|
|
2032
2222
|
step === "address" ? /* @__PURE__ */ jsx(AddressForm, { onComplete: () => setStep("shipping") }) : step === "shipping" ? /* @__PURE__ */ jsx(ShippingPicker, { onComplete: () => setStep("payment"), locale }) : /* @__PURE__ */ jsx(
|
|
2033
2223
|
PaymentSection,
|
|
2034
2224
|
{
|
|
2225
|
+
onSubscriptionPayment,
|
|
2035
2226
|
returnUrl,
|
|
2036
2227
|
cancelUrl,
|
|
2037
2228
|
onOrderPlaced,
|
|
@@ -2136,10 +2327,10 @@ function DefaultConfirmation({ checkout }) {
|
|
|
2136
2327
|
}
|
|
2137
2328
|
function useOrderStatus(statusToken, initial) {
|
|
2138
2329
|
const client = useStorefront();
|
|
2139
|
-
const [order, setOrder] =
|
|
2140
|
-
const [loading, setLoading] =
|
|
2141
|
-
const [error, setError] =
|
|
2142
|
-
const load =
|
|
2330
|
+
const [order, setOrder] = React27.useState(initial ?? null);
|
|
2331
|
+
const [loading, setLoading] = React27.useState(false);
|
|
2332
|
+
const [error, setError] = React27.useState(null);
|
|
2333
|
+
const load = React27.useCallback(async () => {
|
|
2143
2334
|
if (!statusToken) return;
|
|
2144
2335
|
setLoading(true);
|
|
2145
2336
|
try {
|
|
@@ -2151,7 +2342,7 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2151
2342
|
setLoading(false);
|
|
2152
2343
|
}
|
|
2153
2344
|
}, [client, statusToken]);
|
|
2154
|
-
|
|
2345
|
+
React27.useEffect(() => {
|
|
2155
2346
|
if (initial) return;
|
|
2156
2347
|
void load();
|
|
2157
2348
|
}, [load, initial]);
|
|
@@ -2159,12 +2350,12 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2159
2350
|
}
|
|
2160
2351
|
function usePayment(token, initial) {
|
|
2161
2352
|
const client = useStorefront();
|
|
2162
|
-
const [payment, setPayment] =
|
|
2163
|
-
const [methods, setMethods] =
|
|
2164
|
-
const [loading, setLoading] =
|
|
2165
|
-
const [busy, setBusy] =
|
|
2166
|
-
const [error, setError] =
|
|
2167
|
-
const load =
|
|
2353
|
+
const [payment, setPayment] = React27.useState(initial ?? null);
|
|
2354
|
+
const [methods, setMethods] = React27.useState([]);
|
|
2355
|
+
const [loading, setLoading] = React27.useState(false);
|
|
2356
|
+
const [busy, setBusy] = React27.useState(false);
|
|
2357
|
+
const [error, setError] = React27.useState(null);
|
|
2358
|
+
const load = React27.useCallback(async () => {
|
|
2168
2359
|
if (!token) return;
|
|
2169
2360
|
setLoading(true);
|
|
2170
2361
|
try {
|
|
@@ -2181,7 +2372,7 @@ function usePayment(token, initial) {
|
|
|
2181
2372
|
setLoading(false);
|
|
2182
2373
|
}
|
|
2183
2374
|
}, [client, token]);
|
|
2184
|
-
|
|
2375
|
+
React27.useEffect(() => {
|
|
2185
2376
|
if (!token) return;
|
|
2186
2377
|
if (initial && initial.token === token) {
|
|
2187
2378
|
setPayment(initial);
|
|
@@ -2190,7 +2381,7 @@ function usePayment(token, initial) {
|
|
|
2190
2381
|
}
|
|
2191
2382
|
void load();
|
|
2192
2383
|
}, [token, load]);
|
|
2193
|
-
const selectMethod =
|
|
2384
|
+
const selectMethod = React27.useCallback(
|
|
2194
2385
|
async (method) => {
|
|
2195
2386
|
if (!token) return null;
|
|
2196
2387
|
setBusy(true);
|
|
@@ -2208,7 +2399,7 @@ function usePayment(token, initial) {
|
|
|
2208
2399
|
},
|
|
2209
2400
|
[client, token]
|
|
2210
2401
|
);
|
|
2211
|
-
const startPayment =
|
|
2402
|
+
const startPayment = React27.useCallback(
|
|
2212
2403
|
async (input) => {
|
|
2213
2404
|
if (!token) return null;
|
|
2214
2405
|
setBusy(true);
|
|
@@ -2645,8 +2836,8 @@ function Row4({
|
|
|
2645
2836
|
}
|
|
2646
2837
|
function CopyRow({ label, value }) {
|
|
2647
2838
|
const t = useT();
|
|
2648
|
-
const [copied, setCopied] =
|
|
2649
|
-
|
|
2839
|
+
const [copied, setCopied] = React27.useState(false);
|
|
2840
|
+
React27.useEffect(() => {
|
|
2650
2841
|
if (!copied) return;
|
|
2651
2842
|
const id = setTimeout(() => setCopied(false), 1800);
|
|
2652
2843
|
return () => clearTimeout(id);
|
|
@@ -2730,12 +2921,12 @@ function PaymentPage({
|
|
|
2730
2921
|
const t = useT();
|
|
2731
2922
|
const loc = useFormatLocale(locale);
|
|
2732
2923
|
const { payment, methods, loading, busy, error, reload, selectMethod, startPayment } = usePayment(token, initial);
|
|
2733
|
-
const [verifying, setVerifying] =
|
|
2734
|
-
|
|
2924
|
+
const [verifying, setVerifying] = React27.useState(false);
|
|
2925
|
+
React27.useEffect(() => {
|
|
2735
2926
|
if (token) setVerifying(consumeHandoff(token));
|
|
2736
2927
|
}, [token]);
|
|
2737
2928
|
const settled = payment != null && !payment.can_pay && payment.blocked_reason === "already_paid";
|
|
2738
|
-
|
|
2929
|
+
React27.useEffect(() => {
|
|
2739
2930
|
if (!verifying || !token || settled) return;
|
|
2740
2931
|
let attempts = 0;
|
|
2741
2932
|
const id = setInterval(() => {
|
|
@@ -2750,7 +2941,7 @@ function PaymentPage({
|
|
|
2750
2941
|
}, POLL_MS);
|
|
2751
2942
|
return () => clearInterval(id);
|
|
2752
2943
|
}, [verifying, token, settled, reload]);
|
|
2753
|
-
|
|
2944
|
+
React27.useEffect(() => {
|
|
2754
2945
|
if (!settled || !token || !payment) return;
|
|
2755
2946
|
setVerifying(false);
|
|
2756
2947
|
forgetHandoff(token);
|
|
@@ -2831,13 +3022,13 @@ function Amount({ label, value }) {
|
|
|
2831
3022
|
function Link({ href, children }) {
|
|
2832
3023
|
return /* @__PURE__ */ jsx("a", { href, style: { color: "var(--nv-brand)", fontSize: "0.875rem" }, children });
|
|
2833
3024
|
}
|
|
2834
|
-
var AccountContext =
|
|
3025
|
+
var AccountContext = React27.createContext(null);
|
|
2835
3026
|
function AccountProvider({ children }) {
|
|
2836
3027
|
const client = useStorefront();
|
|
2837
|
-
const [customer, setCustomer] =
|
|
2838
|
-
const [loading, setLoading] =
|
|
2839
|
-
const [error, setError] =
|
|
2840
|
-
const refresh =
|
|
3028
|
+
const [customer, setCustomer] = React27.useState(null);
|
|
3029
|
+
const [loading, setLoading] = React27.useState(true);
|
|
3030
|
+
const [error, setError] = React27.useState(false);
|
|
3031
|
+
const refresh = React27.useCallback(async () => {
|
|
2841
3032
|
setLoading(true);
|
|
2842
3033
|
setError(false);
|
|
2843
3034
|
try {
|
|
@@ -2848,10 +3039,10 @@ function AccountProvider({ children }) {
|
|
|
2848
3039
|
setLoading(false);
|
|
2849
3040
|
}
|
|
2850
3041
|
}, [client]);
|
|
2851
|
-
|
|
3042
|
+
React27.useEffect(() => {
|
|
2852
3043
|
void refresh();
|
|
2853
3044
|
}, [refresh]);
|
|
2854
|
-
const value =
|
|
3045
|
+
const value = React27.useMemo(
|
|
2855
3046
|
() => ({
|
|
2856
3047
|
customer,
|
|
2857
3048
|
signedIn: customer != null,
|
|
@@ -2874,7 +3065,7 @@ function AccountProvider({ children }) {
|
|
|
2874
3065
|
return /* @__PURE__ */ jsx(AccountContext.Provider, { value, children });
|
|
2875
3066
|
}
|
|
2876
3067
|
function useAccount() {
|
|
2877
|
-
const ctx =
|
|
3068
|
+
const ctx = React27.useContext(AccountContext);
|
|
2878
3069
|
if (!ctx) {
|
|
2879
3070
|
throw new Error("useAccount must be used inside <AccountProvider>.");
|
|
2880
3071
|
}
|
|
@@ -2882,9 +3073,9 @@ function useAccount() {
|
|
|
2882
3073
|
}
|
|
2883
3074
|
function useMagicLinkVerify(token) {
|
|
2884
3075
|
const { verify } = useAccount();
|
|
2885
|
-
const [status, setStatus] =
|
|
2886
|
-
const done =
|
|
2887
|
-
|
|
3076
|
+
const [status, setStatus] = React27.useState("idle");
|
|
3077
|
+
const done = React27.useRef(false);
|
|
3078
|
+
React27.useEffect(() => {
|
|
2888
3079
|
if (!token || done.current) return;
|
|
2889
3080
|
done.current = true;
|
|
2890
3081
|
setStatus("verifying");
|
|
@@ -2895,11 +3086,11 @@ function useMagicLinkVerify(token) {
|
|
|
2895
3086
|
function LoginForm({ verifyPath = "/account/verify", onSent }) {
|
|
2896
3087
|
const t = useT();
|
|
2897
3088
|
const { requestMagicLink } = useAccount();
|
|
2898
|
-
const [email, setEmail] =
|
|
2899
|
-
const [busy, setBusy] =
|
|
2900
|
-
const [sent, setSent] =
|
|
2901
|
-
const [error, setError] =
|
|
2902
|
-
const [devLink, setDevLink] =
|
|
3089
|
+
const [email, setEmail] = React27.useState("");
|
|
3090
|
+
const [busy, setBusy] = React27.useState(false);
|
|
3091
|
+
const [sent, setSent] = React27.useState(false);
|
|
3092
|
+
const [error, setError] = React27.useState(false);
|
|
3093
|
+
const [devLink, setDevLink] = React27.useState(null);
|
|
2903
3094
|
async function submit(e) {
|
|
2904
3095
|
e.preventDefault();
|
|
2905
3096
|
setBusy(true);
|
|
@@ -3011,14 +3202,14 @@ function OrderHistory({
|
|
|
3011
3202
|
const loc = useFormatLocale(locale);
|
|
3012
3203
|
const { signedIn } = useAccount();
|
|
3013
3204
|
const client = useStorefront();
|
|
3014
|
-
const [orders, setOrders] =
|
|
3015
|
-
const [error, setError] =
|
|
3016
|
-
const [nextCursor, setNextCursor] =
|
|
3017
|
-
const [pageLoading, setPageLoading] =
|
|
3018
|
-
const [reload, setReload] =
|
|
3019
|
-
const [reordering, setReordering] =
|
|
3020
|
-
const [notice, setNotice] =
|
|
3021
|
-
|
|
3205
|
+
const [orders, setOrders] = React27.useState(null);
|
|
3206
|
+
const [error, setError] = React27.useState(false);
|
|
3207
|
+
const [nextCursor, setNextCursor] = React27.useState(null);
|
|
3208
|
+
const [pageLoading, setPageLoading] = React27.useState(false);
|
|
3209
|
+
const [reload, setReload] = React27.useState(0);
|
|
3210
|
+
const [reordering, setReordering] = React27.useState(null);
|
|
3211
|
+
const [notice, setNotice] = React27.useState(null);
|
|
3212
|
+
React27.useEffect(() => {
|
|
3022
3213
|
let live = true;
|
|
3023
3214
|
if (!signedIn) {
|
|
3024
3215
|
setOrders([]);
|
|
@@ -3129,7 +3320,7 @@ function OrderHistory({
|
|
|
3129
3320
|
}
|
|
3130
3321
|
function AddressForm2({ initial, onSubmit, onCancel, busy }) {
|
|
3131
3322
|
const t = useT();
|
|
3132
|
-
const [form, setForm] =
|
|
3323
|
+
const [form, setForm] = React27.useState(() => ({
|
|
3133
3324
|
first_name: initial?.first_name ?? "",
|
|
3134
3325
|
last_name: initial?.last_name ?? "",
|
|
3135
3326
|
company: initial?.company ?? "",
|
|
@@ -3227,17 +3418,17 @@ function AddressBook() {
|
|
|
3227
3418
|
const t = useT();
|
|
3228
3419
|
const { signedIn } = useAccount();
|
|
3229
3420
|
const client = useStorefront();
|
|
3230
|
-
const [addresses, setAddresses] =
|
|
3231
|
-
const [mode, setMode] =
|
|
3232
|
-
const [busy, setBusy] =
|
|
3233
|
-
const load =
|
|
3421
|
+
const [addresses, setAddresses] = React27.useState(null);
|
|
3422
|
+
const [mode, setMode] = React27.useState({ kind: "list" });
|
|
3423
|
+
const [busy, setBusy] = React27.useState(false);
|
|
3424
|
+
const load = React27.useCallback(() => {
|
|
3234
3425
|
if (!signedIn) {
|
|
3235
3426
|
setAddresses([]);
|
|
3236
3427
|
return;
|
|
3237
3428
|
}
|
|
3238
3429
|
client.account.addresses().then(setAddresses).catch(() => setAddresses([]));
|
|
3239
3430
|
}, [signedIn, client]);
|
|
3240
|
-
|
|
3431
|
+
React27.useEffect(() => {
|
|
3241
3432
|
load();
|
|
3242
3433
|
}, [load]);
|
|
3243
3434
|
async function run(fn) {
|
|
@@ -3291,10 +3482,10 @@ function ProfileEditor() {
|
|
|
3291
3482
|
const t = useT();
|
|
3292
3483
|
const client = useStorefront();
|
|
3293
3484
|
const { customer, refresh } = useAccount();
|
|
3294
|
-
const [editing, setEditing] =
|
|
3295
|
-
const [busy, setBusy] =
|
|
3296
|
-
const [error, setError] =
|
|
3297
|
-
const [form, setForm] =
|
|
3485
|
+
const [editing, setEditing] = React27.useState(false);
|
|
3486
|
+
const [busy, setBusy] = React27.useState(false);
|
|
3487
|
+
const [error, setError] = React27.useState(false);
|
|
3488
|
+
const [form, setForm] = React27.useState({});
|
|
3298
3489
|
if (!customer) return null;
|
|
3299
3490
|
function startEdit() {
|
|
3300
3491
|
setForm({
|
|
@@ -3375,13 +3566,13 @@ function AccountSecurity() {
|
|
|
3375
3566
|
const client = useStorefront();
|
|
3376
3567
|
const account = useAccount();
|
|
3377
3568
|
const t = useT();
|
|
3378
|
-
const [sessions, setSessions] =
|
|
3379
|
-
const [email, setEmail] =
|
|
3380
|
-
const [busy, setBusy] =
|
|
3381
|
-
const [notice, setNotice] =
|
|
3382
|
-
const [confirmClose, setConfirmClose] =
|
|
3383
|
-
const [retry, setRetry] =
|
|
3384
|
-
|
|
3569
|
+
const [sessions, setSessions] = React27.useState(null);
|
|
3570
|
+
const [email, setEmail] = React27.useState("");
|
|
3571
|
+
const [busy, setBusy] = React27.useState(false);
|
|
3572
|
+
const [notice, setNotice] = React27.useState(null);
|
|
3573
|
+
const [confirmClose, setConfirmClose] = React27.useState(false);
|
|
3574
|
+
const [retry, setRetry] = React27.useState(0);
|
|
3575
|
+
React27.useEffect(() => {
|
|
3385
3576
|
let live = true;
|
|
3386
3577
|
if (account.signedIn) client.account.sessions().then((s) => {
|
|
3387
3578
|
if (live) setSessions(s);
|
|
@@ -3444,6 +3635,662 @@ function AccountSecurity() {
|
|
|
3444
3635
|
] }) : /* @__PURE__ */ jsx(Button, { disabled: busy, onClick: () => setConfirmClose(true), children: t("account.closeAccount") })
|
|
3445
3636
|
] });
|
|
3446
3637
|
}
|
|
3638
|
+
function useCustomerSubscriptions() {
|
|
3639
|
+
const client = useStorefront();
|
|
3640
|
+
const { customer } = useAccount();
|
|
3641
|
+
const [dataOwner, setDataOwner] = React27.useState(
|
|
3642
|
+
void 0
|
|
3643
|
+
);
|
|
3644
|
+
const [subscriptions, setSubscriptions] = React27.useState([]);
|
|
3645
|
+
const [loading, setLoading] = React27.useState(false);
|
|
3646
|
+
const [error, setError] = React27.useState(null);
|
|
3647
|
+
const generation = React27.useRef(0);
|
|
3648
|
+
const owner = customer?.id;
|
|
3649
|
+
const api = client.account.subscriptions;
|
|
3650
|
+
const refresh = React27.useCallback(async () => {
|
|
3651
|
+
const current = ++generation.current;
|
|
3652
|
+
setError(null);
|
|
3653
|
+
if (!owner) {
|
|
3654
|
+
setSubscriptions([]);
|
|
3655
|
+
setLoading(false);
|
|
3656
|
+
return;
|
|
3657
|
+
}
|
|
3658
|
+
setLoading(true);
|
|
3659
|
+
try {
|
|
3660
|
+
const rows = await api.list();
|
|
3661
|
+
if (current === generation.current) {
|
|
3662
|
+
setSubscriptions(rows);
|
|
3663
|
+
setDataOwner(owner);
|
|
3664
|
+
setError(null);
|
|
3665
|
+
}
|
|
3666
|
+
} catch (err) {
|
|
3667
|
+
if (current === generation.current)
|
|
3668
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3669
|
+
} finally {
|
|
3670
|
+
if (current === generation.current) setLoading(false);
|
|
3671
|
+
}
|
|
3672
|
+
}, [owner, api]);
|
|
3673
|
+
React27.useEffect(() => {
|
|
3674
|
+
setSubscriptions([]);
|
|
3675
|
+
void refresh();
|
|
3676
|
+
return () => {
|
|
3677
|
+
generation.current++;
|
|
3678
|
+
};
|
|
3679
|
+
}, [refresh]);
|
|
3680
|
+
return {
|
|
3681
|
+
subscriptions: dataOwner === owner ? subscriptions : [],
|
|
3682
|
+
loading,
|
|
3683
|
+
error,
|
|
3684
|
+
refresh,
|
|
3685
|
+
api
|
|
3686
|
+
};
|
|
3687
|
+
}
|
|
3688
|
+
function useCustomerSubscription(id) {
|
|
3689
|
+
const client = useStorefront();
|
|
3690
|
+
const { customer } = useAccount();
|
|
3691
|
+
const [readRevision, setReadRevision] = React27.useState(0);
|
|
3692
|
+
const [dataKey, setDataKey] = React27.useState("");
|
|
3693
|
+
const [subscription, setSubscription] = React27.useState(null);
|
|
3694
|
+
const [loading, setLoading] = React27.useState(false);
|
|
3695
|
+
const [error, setError] = React27.useState(null);
|
|
3696
|
+
const generation = React27.useRef(0);
|
|
3697
|
+
const owner = customer?.id;
|
|
3698
|
+
const api = client.account.subscriptions;
|
|
3699
|
+
const refresh = React27.useCallback(async () => {
|
|
3700
|
+
const current = ++generation.current;
|
|
3701
|
+
setError(null);
|
|
3702
|
+
if (!owner || !id) {
|
|
3703
|
+
setSubscription(null);
|
|
3704
|
+
setLoading(false);
|
|
3705
|
+
return;
|
|
3706
|
+
}
|
|
3707
|
+
setLoading(true);
|
|
3708
|
+
try {
|
|
3709
|
+
const row = await api.management(id);
|
|
3710
|
+
if (current === generation.current) {
|
|
3711
|
+
setSubscription(row);
|
|
3712
|
+
setReadRevision((n) => n + 1);
|
|
3713
|
+
setDataKey(`${owner}:${id}`);
|
|
3714
|
+
setError(null);
|
|
3715
|
+
}
|
|
3716
|
+
} catch (err) {
|
|
3717
|
+
if (current === generation.current)
|
|
3718
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
3719
|
+
} finally {
|
|
3720
|
+
if (current === generation.current) setLoading(false);
|
|
3721
|
+
}
|
|
3722
|
+
}, [owner, id, api]);
|
|
3723
|
+
React27.useEffect(() => {
|
|
3724
|
+
setSubscription(null);
|
|
3725
|
+
void refresh();
|
|
3726
|
+
return () => {
|
|
3727
|
+
generation.current++;
|
|
3728
|
+
};
|
|
3729
|
+
}, [refresh]);
|
|
3730
|
+
return {
|
|
3731
|
+
subscription: dataKey === `${owner}:${id}` ? subscription : null,
|
|
3732
|
+
loading,
|
|
3733
|
+
error,
|
|
3734
|
+
refresh,
|
|
3735
|
+
api,
|
|
3736
|
+
readRevision
|
|
3737
|
+
};
|
|
3738
|
+
}
|
|
3739
|
+
function SubscriptionList({ href, locale }) {
|
|
3740
|
+
const t = useT();
|
|
3741
|
+
const { subscriptions, loading, error } = useCustomerSubscriptions();
|
|
3742
|
+
return /* @__PURE__ */ jsxs("section", { style: { fontFamily: "var(--nv-font)", color: "var(--nv-fg)" }, children: [
|
|
3743
|
+
/* @__PURE__ */ jsx("h2", { style: { fontSize: "1.125rem", fontWeight: 500 }, children: t("subscriptions.title") }),
|
|
3744
|
+
loading ? /* @__PURE__ */ jsx("p", { children: t("checkout.loading") }) : error ? /* @__PURE__ */ jsx("p", { role: "alert", children: error.message }) : subscriptions.length === 0 ? /* @__PURE__ */ jsx("p", { children: t("subscriptions.empty") }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: subscriptions.map((sub) => /* @__PURE__ */ jsx("li", { style: { borderBottom: "1px solid var(--nv-border)", padding: "0.8rem 0" }, children: /* @__PURE__ */ jsxs("a", { href: href(sub.id), style: { color: "inherit", display: "flex", justifyContent: "space-between", gap: "1rem", flexWrap: "wrap" }, children: [
|
|
3745
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
3746
|
+
"#",
|
|
3747
|
+
sub.id
|
|
3748
|
+
] }),
|
|
3749
|
+
/* @__PURE__ */ jsx("span", { children: t(`subscriptions.status.${sub.status}`) }),
|
|
3750
|
+
sub.status === "active" && sub.next_at ? /* @__PURE__ */ jsx("time", { dateTime: sub.next_at, children: new Date(sub.next_at).toLocaleDateString(locale) }) : null
|
|
3751
|
+
] }) }, sub.id)) })
|
|
3752
|
+
] });
|
|
3753
|
+
}
|
|
3754
|
+
var subscriptionPanel = {
|
|
3755
|
+
fontFamily: "var(--nv-font)",
|
|
3756
|
+
color: "var(--nv-fg)",
|
|
3757
|
+
background: "var(--nv-bg)",
|
|
3758
|
+
border: "1px solid var(--nv-border)",
|
|
3759
|
+
borderRadius: "var(--nv-radius)",
|
|
3760
|
+
padding: "1rem"
|
|
3761
|
+
};
|
|
3762
|
+
function SubscriptionButton({
|
|
3763
|
+
primary = false,
|
|
3764
|
+
style,
|
|
3765
|
+
...props
|
|
3766
|
+
}) {
|
|
3767
|
+
return /* @__PURE__ */ jsx(
|
|
3768
|
+
"button",
|
|
3769
|
+
{
|
|
3770
|
+
type: "button",
|
|
3771
|
+
...props,
|
|
3772
|
+
style: {
|
|
3773
|
+
font: "inherit",
|
|
3774
|
+
fontWeight: 500,
|
|
3775
|
+
padding: ".5rem .85rem",
|
|
3776
|
+
borderRadius: "var(--nv-radius)",
|
|
3777
|
+
border: "1px solid var(--nv-border)",
|
|
3778
|
+
background: primary ? "var(--nv-brand)" : "var(--nv-bg)",
|
|
3779
|
+
color: primary ? "var(--nv-brand-foreground)" : "var(--nv-fg)",
|
|
3780
|
+
cursor: props.disabled ? "default" : "pointer",
|
|
3781
|
+
opacity: props.disabled ? 0.55 : 1,
|
|
3782
|
+
...style
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
);
|
|
3786
|
+
}
|
|
3787
|
+
function ProductSelection({
|
|
3788
|
+
load,
|
|
3789
|
+
value,
|
|
3790
|
+
onChange,
|
|
3791
|
+
initialItems = [],
|
|
3792
|
+
locale,
|
|
3793
|
+
maxQuantity = 99
|
|
3794
|
+
}) {
|
|
3795
|
+
const t = useT(), loc = useFormatLocale(locale), [query, setQuery] = React27.useState(""), [page, setPage] = React27.useState(0), [items, setItems] = React27.useState([]), [more, setMore] = React27.useState(false), [busy, setBusy] = React27.useState(false), [error, setError] = React27.useState("");
|
|
3796
|
+
const known = React27.useRef({});
|
|
3797
|
+
for (const item of initialItems) known.current[item.variant_id] = item;
|
|
3798
|
+
React27.useEffect(() => {
|
|
3799
|
+
const controller = new AbortController();
|
|
3800
|
+
setBusy(true);
|
|
3801
|
+
setError("");
|
|
3802
|
+
const timer = setTimeout(() => {
|
|
3803
|
+
void load(query, page * 50, controller.signal).then((result) => {
|
|
3804
|
+
if (controller.signal.aborted) return;
|
|
3805
|
+
for (const item of result.items)
|
|
3806
|
+
known.current[item.variant_id] = item;
|
|
3807
|
+
setItems((old) => page ? [...old, ...result.items] : result.items);
|
|
3808
|
+
setMore(result.more);
|
|
3809
|
+
}).catch((err) => {
|
|
3810
|
+
if (!controller.signal.aborted)
|
|
3811
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
3812
|
+
}).finally(() => {
|
|
3813
|
+
if (!controller.signal.aborted) setBusy(false);
|
|
3814
|
+
});
|
|
3815
|
+
}, 180);
|
|
3816
|
+
return () => {
|
|
3817
|
+
clearTimeout(timer);
|
|
3818
|
+
controller.abort();
|
|
3819
|
+
};
|
|
3820
|
+
}, [load, query, page]);
|
|
3821
|
+
const selected = Object.keys(value).filter((id) => value[id] > 0).map((id) => known.current[id]).filter(Boolean);
|
|
3822
|
+
return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: ".75rem" }, children: [
|
|
3823
|
+
selected.map((p) => /* @__PURE__ */ jsxs(
|
|
3824
|
+
"label",
|
|
3825
|
+
{
|
|
3826
|
+
style: {
|
|
3827
|
+
display: "flex",
|
|
3828
|
+
justifyContent: "space-between",
|
|
3829
|
+
gap: "1rem",
|
|
3830
|
+
alignItems: "center"
|
|
3831
|
+
},
|
|
3832
|
+
children: [
|
|
3833
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
3834
|
+
p.title,
|
|
3835
|
+
" ",
|
|
3836
|
+
p.variant_title,
|
|
3837
|
+
/* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: p.sku })
|
|
3838
|
+
] }),
|
|
3839
|
+
/* @__PURE__ */ jsx(
|
|
3840
|
+
"input",
|
|
3841
|
+
{
|
|
3842
|
+
"aria-label": `${t("subscriptions.quantity")}: ${p.title}`,
|
|
3843
|
+
type: "number",
|
|
3844
|
+
min: 0,
|
|
3845
|
+
max: maxQuantity,
|
|
3846
|
+
value: value[p.variant_id] ?? 0,
|
|
3847
|
+
onChange: (e) => onChange({ ...value, [p.variant_id]: Number(e.target.value) }),
|
|
3848
|
+
style: {
|
|
3849
|
+
font: "inherit",
|
|
3850
|
+
width: "4.5rem",
|
|
3851
|
+
padding: ".5rem",
|
|
3852
|
+
border: "1px solid var(--nv-border)",
|
|
3853
|
+
borderRadius: "var(--nv-radius)",
|
|
3854
|
+
color: "var(--nv-fg)",
|
|
3855
|
+
background: "var(--nv-bg)"
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
)
|
|
3859
|
+
]
|
|
3860
|
+
},
|
|
3861
|
+
p.variant_id
|
|
3862
|
+
)),
|
|
3863
|
+
/* @__PURE__ */ jsx(
|
|
3864
|
+
"input",
|
|
3865
|
+
{
|
|
3866
|
+
"aria-label": t("subscriptions.searchProducts"),
|
|
3867
|
+
placeholder: t("subscriptions.searchProducts"),
|
|
3868
|
+
value: query,
|
|
3869
|
+
onChange: (e) => {
|
|
3870
|
+
setQuery(e.target.value);
|
|
3871
|
+
setPage(0);
|
|
3872
|
+
setItems([]);
|
|
3873
|
+
},
|
|
3874
|
+
style: {
|
|
3875
|
+
font: "inherit",
|
|
3876
|
+
padding: ".65rem",
|
|
3877
|
+
border: "1px solid var(--nv-border)",
|
|
3878
|
+
borderRadius: "var(--nv-radius)",
|
|
3879
|
+
background: "var(--nv-bg)",
|
|
3880
|
+
color: "var(--nv-fg)"
|
|
3881
|
+
}
|
|
3882
|
+
}
|
|
3883
|
+
),
|
|
3884
|
+
/* @__PURE__ */ jsx(
|
|
3885
|
+
"ul",
|
|
3886
|
+
{
|
|
3887
|
+
style: {
|
|
3888
|
+
listStyle: "none",
|
|
3889
|
+
padding: 0,
|
|
3890
|
+
margin: 0,
|
|
3891
|
+
maxHeight: "18rem",
|
|
3892
|
+
overflowY: "auto"
|
|
3893
|
+
},
|
|
3894
|
+
children: items.filter((p) => !value[p.variant_id]).map((p) => /* @__PURE__ */ jsxs(
|
|
3895
|
+
"li",
|
|
3896
|
+
{
|
|
3897
|
+
style: {
|
|
3898
|
+
display: "flex",
|
|
3899
|
+
alignItems: "center",
|
|
3900
|
+
justifyContent: "space-between",
|
|
3901
|
+
gap: "1rem",
|
|
3902
|
+
padding: ".5rem 0",
|
|
3903
|
+
borderBottom: "1px solid var(--nv-border)"
|
|
3904
|
+
},
|
|
3905
|
+
children: [
|
|
3906
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
3907
|
+
p.title,
|
|
3908
|
+
" ",
|
|
3909
|
+
p.variant_title,
|
|
3910
|
+
/* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: formatCents(p.unit_gross, p.currency, loc) })
|
|
3911
|
+
] }),
|
|
3912
|
+
/* @__PURE__ */ jsx(
|
|
3913
|
+
SubscriptionButton,
|
|
3914
|
+
{
|
|
3915
|
+
"aria-label": `${t("subscriptions.addProduct")}: ${p.title}`,
|
|
3916
|
+
onClick: () => onChange({ ...value, [p.variant_id]: 1 }),
|
|
3917
|
+
children: "+"
|
|
3918
|
+
}
|
|
3919
|
+
)
|
|
3920
|
+
]
|
|
3921
|
+
},
|
|
3922
|
+
p.variant_id
|
|
3923
|
+
))
|
|
3924
|
+
}
|
|
3925
|
+
),
|
|
3926
|
+
busy ? /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.loadingProducts") }) : more ? /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => setPage((p) => p + 1), children: t("account.loadMore") }) : null,
|
|
3927
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
|
|
3928
|
+
] });
|
|
3929
|
+
}
|
|
3930
|
+
function SubscriptionManager({
|
|
3931
|
+
subscriptionId,
|
|
3932
|
+
locale
|
|
3933
|
+
}) {
|
|
3934
|
+
const state = useCustomerSubscription(subscriptionId), { customer } = useAccount(), t = useT();
|
|
3935
|
+
if (!customer) return null;
|
|
3936
|
+
if (state.error)
|
|
3937
|
+
return /* @__PURE__ */ jsxs("p", { role: "alert", children: [
|
|
3938
|
+
state.error.message,
|
|
3939
|
+
" ",
|
|
3940
|
+
/* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => void state.refresh(), children: t("account.retry") })
|
|
3941
|
+
] });
|
|
3942
|
+
if (!state.subscription)
|
|
3943
|
+
return /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.loading") });
|
|
3944
|
+
return /* @__PURE__ */ jsx(
|
|
3945
|
+
Manager,
|
|
3946
|
+
{
|
|
3947
|
+
state,
|
|
3948
|
+
locale
|
|
3949
|
+
},
|
|
3950
|
+
`${customer.id}:${subscriptionId}:${state.subscription.version}:${state.readRevision}`
|
|
3951
|
+
);
|
|
3952
|
+
}
|
|
3953
|
+
function Manager({
|
|
3954
|
+
state,
|
|
3955
|
+
locale
|
|
3956
|
+
}) {
|
|
3957
|
+
const { subscription: s, api, refresh } = state, t = useT(), loc = useFormatLocale(locale);
|
|
3958
|
+
const [mode, setMode] = React27.useState(null);
|
|
3959
|
+
const [option, setOption] = React27.useState("");
|
|
3960
|
+
const [quantities, setQuantities] = React27.useState(
|
|
3961
|
+
{}
|
|
3962
|
+
);
|
|
3963
|
+
const [quote, setQuote] = React27.useState(null);
|
|
3964
|
+
const [busy, setBusy] = React27.useState(false), [locked, setLocked] = React27.useState(false), [error, setError] = React27.useState("");
|
|
3965
|
+
const [consent, setConsent] = React27.useState(false);
|
|
3966
|
+
const upcoming = s?.upcoming, heading = React27.useRef(null);
|
|
3967
|
+
const loader = React27.useCallback(
|
|
3968
|
+
(q, offset, signal) => api.products(s.id, {
|
|
3969
|
+
q,
|
|
3970
|
+
offset,
|
|
3971
|
+
signal,
|
|
3972
|
+
scope: mode === "mix" ? "mix" : "one_time"
|
|
3973
|
+
}),
|
|
3974
|
+
[api, s?.id, mode]
|
|
3975
|
+
);
|
|
3976
|
+
React27.useEffect(() => {
|
|
3977
|
+
if (mode) heading.current?.focus();
|
|
3978
|
+
}, [mode]);
|
|
3979
|
+
if (!s || !upcoming) return null;
|
|
3980
|
+
if (s.status !== "active")
|
|
3981
|
+
return /* @__PURE__ */ jsx("section", { style: subscriptionPanel, children: /* @__PURE__ */ jsx("h2", { children: t(`subscriptions.status.${s.status}`) }) });
|
|
3982
|
+
const money = (n) => formatCents(n, s.currency, loc);
|
|
3983
|
+
const scope = mode === "mix" ? "recurring" : "one_time";
|
|
3984
|
+
const selected = Object.entries(quantities).filter(([, quantity]) => quantity > 0).map(([variant_id, quantity]) => ({ variant_id, quantity }));
|
|
3985
|
+
const changes = mode === "cadence" ? { cadence_option_id: option } : { lines: selected };
|
|
3986
|
+
const amount = quote ? "recurring_total_cents" in quote ? quote.recurring_total_cents : quote.after.total_cents : upcoming.total_cents;
|
|
3987
|
+
const enter = (next) => {
|
|
3988
|
+
setMode(next);
|
|
3989
|
+
setQuote(null);
|
|
3990
|
+
setConsent(false);
|
|
3991
|
+
setError("");
|
|
3992
|
+
setOption("");
|
|
3993
|
+
setQuantities(
|
|
3994
|
+
Object.fromEntries(
|
|
3995
|
+
upcoming.lines.filter(
|
|
3996
|
+
(li) => li.scope === (next === "mix" ? "recurring" : "one_time")
|
|
3997
|
+
).map((li) => [li.variant_id, li.quantity])
|
|
3998
|
+
)
|
|
3999
|
+
);
|
|
4000
|
+
};
|
|
4001
|
+
return /* @__PURE__ */ jsxs(
|
|
4002
|
+
"section",
|
|
4003
|
+
{
|
|
4004
|
+
style: { ...subscriptionPanel, display: "grid", gap: "1rem" },
|
|
4005
|
+
"aria-busy": busy,
|
|
4006
|
+
children: [
|
|
4007
|
+
/* @__PURE__ */ jsxs("header", { children: [
|
|
4008
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: 0, fontWeight: 500 }, children: t("subscriptions.nextDelivery") }),
|
|
4009
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
4010
|
+
t("subscriptions.chargeDate"),
|
|
4011
|
+
":",
|
|
4012
|
+
" ",
|
|
4013
|
+
/* @__PURE__ */ jsx("time", { dateTime: upcoming.due_at, children: new Date(upcoming.due_at).toLocaleString(loc) })
|
|
4014
|
+
] })
|
|
4015
|
+
] }),
|
|
4016
|
+
/* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: upcoming.lines.map((li) => /* @__PURE__ */ jsxs(
|
|
4017
|
+
"li",
|
|
4018
|
+
{
|
|
4019
|
+
style: {
|
|
4020
|
+
display: "flex",
|
|
4021
|
+
justifyContent: "space-between",
|
|
4022
|
+
gap: "1rem",
|
|
4023
|
+
padding: ".65rem 0",
|
|
4024
|
+
borderBottom: "1px solid var(--nv-border)"
|
|
4025
|
+
},
|
|
4026
|
+
children: [
|
|
4027
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
4028
|
+
li.title ?? li.sku,
|
|
4029
|
+
" \xD7 ",
|
|
4030
|
+
li.quantity,
|
|
4031
|
+
li.scope === "one_time" ? /* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: t("subscriptions.onlyNext") }) : null
|
|
4032
|
+
] }),
|
|
4033
|
+
/* @__PURE__ */ jsx("span", { children: money(li.unit_gross * li.quantity - (li.discount_cents ?? 0)) })
|
|
4034
|
+
]
|
|
4035
|
+
},
|
|
4036
|
+
li.line_id
|
|
4037
|
+
)) }),
|
|
4038
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
4039
|
+
/* @__PURE__ */ jsx("span", { children: t("totals.shipping") }),
|
|
4040
|
+
/* @__PURE__ */ jsx("span", { children: money(upcoming.shipping_cents) })
|
|
4041
|
+
] }),
|
|
4042
|
+
/* @__PURE__ */ jsxs(
|
|
4043
|
+
"div",
|
|
4044
|
+
{
|
|
4045
|
+
style: {
|
|
4046
|
+
display: "flex",
|
|
4047
|
+
justifyContent: "space-between",
|
|
4048
|
+
fontWeight: 500
|
|
4049
|
+
},
|
|
4050
|
+
children: [
|
|
4051
|
+
/* @__PURE__ */ jsx("span", { children: t("subscriptions.toCharge") }),
|
|
4052
|
+
/* @__PURE__ */ jsx("span", { children: money(upcoming.total_cents) })
|
|
4053
|
+
]
|
|
4054
|
+
}
|
|
4055
|
+
),
|
|
4056
|
+
upcoming.capabilities.reminder?.enabled ? /* @__PURE__ */ jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
|
|
4057
|
+
t("subscriptions.noticeStatus"),
|
|
4058
|
+
":",
|
|
4059
|
+
" ",
|
|
4060
|
+
t(`subscriptions.notice.${upcoming.reminder?.status ?? "pending"}`)
|
|
4061
|
+
] }) : null,
|
|
4062
|
+
upcoming.charge_held_for_notice && /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.noticeHeld") }),
|
|
4063
|
+
!upcoming.editable ? /* @__PURE__ */ jsx("p", { role: "status", children: t(
|
|
4064
|
+
`subscriptions.locked.${upcoming.locked_reason_code ?? "in_flight"}`
|
|
4065
|
+
) }) : /* @__PURE__ */ jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
|
|
4066
|
+
t("subscriptions.editUntil"),
|
|
4067
|
+
":",
|
|
4068
|
+
" ",
|
|
4069
|
+
new Date(upcoming.edit_cutoff_at).toLocaleString(loc)
|
|
4070
|
+
] }),
|
|
4071
|
+
!mode ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
|
|
4072
|
+
!!upcoming.capabilities.cadence_options?.length && /* @__PURE__ */ jsx(
|
|
4073
|
+
SubscriptionButton,
|
|
4074
|
+
{
|
|
4075
|
+
disabled: !upcoming.editable,
|
|
4076
|
+
onClick: () => enter("cadence"),
|
|
4077
|
+
children: t("subscriptions.changeInterval")
|
|
4078
|
+
}
|
|
4079
|
+
),
|
|
4080
|
+
upcoming.capabilities.one_time_addons && /* @__PURE__ */ jsx(
|
|
4081
|
+
SubscriptionButton,
|
|
4082
|
+
{
|
|
4083
|
+
disabled: !upcoming.editable,
|
|
4084
|
+
onClick: () => enter("addons"),
|
|
4085
|
+
children: t("subscriptions.addNext")
|
|
4086
|
+
}
|
|
4087
|
+
),
|
|
4088
|
+
upcoming.capabilities.mix && /* @__PURE__ */ jsx(
|
|
4089
|
+
SubscriptionButton,
|
|
4090
|
+
{
|
|
4091
|
+
disabled: !upcoming.editable,
|
|
4092
|
+
onClick: () => enter("mix"),
|
|
4093
|
+
children: t("subscriptions.changeMix")
|
|
4094
|
+
}
|
|
4095
|
+
),
|
|
4096
|
+
s.allowed_actions.skip && /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => enter("skip"), children: t("subscriptions.skip") })
|
|
4097
|
+
] }) : /* @__PURE__ */ jsx(
|
|
4098
|
+
"form",
|
|
4099
|
+
{
|
|
4100
|
+
noValidate: true,
|
|
4101
|
+
onSubmit: async (event) => {
|
|
4102
|
+
event.preventDefault();
|
|
4103
|
+
if (busy || locked) return;
|
|
4104
|
+
setBusy(true);
|
|
4105
|
+
setError("");
|
|
4106
|
+
try {
|
|
4107
|
+
if (mode === "skip") {
|
|
4108
|
+
if (!consent) return;
|
|
4109
|
+
try {
|
|
4110
|
+
await api.skipNext(s.id, s.version);
|
|
4111
|
+
await refresh();
|
|
4112
|
+
} catch (err) {
|
|
4113
|
+
setLocked(true);
|
|
4114
|
+
throw err;
|
|
4115
|
+
}
|
|
4116
|
+
} else if (!quote) {
|
|
4117
|
+
const q = mode === "addons" ? await api.previewNextItems(s.id, { addons: selected }) : await api.previewChanges(s.id, changes);
|
|
4118
|
+
setQuote(q);
|
|
4119
|
+
setConsent(false);
|
|
4120
|
+
} else {
|
|
4121
|
+
if (!consent) return;
|
|
4122
|
+
try {
|
|
4123
|
+
if (mode === "addons")
|
|
4124
|
+
await api.commitNextItems(
|
|
4125
|
+
s.id,
|
|
4126
|
+
{ addons: selected },
|
|
4127
|
+
quote,
|
|
4128
|
+
"Customer explicitly accepted the displayed next-delivery contents and total"
|
|
4129
|
+
);
|
|
4130
|
+
else
|
|
4131
|
+
await api.commitChanges(
|
|
4132
|
+
s.id,
|
|
4133
|
+
changes,
|
|
4134
|
+
quote,
|
|
4135
|
+
"Customer explicitly accepted the displayed future terms, dates and price"
|
|
4136
|
+
);
|
|
4137
|
+
await refresh();
|
|
4138
|
+
} catch (err) {
|
|
4139
|
+
setLocked(true);
|
|
4140
|
+
throw err;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
} catch (err) {
|
|
4144
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4145
|
+
} finally {
|
|
4146
|
+
setBusy(false);
|
|
4147
|
+
}
|
|
4148
|
+
},
|
|
4149
|
+
style: {
|
|
4150
|
+
borderTop: "1px solid var(--nv-border)",
|
|
4151
|
+
paddingTop: "1rem",
|
|
4152
|
+
display: "grid",
|
|
4153
|
+
gap: "1rem"
|
|
4154
|
+
},
|
|
4155
|
+
children: /* @__PURE__ */ jsxs(
|
|
4156
|
+
"fieldset",
|
|
4157
|
+
{
|
|
4158
|
+
disabled: busy,
|
|
4159
|
+
style: {
|
|
4160
|
+
border: 0,
|
|
4161
|
+
margin: 0,
|
|
4162
|
+
padding: 0,
|
|
4163
|
+
display: "grid",
|
|
4164
|
+
gap: "1rem",
|
|
4165
|
+
minWidth: 0
|
|
4166
|
+
},
|
|
4167
|
+
children: [
|
|
4168
|
+
/* @__PURE__ */ jsx(
|
|
4169
|
+
"h3",
|
|
4170
|
+
{
|
|
4171
|
+
ref: heading,
|
|
4172
|
+
tabIndex: -1,
|
|
4173
|
+
style: { margin: 0, fontWeight: 500 },
|
|
4174
|
+
children: t(
|
|
4175
|
+
mode === "cadence" ? "subscriptions.changeInterval" : mode === "mix" ? "subscriptions.changeMix" : mode === "skip" ? "subscriptions.skip" : "subscriptions.addNext"
|
|
4176
|
+
)
|
|
4177
|
+
}
|
|
4178
|
+
),
|
|
4179
|
+
mode === "skip" ? /* @__PURE__ */ jsx("p", { children: t("subscriptions.skipWarning") }) : quote ? /* @__PURE__ */ jsxs("div", { role: "status", children: [
|
|
4180
|
+
/* @__PURE__ */ jsx("p", { children: t(
|
|
4181
|
+
mode === "addons" ? "subscriptions.onlyNext" : "subscriptions.futureOnly"
|
|
4182
|
+
) }),
|
|
4183
|
+
mode === "cadence" ? /* @__PURE__ */ jsxs("p", { children: [
|
|
4184
|
+
t("subscriptions.nextUnchanged"),
|
|
4185
|
+
":",
|
|
4186
|
+
" ",
|
|
4187
|
+
new Date(
|
|
4188
|
+
String(quote.after.next_at)
|
|
4189
|
+
).toLocaleString(loc)
|
|
4190
|
+
] }) : null,
|
|
4191
|
+
"upcoming_dates" in quote && quote.upcoming_dates?.map((date) => /* @__PURE__ */ jsx("p", { children: new Date(date).toLocaleString(loc) }, date)),
|
|
4192
|
+
mode === "addons" && "addons" in quote.after && quote.after.addons.map(
|
|
4193
|
+
(li) => /* @__PURE__ */ jsxs("p", { children: [
|
|
4194
|
+
li.title ?? li.sku ?? li.variant_id,
|
|
4195
|
+
" \xD7 ",
|
|
4196
|
+
li.quantity
|
|
4197
|
+
] }, li.variant_id)
|
|
4198
|
+
),
|
|
4199
|
+
"display_items" in quote && quote.display_items?.map((li) => /* @__PURE__ */ jsxs("p", { children: [
|
|
4200
|
+
li.title,
|
|
4201
|
+
" \xD7 ",
|
|
4202
|
+
li.quantity
|
|
4203
|
+
] }, li.variant_id)),
|
|
4204
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
4205
|
+
t("subscriptions.reviewTotal"),
|
|
4206
|
+
": ",
|
|
4207
|
+
money(amount)
|
|
4208
|
+
] })
|
|
4209
|
+
] }) : mode === "cadence" ? /* @__PURE__ */ jsx(
|
|
4210
|
+
"div",
|
|
4211
|
+
{
|
|
4212
|
+
role: "group",
|
|
4213
|
+
"aria-label": t("subscriptions.changeInterval"),
|
|
4214
|
+
style: { display: "flex", gap: ".5rem", flexWrap: "wrap" },
|
|
4215
|
+
children: upcoming.capabilities.cadence_options?.map((o) => /* @__PURE__ */ jsx(
|
|
4216
|
+
SubscriptionButton,
|
|
4217
|
+
{
|
|
4218
|
+
"aria-pressed": option === o.id,
|
|
4219
|
+
onClick: () => setOption(o.id),
|
|
4220
|
+
children: t(`subscriptions.unit.${o.unit}`, { count: o.count })
|
|
4221
|
+
},
|
|
4222
|
+
o.id
|
|
4223
|
+
))
|
|
4224
|
+
}
|
|
4225
|
+
) : /* @__PURE__ */ jsx(
|
|
4226
|
+
ProductSelection,
|
|
4227
|
+
{
|
|
4228
|
+
maxQuantity: mode === "mix" ? upcoming.capabilities.mix?.per_sku_max ?? 99 : 99,
|
|
4229
|
+
load: loader,
|
|
4230
|
+
value: quantities,
|
|
4231
|
+
onChange: setQuantities,
|
|
4232
|
+
initialItems: upcoming.lines.filter((li) => li.scope === scope).map((li) => ({
|
|
4233
|
+
...li,
|
|
4234
|
+
title: li.title ?? li.sku ?? li.variant_id,
|
|
4235
|
+
currency: s.currency,
|
|
4236
|
+
variant_title: li.variant_title ?? null,
|
|
4237
|
+
sku: li.sku ?? null
|
|
4238
|
+
})),
|
|
4239
|
+
locale: loc
|
|
4240
|
+
}
|
|
4241
|
+
),
|
|
4242
|
+
(quote || mode === "skip") && /* @__PURE__ */ jsxs(
|
|
4243
|
+
"label",
|
|
4244
|
+
{
|
|
4245
|
+
style: {
|
|
4246
|
+
display: "flex",
|
|
4247
|
+
gap: ".5rem",
|
|
4248
|
+
alignItems: "flex-start"
|
|
4249
|
+
},
|
|
4250
|
+
children: [
|
|
4251
|
+
/* @__PURE__ */ jsx(
|
|
4252
|
+
"input",
|
|
4253
|
+
{
|
|
4254
|
+
type: "checkbox",
|
|
4255
|
+
checked: consent,
|
|
4256
|
+
onChange: (e) => setConsent(e.target.checked)
|
|
4257
|
+
}
|
|
4258
|
+
),
|
|
4259
|
+
t("subscriptions.acceptChange")
|
|
4260
|
+
]
|
|
4261
|
+
}
|
|
4262
|
+
),
|
|
4263
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
|
|
4264
|
+
locked ? /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => void refresh(), children: t("subscriptions.reloadBeforeRetry") }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
|
|
4265
|
+
/* @__PURE__ */ jsx(
|
|
4266
|
+
SubscriptionButton,
|
|
4267
|
+
{
|
|
4268
|
+
disabled: busy,
|
|
4269
|
+
onClick: () => quote ? (setQuote(null), setConsent(false)) : enter(null),
|
|
4270
|
+
children: t(quote ? "subscriptions.back" : "account.cancel")
|
|
4271
|
+
}
|
|
4272
|
+
),
|
|
4273
|
+
/* @__PURE__ */ jsx(
|
|
4274
|
+
SubscriptionButton,
|
|
4275
|
+
{
|
|
4276
|
+
type: "submit",
|
|
4277
|
+
primary: true,
|
|
4278
|
+
disabled: busy || (quote || mode === "skip" ? !consent : mode === "cadence" ? !option : mode === "mix" ? !selected.length : false),
|
|
4279
|
+
children: busy ? t("subscriptions.loading") : t(
|
|
4280
|
+
quote || mode === "skip" ? "subscriptions.confirmChange" : "subscriptions.reviewChange"
|
|
4281
|
+
)
|
|
4282
|
+
}
|
|
4283
|
+
)
|
|
4284
|
+
] })
|
|
4285
|
+
]
|
|
4286
|
+
}
|
|
4287
|
+
)
|
|
4288
|
+
}
|
|
4289
|
+
)
|
|
4290
|
+
]
|
|
4291
|
+
}
|
|
4292
|
+
);
|
|
4293
|
+
}
|
|
3447
4294
|
function serializeJsonLd(data) {
|
|
3448
4295
|
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
3449
4296
|
}
|
|
@@ -3547,7 +4394,119 @@ function OrganizationJsonLd({
|
|
|
3547
4394
|
});
|
|
3548
4395
|
return /* @__PURE__ */ jsx(JsonLd, { data });
|
|
3549
4396
|
}
|
|
4397
|
+
function Mix({
|
|
4398
|
+
plan,
|
|
4399
|
+
marketId,
|
|
4400
|
+
locale,
|
|
4401
|
+
onAdded
|
|
4402
|
+
}) {
|
|
4403
|
+
const client = useStorefront(), cart = useCart(), t = useT(), [value, setValue] = React27.useState({}), [busy, setBusy] = React27.useState(false), [error, setError] = React27.useState(""), [locked, setLocked] = React27.useState(false);
|
|
4404
|
+
const load = React27.useCallback(
|
|
4405
|
+
(q, offset, signal) => client.subscriptions.mixProducts(plan.id, {
|
|
4406
|
+
q,
|
|
4407
|
+
offset,
|
|
4408
|
+
marketId,
|
|
4409
|
+
signal
|
|
4410
|
+
}),
|
|
4411
|
+
[client, plan.id, marketId]
|
|
4412
|
+
);
|
|
4413
|
+
const loc = useFormatLocale(locale);
|
|
4414
|
+
const mix = plan.capabilities?.mix;
|
|
4415
|
+
if (!mix) return null;
|
|
4416
|
+
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 }));
|
|
4417
|
+
const valid = units >= mix.min_units && units <= mix.max_units && lines.every(
|
|
4418
|
+
(li) => Number.isInteger(li.quantity) && li.quantity <= (mix.per_sku_max ?? 99)
|
|
4419
|
+
) && (mix.groups ?? []).every((g) => {
|
|
4420
|
+
const n = lines.filter((li) => g.variant_ids.includes(li.variant_id)).reduce((a, li) => a + li.quantity, 0);
|
|
4421
|
+
return n >= g.min_units && n <= g.max_units;
|
|
4422
|
+
});
|
|
4423
|
+
return /* @__PURE__ */ jsxs("section", { style: { ...subscriptionPanel, display: "grid", gap: "1rem" }, children: [
|
|
4424
|
+
/* @__PURE__ */ jsx("h2", { style: { fontWeight: 500, margin: 0 }, children: plan.name }),
|
|
4425
|
+
plan.price && /* @__PURE__ */ jsxs("p", { children: [
|
|
4426
|
+
formatCents(plan.price.amount_cents, plan.price.currency, loc),
|
|
4427
|
+
" \xB7",
|
|
4428
|
+
" ",
|
|
4429
|
+
t("subscriptions.mixPrice")
|
|
4430
|
+
] }),
|
|
4431
|
+
/* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.mixCount", {
|
|
4432
|
+
count: units,
|
|
4433
|
+
min: mix.min_units,
|
|
4434
|
+
max: mix.max_units
|
|
4435
|
+
}) }),
|
|
4436
|
+
mix.groups?.map((g) => /* @__PURE__ */ jsxs("p", { children: [
|
|
4437
|
+
g.name,
|
|
4438
|
+
": ",
|
|
4439
|
+
g.min_units,
|
|
4440
|
+
"\u2013",
|
|
4441
|
+
g.max_units
|
|
4442
|
+
] }, g.name)),
|
|
4443
|
+
/* @__PURE__ */ jsx(
|
|
4444
|
+
"fieldset",
|
|
4445
|
+
{
|
|
4446
|
+
disabled: busy || locked,
|
|
4447
|
+
style: { border: 0, padding: 0, margin: 0, minWidth: 0 },
|
|
4448
|
+
children: /* @__PURE__ */ jsx(
|
|
4449
|
+
ProductSelection,
|
|
4450
|
+
{
|
|
4451
|
+
maxQuantity: mix.per_sku_max ?? 99,
|
|
4452
|
+
load,
|
|
4453
|
+
value,
|
|
4454
|
+
onChange: (v) => {
|
|
4455
|
+
setValue(v);
|
|
4456
|
+
setError("");
|
|
4457
|
+
},
|
|
4458
|
+
locale
|
|
4459
|
+
}
|
|
4460
|
+
)
|
|
4461
|
+
}
|
|
4462
|
+
),
|
|
4463
|
+
error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
|
|
4464
|
+
locked ? /* @__PURE__ */ jsx(
|
|
4465
|
+
SubscriptionButton,
|
|
4466
|
+
{
|
|
4467
|
+
onClick: async () => {
|
|
4468
|
+
await cart.refresh();
|
|
4469
|
+
setLocked(false);
|
|
4470
|
+
},
|
|
4471
|
+
children: t("subscriptions.reloadBeforeRetry")
|
|
4472
|
+
}
|
|
4473
|
+
) : /* @__PURE__ */ jsx(
|
|
4474
|
+
SubscriptionButton,
|
|
4475
|
+
{
|
|
4476
|
+
primary: true,
|
|
4477
|
+
disabled: !valid || busy || cart.loading,
|
|
4478
|
+
onClick: async () => {
|
|
4479
|
+
setBusy(true);
|
|
4480
|
+
setError("");
|
|
4481
|
+
try {
|
|
4482
|
+
await cart.setSubscriptionMix(
|
|
4483
|
+
plan.id,
|
|
4484
|
+
lines,
|
|
4485
|
+
cart.cart?.line_items.filter((li) => li.subscription_plan_id).map((li) => li.id) ?? []
|
|
4486
|
+
);
|
|
4487
|
+
onAdded?.();
|
|
4488
|
+
} catch (err) {
|
|
4489
|
+
setLocked(true);
|
|
4490
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
4491
|
+
} finally {
|
|
4492
|
+
setBusy(false);
|
|
4493
|
+
}
|
|
4494
|
+
},
|
|
4495
|
+
children: t("subscriptions.addMix")
|
|
4496
|
+
}
|
|
4497
|
+
)
|
|
4498
|
+
] });
|
|
4499
|
+
}
|
|
4500
|
+
function SubscriptionMix(props) {
|
|
4501
|
+
return /* @__PURE__ */ jsx(
|
|
4502
|
+
Mix,
|
|
4503
|
+
{
|
|
4504
|
+
...props
|
|
4505
|
+
},
|
|
4506
|
+
`${props.plan.id}:${props.plan.version}:${props.marketId ?? ""}`
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
3550
4509
|
|
|
3551
|
-
export { AccountDashboard, AccountProvider, AccountSecurity, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, ShippingPicker, SortSelect, StatusBanner, StockBadge, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
|
|
4510
|
+
export { AccountDashboard, AccountProvider, AccountSecurity, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, PurchaseOptions, ShippingPicker, SortSelect, StatusBanner, StockBadge, SubscriptionList, SubscriptionManager, SubscriptionMix, SubscriptionTerms, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useCustomerSubscription, useCustomerSubscriptions, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
|
|
3552
4511
|
//# sourceMappingURL=index.js.map
|
|
3553
4512
|
//# sourceMappingURL=index.js.map
|