@nevios/storefront-kit 1.2.0 → 1.4.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 +480 -126
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +133 -7
- package/dist/index.d.ts +133 -7
- package/dist/index.js +475 -127
- 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",
|
|
@@ -248,6 +258,20 @@ var en = {
|
|
|
248
258
|
"account.signOut": "Sign out",
|
|
249
259
|
"account.greeting": "Signed in as {email}",
|
|
250
260
|
"account.orders": "Your orders",
|
|
261
|
+
"account.loadError": "Could not load your account. Please try again.",
|
|
262
|
+
"account.retry": "Try again",
|
|
263
|
+
"account.loadMore": "Load more",
|
|
264
|
+
"account.signInError": "Could not request a sign-in link. Please try again shortly.",
|
|
265
|
+
"account.security": "Account security",
|
|
266
|
+
"account.newEmail": "New email",
|
|
267
|
+
"account.changeEmail": "Verify new email",
|
|
268
|
+
"account.recentLogin": "Sign in again by email before making this change.",
|
|
269
|
+
"account.sessions": "Sessions",
|
|
270
|
+
"account.currentSession": "Current session",
|
|
271
|
+
"account.revokeSession": "Sign out",
|
|
272
|
+
"account.closeAccount": "Close account",
|
|
273
|
+
"account.closeWarning": "Closing your account signs out all devices and disables account access. Orders and invoices are retained. Contact the shop to restore access.",
|
|
274
|
+
"account.confirmClose": "Confirm account closure",
|
|
251
275
|
"account.noOrders": "No orders yet.",
|
|
252
276
|
"account.addresses": "Saved addresses",
|
|
253
277
|
"account.noAddresses": "No saved addresses yet.",
|
|
@@ -286,6 +310,16 @@ var en = {
|
|
|
286
310
|
|
|
287
311
|
// src/i18n/cs.ts
|
|
288
312
|
var cs = {
|
|
313
|
+
"subscriptions.purchaseOption": "Zp\u016Fsob n\xE1kupu",
|
|
314
|
+
"subscriptions.empty": "Zat\xEDm nem\xE1te \u017E\xE1dn\xE9 p\u0159edplatn\xE9.",
|
|
315
|
+
"subscriptions.status.active": "Aktivn\xED",
|
|
316
|
+
"subscriptions.status.paused": "Pozastaveno",
|
|
317
|
+
"subscriptions.status.pending": "\u010Cek\xE1 na platbu",
|
|
318
|
+
"subscriptions.status.cancelled": "Zru\u0161eno",
|
|
319
|
+
"subscriptions.title": "P\u0159edplatn\xE9",
|
|
320
|
+
"subscriptions.consent": "Souhlas\xEDm s uvedenou opakovanou cenou, pl\xE1nem doru\u010Den\xED a podm\xEDnkami.",
|
|
321
|
+
"subscriptions.eachDelivery": "Za ka\u017Ed\xE9 doru\u010Den\xED",
|
|
322
|
+
"subscriptions.once": "Jednor\xE1zov\xFD n\xE1kup",
|
|
289
323
|
"common.free": "Zdarma",
|
|
290
324
|
"common.close": "Zav\u0159\xEDt",
|
|
291
325
|
"common.loadingShort": "\u2026",
|
|
@@ -429,6 +463,20 @@ var cs = {
|
|
|
429
463
|
"account.signOut": "Odhl\xE1sit se",
|
|
430
464
|
"account.greeting": "P\u0159ihl\xE1\u0161en jako {email}",
|
|
431
465
|
"account.orders": "Va\u0161e objedn\xE1vky",
|
|
466
|
+
"account.loadError": "\xDAdaje se nepoda\u0159ilo na\u010D\xEDst. Zkuste to znovu.",
|
|
467
|
+
"account.retry": "Zkusit znovu",
|
|
468
|
+
"account.loadMore": "Na\u010D\xEDst dal\u0161\xED",
|
|
469
|
+
"account.signInError": "P\u0159ihl\xE1\u0161en\xED se nepoda\u0159ilo zah\xE1jit. Zkuste to za chv\xEDli znovu.",
|
|
470
|
+
"account.security": "Zabezpe\u010Den\xED \xFA\u010Dtu",
|
|
471
|
+
"account.newEmail": "Nov\xFD e-mail",
|
|
472
|
+
"account.changeEmail": "Ov\u011B\u0159it nov\xFD e-mail",
|
|
473
|
+
"account.recentLogin": "Pro tuto zm\u011Bnu se nejprve znovu p\u0159ihlaste p\u0159es e-mail.",
|
|
474
|
+
"account.sessions": "P\u0159ihl\xE1\u0161en\xED",
|
|
475
|
+
"account.currentSession": "Toto p\u0159ihl\xE1\u0161en\xED",
|
|
476
|
+
"account.revokeSession": "Odhl\xE1sit",
|
|
477
|
+
"account.closeAccount": "Uzav\u0159\xEDt \xFA\u010Det",
|
|
478
|
+
"account.closeWarning": "Uzav\u0159en\xEDm \xFA\u010Dtu ukon\u010D\xEDte p\u0159\xEDstup a odhl\xE1s\xEDte v\u0161echna za\u0159\xEDzen\xED. Objedn\xE1vky a doklady z\u016Fstanou zachov\xE1ny. Obnoven\xED p\u0159\xEDstupu \u0159e\u0161\xED podpora obchodu.",
|
|
479
|
+
"account.confirmClose": "Potvrdit uzav\u0159en\xED \xFA\u010Dtu",
|
|
432
480
|
"account.noOrders": "Zat\xEDm \u017E\xE1dn\xE9 objedn\xE1vky.",
|
|
433
481
|
"account.addresses": "Ulo\u017Een\xE9 adresy",
|
|
434
482
|
"account.noAddresses": "Zat\xEDm \u017E\xE1dn\xE9 ulo\u017Een\xE9 adresy.",
|
|
@@ -513,13 +561,13 @@ function makeI18n(locale, messages) {
|
|
|
513
561
|
return { locale, messages, t };
|
|
514
562
|
}
|
|
515
563
|
var DEFAULT_I18N = makeI18n("en", en);
|
|
516
|
-
var I18nContext =
|
|
564
|
+
var I18nContext = React27__namespace.createContext(null);
|
|
517
565
|
function I18nProvider({
|
|
518
566
|
locale,
|
|
519
567
|
messages,
|
|
520
568
|
children
|
|
521
569
|
}) {
|
|
522
|
-
const value =
|
|
570
|
+
const value = React27__namespace.useMemo(() => {
|
|
523
571
|
const lang = langOf(locale);
|
|
524
572
|
if (locale && !(lang in BUNDLED) && !messages) {
|
|
525
573
|
devWarn(
|
|
@@ -533,7 +581,7 @@ function I18nProvider({
|
|
|
533
581
|
return /* @__PURE__ */ jsxRuntime.jsx(I18nContext.Provider, { value, children });
|
|
534
582
|
}
|
|
535
583
|
function useI18n() {
|
|
536
|
-
return
|
|
584
|
+
return React27__namespace.useContext(I18nContext) ?? DEFAULT_I18N;
|
|
537
585
|
}
|
|
538
586
|
function useT() {
|
|
539
587
|
return useI18n().t;
|
|
@@ -657,7 +705,7 @@ function ProductCard({ product, href, locale }) {
|
|
|
657
705
|
var MAIN_WIDTHS = [600, 800, 1e3];
|
|
658
706
|
var THUMB_WIDTHS = [56, 112];
|
|
659
707
|
function ProductGallery({ media, alt }) {
|
|
660
|
-
const [active, setActive] =
|
|
708
|
+
const [active, setActive] = React27__namespace.useState(0);
|
|
661
709
|
const imageBase = useStorefrontOptional()?.imageBase;
|
|
662
710
|
if (media.length === 0) {
|
|
663
711
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -745,10 +793,10 @@ function matchVariant(product, selected) {
|
|
|
745
793
|
) ?? null;
|
|
746
794
|
}
|
|
747
795
|
function VariantPicker({ product, onChange }) {
|
|
748
|
-
const [selected, setSelected] =
|
|
796
|
+
const [selected, setSelected] = React27__namespace.useState(
|
|
749
797
|
() => product.variants[0]?.options ?? []
|
|
750
798
|
);
|
|
751
|
-
|
|
799
|
+
React27__namespace.useEffect(() => {
|
|
752
800
|
onChange?.(matchVariant(product, selected));
|
|
753
801
|
}, [product, selected]);
|
|
754
802
|
if (product.options.length === 0) return null;
|
|
@@ -789,8 +837,8 @@ function BackInStockForm({
|
|
|
789
837
|
}) {
|
|
790
838
|
const t = useT();
|
|
791
839
|
const client = useStorefront();
|
|
792
|
-
const [email, setEmail] =
|
|
793
|
-
const [state, setState] =
|
|
840
|
+
const [email, setEmail] = React27__namespace.useState(defaultEmail);
|
|
841
|
+
const [state, setState] = React27__namespace.useState("idle");
|
|
794
842
|
if (!variant || variant.availability?.status !== "out_of_stock") return null;
|
|
795
843
|
async function submit(e) {
|
|
796
844
|
e.preventDefault();
|
|
@@ -890,7 +938,7 @@ function ProductDetailPage({
|
|
|
890
938
|
locale,
|
|
891
939
|
addToCart
|
|
892
940
|
}) {
|
|
893
|
-
const [variant, setVariant] =
|
|
941
|
+
const [variant, setVariant] = React27__namespace.useState(
|
|
894
942
|
product.variants[0] ?? null
|
|
895
943
|
);
|
|
896
944
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -1122,12 +1170,26 @@ function TagFilter({ tags, selected = [], onChange }) {
|
|
|
1122
1170
|
}) })
|
|
1123
1171
|
] });
|
|
1124
1172
|
}
|
|
1125
|
-
|
|
1173
|
+
function PurchaseOptions({ plans, value, onChange, disabled, locale }) {
|
|
1174
|
+
const t = useT();
|
|
1175
|
+
const loc = useFormatLocale(locale);
|
|
1176
|
+
const name = React27__namespace.useId();
|
|
1177
|
+
const options = [{ id: null, name: t("subscriptions.once"), price: void 0 }, ...plans];
|
|
1178
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { disabled, style: { border: 0, padding: 0, margin: 0, display: "grid", gap: "0.5rem" }, children: [
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { style: { marginBottom: "0.5rem", fontWeight: 500 }, children: t("subscriptions.purchaseOption") }),
|
|
1180
|
+
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: [
|
|
1181
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name, checked: value === option.id, onChange: () => onChange(option.id) }),
|
|
1182
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1 }, children: option.name }),
|
|
1183
|
+
option.price ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatCents(option.price.amount_cents, option.price.currency, loc) }) : null
|
|
1184
|
+
] }, option.id ?? "once"))
|
|
1185
|
+
] });
|
|
1186
|
+
}
|
|
1187
|
+
var CartContext = React27__namespace.createContext(null);
|
|
1126
1188
|
function CartProvider({ children }) {
|
|
1127
1189
|
const client = useStorefront();
|
|
1128
|
-
const [cart, setCart] =
|
|
1129
|
-
const [loading, setLoading] =
|
|
1130
|
-
const run =
|
|
1190
|
+
const [cart, setCart] = React27__namespace.useState(null);
|
|
1191
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
1192
|
+
const run = React27__namespace.useCallback(
|
|
1131
1193
|
async (op) => {
|
|
1132
1194
|
setLoading(true);
|
|
1133
1195
|
try {
|
|
@@ -1140,7 +1202,7 @@ function CartProvider({ children }) {
|
|
|
1140
1202
|
},
|
|
1141
1203
|
[]
|
|
1142
1204
|
);
|
|
1143
|
-
const refresh =
|
|
1205
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
1144
1206
|
setLoading(true);
|
|
1145
1207
|
try {
|
|
1146
1208
|
setCart(await client.cart.get());
|
|
@@ -1148,16 +1210,17 @@ function CartProvider({ children }) {
|
|
|
1148
1210
|
setLoading(false);
|
|
1149
1211
|
}
|
|
1150
1212
|
}, [client]);
|
|
1151
|
-
|
|
1213
|
+
React27__namespace.useEffect(() => {
|
|
1152
1214
|
void refresh();
|
|
1153
1215
|
}, [refresh]);
|
|
1154
|
-
const value =
|
|
1216
|
+
const value = React27__namespace.useMemo(
|
|
1155
1217
|
() => ({
|
|
1156
1218
|
cart,
|
|
1157
1219
|
loading,
|
|
1158
1220
|
itemCount: cart?.totals.item_count ?? 0,
|
|
1159
1221
|
addLine: (input) => run(() => client.cart.addLine(input)),
|
|
1160
1222
|
updateLine: (lineId, quantity) => run(() => client.cart.updateLine(lineId, quantity)),
|
|
1223
|
+
clearSubscription: () => run(() => client.cart.clearSubscription()),
|
|
1161
1224
|
removeLine: (lineId) => run(() => client.cart.removeLine(lineId)),
|
|
1162
1225
|
refresh
|
|
1163
1226
|
}),
|
|
@@ -1166,7 +1229,7 @@ function CartProvider({ children }) {
|
|
|
1166
1229
|
return /* @__PURE__ */ jsxRuntime.jsx(CartContext.Provider, { value, children });
|
|
1167
1230
|
}
|
|
1168
1231
|
function useCart() {
|
|
1169
|
-
const ctx =
|
|
1232
|
+
const ctx = React27__namespace.useContext(CartContext);
|
|
1170
1233
|
if (!ctx) {
|
|
1171
1234
|
throw new Error("useCart must be used inside <CartProvider>.");
|
|
1172
1235
|
}
|
|
@@ -1175,13 +1238,14 @@ function useCart() {
|
|
|
1175
1238
|
function AddToCart({
|
|
1176
1239
|
variant,
|
|
1177
1240
|
quantity = 1,
|
|
1241
|
+
subscriptionPlanId,
|
|
1178
1242
|
children,
|
|
1179
1243
|
onAdded
|
|
1180
1244
|
}) {
|
|
1181
1245
|
const t = useT();
|
|
1182
1246
|
const { addLine } = useCart();
|
|
1183
|
-
const [busy, setBusy] =
|
|
1184
|
-
const [error, setError] =
|
|
1247
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
1248
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
1185
1249
|
const disabled = variant == null || busy;
|
|
1186
1250
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
1187
1251
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1194,7 +1258,7 @@ function AddToCart({
|
|
|
1194
1258
|
setBusy(true);
|
|
1195
1259
|
setError(null);
|
|
1196
1260
|
try {
|
|
1197
|
-
await addLine({ variant_id: variant.id, quantity });
|
|
1261
|
+
await addLine({ variant_id: variant.id, quantity, ...subscriptionPlanId ? { subscription_plan_id: subscriptionPlanId } : {} });
|
|
1198
1262
|
onAdded?.();
|
|
1199
1263
|
} catch (err) {
|
|
1200
1264
|
const code = err?.code;
|
|
@@ -1223,7 +1287,7 @@ function AddToCart({
|
|
|
1223
1287
|
function CartLineItem({ line, locale }) {
|
|
1224
1288
|
const t = useT();
|
|
1225
1289
|
const loc = useFormatLocale(locale);
|
|
1226
|
-
const { updateLine, removeLine } = useCart();
|
|
1290
|
+
const { cart, updateLine, removeLine } = useCart();
|
|
1227
1291
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.85rem", alignItems: "flex-start", padding: "0.75rem 0" }, children: [
|
|
1228
1292
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1229
1293
|
"div",
|
|
@@ -1252,6 +1316,7 @@ function CartLineItem({ line, locale }) {
|
|
|
1252
1316
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
1253
1317
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontWeight: 500, lineHeight: 1.3 }, children: line.title }),
|
|
1254
1318
|
line.variant_title ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: line.variant_title }) : null,
|
|
1319
|
+
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,
|
|
1255
1320
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", marginTop: "0.5rem" }, children: [
|
|
1256
1321
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1257
1322
|
"div",
|
|
@@ -1330,7 +1395,7 @@ function CartSummary({
|
|
|
1330
1395
|
const { cart } = useCart();
|
|
1331
1396
|
if (cart == null) return null;
|
|
1332
1397
|
const t = cart.totals;
|
|
1333
|
-
const empty = t.item_count === 0;
|
|
1398
|
+
const empty = t.item_count === 0 || cart.subscription?.available === false;
|
|
1334
1399
|
const cta = checkoutLabel ?? tr("cart.checkout");
|
|
1335
1400
|
const ctaStyle = {
|
|
1336
1401
|
display: "block",
|
|
@@ -1351,6 +1416,7 @@ function CartSummary({
|
|
|
1351
1416
|
t.tax_cents > 0 ? /* @__PURE__ */ jsxRuntime.jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, loc), muted: true }) : null,
|
|
1352
1417
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { height: 1, background: "var(--nv-border)" } }),
|
|
1353
1418
|
/* @__PURE__ */ jsxRuntime.jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, loc), strong: true }),
|
|
1419
|
+
cart.subscription?.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", style: { margin: 0 }, children: cart.subscription.error.message }) : null,
|
|
1354
1420
|
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 })
|
|
1355
1421
|
] });
|
|
1356
1422
|
}
|
|
@@ -1457,15 +1523,15 @@ function CartDrawer({
|
|
|
1457
1523
|
}
|
|
1458
1524
|
);
|
|
1459
1525
|
}
|
|
1460
|
-
var CheckoutContext =
|
|
1526
|
+
var CheckoutContext = React27__namespace.createContext(null);
|
|
1461
1527
|
function CheckoutProvider({
|
|
1462
1528
|
children
|
|
1463
1529
|
}) {
|
|
1464
1530
|
const client = useStorefront();
|
|
1465
|
-
const [checkout, setCheckout] =
|
|
1466
|
-
const [loading, setLoading] =
|
|
1467
|
-
const [error, setError] =
|
|
1468
|
-
const run =
|
|
1531
|
+
const [checkout, setCheckout] = React27__namespace.useState(null);
|
|
1532
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
1533
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
1534
|
+
const run = React27__namespace.useCallback(
|
|
1469
1535
|
async (op, store) => {
|
|
1470
1536
|
setLoading(true);
|
|
1471
1537
|
try {
|
|
@@ -1483,23 +1549,36 @@ function CheckoutProvider({
|
|
|
1483
1549
|
},
|
|
1484
1550
|
[]
|
|
1485
1551
|
);
|
|
1486
|
-
const refresh =
|
|
1552
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
1487
1553
|
setLoading(true);
|
|
1488
1554
|
try {
|
|
1489
1555
|
setCheckout(await client.checkout.get());
|
|
1490
1556
|
setError(null);
|
|
1557
|
+
} catch (err) {
|
|
1558
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
1491
1559
|
} finally {
|
|
1492
1560
|
setLoading(false);
|
|
1493
1561
|
}
|
|
1494
1562
|
}, [client]);
|
|
1495
|
-
|
|
1563
|
+
React27__namespace.useEffect(() => {
|
|
1496
1564
|
void refresh();
|
|
1497
1565
|
}, [refresh]);
|
|
1498
|
-
const pay =
|
|
1566
|
+
const pay = React27__namespace.useCallback(
|
|
1499
1567
|
async (opts = {}) => {
|
|
1500
1568
|
const co = checkout;
|
|
1501
1569
|
if (!co) throw new Error("pay() called before a checkout exists");
|
|
1502
1570
|
const idempotencyKey = opts.idempotencyKey ?? co.token;
|
|
1571
|
+
if (co.subscription) {
|
|
1572
|
+
if (co.subscription.subscription_id) {
|
|
1573
|
+
const payment = await run(() => client.checkout.subscriptionPayment(), () => null);
|
|
1574
|
+
setCheckout(await client.checkout.get());
|
|
1575
|
+
return { kind: "subscription_payment", payment };
|
|
1576
|
+
}
|
|
1577
|
+
if (!opts.subscription) throw new Error("Accept the current subscription terms before paying");
|
|
1578
|
+
const subscription = await run(() => client.checkout.enrollSubscription(opts.subscription), () => null);
|
|
1579
|
+
setCheckout(await client.checkout.get());
|
|
1580
|
+
return { kind: "subscription", subscription };
|
|
1581
|
+
}
|
|
1503
1582
|
if (co.payment_category === "gateway") {
|
|
1504
1583
|
const start = await run(
|
|
1505
1584
|
() => client.checkout.startPayment({ return_url: opts.returnUrl, cancel_url: opts.cancelUrl }),
|
|
@@ -1518,11 +1597,18 @@ function CheckoutProvider({
|
|
|
1518
1597
|
},
|
|
1519
1598
|
[checkout, client, run]
|
|
1520
1599
|
);
|
|
1521
|
-
const value =
|
|
1600
|
+
const value = React27__namespace.useMemo(
|
|
1522
1601
|
() => ({
|
|
1523
1602
|
checkout,
|
|
1524
1603
|
loading,
|
|
1525
1604
|
error,
|
|
1605
|
+
selectSubscription: (selection) => run(async () => {
|
|
1606
|
+
const quote = await client.checkout.selectSubscription(selection);
|
|
1607
|
+
setCheckout(await client.checkout.get());
|
|
1608
|
+
return quote;
|
|
1609
|
+
}, () => null),
|
|
1610
|
+
clearSubscription: () => run(() => client.checkout.clearSubscription(), (r) => r),
|
|
1611
|
+
reviewSubscription: () => run(() => client.checkout.reviewSubscription(), () => null),
|
|
1526
1612
|
create: (cartToken, input) => run(() => client.checkout.create(cartToken, input), (r) => r),
|
|
1527
1613
|
setAddress: (input) => run(() => client.checkout.setAddress(input), (r) => r),
|
|
1528
1614
|
shippingQuotes: () => run(() => client.checkout.shippingQuotes(), () => null),
|
|
@@ -1543,7 +1629,7 @@ function CheckoutProvider({
|
|
|
1543
1629
|
return /* @__PURE__ */ jsxRuntime.jsx(CheckoutContext.Provider, { value, children });
|
|
1544
1630
|
}
|
|
1545
1631
|
function useCheckout() {
|
|
1546
|
-
const ctx =
|
|
1632
|
+
const ctx = React27__namespace.useContext(CheckoutContext);
|
|
1547
1633
|
if (!ctx) {
|
|
1548
1634
|
throw new Error("useCheckout must be used inside <CheckoutProvider>.");
|
|
1549
1635
|
}
|
|
@@ -1702,10 +1788,25 @@ function AddressForm({
|
|
|
1702
1788
|
}) {
|
|
1703
1789
|
const t = useT();
|
|
1704
1790
|
const { checkout, setAddress, loading } = useCheckout();
|
|
1705
|
-
const [form, setForm] =
|
|
1791
|
+
const [form, setForm] = React27__namespace.useState(
|
|
1706
1792
|
() => seed(checkout?.email ?? null, checkout?.shipping_address ?? null)
|
|
1707
1793
|
);
|
|
1708
|
-
const
|
|
1794
|
+
const touched = React27__namespace.useRef(/* @__PURE__ */ new Set());
|
|
1795
|
+
React27__namespace.useEffect(() => {
|
|
1796
|
+
const prefill = checkout?.customer_prefill;
|
|
1797
|
+
const addresses = prefill?.addresses.filter((a) => !a.is_pickup);
|
|
1798
|
+
const saved = addresses?.find((a) => a.is_default) ?? addresses?.[0];
|
|
1799
|
+
const address = checkout?.shipping_address ?? (saved ? { ...saved, postal_code: saved.zip } : null);
|
|
1800
|
+
const defaults = seed(checkout?.email ?? prefill?.email ?? null, address);
|
|
1801
|
+
defaults.first_name || (defaults.first_name = prefill?.first_name ?? "");
|
|
1802
|
+
defaults.last_name || (defaults.last_name = prefill?.last_name ?? "");
|
|
1803
|
+
defaults.phone || (defaults.phone = prefill?.phone ?? "");
|
|
1804
|
+
setForm((current) => Object.fromEntries(Object.entries(current).map(([key, value]) => [key, touched.current.has(key) ? value : value || defaults[key]])));
|
|
1805
|
+
}, [checkout?.token]);
|
|
1806
|
+
const set = (key) => (e) => {
|
|
1807
|
+
touched.current.add(key);
|
|
1808
|
+
setForm((f) => ({ ...f, [key]: e.target.value }));
|
|
1809
|
+
};
|
|
1709
1810
|
async function submit(e) {
|
|
1710
1811
|
e.preventDefault();
|
|
1711
1812
|
const { email, ...rest } = form;
|
|
@@ -1756,11 +1857,11 @@ function ShippingPicker({
|
|
|
1756
1857
|
const loc = useFormatLocale(locale);
|
|
1757
1858
|
const { checkout, shippingQuotes, setShippingMethod, loading } = useCheckout();
|
|
1758
1859
|
const heading = title ?? t("checkout.shippingMethod");
|
|
1759
|
-
const [quotes, setQuotes] =
|
|
1760
|
-
const [selected, setSelected] =
|
|
1761
|
-
const [fetching, setFetching] =
|
|
1860
|
+
const [quotes, setQuotes] = React27__namespace.useState(null);
|
|
1861
|
+
const [selected, setSelected] = React27__namespace.useState(null);
|
|
1862
|
+
const [fetching, setFetching] = React27__namespace.useState(true);
|
|
1762
1863
|
const addressKey = checkout?.shipping_address ? `${checkout.shipping_address.country_code}:${checkout.shipping_address.postal_code}` : null;
|
|
1763
|
-
|
|
1864
|
+
React27__namespace.useEffect(() => {
|
|
1764
1865
|
let live = true;
|
|
1765
1866
|
setFetching(true);
|
|
1766
1867
|
shippingQuotes().then((qs) => {
|
|
@@ -1788,17 +1889,13 @@ function ShippingPicker({
|
|
|
1788
1889
|
}
|
|
1789
1890
|
return /* @__PURE__ */ jsxRuntime.jsxs(Section, { title: heading, children: [
|
|
1790
1891
|
/* @__PURE__ */ jsxRuntime.jsx("div", { role: "radiogroup", style: { display: "flex", flexDirection: "column", gap: "0.6rem" }, children: quotes.map((q) => {
|
|
1791
|
-
const days = estDays(q, t);
|
|
1792
1892
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1793
1893
|
SelectRow,
|
|
1794
1894
|
{
|
|
1795
1895
|
selected: selected === q.rate_id,
|
|
1796
1896
|
onSelect: () => setSelected(q.rate_id),
|
|
1797
1897
|
trailing: q.is_free ? t("common.free") : formatCents(q.price_cents, q.currency, loc),
|
|
1798
|
-
children: /* @__PURE__ */ jsxRuntime.
|
|
1799
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: q.label }),
|
|
1800
|
-
days ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: days }) : null
|
|
1801
|
-
] })
|
|
1898
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: q.label }) })
|
|
1802
1899
|
},
|
|
1803
1900
|
q.rate_id
|
|
1804
1901
|
);
|
|
@@ -1806,15 +1903,6 @@ function ShippingPicker({
|
|
|
1806
1903
|
/* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: submit, busy: loading, disabled: selected == null, children: submitLabel ?? t("checkout.continueToPayment") }) })
|
|
1807
1904
|
] });
|
|
1808
1905
|
}
|
|
1809
|
-
function estDays(q, t) {
|
|
1810
|
-
const { estimated_days_min: lo, estimated_days_max: hi } = q;
|
|
1811
|
-
if (lo == null && hi == null) return null;
|
|
1812
|
-
if (lo != null && hi != null) {
|
|
1813
|
-
return lo === hi ? t("shipping.days", { count: lo }) : t("shipping.daysRange", { min: lo, max: hi });
|
|
1814
|
-
}
|
|
1815
|
-
const n = lo ?? hi;
|
|
1816
|
-
return t("shipping.days", { count: n });
|
|
1817
|
-
}
|
|
1818
1906
|
function PaymentMethodPicker({
|
|
1819
1907
|
methods,
|
|
1820
1908
|
value,
|
|
@@ -1848,20 +1936,45 @@ function PaymentMethodPicker({
|
|
|
1848
1936
|
}
|
|
1849
1937
|
);
|
|
1850
1938
|
}
|
|
1939
|
+
function SubscriptionTerms({ quote, checked, onChange, locale }) {
|
|
1940
|
+
const t = useT();
|
|
1941
|
+
const loc = useFormatLocale(locale);
|
|
1942
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { style: { margin: 0, padding: "1rem", border: "1px solid var(--nv-border)", borderRadius: "var(--nv-radius)" }, children: [
|
|
1943
|
+
/* @__PURE__ */ jsxRuntime.jsx("legend", { children: t("subscriptions.title") }),
|
|
1944
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0 0 0.5rem", fontWeight: 500 }, children: [
|
|
1945
|
+
t("subscriptions.eachDelivery"),
|
|
1946
|
+
": ",
|
|
1947
|
+
formatCents(quote.recurring_total_cents, quote.currency, loc)
|
|
1948
|
+
] }),
|
|
1949
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 0.75rem", whiteSpace: "pre-wrap" }, children: quote.terms_text }),
|
|
1950
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { style: { display: "flex", alignItems: "flex-start", gap: "0.6rem", cursor: "pointer" }, children: [
|
|
1951
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked, onChange: (event) => onChange(event.target.checked) }),
|
|
1952
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t("subscriptions.consent") })
|
|
1953
|
+
] })
|
|
1954
|
+
] });
|
|
1955
|
+
}
|
|
1851
1956
|
function PaymentSection({
|
|
1852
1957
|
title,
|
|
1853
1958
|
returnUrl,
|
|
1854
1959
|
cancelUrl,
|
|
1855
1960
|
onOrderPlaced,
|
|
1856
|
-
locale
|
|
1961
|
+
locale,
|
|
1962
|
+
onSubscriptionPayment
|
|
1857
1963
|
}) {
|
|
1858
1964
|
const t = useT();
|
|
1859
1965
|
const heading = title ?? t("checkout.payment");
|
|
1860
|
-
const { checkout, paymentMethods, setPaymentMethod, pay, loading } = useCheckout();
|
|
1861
|
-
const [methods, setMethods] =
|
|
1862
|
-
const [fetching, setFetching] =
|
|
1966
|
+
const { checkout, paymentMethods, setPaymentMethod, pay, loading, refresh } = useCheckout();
|
|
1967
|
+
const [methods, setMethods] = React27__namespace.useState(null);
|
|
1968
|
+
const [fetching, setFetching] = React27__namespace.useState(true);
|
|
1863
1969
|
const selected = checkout?.payment_method ?? null;
|
|
1864
|
-
|
|
1970
|
+
const [acceptedDigest, setAcceptedDigest] = React27__namespace.useState(null);
|
|
1971
|
+
const [actionBusy, setActionBusy] = React27__namespace.useState(false);
|
|
1972
|
+
const quote = checkout?.subscription?.quote;
|
|
1973
|
+
const needsConsent = Boolean(checkout?.subscription?.requires_consent);
|
|
1974
|
+
const clickInFlight = React27__namespace.useRef(false);
|
|
1975
|
+
const [actionError, setActionError] = React27__namespace.useState(null);
|
|
1976
|
+
const accepted = Boolean(quote && acceptedDigest === quote.quote_digest);
|
|
1977
|
+
React27__namespace.useEffect(() => {
|
|
1865
1978
|
let live = true;
|
|
1866
1979
|
setFetching(true);
|
|
1867
1980
|
paymentMethods().then((ms) => {
|
|
@@ -1874,12 +1987,30 @@ function PaymentSection({
|
|
|
1874
1987
|
return () => {
|
|
1875
1988
|
live = false;
|
|
1876
1989
|
};
|
|
1877
|
-
}, []);
|
|
1990
|
+
}, [checkout?.token, checkout?.shipping_method_id, checkout?.subscription?.selection.plan_id]);
|
|
1878
1991
|
const selectedMethod = methods?.find((m) => m.method === selected) ?? null;
|
|
1879
1992
|
const isGateway = selectedMethod?.category === "gateway";
|
|
1880
1993
|
async function placeOrder() {
|
|
1881
|
-
|
|
1882
|
-
|
|
1994
|
+
if (clickInFlight.current) return;
|
|
1995
|
+
clickInFlight.current = true;
|
|
1996
|
+
setActionBusy(true);
|
|
1997
|
+
setActionError(null);
|
|
1998
|
+
try {
|
|
1999
|
+
const result = await pay({
|
|
2000
|
+
returnUrl,
|
|
2001
|
+
cancelUrl,
|
|
2002
|
+
...needsConsent && quote && accepted ? { subscription: { quote, consent: true } } : {}
|
|
2003
|
+
});
|
|
2004
|
+
if (result.kind === "subscription") await onSubscriptionPayment?.(result.subscription.payment);
|
|
2005
|
+
if (result.kind === "subscription_payment") await onSubscriptionPayment?.(result.payment);
|
|
2006
|
+
if (result.kind === "subscription" || result.kind === "subscription_payment") await refresh();
|
|
2007
|
+
if (result.kind === "order") onOrderPlaced?.(result.checkout);
|
|
2008
|
+
} catch (err) {
|
|
2009
|
+
setActionError(err instanceof Error ? err.message : String(err));
|
|
2010
|
+
} finally {
|
|
2011
|
+
clickInFlight.current = false;
|
|
2012
|
+
setActionBusy(false);
|
|
2013
|
+
}
|
|
1883
2014
|
}
|
|
1884
2015
|
if (fetching && methods == null) {
|
|
1885
2016
|
return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: heading, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("checkout.loadingPayment") }) });
|
|
@@ -1899,9 +2030,12 @@ function PaymentSection({
|
|
|
1899
2030
|
label: heading
|
|
1900
2031
|
}
|
|
1901
2032
|
),
|
|
2033
|
+
quote && needsConsent ? /* @__PURE__ */ jsxRuntime.jsx(SubscriptionTerms, { locale, quote, checked: accepted, onChange: (value) => setAcceptedDigest(value && quote ? quote.quote_digest : null) }) : null,
|
|
2034
|
+
checkout?.subscription?.error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: checkout.subscription.error.message }) : null,
|
|
2035
|
+
actionError ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: actionError }) : null,
|
|
1902
2036
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1903
|
-
/* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: placeOrder, busy: loading, disabled: selected == null, children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
|
|
1904
|
-
isGateway ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
2037
|
+
/* @__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") }),
|
|
2038
|
+
isGateway && !checkout?.subscription ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
|
|
1905
2039
|
] })
|
|
1906
2040
|
] });
|
|
1907
2041
|
}
|
|
@@ -1989,6 +2123,7 @@ function reachedStep(checkout) {
|
|
|
1989
2123
|
}
|
|
1990
2124
|
function CheckoutFlow({
|
|
1991
2125
|
cartToken,
|
|
2126
|
+
onSubscriptionPayment,
|
|
1992
2127
|
onOrderPlaced,
|
|
1993
2128
|
emptyState,
|
|
1994
2129
|
renderConfirmation,
|
|
@@ -1998,19 +2133,19 @@ function CheckoutFlow({
|
|
|
1998
2133
|
}) {
|
|
1999
2134
|
const t = useT();
|
|
2000
2135
|
const { checkout, loading, create } = useCheckout();
|
|
2001
|
-
const [step, setStep] =
|
|
2002
|
-
const [bootstrapped, setBootstrapped] =
|
|
2003
|
-
|
|
2136
|
+
const [step, setStep] = React27__namespace.useState("address");
|
|
2137
|
+
const [bootstrapped, setBootstrapped] = React27__namespace.useState(false);
|
|
2138
|
+
React27__namespace.useEffect(() => {
|
|
2004
2139
|
if (checkout || !cartToken || bootstrapped) return;
|
|
2005
2140
|
setBootstrapped(true);
|
|
2006
2141
|
void create(cartToken).catch(() => {
|
|
2007
2142
|
});
|
|
2008
2143
|
}, [checkout, cartToken, bootstrapped, create]);
|
|
2009
2144
|
const serverStep = reachedStep(checkout);
|
|
2010
|
-
|
|
2145
|
+
React27__namespace.useEffect(() => {
|
|
2011
2146
|
setStep((cur) => rank(serverStep) > rank(cur) ? serverStep : cur);
|
|
2012
2147
|
}, [serverStep]);
|
|
2013
|
-
if (checkout?.order_id) {
|
|
2148
|
+
if (checkout?.order_id && (!checkout.subscription || checkout.phase === "success")) {
|
|
2014
2149
|
return /* @__PURE__ */ jsxRuntime.jsx(Shell, { summary: /* @__PURE__ */ jsxRuntime.jsx(OrderSummary, { locale }), children: renderConfirmation ? renderConfirmation(checkout) : /* @__PURE__ */ jsxRuntime.jsx(DefaultConfirmation, { checkout }) });
|
|
2015
2150
|
}
|
|
2016
2151
|
if (!checkout) {
|
|
@@ -2024,6 +2159,7 @@ function CheckoutFlow({
|
|
|
2024
2159
|
step === "address" ? /* @__PURE__ */ jsxRuntime.jsx(AddressForm, { onComplete: () => setStep("shipping") }) : step === "shipping" ? /* @__PURE__ */ jsxRuntime.jsx(ShippingPicker, { onComplete: () => setStep("payment"), locale }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2025
2160
|
PaymentSection,
|
|
2026
2161
|
{
|
|
2162
|
+
onSubscriptionPayment,
|
|
2027
2163
|
returnUrl,
|
|
2028
2164
|
cancelUrl,
|
|
2029
2165
|
onOrderPlaced,
|
|
@@ -2128,10 +2264,10 @@ function DefaultConfirmation({ checkout }) {
|
|
|
2128
2264
|
}
|
|
2129
2265
|
function useOrderStatus(statusToken, initial) {
|
|
2130
2266
|
const client = useStorefront();
|
|
2131
|
-
const [order, setOrder] =
|
|
2132
|
-
const [loading, setLoading] =
|
|
2133
|
-
const [error, setError] =
|
|
2134
|
-
const load =
|
|
2267
|
+
const [order, setOrder] = React27__namespace.useState(initial ?? null);
|
|
2268
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
2269
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
2270
|
+
const load = React27__namespace.useCallback(async () => {
|
|
2135
2271
|
if (!statusToken) return;
|
|
2136
2272
|
setLoading(true);
|
|
2137
2273
|
try {
|
|
@@ -2143,7 +2279,7 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2143
2279
|
setLoading(false);
|
|
2144
2280
|
}
|
|
2145
2281
|
}, [client, statusToken]);
|
|
2146
|
-
|
|
2282
|
+
React27__namespace.useEffect(() => {
|
|
2147
2283
|
if (initial) return;
|
|
2148
2284
|
void load();
|
|
2149
2285
|
}, [load, initial]);
|
|
@@ -2151,12 +2287,12 @@ function useOrderStatus(statusToken, initial) {
|
|
|
2151
2287
|
}
|
|
2152
2288
|
function usePayment(token, initial) {
|
|
2153
2289
|
const client = useStorefront();
|
|
2154
|
-
const [payment, setPayment] =
|
|
2155
|
-
const [methods, setMethods] =
|
|
2156
|
-
const [loading, setLoading] =
|
|
2157
|
-
const [busy, setBusy] =
|
|
2158
|
-
const [error, setError] =
|
|
2159
|
-
const load =
|
|
2290
|
+
const [payment, setPayment] = React27__namespace.useState(initial ?? null);
|
|
2291
|
+
const [methods, setMethods] = React27__namespace.useState([]);
|
|
2292
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
2293
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
2294
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
2295
|
+
const load = React27__namespace.useCallback(async () => {
|
|
2160
2296
|
if (!token) return;
|
|
2161
2297
|
setLoading(true);
|
|
2162
2298
|
try {
|
|
@@ -2173,7 +2309,7 @@ function usePayment(token, initial) {
|
|
|
2173
2309
|
setLoading(false);
|
|
2174
2310
|
}
|
|
2175
2311
|
}, [client, token]);
|
|
2176
|
-
|
|
2312
|
+
React27__namespace.useEffect(() => {
|
|
2177
2313
|
if (!token) return;
|
|
2178
2314
|
if (initial && initial.token === token) {
|
|
2179
2315
|
setPayment(initial);
|
|
@@ -2182,7 +2318,7 @@ function usePayment(token, initial) {
|
|
|
2182
2318
|
}
|
|
2183
2319
|
void load();
|
|
2184
2320
|
}, [token, load]);
|
|
2185
|
-
const selectMethod =
|
|
2321
|
+
const selectMethod = React27__namespace.useCallback(
|
|
2186
2322
|
async (method) => {
|
|
2187
2323
|
if (!token) return null;
|
|
2188
2324
|
setBusy(true);
|
|
@@ -2200,7 +2336,7 @@ function usePayment(token, initial) {
|
|
|
2200
2336
|
},
|
|
2201
2337
|
[client, token]
|
|
2202
2338
|
);
|
|
2203
|
-
const startPayment =
|
|
2339
|
+
const startPayment = React27__namespace.useCallback(
|
|
2204
2340
|
async (input) => {
|
|
2205
2341
|
if (!token) return null;
|
|
2206
2342
|
setBusy(true);
|
|
@@ -2637,8 +2773,8 @@ function Row4({
|
|
|
2637
2773
|
}
|
|
2638
2774
|
function CopyRow({ label, value }) {
|
|
2639
2775
|
const t = useT();
|
|
2640
|
-
const [copied, setCopied] =
|
|
2641
|
-
|
|
2776
|
+
const [copied, setCopied] = React27__namespace.useState(false);
|
|
2777
|
+
React27__namespace.useEffect(() => {
|
|
2642
2778
|
if (!copied) return;
|
|
2643
2779
|
const id = setTimeout(() => setCopied(false), 1800);
|
|
2644
2780
|
return () => clearTimeout(id);
|
|
@@ -2722,12 +2858,12 @@ function PaymentPage({
|
|
|
2722
2858
|
const t = useT();
|
|
2723
2859
|
const loc = useFormatLocale(locale);
|
|
2724
2860
|
const { payment, methods, loading, busy, error, reload, selectMethod, startPayment } = usePayment(token, initial);
|
|
2725
|
-
const [verifying, setVerifying] =
|
|
2726
|
-
|
|
2861
|
+
const [verifying, setVerifying] = React27__namespace.useState(false);
|
|
2862
|
+
React27__namespace.useEffect(() => {
|
|
2727
2863
|
if (token) setVerifying(consumeHandoff(token));
|
|
2728
2864
|
}, [token]);
|
|
2729
2865
|
const settled = payment != null && !payment.can_pay && payment.blocked_reason === "already_paid";
|
|
2730
|
-
|
|
2866
|
+
React27__namespace.useEffect(() => {
|
|
2731
2867
|
if (!verifying || !token || settled) return;
|
|
2732
2868
|
let attempts = 0;
|
|
2733
2869
|
const id = setInterval(() => {
|
|
@@ -2742,7 +2878,7 @@ function PaymentPage({
|
|
|
2742
2878
|
}, POLL_MS);
|
|
2743
2879
|
return () => clearInterval(id);
|
|
2744
2880
|
}, [verifying, token, settled, reload]);
|
|
2745
|
-
|
|
2881
|
+
React27__namespace.useEffect(() => {
|
|
2746
2882
|
if (!settled || !token || !payment) return;
|
|
2747
2883
|
setVerifying(false);
|
|
2748
2884
|
forgetHandoff(token);
|
|
@@ -2823,27 +2959,32 @@ function Amount({ label, value }) {
|
|
|
2823
2959
|
function Link({ href, children }) {
|
|
2824
2960
|
return /* @__PURE__ */ jsxRuntime.jsx("a", { href, style: { color: "var(--nv-brand)", fontSize: "0.875rem" }, children });
|
|
2825
2961
|
}
|
|
2826
|
-
var AccountContext =
|
|
2962
|
+
var AccountContext = React27__namespace.createContext(null);
|
|
2827
2963
|
function AccountProvider({ children }) {
|
|
2828
2964
|
const client = useStorefront();
|
|
2829
|
-
const [customer, setCustomer] =
|
|
2830
|
-
const [loading, setLoading] =
|
|
2831
|
-
const
|
|
2965
|
+
const [customer, setCustomer] = React27__namespace.useState(null);
|
|
2966
|
+
const [loading, setLoading] = React27__namespace.useState(true);
|
|
2967
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
2968
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
2832
2969
|
setLoading(true);
|
|
2970
|
+
setError(false);
|
|
2833
2971
|
try {
|
|
2834
2972
|
setCustomer(await client.account.me());
|
|
2973
|
+
} catch {
|
|
2974
|
+
setError(true);
|
|
2835
2975
|
} finally {
|
|
2836
2976
|
setLoading(false);
|
|
2837
2977
|
}
|
|
2838
2978
|
}, [client]);
|
|
2839
|
-
|
|
2979
|
+
React27__namespace.useEffect(() => {
|
|
2840
2980
|
void refresh();
|
|
2841
2981
|
}, [refresh]);
|
|
2842
|
-
const value =
|
|
2982
|
+
const value = React27__namespace.useMemo(
|
|
2843
2983
|
() => ({
|
|
2844
2984
|
customer,
|
|
2845
2985
|
signedIn: customer != null,
|
|
2846
2986
|
loading,
|
|
2987
|
+
error,
|
|
2847
2988
|
requestMagicLink: (email) => client.account.requestMagicLink(email),
|
|
2848
2989
|
verify: async (token) => {
|
|
2849
2990
|
const c = await client.account.verify(token);
|
|
@@ -2856,12 +2997,12 @@ function AccountProvider({ children }) {
|
|
|
2856
2997
|
},
|
|
2857
2998
|
refresh
|
|
2858
2999
|
}),
|
|
2859
|
-
[customer, loading, client, refresh]
|
|
3000
|
+
[customer, loading, error, client, refresh]
|
|
2860
3001
|
);
|
|
2861
3002
|
return /* @__PURE__ */ jsxRuntime.jsx(AccountContext.Provider, { value, children });
|
|
2862
3003
|
}
|
|
2863
3004
|
function useAccount() {
|
|
2864
|
-
const ctx =
|
|
3005
|
+
const ctx = React27__namespace.useContext(AccountContext);
|
|
2865
3006
|
if (!ctx) {
|
|
2866
3007
|
throw new Error("useAccount must be used inside <AccountProvider>.");
|
|
2867
3008
|
}
|
|
@@ -2869,9 +3010,9 @@ function useAccount() {
|
|
|
2869
3010
|
}
|
|
2870
3011
|
function useMagicLinkVerify(token) {
|
|
2871
3012
|
const { verify } = useAccount();
|
|
2872
|
-
const [status, setStatus] =
|
|
2873
|
-
const done =
|
|
2874
|
-
|
|
3013
|
+
const [status, setStatus] = React27__namespace.useState("idle");
|
|
3014
|
+
const done = React27__namespace.useRef(false);
|
|
3015
|
+
React27__namespace.useEffect(() => {
|
|
2875
3016
|
if (!token || done.current) return;
|
|
2876
3017
|
done.current = true;
|
|
2877
3018
|
setStatus("verifying");
|
|
@@ -2882,18 +3023,22 @@ function useMagicLinkVerify(token) {
|
|
|
2882
3023
|
function LoginForm({ verifyPath = "/account/verify", onSent }) {
|
|
2883
3024
|
const t = useT();
|
|
2884
3025
|
const { requestMagicLink } = useAccount();
|
|
2885
|
-
const [email, setEmail] =
|
|
2886
|
-
const [busy, setBusy] =
|
|
2887
|
-
const [sent, setSent] =
|
|
2888
|
-
const [
|
|
3026
|
+
const [email, setEmail] = React27__namespace.useState("");
|
|
3027
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3028
|
+
const [sent, setSent] = React27__namespace.useState(false);
|
|
3029
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3030
|
+
const [devLink, setDevLink] = React27__namespace.useState(null);
|
|
2889
3031
|
async function submit(e) {
|
|
2890
3032
|
e.preventDefault();
|
|
2891
3033
|
setBusy(true);
|
|
3034
|
+
setError(false);
|
|
2892
3035
|
try {
|
|
2893
3036
|
const r = await requestMagicLink(email);
|
|
2894
3037
|
setSent(true);
|
|
2895
3038
|
if (r.devToken) setDevLink(`${verifyPath}?token=${encodeURIComponent(r.devToken)}`);
|
|
2896
3039
|
onSent?.(email);
|
|
3040
|
+
} catch {
|
|
3041
|
+
setError(true);
|
|
2897
3042
|
} finally {
|
|
2898
3043
|
setBusy(false);
|
|
2899
3044
|
}
|
|
@@ -2932,6 +3077,7 @@ function LoginForm({ verifyPath = "/account/verify", onSent }) {
|
|
|
2932
3077
|
}
|
|
2933
3078
|
)
|
|
2934
3079
|
] }),
|
|
3080
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "alert", children: t("account.signInError") }) : null,
|
|
2935
3081
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2936
3082
|
"button",
|
|
2937
3083
|
{
|
|
@@ -2993,20 +3139,47 @@ function OrderHistory({
|
|
|
2993
3139
|
const loc = useFormatLocale(locale);
|
|
2994
3140
|
const { signedIn } = useAccount();
|
|
2995
3141
|
const client = useStorefront();
|
|
2996
|
-
const [orders, setOrders] =
|
|
2997
|
-
const [
|
|
2998
|
-
const [
|
|
2999
|
-
|
|
3142
|
+
const [orders, setOrders] = React27__namespace.useState(null);
|
|
3143
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3144
|
+
const [nextCursor, setNextCursor] = React27__namespace.useState(null);
|
|
3145
|
+
const [pageLoading, setPageLoading] = React27__namespace.useState(false);
|
|
3146
|
+
const [reload, setReload] = React27__namespace.useState(0);
|
|
3147
|
+
const [reordering, setReordering] = React27__namespace.useState(null);
|
|
3148
|
+
const [notice, setNotice] = React27__namespace.useState(null);
|
|
3149
|
+
React27__namespace.useEffect(() => {
|
|
3000
3150
|
let live = true;
|
|
3001
3151
|
if (!signedIn) {
|
|
3002
3152
|
setOrders([]);
|
|
3003
3153
|
return;
|
|
3004
3154
|
}
|
|
3005
|
-
|
|
3155
|
+
setError(false);
|
|
3156
|
+
setOrders(null);
|
|
3157
|
+
client.account.ordersPage().then((page) => {
|
|
3158
|
+
if (live) {
|
|
3159
|
+
setOrders(page.orders);
|
|
3160
|
+
setNextCursor(page.nextCursor);
|
|
3161
|
+
}
|
|
3162
|
+
}).catch(() => {
|
|
3163
|
+
if (live) setError(true);
|
|
3164
|
+
});
|
|
3006
3165
|
return () => {
|
|
3007
3166
|
live = false;
|
|
3008
3167
|
};
|
|
3009
|
-
}, [signedIn, client]);
|
|
3168
|
+
}, [signedIn, client, reload]);
|
|
3169
|
+
async function loadMore() {
|
|
3170
|
+
if (!nextCursor || pageLoading) return;
|
|
3171
|
+
setPageLoading(true);
|
|
3172
|
+
setError(false);
|
|
3173
|
+
try {
|
|
3174
|
+
const page = await client.account.ordersPage({ beforeId: nextCursor });
|
|
3175
|
+
setOrders((previous) => [...previous ?? [], ...page.orders]);
|
|
3176
|
+
setNextCursor(page.nextCursor);
|
|
3177
|
+
} catch {
|
|
3178
|
+
setError(true);
|
|
3179
|
+
} finally {
|
|
3180
|
+
setPageLoading(false);
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3010
3183
|
async function reorder(statusToken) {
|
|
3011
3184
|
setReordering(statusToken);
|
|
3012
3185
|
setNotice(null);
|
|
@@ -3027,7 +3200,11 @@ function OrderHistory({
|
|
|
3027
3200
|
}
|
|
3028
3201
|
return /* @__PURE__ */ jsxRuntime.jsxs("section", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
|
|
3029
3202
|
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "1.1rem", fontWeight: 600 }, children: t("account.orders") }),
|
|
3030
|
-
|
|
3203
|
+
error ? /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "alert", children: [
|
|
3204
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: t("account.loadError") }),
|
|
3205
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => orders ? void loadMore() : setReload((x) => x + 1), children: t("account.retry") })
|
|
3206
|
+
] }) : null,
|
|
3207
|
+
orders == null ? error ? null : /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("common.loadingShort") }) : orders.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("account.noOrders") }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.5rem" }, children: orders.map((o) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3031
3208
|
"li",
|
|
3032
3209
|
{
|
|
3033
3210
|
style: {
|
|
@@ -3074,12 +3251,13 @@ function OrderHistory({
|
|
|
3074
3251
|
]
|
|
3075
3252
|
},
|
|
3076
3253
|
o.status_token
|
|
3077
|
-
)) })
|
|
3254
|
+
)) }),
|
|
3255
|
+
nextCursor ? /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", disabled: pageLoading, onClick: () => void loadMore(), children: pageLoading ? t("common.loadingShort") : t("account.loadMore") }) : null
|
|
3078
3256
|
] });
|
|
3079
3257
|
}
|
|
3080
3258
|
function AddressForm2({ initial, onSubmit, onCancel, busy }) {
|
|
3081
3259
|
const t = useT();
|
|
3082
|
-
const [form, setForm] =
|
|
3260
|
+
const [form, setForm] = React27__namespace.useState(() => ({
|
|
3083
3261
|
first_name: initial?.first_name ?? "",
|
|
3084
3262
|
last_name: initial?.last_name ?? "",
|
|
3085
3263
|
company: initial?.company ?? "",
|
|
@@ -3177,17 +3355,17 @@ function AddressBook() {
|
|
|
3177
3355
|
const t = useT();
|
|
3178
3356
|
const { signedIn } = useAccount();
|
|
3179
3357
|
const client = useStorefront();
|
|
3180
|
-
const [addresses, setAddresses] =
|
|
3181
|
-
const [mode, setMode] =
|
|
3182
|
-
const [busy, setBusy] =
|
|
3183
|
-
const load =
|
|
3358
|
+
const [addresses, setAddresses] = React27__namespace.useState(null);
|
|
3359
|
+
const [mode, setMode] = React27__namespace.useState({ kind: "list" });
|
|
3360
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3361
|
+
const load = React27__namespace.useCallback(() => {
|
|
3184
3362
|
if (!signedIn) {
|
|
3185
3363
|
setAddresses([]);
|
|
3186
3364
|
return;
|
|
3187
3365
|
}
|
|
3188
3366
|
client.account.addresses().then(setAddresses).catch(() => setAddresses([]));
|
|
3189
3367
|
}, [signedIn, client]);
|
|
3190
|
-
|
|
3368
|
+
React27__namespace.useEffect(() => {
|
|
3191
3369
|
load();
|
|
3192
3370
|
}, [load]);
|
|
3193
3371
|
async function run(fn) {
|
|
@@ -3241,10 +3419,10 @@ function ProfileEditor() {
|
|
|
3241
3419
|
const t = useT();
|
|
3242
3420
|
const client = useStorefront();
|
|
3243
3421
|
const { customer, refresh } = useAccount();
|
|
3244
|
-
const [editing, setEditing] =
|
|
3245
|
-
const [busy, setBusy] =
|
|
3246
|
-
const [error, setError] =
|
|
3247
|
-
const [form, setForm] =
|
|
3422
|
+
const [editing, setEditing] = React27__namespace.useState(false);
|
|
3423
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3424
|
+
const [error, setError] = React27__namespace.useState(false);
|
|
3425
|
+
const [form, setForm] = React27__namespace.useState({});
|
|
3248
3426
|
if (!customer) return null;
|
|
3249
3427
|
function startEdit() {
|
|
3250
3428
|
setForm({
|
|
@@ -3321,6 +3499,176 @@ function ProfileEditor() {
|
|
|
3321
3499
|
] })
|
|
3322
3500
|
] });
|
|
3323
3501
|
}
|
|
3502
|
+
function AccountSecurity() {
|
|
3503
|
+
const client = useStorefront();
|
|
3504
|
+
const account = useAccount();
|
|
3505
|
+
const t = useT();
|
|
3506
|
+
const [sessions, setSessions] = React27__namespace.useState(null);
|
|
3507
|
+
const [email, setEmail] = React27__namespace.useState("");
|
|
3508
|
+
const [busy, setBusy] = React27__namespace.useState(false);
|
|
3509
|
+
const [notice, setNotice] = React27__namespace.useState(null);
|
|
3510
|
+
const [confirmClose, setConfirmClose] = React27__namespace.useState(false);
|
|
3511
|
+
const [retry, setRetry] = React27__namespace.useState(0);
|
|
3512
|
+
React27__namespace.useEffect(() => {
|
|
3513
|
+
let live = true;
|
|
3514
|
+
if (account.signedIn) client.account.sessions().then((s) => {
|
|
3515
|
+
if (live) setSessions(s);
|
|
3516
|
+
}).catch(() => {
|
|
3517
|
+
if (live) setNotice(t("account.loadError"));
|
|
3518
|
+
});
|
|
3519
|
+
return () => {
|
|
3520
|
+
live = false;
|
|
3521
|
+
};
|
|
3522
|
+
}, [client, account.signedIn, retry, t]);
|
|
3523
|
+
async function act(operation) {
|
|
3524
|
+
setBusy(true);
|
|
3525
|
+
setNotice(null);
|
|
3526
|
+
try {
|
|
3527
|
+
await operation();
|
|
3528
|
+
} catch (e) {
|
|
3529
|
+
setNotice(e instanceof storefrontJs.StorefrontError && e.status === 401 ? t("account.recentLogin") : t("account.loadError"));
|
|
3530
|
+
} finally {
|
|
3531
|
+
setBusy(false);
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
if (!account.signedIn) return null;
|
|
3535
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { style: { display: "grid", gap: "0.75rem" }, children: [
|
|
3536
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: 0, fontSize: "1.1rem", fontWeight: 600 }, children: t("account.security") }),
|
|
3537
|
+
notice ? /* @__PURE__ */ jsxRuntime.jsx("p", { role: "status", children: notice }) : null,
|
|
3538
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3539
|
+
"form",
|
|
3540
|
+
{
|
|
3541
|
+
style: { display: "flex", gap: "0.75rem", flexWrap: "wrap", alignItems: "end" },
|
|
3542
|
+
onSubmit: (e) => {
|
|
3543
|
+
e.preventDefault();
|
|
3544
|
+
void act(async () => {
|
|
3545
|
+
await client.account.requestEmailChange(email);
|
|
3546
|
+
setNotice(t("account.checkEmail"));
|
|
3547
|
+
});
|
|
3548
|
+
},
|
|
3549
|
+
children: [
|
|
3550
|
+
/* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("account.newEmail"), type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }),
|
|
3551
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", busy, children: t("account.changeEmail") })
|
|
3552
|
+
]
|
|
3553
|
+
}
|
|
3554
|
+
),
|
|
3555
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { children: t("account.sessions") }),
|
|
3556
|
+
sessions ? sessions.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", justifyContent: "space-between" }, children: [
|
|
3557
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: s.current ? t("account.currentSession") : /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: s.created_at, children: s.created_at.slice(0, 10) }) }),
|
|
3558
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy, onClick: () => void act(async () => {
|
|
3559
|
+
await client.account.revokeSession(s.id);
|
|
3560
|
+
if (s.current) await account.logout();
|
|
3561
|
+
else setSessions((old) => old?.filter((row) => row.id !== s.id) ?? null);
|
|
3562
|
+
}), children: t("account.revokeSession") })
|
|
3563
|
+
] }, s.id)) : /* @__PURE__ */ jsxRuntime.jsx(Button, { onClick: () => setRetry((x) => x + 1), children: t("account.retry") }),
|
|
3564
|
+
confirmClose ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3565
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: t("account.closeWarning") }),
|
|
3566
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { busy, onClick: () => void act(async () => {
|
|
3567
|
+
await client.account.close();
|
|
3568
|
+
await account.refresh();
|
|
3569
|
+
}), children: t("account.confirmClose") }),
|
|
3570
|
+
" ",
|
|
3571
|
+
/* @__PURE__ */ jsxRuntime.jsx(Button, { disabled: busy, onClick: () => setConfirmClose(false), children: t("account.cancel") })
|
|
3572
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(Button, { disabled: busy, onClick: () => setConfirmClose(true), children: t("account.closeAccount") })
|
|
3573
|
+
] });
|
|
3574
|
+
}
|
|
3575
|
+
function useCustomerSubscriptions() {
|
|
3576
|
+
const client = useStorefront();
|
|
3577
|
+
const { customer } = useAccount();
|
|
3578
|
+
const [dataOwner, setDataOwner] = React27__namespace.useState(void 0);
|
|
3579
|
+
const [subscriptions, setSubscriptions] = React27__namespace.useState([]);
|
|
3580
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
3581
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
3582
|
+
const generation = React27__namespace.useRef(0);
|
|
3583
|
+
const owner = customer?.id;
|
|
3584
|
+
const api = client.account.subscriptions;
|
|
3585
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
3586
|
+
const current = ++generation.current;
|
|
3587
|
+
setError(null);
|
|
3588
|
+
if (!owner) {
|
|
3589
|
+
setSubscriptions([]);
|
|
3590
|
+
setLoading(false);
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
setLoading(true);
|
|
3594
|
+
try {
|
|
3595
|
+
const rows = await api.list();
|
|
3596
|
+
if (current === generation.current) {
|
|
3597
|
+
setSubscriptions(rows);
|
|
3598
|
+
setDataOwner(owner);
|
|
3599
|
+
setError(null);
|
|
3600
|
+
}
|
|
3601
|
+
} catch (err) {
|
|
3602
|
+
if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
|
|
3603
|
+
} finally {
|
|
3604
|
+
if (current === generation.current) setLoading(false);
|
|
3605
|
+
}
|
|
3606
|
+
}, [owner, api]);
|
|
3607
|
+
React27__namespace.useEffect(() => {
|
|
3608
|
+
setSubscriptions([]);
|
|
3609
|
+
void refresh();
|
|
3610
|
+
return () => {
|
|
3611
|
+
generation.current++;
|
|
3612
|
+
};
|
|
3613
|
+
}, [refresh]);
|
|
3614
|
+
return { subscriptions: dataOwner === owner ? subscriptions : [], loading, error, refresh, api };
|
|
3615
|
+
}
|
|
3616
|
+
function useCustomerSubscription(id) {
|
|
3617
|
+
const client = useStorefront();
|
|
3618
|
+
const { customer } = useAccount();
|
|
3619
|
+
const [dataKey, setDataKey] = React27__namespace.useState("");
|
|
3620
|
+
const [subscription, setSubscription] = React27__namespace.useState(null);
|
|
3621
|
+
const [loading, setLoading] = React27__namespace.useState(false);
|
|
3622
|
+
const [error, setError] = React27__namespace.useState(null);
|
|
3623
|
+
const generation = React27__namespace.useRef(0);
|
|
3624
|
+
const owner = customer?.id;
|
|
3625
|
+
const api = client.account.subscriptions;
|
|
3626
|
+
const refresh = React27__namespace.useCallback(async () => {
|
|
3627
|
+
const current = ++generation.current;
|
|
3628
|
+
setError(null);
|
|
3629
|
+
if (!owner || !id) {
|
|
3630
|
+
setSubscription(null);
|
|
3631
|
+
setLoading(false);
|
|
3632
|
+
return;
|
|
3633
|
+
}
|
|
3634
|
+
setLoading(true);
|
|
3635
|
+
try {
|
|
3636
|
+
const row = await api.management(id);
|
|
3637
|
+
if (current === generation.current) {
|
|
3638
|
+
setSubscription(row);
|
|
3639
|
+
setDataKey(`${owner}:${id}`);
|
|
3640
|
+
setError(null);
|
|
3641
|
+
}
|
|
3642
|
+
} catch (err) {
|
|
3643
|
+
if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
|
|
3644
|
+
} finally {
|
|
3645
|
+
if (current === generation.current) setLoading(false);
|
|
3646
|
+
}
|
|
3647
|
+
}, [owner, id, api]);
|
|
3648
|
+
React27__namespace.useEffect(() => {
|
|
3649
|
+
setSubscription(null);
|
|
3650
|
+
void refresh();
|
|
3651
|
+
return () => {
|
|
3652
|
+
generation.current++;
|
|
3653
|
+
};
|
|
3654
|
+
}, [refresh]);
|
|
3655
|
+
return { subscription: dataKey === `${owner}:${id}` ? subscription : null, loading, error, refresh, api };
|
|
3656
|
+
}
|
|
3657
|
+
function SubscriptionList({ href, locale }) {
|
|
3658
|
+
const t = useT();
|
|
3659
|
+
const { subscriptions, loading, error } = useCustomerSubscriptions();
|
|
3660
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("section", { style: { fontFamily: "var(--nv-font)", color: "var(--nv-fg)" }, children: [
|
|
3661
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.125rem", fontWeight: 500 }, children: t("subscriptions.title") }),
|
|
3662
|
+
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: [
|
|
3663
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
3664
|
+
"#",
|
|
3665
|
+
sub.id
|
|
3666
|
+
] }),
|
|
3667
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`subscriptions.status.${sub.status}`) }),
|
|
3668
|
+
sub.status === "active" && sub.next_at ? /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: sub.next_at, children: new Date(sub.next_at).toLocaleDateString(locale) }) : null
|
|
3669
|
+
] }) }, sub.id)) })
|
|
3670
|
+
] });
|
|
3671
|
+
}
|
|
3324
3672
|
function serializeJsonLd(data) {
|
|
3325
3673
|
return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
|
|
3326
3674
|
}
|
|
@@ -3427,6 +3775,7 @@ function OrganizationJsonLd({
|
|
|
3427
3775
|
|
|
3428
3776
|
exports.AccountDashboard = AccountDashboard;
|
|
3429
3777
|
exports.AccountProvider = AccountProvider;
|
|
3778
|
+
exports.AccountSecurity = AccountSecurity;
|
|
3430
3779
|
exports.AddToCart = AddToCart;
|
|
3431
3780
|
exports.AddressBook = AddressBook;
|
|
3432
3781
|
exports.AddressForm = AddressForm;
|
|
@@ -3463,10 +3812,13 @@ exports.ProductDetailPage = ProductDetailPage;
|
|
|
3463
3812
|
exports.ProductGallery = ProductGallery;
|
|
3464
3813
|
exports.ProductJsonLd = ProductJsonLd;
|
|
3465
3814
|
exports.ProfileEditor = ProfileEditor;
|
|
3815
|
+
exports.PurchaseOptions = PurchaseOptions;
|
|
3466
3816
|
exports.ShippingPicker = ShippingPicker;
|
|
3467
3817
|
exports.SortSelect = SortSelect;
|
|
3468
3818
|
exports.StatusBanner = StatusBanner;
|
|
3469
3819
|
exports.StockBadge = StockBadge;
|
|
3820
|
+
exports.SubscriptionList = SubscriptionList;
|
|
3821
|
+
exports.SubscriptionTerms = SubscriptionTerms;
|
|
3470
3822
|
exports.TagFilter = TagFilter;
|
|
3471
3823
|
exports.ThemeProvider = ThemeProvider;
|
|
3472
3824
|
exports.TransferInstructions = TransferInstructions;
|
|
@@ -3484,6 +3836,8 @@ exports.themeToCssVars = themeToCssVars;
|
|
|
3484
3836
|
exports.useAccount = useAccount;
|
|
3485
3837
|
exports.useCart = useCart;
|
|
3486
3838
|
exports.useCheckout = useCheckout;
|
|
3839
|
+
exports.useCustomerSubscription = useCustomerSubscription;
|
|
3840
|
+
exports.useCustomerSubscriptions = useCustomerSubscriptions;
|
|
3487
3841
|
exports.useI18n = useI18n;
|
|
3488
3842
|
exports.useMagicLinkVerify = useMagicLinkVerify;
|
|
3489
3843
|
exports.useOrderStatus = useOrderStatus;
|