@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/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React10 = require('react');
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 React10__namespace = /*#__PURE__*/_interopNamespace(React10);
25
+ var React27__namespace = /*#__PURE__*/_interopNamespace(React27);
26
26
 
27
27
  // src/provider.tsx
28
- var StorefrontContext = React10__namespace.createContext(null);
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 = React10__namespace.useContext(StorefrontContext);
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 React10__namespace.useContext(StorefrontContext);
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 React10__namespace.createElement(
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 = React10__namespace.createContext(null);
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 = React10__namespace.useMemo(() => {
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 React10__namespace.useContext(I18nContext) ?? DEFAULT_I18N;
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] = React10__namespace.useState(0);
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] = React10__namespace.useState(
796
+ const [selected, setSelected] = React27__namespace.useState(
749
797
  () => product.variants[0]?.options ?? []
750
798
  );
751
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState(defaultEmail);
793
- const [state, setState] = React10__namespace.useState("idle");
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] = React10__namespace.useState(
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
- var CartContext = React10__namespace.createContext(null);
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] = React10__namespace.useState(null);
1129
- const [loading, setLoading] = React10__namespace.useState(false);
1130
- const run = React10__namespace.useCallback(
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 = React10__namespace.useCallback(async () => {
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
- React10__namespace.useEffect(() => {
1213
+ React27__namespace.useEffect(() => {
1152
1214
  void refresh();
1153
1215
  }, [refresh]);
1154
- const value = React10__namespace.useMemo(
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 = React10__namespace.useContext(CartContext);
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] = React10__namespace.useState(false);
1184
- const [error, setError] = React10__namespace.useState(null);
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 = React10__namespace.createContext(null);
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] = React10__namespace.useState(null);
1466
- const [loading, setLoading] = React10__namespace.useState(false);
1467
- const [error, setError] = React10__namespace.useState(null);
1468
- const run = React10__namespace.useCallback(
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 = React10__namespace.useCallback(async () => {
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
- React10__namespace.useEffect(() => {
1563
+ React27__namespace.useEffect(() => {
1496
1564
  void refresh();
1497
1565
  }, [refresh]);
1498
- const pay = React10__namespace.useCallback(
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 = React10__namespace.useMemo(
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 = React10__namespace.useContext(CheckoutContext);
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] = React10__namespace.useState(
1791
+ const [form, setForm] = React27__namespace.useState(
1706
1792
  () => seed(checkout?.email ?? null, checkout?.shipping_address ?? null)
1707
1793
  );
1708
- const set = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value }));
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] = React10__namespace.useState(null);
1760
- const [selected, setSelected] = React10__namespace.useState(null);
1761
- const [fetching, setFetching] = React10__namespace.useState(true);
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
- React10__namespace.useEffect(() => {
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.jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
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] = React10__namespace.useState(null);
1862
- const [fetching, setFetching] = React10__namespace.useState(true);
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
- React10__namespace.useEffect(() => {
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
- const result = await pay({ returnUrl, cancelUrl });
1882
- if (result.kind === "order") onOrderPlaced?.(result.checkout);
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] = React10__namespace.useState("address");
2002
- const [bootstrapped, setBootstrapped] = React10__namespace.useState(false);
2003
- React10__namespace.useEffect(() => {
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
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState(initial ?? null);
2132
- const [loading, setLoading] = React10__namespace.useState(false);
2133
- const [error, setError] = React10__namespace.useState(null);
2134
- const load = React10__namespace.useCallback(async () => {
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
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState(initial ?? null);
2155
- const [methods, setMethods] = React10__namespace.useState([]);
2156
- const [loading, setLoading] = React10__namespace.useState(false);
2157
- const [busy, setBusy] = React10__namespace.useState(false);
2158
- const [error, setError] = React10__namespace.useState(null);
2159
- const load = React10__namespace.useCallback(async () => {
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
- React10__namespace.useEffect(() => {
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 = React10__namespace.useCallback(
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 = React10__namespace.useCallback(
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] = React10__namespace.useState(false);
2641
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState(false);
2726
- React10__namespace.useEffect(() => {
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
- React10__namespace.useEffect(() => {
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
- React10__namespace.useEffect(() => {
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 = React10__namespace.createContext(null);
2962
+ var AccountContext = React27__namespace.createContext(null);
2827
2963
  function AccountProvider({ children }) {
2828
2964
  const client = useStorefront();
2829
- const [customer, setCustomer] = React10__namespace.useState(null);
2830
- const [loading, setLoading] = React10__namespace.useState(true);
2831
- const refresh = React10__namespace.useCallback(async () => {
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
- React10__namespace.useEffect(() => {
2979
+ React27__namespace.useEffect(() => {
2840
2980
  void refresh();
2841
2981
  }, [refresh]);
2842
- const value = React10__namespace.useMemo(
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 = React10__namespace.useContext(AccountContext);
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] = React10__namespace.useState("idle");
2873
- const done = React10__namespace.useRef(false);
2874
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState("");
2886
- const [busy, setBusy] = React10__namespace.useState(false);
2887
- const [sent, setSent] = React10__namespace.useState(false);
2888
- const [devLink, setDevLink] = React10__namespace.useState(null);
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] = React10__namespace.useState(null);
2997
- const [reordering, setReordering] = React10__namespace.useState(null);
2998
- const [notice, setNotice] = React10__namespace.useState(null);
2999
- React10__namespace.useEffect(() => {
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
- client.account.orders().then((o) => live && setOrders(o)).catch(() => live && setOrders([]));
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
- orders == 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(
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] = React10__namespace.useState(() => ({
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] = React10__namespace.useState(null);
3181
- const [mode, setMode] = React10__namespace.useState({ kind: "list" });
3182
- const [busy, setBusy] = React10__namespace.useState(false);
3183
- const load = React10__namespace.useCallback(() => {
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
- React10__namespace.useEffect(() => {
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] = React10__namespace.useState(false);
3245
- const [busy, setBusy] = React10__namespace.useState(false);
3246
- const [error, setError] = React10__namespace.useState(false);
3247
- const [form, setForm] = React10__namespace.useState({});
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;