@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.js CHANGED
@@ -1,14 +1,14 @@
1
- import * as React10 from 'react';
1
+ import * as React27 from 'react';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { imageSrcSet } from '@nevios/storefront-js';
3
+ import { imageSrcSet, StorefrontError } from '@nevios/storefront-js';
4
4
 
5
5
  // src/provider.tsx
6
- var StorefrontContext = React10.createContext(null);
6
+ var StorefrontContext = React27.createContext(null);
7
7
  function NeviosProvider({ client, children }) {
8
8
  return /* @__PURE__ */ jsx(StorefrontContext.Provider, { value: client, children });
9
9
  }
10
10
  function useStorefront() {
11
- const client = React10.useContext(StorefrontContext);
11
+ const client = React27.useContext(StorefrontContext);
12
12
  if (!client) {
13
13
  throw new Error(
14
14
  "useStorefront must be used inside <NeviosProvider>. Wrap your app root with it."
@@ -17,7 +17,7 @@ function useStorefront() {
17
17
  return client;
18
18
  }
19
19
  function useStorefrontOptional() {
20
- return React10.useContext(StorefrontContext);
20
+ return React27.useContext(StorefrontContext);
21
21
  }
22
22
  var DEFAULTS = {
23
23
  brand: "#111111",
@@ -53,7 +53,7 @@ function ThemeProvider({
53
53
  as: Tag = "div"
54
54
  }) {
55
55
  const style = themeToCssVars(theme);
56
- return React10.createElement(
56
+ return React27.createElement(
57
57
  Tag,
58
58
  { "data-nevios-theme": "", style },
59
59
  children
@@ -62,6 +62,16 @@ function ThemeProvider({
62
62
 
63
63
  // src/i18n/en.ts
64
64
  var en = {
65
+ "subscriptions.purchaseOption": "Purchase option",
66
+ "subscriptions.empty": "You have no subscriptions yet.",
67
+ "subscriptions.status.active": "Active",
68
+ "subscriptions.status.paused": "Paused",
69
+ "subscriptions.status.pending": "Awaiting payment",
70
+ "subscriptions.status.cancelled": "Cancelled",
71
+ "subscriptions.title": "Subscription",
72
+ "subscriptions.consent": "I agree to the recurring price, delivery schedule and terms shown above.",
73
+ "subscriptions.eachDelivery": "For each delivery",
74
+ "subscriptions.once": "One-time purchase",
65
75
  // common
66
76
  "common.free": "Free",
67
77
  "common.close": "Close",
@@ -226,6 +236,20 @@ var en = {
226
236
  "account.signOut": "Sign out",
227
237
  "account.greeting": "Signed in as {email}",
228
238
  "account.orders": "Your orders",
239
+ "account.loadError": "Could not load your account. Please try again.",
240
+ "account.retry": "Try again",
241
+ "account.loadMore": "Load more",
242
+ "account.signInError": "Could not request a sign-in link. Please try again shortly.",
243
+ "account.security": "Account security",
244
+ "account.newEmail": "New email",
245
+ "account.changeEmail": "Verify new email",
246
+ "account.recentLogin": "Sign in again by email before making this change.",
247
+ "account.sessions": "Sessions",
248
+ "account.currentSession": "Current session",
249
+ "account.revokeSession": "Sign out",
250
+ "account.closeAccount": "Close account",
251
+ "account.closeWarning": "Closing your account signs out all devices and disables account access. Orders and invoices are retained. Contact the shop to restore access.",
252
+ "account.confirmClose": "Confirm account closure",
229
253
  "account.noOrders": "No orders yet.",
230
254
  "account.addresses": "Saved addresses",
231
255
  "account.noAddresses": "No saved addresses yet.",
@@ -264,6 +288,16 @@ var en = {
264
288
 
265
289
  // src/i18n/cs.ts
266
290
  var cs = {
291
+ "subscriptions.purchaseOption": "Zp\u016Fsob n\xE1kupu",
292
+ "subscriptions.empty": "Zat\xEDm nem\xE1te \u017E\xE1dn\xE9 p\u0159edplatn\xE9.",
293
+ "subscriptions.status.active": "Aktivn\xED",
294
+ "subscriptions.status.paused": "Pozastaveno",
295
+ "subscriptions.status.pending": "\u010Cek\xE1 na platbu",
296
+ "subscriptions.status.cancelled": "Zru\u0161eno",
297
+ "subscriptions.title": "P\u0159edplatn\xE9",
298
+ "subscriptions.consent": "Souhlas\xEDm s uvedenou opakovanou cenou, pl\xE1nem doru\u010Den\xED a podm\xEDnkami.",
299
+ "subscriptions.eachDelivery": "Za ka\u017Ed\xE9 doru\u010Den\xED",
300
+ "subscriptions.once": "Jednor\xE1zov\xFD n\xE1kup",
267
301
  "common.free": "Zdarma",
268
302
  "common.close": "Zav\u0159\xEDt",
269
303
  "common.loadingShort": "\u2026",
@@ -407,6 +441,20 @@ var cs = {
407
441
  "account.signOut": "Odhl\xE1sit se",
408
442
  "account.greeting": "P\u0159ihl\xE1\u0161en jako {email}",
409
443
  "account.orders": "Va\u0161e objedn\xE1vky",
444
+ "account.loadError": "\xDAdaje se nepoda\u0159ilo na\u010D\xEDst. Zkuste to znovu.",
445
+ "account.retry": "Zkusit znovu",
446
+ "account.loadMore": "Na\u010D\xEDst dal\u0161\xED",
447
+ "account.signInError": "P\u0159ihl\xE1\u0161en\xED se nepoda\u0159ilo zah\xE1jit. Zkuste to za chv\xEDli znovu.",
448
+ "account.security": "Zabezpe\u010Den\xED \xFA\u010Dtu",
449
+ "account.newEmail": "Nov\xFD e-mail",
450
+ "account.changeEmail": "Ov\u011B\u0159it nov\xFD e-mail",
451
+ "account.recentLogin": "Pro tuto zm\u011Bnu se nejprve znovu p\u0159ihlaste p\u0159es e-mail.",
452
+ "account.sessions": "P\u0159ihl\xE1\u0161en\xED",
453
+ "account.currentSession": "Toto p\u0159ihl\xE1\u0161en\xED",
454
+ "account.revokeSession": "Odhl\xE1sit",
455
+ "account.closeAccount": "Uzav\u0159\xEDt \xFA\u010Det",
456
+ "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.",
457
+ "account.confirmClose": "Potvrdit uzav\u0159en\xED \xFA\u010Dtu",
410
458
  "account.noOrders": "Zat\xEDm \u017E\xE1dn\xE9 objedn\xE1vky.",
411
459
  "account.addresses": "Ulo\u017Een\xE9 adresy",
412
460
  "account.noAddresses": "Zat\xEDm \u017E\xE1dn\xE9 ulo\u017Een\xE9 adresy.",
@@ -491,13 +539,13 @@ function makeI18n(locale, messages) {
491
539
  return { locale, messages, t };
492
540
  }
493
541
  var DEFAULT_I18N = makeI18n("en", en);
494
- var I18nContext = React10.createContext(null);
542
+ var I18nContext = React27.createContext(null);
495
543
  function I18nProvider({
496
544
  locale,
497
545
  messages,
498
546
  children
499
547
  }) {
500
- const value = React10.useMemo(() => {
548
+ const value = React27.useMemo(() => {
501
549
  const lang = langOf(locale);
502
550
  if (locale && !(lang in BUNDLED) && !messages) {
503
551
  devWarn(
@@ -511,7 +559,7 @@ function I18nProvider({
511
559
  return /* @__PURE__ */ jsx(I18nContext.Provider, { value, children });
512
560
  }
513
561
  function useI18n() {
514
- return React10.useContext(I18nContext) ?? DEFAULT_I18N;
562
+ return React27.useContext(I18nContext) ?? DEFAULT_I18N;
515
563
  }
516
564
  function useT() {
517
565
  return useI18n().t;
@@ -635,7 +683,7 @@ function ProductCard({ product, href, locale }) {
635
683
  var MAIN_WIDTHS = [600, 800, 1e3];
636
684
  var THUMB_WIDTHS = [56, 112];
637
685
  function ProductGallery({ media, alt }) {
638
- const [active, setActive] = React10.useState(0);
686
+ const [active, setActive] = React27.useState(0);
639
687
  const imageBase = useStorefrontOptional()?.imageBase;
640
688
  if (media.length === 0) {
641
689
  return /* @__PURE__ */ jsx(
@@ -723,10 +771,10 @@ function matchVariant(product, selected) {
723
771
  ) ?? null;
724
772
  }
725
773
  function VariantPicker({ product, onChange }) {
726
- const [selected, setSelected] = React10.useState(
774
+ const [selected, setSelected] = React27.useState(
727
775
  () => product.variants[0]?.options ?? []
728
776
  );
729
- React10.useEffect(() => {
777
+ React27.useEffect(() => {
730
778
  onChange?.(matchVariant(product, selected));
731
779
  }, [product, selected]);
732
780
  if (product.options.length === 0) return null;
@@ -767,8 +815,8 @@ function BackInStockForm({
767
815
  }) {
768
816
  const t = useT();
769
817
  const client = useStorefront();
770
- const [email, setEmail] = React10.useState(defaultEmail);
771
- const [state, setState] = React10.useState("idle");
818
+ const [email, setEmail] = React27.useState(defaultEmail);
819
+ const [state, setState] = React27.useState("idle");
772
820
  if (!variant || variant.availability?.status !== "out_of_stock") return null;
773
821
  async function submit(e) {
774
822
  e.preventDefault();
@@ -868,7 +916,7 @@ function ProductDetailPage({
868
916
  locale,
869
917
  addToCart
870
918
  }) {
871
- const [variant, setVariant] = React10.useState(
919
+ const [variant, setVariant] = React27.useState(
872
920
  product.variants[0] ?? null
873
921
  );
874
922
  return /* @__PURE__ */ jsxs(
@@ -1100,12 +1148,26 @@ function TagFilter({ tags, selected = [], onChange }) {
1100
1148
  }) })
1101
1149
  ] });
1102
1150
  }
1103
- var CartContext = React10.createContext(null);
1151
+ function PurchaseOptions({ plans, value, onChange, disabled, locale }) {
1152
+ const t = useT();
1153
+ const loc = useFormatLocale(locale);
1154
+ const name = React27.useId();
1155
+ const options = [{ id: null, name: t("subscriptions.once"), price: void 0 }, ...plans];
1156
+ return /* @__PURE__ */ jsxs("fieldset", { disabled, style: { border: 0, padding: 0, margin: 0, display: "grid", gap: "0.5rem" }, children: [
1157
+ /* @__PURE__ */ jsx("legend", { style: { marginBottom: "0.5rem", fontWeight: 500 }, children: t("subscriptions.purchaseOption") }),
1158
+ options.map((option) => /* @__PURE__ */ jsxs("label", { style: { display: "flex", gap: "0.6rem", alignItems: "center", padding: "0.75rem", border: `1px solid ${value === option.id ? "var(--nv-fg)" : "var(--nv-border)"}`, borderRadius: "var(--nv-radius)", cursor: "pointer" }, children: [
1159
+ /* @__PURE__ */ jsx("input", { type: "radio", name, checked: value === option.id, onChange: () => onChange(option.id) }),
1160
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children: option.name }),
1161
+ option.price ? /* @__PURE__ */ jsx("span", { children: formatCents(option.price.amount_cents, option.price.currency, loc) }) : null
1162
+ ] }, option.id ?? "once"))
1163
+ ] });
1164
+ }
1165
+ var CartContext = React27.createContext(null);
1104
1166
  function CartProvider({ children }) {
1105
1167
  const client = useStorefront();
1106
- const [cart, setCart] = React10.useState(null);
1107
- const [loading, setLoading] = React10.useState(false);
1108
- const run = React10.useCallback(
1168
+ const [cart, setCart] = React27.useState(null);
1169
+ const [loading, setLoading] = React27.useState(false);
1170
+ const run = React27.useCallback(
1109
1171
  async (op) => {
1110
1172
  setLoading(true);
1111
1173
  try {
@@ -1118,7 +1180,7 @@ function CartProvider({ children }) {
1118
1180
  },
1119
1181
  []
1120
1182
  );
1121
- const refresh = React10.useCallback(async () => {
1183
+ const refresh = React27.useCallback(async () => {
1122
1184
  setLoading(true);
1123
1185
  try {
1124
1186
  setCart(await client.cart.get());
@@ -1126,16 +1188,17 @@ function CartProvider({ children }) {
1126
1188
  setLoading(false);
1127
1189
  }
1128
1190
  }, [client]);
1129
- React10.useEffect(() => {
1191
+ React27.useEffect(() => {
1130
1192
  void refresh();
1131
1193
  }, [refresh]);
1132
- const value = React10.useMemo(
1194
+ const value = React27.useMemo(
1133
1195
  () => ({
1134
1196
  cart,
1135
1197
  loading,
1136
1198
  itemCount: cart?.totals.item_count ?? 0,
1137
1199
  addLine: (input) => run(() => client.cart.addLine(input)),
1138
1200
  updateLine: (lineId, quantity) => run(() => client.cart.updateLine(lineId, quantity)),
1201
+ clearSubscription: () => run(() => client.cart.clearSubscription()),
1139
1202
  removeLine: (lineId) => run(() => client.cart.removeLine(lineId)),
1140
1203
  refresh
1141
1204
  }),
@@ -1144,7 +1207,7 @@ function CartProvider({ children }) {
1144
1207
  return /* @__PURE__ */ jsx(CartContext.Provider, { value, children });
1145
1208
  }
1146
1209
  function useCart() {
1147
- const ctx = React10.useContext(CartContext);
1210
+ const ctx = React27.useContext(CartContext);
1148
1211
  if (!ctx) {
1149
1212
  throw new Error("useCart must be used inside <CartProvider>.");
1150
1213
  }
@@ -1153,13 +1216,14 @@ function useCart() {
1153
1216
  function AddToCart({
1154
1217
  variant,
1155
1218
  quantity = 1,
1219
+ subscriptionPlanId,
1156
1220
  children,
1157
1221
  onAdded
1158
1222
  }) {
1159
1223
  const t = useT();
1160
1224
  const { addLine } = useCart();
1161
- const [busy, setBusy] = React10.useState(false);
1162
- const [error, setError] = React10.useState(null);
1225
+ const [busy, setBusy] = React27.useState(false);
1226
+ const [error, setError] = React27.useState(null);
1163
1227
  const disabled = variant == null || busy;
1164
1228
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
1165
1229
  /* @__PURE__ */ jsx(
@@ -1172,7 +1236,7 @@ function AddToCart({
1172
1236
  setBusy(true);
1173
1237
  setError(null);
1174
1238
  try {
1175
- await addLine({ variant_id: variant.id, quantity });
1239
+ await addLine({ variant_id: variant.id, quantity, ...subscriptionPlanId ? { subscription_plan_id: subscriptionPlanId } : {} });
1176
1240
  onAdded?.();
1177
1241
  } catch (err) {
1178
1242
  const code = err?.code;
@@ -1201,7 +1265,7 @@ function AddToCart({
1201
1265
  function CartLineItem({ line, locale }) {
1202
1266
  const t = useT();
1203
1267
  const loc = useFormatLocale(locale);
1204
- const { updateLine, removeLine } = useCart();
1268
+ const { cart, updateLine, removeLine } = useCart();
1205
1269
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "0.85rem", alignItems: "flex-start", padding: "0.75rem 0" }, children: [
1206
1270
  /* @__PURE__ */ jsx(
1207
1271
  "div",
@@ -1230,6 +1294,7 @@ function CartLineItem({ line, locale }) {
1230
1294
  /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1231
1295
  /* @__PURE__ */ jsx("div", { style: { fontWeight: 500, lineHeight: 1.3 }, children: line.title }),
1232
1296
  line.variant_title ? /* @__PURE__ */ jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: line.variant_title }) : null,
1297
+ line.subscription_plan_id ? /* @__PURE__ */ jsx("div", { style: { color: "var(--nv-muted)", fontSize: "0.875em" }, children: cart?.subscription?.plan?.name ?? t("subscriptions.title") }) : null,
1233
1298
  /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", marginTop: "0.5rem" }, children: [
1234
1299
  /* @__PURE__ */ jsxs(
1235
1300
  "div",
@@ -1308,7 +1373,7 @@ function CartSummary({
1308
1373
  const { cart } = useCart();
1309
1374
  if (cart == null) return null;
1310
1375
  const t = cart.totals;
1311
- const empty = t.item_count === 0;
1376
+ const empty = t.item_count === 0 || cart.subscription?.available === false;
1312
1377
  const cta = checkoutLabel ?? tr("cart.checkout");
1313
1378
  const ctaStyle = {
1314
1379
  display: "block",
@@ -1329,6 +1394,7 @@ function CartSummary({
1329
1394
  t.tax_cents > 0 ? /* @__PURE__ */ jsx(Row, { label: tr("cart.tax"), value: formatCents(t.tax_cents, t.currency, loc), muted: true }) : null,
1330
1395
  /* @__PURE__ */ jsx("div", { style: { height: 1, background: "var(--nv-border)" } }),
1331
1396
  /* @__PURE__ */ jsx(Row, { label: tr("cart.total"), value: formatCents(t.total_cents, t.currency, loc), strong: true }),
1397
+ cart.subscription?.error ? /* @__PURE__ */ jsx("p", { role: "alert", style: { margin: 0 }, children: cart.subscription.error.message }) : null,
1332
1398
  checkoutHref && !empty ? /* @__PURE__ */ jsx("a", { href: checkoutHref, style: ctaStyle, children: cta }) : /* @__PURE__ */ jsx("button", { type: "button", disabled: empty, onClick: onCheckout, style: ctaStyle, children: cta })
1333
1399
  ] });
1334
1400
  }
@@ -1435,15 +1501,15 @@ function CartDrawer({
1435
1501
  }
1436
1502
  );
1437
1503
  }
1438
- var CheckoutContext = React10.createContext(null);
1504
+ var CheckoutContext = React27.createContext(null);
1439
1505
  function CheckoutProvider({
1440
1506
  children
1441
1507
  }) {
1442
1508
  const client = useStorefront();
1443
- const [checkout, setCheckout] = React10.useState(null);
1444
- const [loading, setLoading] = React10.useState(false);
1445
- const [error, setError] = React10.useState(null);
1446
- const run = React10.useCallback(
1509
+ const [checkout, setCheckout] = React27.useState(null);
1510
+ const [loading, setLoading] = React27.useState(false);
1511
+ const [error, setError] = React27.useState(null);
1512
+ const run = React27.useCallback(
1447
1513
  async (op, store) => {
1448
1514
  setLoading(true);
1449
1515
  try {
@@ -1461,23 +1527,36 @@ function CheckoutProvider({
1461
1527
  },
1462
1528
  []
1463
1529
  );
1464
- const refresh = React10.useCallback(async () => {
1530
+ const refresh = React27.useCallback(async () => {
1465
1531
  setLoading(true);
1466
1532
  try {
1467
1533
  setCheckout(await client.checkout.get());
1468
1534
  setError(null);
1535
+ } catch (err) {
1536
+ setError(err instanceof Error ? err : new Error(String(err)));
1469
1537
  } finally {
1470
1538
  setLoading(false);
1471
1539
  }
1472
1540
  }, [client]);
1473
- React10.useEffect(() => {
1541
+ React27.useEffect(() => {
1474
1542
  void refresh();
1475
1543
  }, [refresh]);
1476
- const pay = React10.useCallback(
1544
+ const pay = React27.useCallback(
1477
1545
  async (opts = {}) => {
1478
1546
  const co = checkout;
1479
1547
  if (!co) throw new Error("pay() called before a checkout exists");
1480
1548
  const idempotencyKey = opts.idempotencyKey ?? co.token;
1549
+ if (co.subscription) {
1550
+ if (co.subscription.subscription_id) {
1551
+ const payment = await run(() => client.checkout.subscriptionPayment(), () => null);
1552
+ setCheckout(await client.checkout.get());
1553
+ return { kind: "subscription_payment", payment };
1554
+ }
1555
+ if (!opts.subscription) throw new Error("Accept the current subscription terms before paying");
1556
+ const subscription = await run(() => client.checkout.enrollSubscription(opts.subscription), () => null);
1557
+ setCheckout(await client.checkout.get());
1558
+ return { kind: "subscription", subscription };
1559
+ }
1481
1560
  if (co.payment_category === "gateway") {
1482
1561
  const start = await run(
1483
1562
  () => client.checkout.startPayment({ return_url: opts.returnUrl, cancel_url: opts.cancelUrl }),
@@ -1496,11 +1575,18 @@ function CheckoutProvider({
1496
1575
  },
1497
1576
  [checkout, client, run]
1498
1577
  );
1499
- const value = React10.useMemo(
1578
+ const value = React27.useMemo(
1500
1579
  () => ({
1501
1580
  checkout,
1502
1581
  loading,
1503
1582
  error,
1583
+ selectSubscription: (selection) => run(async () => {
1584
+ const quote = await client.checkout.selectSubscription(selection);
1585
+ setCheckout(await client.checkout.get());
1586
+ return quote;
1587
+ }, () => null),
1588
+ clearSubscription: () => run(() => client.checkout.clearSubscription(), (r) => r),
1589
+ reviewSubscription: () => run(() => client.checkout.reviewSubscription(), () => null),
1504
1590
  create: (cartToken, input) => run(() => client.checkout.create(cartToken, input), (r) => r),
1505
1591
  setAddress: (input) => run(() => client.checkout.setAddress(input), (r) => r),
1506
1592
  shippingQuotes: () => run(() => client.checkout.shippingQuotes(), () => null),
@@ -1521,7 +1607,7 @@ function CheckoutProvider({
1521
1607
  return /* @__PURE__ */ jsx(CheckoutContext.Provider, { value, children });
1522
1608
  }
1523
1609
  function useCheckout() {
1524
- const ctx = React10.useContext(CheckoutContext);
1610
+ const ctx = React27.useContext(CheckoutContext);
1525
1611
  if (!ctx) {
1526
1612
  throw new Error("useCheckout must be used inside <CheckoutProvider>.");
1527
1613
  }
@@ -1680,10 +1766,25 @@ function AddressForm({
1680
1766
  }) {
1681
1767
  const t = useT();
1682
1768
  const { checkout, setAddress, loading } = useCheckout();
1683
- const [form, setForm] = React10.useState(
1769
+ const [form, setForm] = React27.useState(
1684
1770
  () => seed(checkout?.email ?? null, checkout?.shipping_address ?? null)
1685
1771
  );
1686
- const set = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value }));
1772
+ const touched = React27.useRef(/* @__PURE__ */ new Set());
1773
+ React27.useEffect(() => {
1774
+ const prefill = checkout?.customer_prefill;
1775
+ const addresses = prefill?.addresses.filter((a) => !a.is_pickup);
1776
+ const saved = addresses?.find((a) => a.is_default) ?? addresses?.[0];
1777
+ const address = checkout?.shipping_address ?? (saved ? { ...saved, postal_code: saved.zip } : null);
1778
+ const defaults = seed(checkout?.email ?? prefill?.email ?? null, address);
1779
+ defaults.first_name || (defaults.first_name = prefill?.first_name ?? "");
1780
+ defaults.last_name || (defaults.last_name = prefill?.last_name ?? "");
1781
+ defaults.phone || (defaults.phone = prefill?.phone ?? "");
1782
+ setForm((current) => Object.fromEntries(Object.entries(current).map(([key, value]) => [key, touched.current.has(key) ? value : value || defaults[key]])));
1783
+ }, [checkout?.token]);
1784
+ const set = (key) => (e) => {
1785
+ touched.current.add(key);
1786
+ setForm((f) => ({ ...f, [key]: e.target.value }));
1787
+ };
1687
1788
  async function submit(e) {
1688
1789
  e.preventDefault();
1689
1790
  const { email, ...rest } = form;
@@ -1734,11 +1835,11 @@ function ShippingPicker({
1734
1835
  const loc = useFormatLocale(locale);
1735
1836
  const { checkout, shippingQuotes, setShippingMethod, loading } = useCheckout();
1736
1837
  const heading = title ?? t("checkout.shippingMethod");
1737
- const [quotes, setQuotes] = React10.useState(null);
1738
- const [selected, setSelected] = React10.useState(null);
1739
- const [fetching, setFetching] = React10.useState(true);
1838
+ const [quotes, setQuotes] = React27.useState(null);
1839
+ const [selected, setSelected] = React27.useState(null);
1840
+ const [fetching, setFetching] = React27.useState(true);
1740
1841
  const addressKey = checkout?.shipping_address ? `${checkout.shipping_address.country_code}:${checkout.shipping_address.postal_code}` : null;
1741
- React10.useEffect(() => {
1842
+ React27.useEffect(() => {
1742
1843
  let live = true;
1743
1844
  setFetching(true);
1744
1845
  shippingQuotes().then((qs) => {
@@ -1766,17 +1867,13 @@ function ShippingPicker({
1766
1867
  }
1767
1868
  return /* @__PURE__ */ jsxs(Section, { title: heading, children: [
1768
1869
  /* @__PURE__ */ jsx("div", { role: "radiogroup", style: { display: "flex", flexDirection: "column", gap: "0.6rem" }, children: quotes.map((q) => {
1769
- const days = estDays(q, t);
1770
1870
  return /* @__PURE__ */ jsx(
1771
1871
  SelectRow,
1772
1872
  {
1773
1873
  selected: selected === q.rate_id,
1774
1874
  onSelect: () => setSelected(q.rate_id),
1775
1875
  trailing: q.is_free ? t("common.free") : formatCents(q.price_cents, q.currency, loc),
1776
- children: /* @__PURE__ */ jsxs("span", { style: { display: "flex", flexDirection: "column" }, children: [
1777
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: q.label }),
1778
- days ? /* @__PURE__ */ jsx("span", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)" }, children: days }) : null
1779
- ] })
1876
+ children: /* @__PURE__ */ jsx("span", { style: { display: "flex", flexDirection: "column" }, children: /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: q.label }) })
1780
1877
  },
1781
1878
  q.rate_id
1782
1879
  );
@@ -1784,15 +1881,6 @@ function ShippingPicker({
1784
1881
  /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Button, { onClick: submit, busy: loading, disabled: selected == null, children: submitLabel ?? t("checkout.continueToPayment") }) })
1785
1882
  ] });
1786
1883
  }
1787
- function estDays(q, t) {
1788
- const { estimated_days_min: lo, estimated_days_max: hi } = q;
1789
- if (lo == null && hi == null) return null;
1790
- if (lo != null && hi != null) {
1791
- return lo === hi ? t("shipping.days", { count: lo }) : t("shipping.daysRange", { min: lo, max: hi });
1792
- }
1793
- const n = lo ?? hi;
1794
- return t("shipping.days", { count: n });
1795
- }
1796
1884
  function PaymentMethodPicker({
1797
1885
  methods,
1798
1886
  value,
@@ -1826,20 +1914,45 @@ function PaymentMethodPicker({
1826
1914
  }
1827
1915
  );
1828
1916
  }
1917
+ function SubscriptionTerms({ quote, checked, onChange, locale }) {
1918
+ const t = useT();
1919
+ const loc = useFormatLocale(locale);
1920
+ return /* @__PURE__ */ jsxs("fieldset", { style: { margin: 0, padding: "1rem", border: "1px solid var(--nv-border)", borderRadius: "var(--nv-radius)" }, children: [
1921
+ /* @__PURE__ */ jsx("legend", { children: t("subscriptions.title") }),
1922
+ /* @__PURE__ */ jsxs("p", { style: { margin: "0 0 0.5rem", fontWeight: 500 }, children: [
1923
+ t("subscriptions.eachDelivery"),
1924
+ ": ",
1925
+ formatCents(quote.recurring_total_cents, quote.currency, loc)
1926
+ ] }),
1927
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 0.75rem", whiteSpace: "pre-wrap" }, children: quote.terms_text }),
1928
+ /* @__PURE__ */ jsxs("label", { style: { display: "flex", alignItems: "flex-start", gap: "0.6rem", cursor: "pointer" }, children: [
1929
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked, onChange: (event) => onChange(event.target.checked) }),
1930
+ /* @__PURE__ */ jsx("span", { children: t("subscriptions.consent") })
1931
+ ] })
1932
+ ] });
1933
+ }
1829
1934
  function PaymentSection({
1830
1935
  title,
1831
1936
  returnUrl,
1832
1937
  cancelUrl,
1833
1938
  onOrderPlaced,
1834
- locale
1939
+ locale,
1940
+ onSubscriptionPayment
1835
1941
  }) {
1836
1942
  const t = useT();
1837
1943
  const heading = title ?? t("checkout.payment");
1838
- const { checkout, paymentMethods, setPaymentMethod, pay, loading } = useCheckout();
1839
- const [methods, setMethods] = React10.useState(null);
1840
- const [fetching, setFetching] = React10.useState(true);
1944
+ const { checkout, paymentMethods, setPaymentMethod, pay, loading, refresh } = useCheckout();
1945
+ const [methods, setMethods] = React27.useState(null);
1946
+ const [fetching, setFetching] = React27.useState(true);
1841
1947
  const selected = checkout?.payment_method ?? null;
1842
- React10.useEffect(() => {
1948
+ const [acceptedDigest, setAcceptedDigest] = React27.useState(null);
1949
+ const [actionBusy, setActionBusy] = React27.useState(false);
1950
+ const quote = checkout?.subscription?.quote;
1951
+ const needsConsent = Boolean(checkout?.subscription?.requires_consent);
1952
+ const clickInFlight = React27.useRef(false);
1953
+ const [actionError, setActionError] = React27.useState(null);
1954
+ const accepted = Boolean(quote && acceptedDigest === quote.quote_digest);
1955
+ React27.useEffect(() => {
1843
1956
  let live = true;
1844
1957
  setFetching(true);
1845
1958
  paymentMethods().then((ms) => {
@@ -1852,12 +1965,30 @@ function PaymentSection({
1852
1965
  return () => {
1853
1966
  live = false;
1854
1967
  };
1855
- }, []);
1968
+ }, [checkout?.token, checkout?.shipping_method_id, checkout?.subscription?.selection.plan_id]);
1856
1969
  const selectedMethod = methods?.find((m) => m.method === selected) ?? null;
1857
1970
  const isGateway = selectedMethod?.category === "gateway";
1858
1971
  async function placeOrder() {
1859
- const result = await pay({ returnUrl, cancelUrl });
1860
- if (result.kind === "order") onOrderPlaced?.(result.checkout);
1972
+ if (clickInFlight.current) return;
1973
+ clickInFlight.current = true;
1974
+ setActionBusy(true);
1975
+ setActionError(null);
1976
+ try {
1977
+ const result = await pay({
1978
+ returnUrl,
1979
+ cancelUrl,
1980
+ ...needsConsent && quote && accepted ? { subscription: { quote, consent: true } } : {}
1981
+ });
1982
+ if (result.kind === "subscription") await onSubscriptionPayment?.(result.subscription.payment);
1983
+ if (result.kind === "subscription_payment") await onSubscriptionPayment?.(result.payment);
1984
+ if (result.kind === "subscription" || result.kind === "subscription_payment") await refresh();
1985
+ if (result.kind === "order") onOrderPlaced?.(result.checkout);
1986
+ } catch (err) {
1987
+ setActionError(err instanceof Error ? err.message : String(err));
1988
+ } finally {
1989
+ clickInFlight.current = false;
1990
+ setActionBusy(false);
1991
+ }
1861
1992
  }
1862
1993
  if (fetching && methods == null) {
1863
1994
  return /* @__PURE__ */ jsx(Section, { title: heading, children: /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("checkout.loadingPayment") }) });
@@ -1877,9 +2008,12 @@ function PaymentSection({
1877
2008
  label: heading
1878
2009
  }
1879
2010
  ),
2011
+ quote && needsConsent ? /* @__PURE__ */ jsx(SubscriptionTerms, { locale, quote, checked: accepted, onChange: (value) => setAcceptedDigest(value && quote ? quote.quote_digest : null) }) : null,
2012
+ checkout?.subscription?.error ? /* @__PURE__ */ jsx("p", { role: "alert", children: checkout.subscription.error.message }) : null,
2013
+ actionError ? /* @__PURE__ */ jsx("p", { role: "alert", children: actionError }) : null,
1880
2014
  /* @__PURE__ */ jsxs("div", { children: [
1881
- /* @__PURE__ */ jsx(Button, { onClick: placeOrder, busy: loading, disabled: selected == null, children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
1882
- isGateway ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
2015
+ /* @__PURE__ */ jsx(Button, { onClick: placeOrder, busy: loading || actionBusy, disabled: selected == null || Boolean(checkout?.subscription) && (needsConsent && (!accepted || !quote) || !onSubscriptionPayment || !checkout?.subscription?.available), children: isGateway ? t("checkout.payNow") : t("checkout.placeOrder") }),
2016
+ isGateway && !checkout?.subscription ? /* @__PURE__ */ jsx("p", { style: { fontSize: "0.8125rem", color: "var(--nv-muted)", marginTop: "0.6rem" }, children: t("checkout.gatewayRedirectNote") }) : null
1883
2017
  ] })
1884
2018
  ] });
1885
2019
  }
@@ -1967,6 +2101,7 @@ function reachedStep(checkout) {
1967
2101
  }
1968
2102
  function CheckoutFlow({
1969
2103
  cartToken,
2104
+ onSubscriptionPayment,
1970
2105
  onOrderPlaced,
1971
2106
  emptyState,
1972
2107
  renderConfirmation,
@@ -1976,19 +2111,19 @@ function CheckoutFlow({
1976
2111
  }) {
1977
2112
  const t = useT();
1978
2113
  const { checkout, loading, create } = useCheckout();
1979
- const [step, setStep] = React10.useState("address");
1980
- const [bootstrapped, setBootstrapped] = React10.useState(false);
1981
- React10.useEffect(() => {
2114
+ const [step, setStep] = React27.useState("address");
2115
+ const [bootstrapped, setBootstrapped] = React27.useState(false);
2116
+ React27.useEffect(() => {
1982
2117
  if (checkout || !cartToken || bootstrapped) return;
1983
2118
  setBootstrapped(true);
1984
2119
  void create(cartToken).catch(() => {
1985
2120
  });
1986
2121
  }, [checkout, cartToken, bootstrapped, create]);
1987
2122
  const serverStep = reachedStep(checkout);
1988
- React10.useEffect(() => {
2123
+ React27.useEffect(() => {
1989
2124
  setStep((cur) => rank(serverStep) > rank(cur) ? serverStep : cur);
1990
2125
  }, [serverStep]);
1991
- if (checkout?.order_id) {
2126
+ if (checkout?.order_id && (!checkout.subscription || checkout.phase === "success")) {
1992
2127
  return /* @__PURE__ */ jsx(Shell, { summary: /* @__PURE__ */ jsx(OrderSummary, { locale }), children: renderConfirmation ? renderConfirmation(checkout) : /* @__PURE__ */ jsx(DefaultConfirmation, { checkout }) });
1993
2128
  }
1994
2129
  if (!checkout) {
@@ -2002,6 +2137,7 @@ function CheckoutFlow({
2002
2137
  step === "address" ? /* @__PURE__ */ jsx(AddressForm, { onComplete: () => setStep("shipping") }) : step === "shipping" ? /* @__PURE__ */ jsx(ShippingPicker, { onComplete: () => setStep("payment"), locale }) : /* @__PURE__ */ jsx(
2003
2138
  PaymentSection,
2004
2139
  {
2140
+ onSubscriptionPayment,
2005
2141
  returnUrl,
2006
2142
  cancelUrl,
2007
2143
  onOrderPlaced,
@@ -2106,10 +2242,10 @@ function DefaultConfirmation({ checkout }) {
2106
2242
  }
2107
2243
  function useOrderStatus(statusToken, initial) {
2108
2244
  const client = useStorefront();
2109
- const [order, setOrder] = React10.useState(initial ?? null);
2110
- const [loading, setLoading] = React10.useState(false);
2111
- const [error, setError] = React10.useState(null);
2112
- const load = React10.useCallback(async () => {
2245
+ const [order, setOrder] = React27.useState(initial ?? null);
2246
+ const [loading, setLoading] = React27.useState(false);
2247
+ const [error, setError] = React27.useState(null);
2248
+ const load = React27.useCallback(async () => {
2113
2249
  if (!statusToken) return;
2114
2250
  setLoading(true);
2115
2251
  try {
@@ -2121,7 +2257,7 @@ function useOrderStatus(statusToken, initial) {
2121
2257
  setLoading(false);
2122
2258
  }
2123
2259
  }, [client, statusToken]);
2124
- React10.useEffect(() => {
2260
+ React27.useEffect(() => {
2125
2261
  if (initial) return;
2126
2262
  void load();
2127
2263
  }, [load, initial]);
@@ -2129,12 +2265,12 @@ function useOrderStatus(statusToken, initial) {
2129
2265
  }
2130
2266
  function usePayment(token, initial) {
2131
2267
  const client = useStorefront();
2132
- const [payment, setPayment] = React10.useState(initial ?? null);
2133
- const [methods, setMethods] = React10.useState([]);
2134
- const [loading, setLoading] = React10.useState(false);
2135
- const [busy, setBusy] = React10.useState(false);
2136
- const [error, setError] = React10.useState(null);
2137
- const load = React10.useCallback(async () => {
2268
+ const [payment, setPayment] = React27.useState(initial ?? null);
2269
+ const [methods, setMethods] = React27.useState([]);
2270
+ const [loading, setLoading] = React27.useState(false);
2271
+ const [busy, setBusy] = React27.useState(false);
2272
+ const [error, setError] = React27.useState(null);
2273
+ const load = React27.useCallback(async () => {
2138
2274
  if (!token) return;
2139
2275
  setLoading(true);
2140
2276
  try {
@@ -2151,7 +2287,7 @@ function usePayment(token, initial) {
2151
2287
  setLoading(false);
2152
2288
  }
2153
2289
  }, [client, token]);
2154
- React10.useEffect(() => {
2290
+ React27.useEffect(() => {
2155
2291
  if (!token) return;
2156
2292
  if (initial && initial.token === token) {
2157
2293
  setPayment(initial);
@@ -2160,7 +2296,7 @@ function usePayment(token, initial) {
2160
2296
  }
2161
2297
  void load();
2162
2298
  }, [token, load]);
2163
- const selectMethod = React10.useCallback(
2299
+ const selectMethod = React27.useCallback(
2164
2300
  async (method) => {
2165
2301
  if (!token) return null;
2166
2302
  setBusy(true);
@@ -2178,7 +2314,7 @@ function usePayment(token, initial) {
2178
2314
  },
2179
2315
  [client, token]
2180
2316
  );
2181
- const startPayment = React10.useCallback(
2317
+ const startPayment = React27.useCallback(
2182
2318
  async (input) => {
2183
2319
  if (!token) return null;
2184
2320
  setBusy(true);
@@ -2615,8 +2751,8 @@ function Row4({
2615
2751
  }
2616
2752
  function CopyRow({ label, value }) {
2617
2753
  const t = useT();
2618
- const [copied, setCopied] = React10.useState(false);
2619
- React10.useEffect(() => {
2754
+ const [copied, setCopied] = React27.useState(false);
2755
+ React27.useEffect(() => {
2620
2756
  if (!copied) return;
2621
2757
  const id = setTimeout(() => setCopied(false), 1800);
2622
2758
  return () => clearTimeout(id);
@@ -2700,12 +2836,12 @@ function PaymentPage({
2700
2836
  const t = useT();
2701
2837
  const loc = useFormatLocale(locale);
2702
2838
  const { payment, methods, loading, busy, error, reload, selectMethod, startPayment } = usePayment(token, initial);
2703
- const [verifying, setVerifying] = React10.useState(false);
2704
- React10.useEffect(() => {
2839
+ const [verifying, setVerifying] = React27.useState(false);
2840
+ React27.useEffect(() => {
2705
2841
  if (token) setVerifying(consumeHandoff(token));
2706
2842
  }, [token]);
2707
2843
  const settled = payment != null && !payment.can_pay && payment.blocked_reason === "already_paid";
2708
- React10.useEffect(() => {
2844
+ React27.useEffect(() => {
2709
2845
  if (!verifying || !token || settled) return;
2710
2846
  let attempts = 0;
2711
2847
  const id = setInterval(() => {
@@ -2720,7 +2856,7 @@ function PaymentPage({
2720
2856
  }, POLL_MS);
2721
2857
  return () => clearInterval(id);
2722
2858
  }, [verifying, token, settled, reload]);
2723
- React10.useEffect(() => {
2859
+ React27.useEffect(() => {
2724
2860
  if (!settled || !token || !payment) return;
2725
2861
  setVerifying(false);
2726
2862
  forgetHandoff(token);
@@ -2801,27 +2937,32 @@ function Amount({ label, value }) {
2801
2937
  function Link({ href, children }) {
2802
2938
  return /* @__PURE__ */ jsx("a", { href, style: { color: "var(--nv-brand)", fontSize: "0.875rem" }, children });
2803
2939
  }
2804
- var AccountContext = React10.createContext(null);
2940
+ var AccountContext = React27.createContext(null);
2805
2941
  function AccountProvider({ children }) {
2806
2942
  const client = useStorefront();
2807
- const [customer, setCustomer] = React10.useState(null);
2808
- const [loading, setLoading] = React10.useState(true);
2809
- const refresh = React10.useCallback(async () => {
2943
+ const [customer, setCustomer] = React27.useState(null);
2944
+ const [loading, setLoading] = React27.useState(true);
2945
+ const [error, setError] = React27.useState(false);
2946
+ const refresh = React27.useCallback(async () => {
2810
2947
  setLoading(true);
2948
+ setError(false);
2811
2949
  try {
2812
2950
  setCustomer(await client.account.me());
2951
+ } catch {
2952
+ setError(true);
2813
2953
  } finally {
2814
2954
  setLoading(false);
2815
2955
  }
2816
2956
  }, [client]);
2817
- React10.useEffect(() => {
2957
+ React27.useEffect(() => {
2818
2958
  void refresh();
2819
2959
  }, [refresh]);
2820
- const value = React10.useMemo(
2960
+ const value = React27.useMemo(
2821
2961
  () => ({
2822
2962
  customer,
2823
2963
  signedIn: customer != null,
2824
2964
  loading,
2965
+ error,
2825
2966
  requestMagicLink: (email) => client.account.requestMagicLink(email),
2826
2967
  verify: async (token) => {
2827
2968
  const c = await client.account.verify(token);
@@ -2834,12 +2975,12 @@ function AccountProvider({ children }) {
2834
2975
  },
2835
2976
  refresh
2836
2977
  }),
2837
- [customer, loading, client, refresh]
2978
+ [customer, loading, error, client, refresh]
2838
2979
  );
2839
2980
  return /* @__PURE__ */ jsx(AccountContext.Provider, { value, children });
2840
2981
  }
2841
2982
  function useAccount() {
2842
- const ctx = React10.useContext(AccountContext);
2983
+ const ctx = React27.useContext(AccountContext);
2843
2984
  if (!ctx) {
2844
2985
  throw new Error("useAccount must be used inside <AccountProvider>.");
2845
2986
  }
@@ -2847,9 +2988,9 @@ function useAccount() {
2847
2988
  }
2848
2989
  function useMagicLinkVerify(token) {
2849
2990
  const { verify } = useAccount();
2850
- const [status, setStatus] = React10.useState("idle");
2851
- const done = React10.useRef(false);
2852
- React10.useEffect(() => {
2991
+ const [status, setStatus] = React27.useState("idle");
2992
+ const done = React27.useRef(false);
2993
+ React27.useEffect(() => {
2853
2994
  if (!token || done.current) return;
2854
2995
  done.current = true;
2855
2996
  setStatus("verifying");
@@ -2860,18 +3001,22 @@ function useMagicLinkVerify(token) {
2860
3001
  function LoginForm({ verifyPath = "/account/verify", onSent }) {
2861
3002
  const t = useT();
2862
3003
  const { requestMagicLink } = useAccount();
2863
- const [email, setEmail] = React10.useState("");
2864
- const [busy, setBusy] = React10.useState(false);
2865
- const [sent, setSent] = React10.useState(false);
2866
- const [devLink, setDevLink] = React10.useState(null);
3004
+ const [email, setEmail] = React27.useState("");
3005
+ const [busy, setBusy] = React27.useState(false);
3006
+ const [sent, setSent] = React27.useState(false);
3007
+ const [error, setError] = React27.useState(false);
3008
+ const [devLink, setDevLink] = React27.useState(null);
2867
3009
  async function submit(e) {
2868
3010
  e.preventDefault();
2869
3011
  setBusy(true);
3012
+ setError(false);
2870
3013
  try {
2871
3014
  const r = await requestMagicLink(email);
2872
3015
  setSent(true);
2873
3016
  if (r.devToken) setDevLink(`${verifyPath}?token=${encodeURIComponent(r.devToken)}`);
2874
3017
  onSent?.(email);
3018
+ } catch {
3019
+ setError(true);
2875
3020
  } finally {
2876
3021
  setBusy(false);
2877
3022
  }
@@ -2910,6 +3055,7 @@ function LoginForm({ verifyPath = "/account/verify", onSent }) {
2910
3055
  }
2911
3056
  )
2912
3057
  ] }),
3058
+ error ? /* @__PURE__ */ jsx("p", { role: "alert", children: t("account.signInError") }) : null,
2913
3059
  /* @__PURE__ */ jsx(
2914
3060
  "button",
2915
3061
  {
@@ -2971,20 +3117,47 @@ function OrderHistory({
2971
3117
  const loc = useFormatLocale(locale);
2972
3118
  const { signedIn } = useAccount();
2973
3119
  const client = useStorefront();
2974
- const [orders, setOrders] = React10.useState(null);
2975
- const [reordering, setReordering] = React10.useState(null);
2976
- const [notice, setNotice] = React10.useState(null);
2977
- React10.useEffect(() => {
3120
+ const [orders, setOrders] = React27.useState(null);
3121
+ const [error, setError] = React27.useState(false);
3122
+ const [nextCursor, setNextCursor] = React27.useState(null);
3123
+ const [pageLoading, setPageLoading] = React27.useState(false);
3124
+ const [reload, setReload] = React27.useState(0);
3125
+ const [reordering, setReordering] = React27.useState(null);
3126
+ const [notice, setNotice] = React27.useState(null);
3127
+ React27.useEffect(() => {
2978
3128
  let live = true;
2979
3129
  if (!signedIn) {
2980
3130
  setOrders([]);
2981
3131
  return;
2982
3132
  }
2983
- client.account.orders().then((o) => live && setOrders(o)).catch(() => live && setOrders([]));
3133
+ setError(false);
3134
+ setOrders(null);
3135
+ client.account.ordersPage().then((page) => {
3136
+ if (live) {
3137
+ setOrders(page.orders);
3138
+ setNextCursor(page.nextCursor);
3139
+ }
3140
+ }).catch(() => {
3141
+ if (live) setError(true);
3142
+ });
2984
3143
  return () => {
2985
3144
  live = false;
2986
3145
  };
2987
- }, [signedIn, client]);
3146
+ }, [signedIn, client, reload]);
3147
+ async function loadMore() {
3148
+ if (!nextCursor || pageLoading) return;
3149
+ setPageLoading(true);
3150
+ setError(false);
3151
+ try {
3152
+ const page = await client.account.ordersPage({ beforeId: nextCursor });
3153
+ setOrders((previous) => [...previous ?? [], ...page.orders]);
3154
+ setNextCursor(page.nextCursor);
3155
+ } catch {
3156
+ setError(true);
3157
+ } finally {
3158
+ setPageLoading(false);
3159
+ }
3160
+ }
2988
3161
  async function reorder(statusToken) {
2989
3162
  setReordering(statusToken);
2990
3163
  setNotice(null);
@@ -3005,7 +3178,11 @@ function OrderHistory({
3005
3178
  }
3006
3179
  return /* @__PURE__ */ jsxs("section", { style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
3007
3180
  /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "1.1rem", fontWeight: 600 }, children: t("account.orders") }),
3008
- orders == null ? /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("common.loadingShort") }) : orders.length === 0 ? /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("account.noOrders") }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.5rem" }, children: orders.map((o) => /* @__PURE__ */ jsxs(
3181
+ error ? /* @__PURE__ */ jsxs("div", { role: "alert", children: [
3182
+ /* @__PURE__ */ jsx("p", { children: t("account.loadError") }),
3183
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => orders ? void loadMore() : setReload((x) => x + 1), children: t("account.retry") })
3184
+ ] }) : null,
3185
+ orders == null ? error ? null : /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("common.loadingShort") }) : orders.length === 0 ? /* @__PURE__ */ jsx("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: t("account.noOrders") }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: "0.5rem" }, children: orders.map((o) => /* @__PURE__ */ jsxs(
3009
3186
  "li",
3010
3187
  {
3011
3188
  style: {
@@ -3052,12 +3229,13 @@ function OrderHistory({
3052
3229
  ]
3053
3230
  },
3054
3231
  o.status_token
3055
- )) })
3232
+ )) }),
3233
+ nextCursor ? /* @__PURE__ */ jsx("button", { type: "button", disabled: pageLoading, onClick: () => void loadMore(), children: pageLoading ? t("common.loadingShort") : t("account.loadMore") }) : null
3056
3234
  ] });
3057
3235
  }
3058
3236
  function AddressForm2({ initial, onSubmit, onCancel, busy }) {
3059
3237
  const t = useT();
3060
- const [form, setForm] = React10.useState(() => ({
3238
+ const [form, setForm] = React27.useState(() => ({
3061
3239
  first_name: initial?.first_name ?? "",
3062
3240
  last_name: initial?.last_name ?? "",
3063
3241
  company: initial?.company ?? "",
@@ -3155,17 +3333,17 @@ function AddressBook() {
3155
3333
  const t = useT();
3156
3334
  const { signedIn } = useAccount();
3157
3335
  const client = useStorefront();
3158
- const [addresses, setAddresses] = React10.useState(null);
3159
- const [mode, setMode] = React10.useState({ kind: "list" });
3160
- const [busy, setBusy] = React10.useState(false);
3161
- const load = React10.useCallback(() => {
3336
+ const [addresses, setAddresses] = React27.useState(null);
3337
+ const [mode, setMode] = React27.useState({ kind: "list" });
3338
+ const [busy, setBusy] = React27.useState(false);
3339
+ const load = React27.useCallback(() => {
3162
3340
  if (!signedIn) {
3163
3341
  setAddresses([]);
3164
3342
  return;
3165
3343
  }
3166
3344
  client.account.addresses().then(setAddresses).catch(() => setAddresses([]));
3167
3345
  }, [signedIn, client]);
3168
- React10.useEffect(() => {
3346
+ React27.useEffect(() => {
3169
3347
  load();
3170
3348
  }, [load]);
3171
3349
  async function run(fn) {
@@ -3219,10 +3397,10 @@ function ProfileEditor() {
3219
3397
  const t = useT();
3220
3398
  const client = useStorefront();
3221
3399
  const { customer, refresh } = useAccount();
3222
- const [editing, setEditing] = React10.useState(false);
3223
- const [busy, setBusy] = React10.useState(false);
3224
- const [error, setError] = React10.useState(false);
3225
- const [form, setForm] = React10.useState({});
3400
+ const [editing, setEditing] = React27.useState(false);
3401
+ const [busy, setBusy] = React27.useState(false);
3402
+ const [error, setError] = React27.useState(false);
3403
+ const [form, setForm] = React27.useState({});
3226
3404
  if (!customer) return null;
3227
3405
  function startEdit() {
3228
3406
  setForm({
@@ -3299,6 +3477,176 @@ function ProfileEditor() {
3299
3477
  ] })
3300
3478
  ] });
3301
3479
  }
3480
+ function AccountSecurity() {
3481
+ const client = useStorefront();
3482
+ const account = useAccount();
3483
+ const t = useT();
3484
+ const [sessions, setSessions] = React27.useState(null);
3485
+ const [email, setEmail] = React27.useState("");
3486
+ const [busy, setBusy] = React27.useState(false);
3487
+ const [notice, setNotice] = React27.useState(null);
3488
+ const [confirmClose, setConfirmClose] = React27.useState(false);
3489
+ const [retry, setRetry] = React27.useState(0);
3490
+ React27.useEffect(() => {
3491
+ let live = true;
3492
+ if (account.signedIn) client.account.sessions().then((s) => {
3493
+ if (live) setSessions(s);
3494
+ }).catch(() => {
3495
+ if (live) setNotice(t("account.loadError"));
3496
+ });
3497
+ return () => {
3498
+ live = false;
3499
+ };
3500
+ }, [client, account.signedIn, retry, t]);
3501
+ async function act(operation) {
3502
+ setBusy(true);
3503
+ setNotice(null);
3504
+ try {
3505
+ await operation();
3506
+ } catch (e) {
3507
+ setNotice(e instanceof StorefrontError && e.status === 401 ? t("account.recentLogin") : t("account.loadError"));
3508
+ } finally {
3509
+ setBusy(false);
3510
+ }
3511
+ }
3512
+ if (!account.signedIn) return null;
3513
+ return /* @__PURE__ */ jsxs("section", { style: { display: "grid", gap: "0.75rem" }, children: [
3514
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontSize: "1.1rem", fontWeight: 600 }, children: t("account.security") }),
3515
+ notice ? /* @__PURE__ */ jsx("p", { role: "status", children: notice }) : null,
3516
+ /* @__PURE__ */ jsxs(
3517
+ "form",
3518
+ {
3519
+ style: { display: "flex", gap: "0.75rem", flexWrap: "wrap", alignItems: "end" },
3520
+ onSubmit: (e) => {
3521
+ e.preventDefault();
3522
+ void act(async () => {
3523
+ await client.account.requestEmailChange(email);
3524
+ setNotice(t("account.checkEmail"));
3525
+ });
3526
+ },
3527
+ children: [
3528
+ /* @__PURE__ */ jsx(Field, { label: t("account.newEmail"), type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value) }),
3529
+ /* @__PURE__ */ jsx(Button, { type: "submit", busy, children: t("account.changeEmail") })
3530
+ ]
3531
+ }
3532
+ ),
3533
+ /* @__PURE__ */ jsx("h3", { children: t("account.sessions") }),
3534
+ sessions ? sessions.map((s) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "0.75rem", justifyContent: "space-between" }, children: [
3535
+ /* @__PURE__ */ jsx("span", { children: s.current ? t("account.currentSession") : /* @__PURE__ */ jsx("time", { dateTime: s.created_at, children: s.created_at.slice(0, 10) }) }),
3536
+ /* @__PURE__ */ jsx(Button, { busy, onClick: () => void act(async () => {
3537
+ await client.account.revokeSession(s.id);
3538
+ if (s.current) await account.logout();
3539
+ else setSessions((old) => old?.filter((row) => row.id !== s.id) ?? null);
3540
+ }), children: t("account.revokeSession") })
3541
+ ] }, s.id)) : /* @__PURE__ */ jsx(Button, { onClick: () => setRetry((x) => x + 1), children: t("account.retry") }),
3542
+ confirmClose ? /* @__PURE__ */ jsxs("div", { children: [
3543
+ /* @__PURE__ */ jsx("p", { children: t("account.closeWarning") }),
3544
+ /* @__PURE__ */ jsx(Button, { busy, onClick: () => void act(async () => {
3545
+ await client.account.close();
3546
+ await account.refresh();
3547
+ }), children: t("account.confirmClose") }),
3548
+ " ",
3549
+ /* @__PURE__ */ jsx(Button, { disabled: busy, onClick: () => setConfirmClose(false), children: t("account.cancel") })
3550
+ ] }) : /* @__PURE__ */ jsx(Button, { disabled: busy, onClick: () => setConfirmClose(true), children: t("account.closeAccount") })
3551
+ ] });
3552
+ }
3553
+ function useCustomerSubscriptions() {
3554
+ const client = useStorefront();
3555
+ const { customer } = useAccount();
3556
+ const [dataOwner, setDataOwner] = React27.useState(void 0);
3557
+ const [subscriptions, setSubscriptions] = React27.useState([]);
3558
+ const [loading, setLoading] = React27.useState(false);
3559
+ const [error, setError] = React27.useState(null);
3560
+ const generation = React27.useRef(0);
3561
+ const owner = customer?.id;
3562
+ const api = client.account.subscriptions;
3563
+ const refresh = React27.useCallback(async () => {
3564
+ const current = ++generation.current;
3565
+ setError(null);
3566
+ if (!owner) {
3567
+ setSubscriptions([]);
3568
+ setLoading(false);
3569
+ return;
3570
+ }
3571
+ setLoading(true);
3572
+ try {
3573
+ const rows = await api.list();
3574
+ if (current === generation.current) {
3575
+ setSubscriptions(rows);
3576
+ setDataOwner(owner);
3577
+ setError(null);
3578
+ }
3579
+ } catch (err) {
3580
+ if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
3581
+ } finally {
3582
+ if (current === generation.current) setLoading(false);
3583
+ }
3584
+ }, [owner, api]);
3585
+ React27.useEffect(() => {
3586
+ setSubscriptions([]);
3587
+ void refresh();
3588
+ return () => {
3589
+ generation.current++;
3590
+ };
3591
+ }, [refresh]);
3592
+ return { subscriptions: dataOwner === owner ? subscriptions : [], loading, error, refresh, api };
3593
+ }
3594
+ function useCustomerSubscription(id) {
3595
+ const client = useStorefront();
3596
+ const { customer } = useAccount();
3597
+ const [dataKey, setDataKey] = React27.useState("");
3598
+ const [subscription, setSubscription] = React27.useState(null);
3599
+ const [loading, setLoading] = React27.useState(false);
3600
+ const [error, setError] = React27.useState(null);
3601
+ const generation = React27.useRef(0);
3602
+ const owner = customer?.id;
3603
+ const api = client.account.subscriptions;
3604
+ const refresh = React27.useCallback(async () => {
3605
+ const current = ++generation.current;
3606
+ setError(null);
3607
+ if (!owner || !id) {
3608
+ setSubscription(null);
3609
+ setLoading(false);
3610
+ return;
3611
+ }
3612
+ setLoading(true);
3613
+ try {
3614
+ const row = await api.management(id);
3615
+ if (current === generation.current) {
3616
+ setSubscription(row);
3617
+ setDataKey(`${owner}:${id}`);
3618
+ setError(null);
3619
+ }
3620
+ } catch (err) {
3621
+ if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
3622
+ } finally {
3623
+ if (current === generation.current) setLoading(false);
3624
+ }
3625
+ }, [owner, id, api]);
3626
+ React27.useEffect(() => {
3627
+ setSubscription(null);
3628
+ void refresh();
3629
+ return () => {
3630
+ generation.current++;
3631
+ };
3632
+ }, [refresh]);
3633
+ return { subscription: dataKey === `${owner}:${id}` ? subscription : null, loading, error, refresh, api };
3634
+ }
3635
+ function SubscriptionList({ href, locale }) {
3636
+ const t = useT();
3637
+ const { subscriptions, loading, error } = useCustomerSubscriptions();
3638
+ return /* @__PURE__ */ jsxs("section", { style: { fontFamily: "var(--nv-font)", color: "var(--nv-fg)" }, children: [
3639
+ /* @__PURE__ */ jsx("h2", { style: { fontSize: "1.125rem", fontWeight: 500 }, children: t("subscriptions.title") }),
3640
+ loading ? /* @__PURE__ */ jsx("p", { children: t("checkout.loading") }) : error ? /* @__PURE__ */ jsx("p", { role: "alert", children: error.message }) : subscriptions.length === 0 ? /* @__PURE__ */ jsx("p", { children: t("subscriptions.empty") }) : /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: subscriptions.map((sub) => /* @__PURE__ */ jsx("li", { style: { borderBottom: "1px solid var(--nv-border)", padding: "0.8rem 0" }, children: /* @__PURE__ */ jsxs("a", { href: href(sub.id), style: { color: "inherit", display: "flex", justifyContent: "space-between", gap: "1rem", flexWrap: "wrap" }, children: [
3641
+ /* @__PURE__ */ jsxs("span", { children: [
3642
+ "#",
3643
+ sub.id
3644
+ ] }),
3645
+ /* @__PURE__ */ jsx("span", { children: t(`subscriptions.status.${sub.status}`) }),
3646
+ sub.status === "active" && sub.next_at ? /* @__PURE__ */ jsx("time", { dateTime: sub.next_at, children: new Date(sub.next_at).toLocaleDateString(locale) }) : null
3647
+ ] }) }, sub.id)) })
3648
+ ] });
3649
+ }
3302
3650
  function serializeJsonLd(data) {
3303
3651
  return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
3304
3652
  }
@@ -3403,6 +3751,6 @@ function OrganizationJsonLd({
3403
3751
  return /* @__PURE__ */ jsx(JsonLd, { data });
3404
3752
  }
3405
3753
 
3406
- export { AccountDashboard, AccountProvider, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, ShippingPicker, SortSelect, StatusBanner, StockBadge, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
3754
+ export { AccountDashboard, AccountProvider, AccountSecurity, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, PurchaseOptions, ShippingPicker, SortSelect, StatusBanner, StockBadge, SubscriptionList, SubscriptionTerms, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useCustomerSubscription, useCustomerSubscriptions, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
3407
3755
  //# sourceMappingURL=index.js.map
3408
3756
  //# sourceMappingURL=index.js.map