@nevios/storefront-kit 1.3.0 → 1.5.0

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