@nevios/storefront-kit 1.4.0 → 1.5.1

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
@@ -226,6 +226,45 @@ var en = {
226
226
  "error.generic.title": "Something went wrong",
227
227
  "error.generic.body": "Please try again in a moment.",
228
228
  "error.home": "Back to home",
229
+ "subscriptions.mixPrice": "Fixed box price; shipping is separate",
230
+ "subscriptions.locked.inactive": "This subscription is not active",
231
+ "subscriptions.locked.cutoff": "The edit cutoff has passed",
232
+ "subscriptions.locked.in_flight": "This shipment or payment is already being processed",
233
+ "subscriptions.quantity": "Quantity",
234
+ "subscriptions.back": "Back",
235
+ "subscriptions.loading": "Loading subscription\u2026",
236
+ "subscriptions.loadingProducts": "Loading products\u2026",
237
+ "subscriptions.chargeDate": "Scheduled charge",
238
+ "subscriptions.noticeHeld": "The charge is waiting for a confirmed advance notice.",
239
+ "subscriptions.nextDelivery": "Next shipment",
240
+ "subscriptions.onlyNext": "Only in this shipment",
241
+ "subscriptions.toCharge": "Amount to charge",
242
+ "subscriptions.noticeStatus": "Renewal notice",
243
+ "subscriptions.notice.pending": "Pending",
244
+ "subscriptions.notice.queued": "Queued",
245
+ "subscriptions.notice.sent": "Sent",
246
+ "subscriptions.notice.failed": "Needs attention",
247
+ "subscriptions.notice.superseded": "Updated notice pending",
248
+ "subscriptions.editUntil": "Changes available until",
249
+ "subscriptions.changeInterval": "Change delivery frequency",
250
+ "subscriptions.addNext": "Add to next shipment",
251
+ "subscriptions.changeMix": "Change my mix",
252
+ "subscriptions.skip": "Skip next shipment",
253
+ "subscriptions.skipWarning": "This shipment and its one-time additions will be skipped. Following shipments keep their regular contents.",
254
+ "subscriptions.futureOnly": "Applies to future shipments. Existing orders stay unchanged.",
255
+ "subscriptions.nextUnchanged": "Next date",
256
+ "subscriptions.reviewTotal": "Reviewed amount",
257
+ "subscriptions.acceptChange": "I agree to the displayed contents, schedule and price.",
258
+ "subscriptions.reloadBeforeRetry": "Reload before another attempt",
259
+ "subscriptions.confirmChange": "Confirm change",
260
+ "subscriptions.reviewChange": "Review change",
261
+ "subscriptions.unit.week": "Every {count} week(s)",
262
+ "subscriptions.unit.month": "Every {count} month(s)",
263
+ "subscriptions.unit.year": "Every {count} year(s)",
264
+ "subscriptions.searchProducts": "Search products",
265
+ "subscriptions.addProduct": "Add product",
266
+ "subscriptions.mixCount": "{count} selected \xB7 choose {min}\u2013{max} units",
267
+ "subscriptions.addMix": "Add mix to cart",
229
268
  // account
230
269
  "account.title": "Your account",
231
270
  "account.signIn": "Sign in",
@@ -432,6 +471,45 @@ var cs = {
432
471
  "error.generic.title": "N\u011Bco se pokazilo",
433
472
  "error.generic.body": "Zkuste to pros\xEDm za chv\xEDli znovu.",
434
473
  "error.home": "Zp\u011Bt na \xFAvod",
474
+ "subscriptions.mixPrice": "Pevn\xE1 cena mixu; doprava se po\u010D\xEDt\xE1 zvl\xE1\u0161\u0165",
475
+ "subscriptions.locked.inactive": "P\u0159edplatn\xE9 nen\xED aktivn\xED.",
476
+ "subscriptions.locked.cutoff": "Term\xEDn pro \xFApravy t\xE9to z\xE1silky ji\u017E uplynul.",
477
+ "subscriptions.locked.in_flight": "Z\xE1silka nebo jej\xED platba se ji\u017E zpracov\xE1v\xE1.",
478
+ "subscriptions.quantity": "Mno\u017Estv\xED",
479
+ "subscriptions.back": "Zp\u011Bt",
480
+ "subscriptions.loading": "Na\u010D\xEDt\xE1n\xED p\u0159edplatn\xE9ho\u2026",
481
+ "subscriptions.loadingProducts": "Na\u010D\xEDt\xE1n\xED produkt\u016F\u2026",
482
+ "subscriptions.chargeDate": "Pl\xE1novan\xE9 str\u017Een\xED",
483
+ "subscriptions.noticeHeld": "Platba \u010Dek\xE1 na potvrzen\xE9 odesl\xE1n\xED upozorn\u011Bn\xED a sjednan\xFD p\u0159edstih.",
484
+ "subscriptions.nextDelivery": "P\u0159\xED\u0161t\xED z\xE1silka",
485
+ "subscriptions.onlyNext": "Jen v t\xE9to z\xE1silce",
486
+ "subscriptions.toCharge": "\u010C\xE1stka ke str\u017Een\xED",
487
+ "subscriptions.noticeStatus": "Upozorn\u011Bn\xED p\u0159ed platbou",
488
+ "subscriptions.notice.pending": "\u010Cek\xE1 na odesl\xE1n\xED",
489
+ "subscriptions.notice.queued": "Ve front\u011B",
490
+ "subscriptions.notice.sent": "Odesl\xE1no",
491
+ "subscriptions.notice.failed": "Vy\u017Eaduje pozornost",
492
+ "subscriptions.notice.superseded": "\u010Cek\xE1 na aktualizovan\xE9 upozorn\u011Bn\xED",
493
+ "subscriptions.editUntil": "\xDApravy mo\u017En\xE9 do",
494
+ "subscriptions.changeInterval": "Zm\u011Bnit interval doru\u010Den\xED",
495
+ "subscriptions.addNext": "P\u0159idat k p\u0159\xED\u0161t\xED z\xE1silce",
496
+ "subscriptions.changeMix": "Zm\u011Bnit vlastn\xED mix",
497
+ "subscriptions.skip": "P\u0159esko\u010Dit p\u0159\xED\u0161t\xED z\xE1silku",
498
+ "subscriptions.skipWarning": "Tato z\xE1silka se p\u0159esko\u010D\xED v\u010Detn\u011B jednor\xE1zov\u011B p\u0159idan\xFDch produkt\u016F. Dal\u0161\xED z\xE1silky budou m\xEDt b\u011B\u017En\xFD obsah p\u0159edplatn\xE9ho.",
499
+ "subscriptions.futureOnly": "Plat\xED pro budouc\xED z\xE1silky. Existuj\xEDc\xED objedn\xE1vky se nezm\u011Bn\xED.",
500
+ "subscriptions.nextUnchanged": "Nejbli\u017E\u0161\xED term\xEDn",
501
+ "subscriptions.reviewTotal": "Potvrzovan\xE1 \u010D\xE1stka",
502
+ "subscriptions.acceptChange": "Souhlas\xEDm se zobrazen\xFDm obsahem, term\xEDny a cenou.",
503
+ "subscriptions.reloadBeforeRetry": "Obnovit p\u0159ed dal\u0161\xEDm pokusem",
504
+ "subscriptions.confirmChange": "Potvrdit zm\u011Bnu",
505
+ "subscriptions.reviewChange": "Zkontrolovat zm\u011Bnu",
506
+ "subscriptions.unit.week": "Ka\u017Ed\xFDch {count} t\xFDdn\u016F",
507
+ "subscriptions.unit.month": "Ka\u017Ed\xFDch {count} m\u011Bs\xEDc\u016F",
508
+ "subscriptions.unit.year": "Ka\u017Ed\xFDch {count} let",
509
+ "subscriptions.searchProducts": "Hledat produkty",
510
+ "subscriptions.addProduct": "P\u0159idat produkt",
511
+ "subscriptions.mixCount": "Vybr\xE1no {count} \xB7 zvolte {min}\u2013{max} kus\u016F",
512
+ "subscriptions.addMix": "P\u0159idat mix do ko\u0161\xEDku",
435
513
  "account.title": "V\xE1\u0161 \xFA\u010Det",
436
514
  "account.signIn": "P\u0159ihl\xE1sit se",
437
515
  "account.signInBlurb": "Zadejte e-mail a po\u0161leme v\xE1m p\u0159ihla\u0161ovac\xED odkaz.",
@@ -1163,7 +1241,9 @@ function PurchaseOptions({ plans, value, onChange, disabled, locale }) {
1163
1241
  ] });
1164
1242
  }
1165
1243
  var CartContext = React27.createContext(null);
1166
- function CartProvider({ children }) {
1244
+ function CartProvider({
1245
+ children
1246
+ }) {
1167
1247
  const client = useStorefront();
1168
1248
  const [cart, setCart] = React27.useState(null);
1169
1249
  const [loading, setLoading] = React27.useState(false);
@@ -1197,6 +1277,11 @@ function CartProvider({ children }) {
1197
1277
  loading,
1198
1278
  itemCount: cart?.totals.item_count ?? 0,
1199
1279
  addLine: (input) => run(() => client.cart.addLine(input)),
1280
+ setSubscriptionMix: (planId, lines, expectedLineIds) => run(
1281
+ () => client.cart.setSubscriptionMix(planId, lines, expectedLineIds, {
1282
+ expectedRevision: cart?.subscription?.revision
1283
+ })
1284
+ ),
1200
1285
  updateLine: (lineId, quantity) => run(() => client.cart.updateLine(lineId, quantity)),
1201
1286
  clearSubscription: () => run(() => client.cart.clearSubscription()),
1202
1287
  removeLine: (lineId) => run(() => client.cart.removeLine(lineId)),
@@ -3553,7 +3638,9 @@ function AccountSecurity() {
3553
3638
  function useCustomerSubscriptions() {
3554
3639
  const client = useStorefront();
3555
3640
  const { customer } = useAccount();
3556
- const [dataOwner, setDataOwner] = React27.useState(void 0);
3641
+ const [dataOwner, setDataOwner] = React27.useState(
3642
+ void 0
3643
+ );
3557
3644
  const [subscriptions, setSubscriptions] = React27.useState([]);
3558
3645
  const [loading, setLoading] = React27.useState(false);
3559
3646
  const [error, setError] = React27.useState(null);
@@ -3577,7 +3664,8 @@ function useCustomerSubscriptions() {
3577
3664
  setError(null);
3578
3665
  }
3579
3666
  } catch (err) {
3580
- if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
3667
+ if (current === generation.current)
3668
+ setError(err instanceof Error ? err : new Error(String(err)));
3581
3669
  } finally {
3582
3670
  if (current === generation.current) setLoading(false);
3583
3671
  }
@@ -3589,11 +3677,18 @@ function useCustomerSubscriptions() {
3589
3677
  generation.current++;
3590
3678
  };
3591
3679
  }, [refresh]);
3592
- return { subscriptions: dataOwner === owner ? subscriptions : [], loading, error, refresh, api };
3680
+ return {
3681
+ subscriptions: dataOwner === owner ? subscriptions : [],
3682
+ loading,
3683
+ error,
3684
+ refresh,
3685
+ api
3686
+ };
3593
3687
  }
3594
3688
  function useCustomerSubscription(id) {
3595
3689
  const client = useStorefront();
3596
3690
  const { customer } = useAccount();
3691
+ const [readRevision, setReadRevision] = React27.useState(0);
3597
3692
  const [dataKey, setDataKey] = React27.useState("");
3598
3693
  const [subscription, setSubscription] = React27.useState(null);
3599
3694
  const [loading, setLoading] = React27.useState(false);
@@ -3614,11 +3709,13 @@ function useCustomerSubscription(id) {
3614
3709
  const row = await api.management(id);
3615
3710
  if (current === generation.current) {
3616
3711
  setSubscription(row);
3712
+ setReadRevision((n) => n + 1);
3617
3713
  setDataKey(`${owner}:${id}`);
3618
3714
  setError(null);
3619
3715
  }
3620
3716
  } catch (err) {
3621
- if (current === generation.current) setError(err instanceof Error ? err : new Error(String(err)));
3717
+ if (current === generation.current)
3718
+ setError(err instanceof Error ? err : new Error(String(err)));
3622
3719
  } finally {
3623
3720
  if (current === generation.current) setLoading(false);
3624
3721
  }
@@ -3630,7 +3727,14 @@ function useCustomerSubscription(id) {
3630
3727
  generation.current++;
3631
3728
  };
3632
3729
  }, [refresh]);
3633
- return { subscription: dataKey === `${owner}:${id}` ? subscription : null, loading, error, refresh, api };
3730
+ return {
3731
+ subscription: dataKey === `${owner}:${id}` ? subscription : null,
3732
+ loading,
3733
+ error,
3734
+ refresh,
3735
+ api,
3736
+ readRevision
3737
+ };
3634
3738
  }
3635
3739
  function SubscriptionList({ href, locale }) {
3636
3740
  const t = useT();
@@ -3647,6 +3751,546 @@ function SubscriptionList({ href, locale }) {
3647
3751
  ] }) }, sub.id)) })
3648
3752
  ] });
3649
3753
  }
3754
+ var subscriptionPanel = {
3755
+ fontFamily: "var(--nv-font)",
3756
+ color: "var(--nv-fg)",
3757
+ background: "var(--nv-bg)",
3758
+ border: "1px solid var(--nv-border)",
3759
+ borderRadius: "var(--nv-radius)",
3760
+ padding: "1rem"
3761
+ };
3762
+ function SubscriptionButton({
3763
+ primary = false,
3764
+ style,
3765
+ ...props
3766
+ }) {
3767
+ return /* @__PURE__ */ jsx(
3768
+ "button",
3769
+ {
3770
+ type: "button",
3771
+ ...props,
3772
+ style: {
3773
+ font: "inherit",
3774
+ fontWeight: 500,
3775
+ padding: ".5rem .85rem",
3776
+ borderRadius: "var(--nv-radius)",
3777
+ border: "1px solid var(--nv-border)",
3778
+ background: primary ? "var(--nv-brand)" : "var(--nv-bg)",
3779
+ color: primary ? "var(--nv-brand-foreground)" : "var(--nv-fg)",
3780
+ cursor: props.disabled ? "default" : "pointer",
3781
+ opacity: props.disabled ? 0.55 : 1,
3782
+ ...style
3783
+ }
3784
+ }
3785
+ );
3786
+ }
3787
+ function ProductSelection({
3788
+ load,
3789
+ value,
3790
+ onChange,
3791
+ initialItems = [],
3792
+ locale,
3793
+ maxQuantity = 99
3794
+ }) {
3795
+ const t = useT(), loc = useFormatLocale(locale), [query, setQuery] = React27.useState(""), [page, setPage] = React27.useState(0), [items, setItems] = React27.useState([]), [more, setMore] = React27.useState(false), [busy, setBusy] = React27.useState(false), [error, setError] = React27.useState("");
3796
+ const known = React27.useRef({});
3797
+ for (const item of initialItems) known.current[item.variant_id] = item;
3798
+ React27.useEffect(() => {
3799
+ const controller = new AbortController();
3800
+ setBusy(true);
3801
+ setError("");
3802
+ const timer = setTimeout(() => {
3803
+ void load(query, page * 50, controller.signal).then((result) => {
3804
+ if (controller.signal.aborted) return;
3805
+ for (const item of result.items)
3806
+ known.current[item.variant_id] = item;
3807
+ setItems((old) => page ? [...old, ...result.items] : result.items);
3808
+ setMore(result.more);
3809
+ }).catch((err) => {
3810
+ if (!controller.signal.aborted)
3811
+ setError(err instanceof Error ? err.message : String(err));
3812
+ }).finally(() => {
3813
+ if (!controller.signal.aborted) setBusy(false);
3814
+ });
3815
+ }, 180);
3816
+ return () => {
3817
+ clearTimeout(timer);
3818
+ controller.abort();
3819
+ };
3820
+ }, [load, query, page]);
3821
+ const selected = Object.keys(value).filter((id) => value[id] > 0).map((id) => known.current[id]).filter(Boolean);
3822
+ return /* @__PURE__ */ jsxs("div", { style: { display: "grid", gap: ".75rem" }, children: [
3823
+ selected.map((p) => /* @__PURE__ */ jsxs(
3824
+ "label",
3825
+ {
3826
+ style: {
3827
+ display: "flex",
3828
+ justifyContent: "space-between",
3829
+ gap: "1rem",
3830
+ alignItems: "center"
3831
+ },
3832
+ children: [
3833
+ /* @__PURE__ */ jsxs("span", { children: [
3834
+ p.title,
3835
+ " ",
3836
+ p.variant_title,
3837
+ /* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: p.sku })
3838
+ ] }),
3839
+ /* @__PURE__ */ jsx(
3840
+ "input",
3841
+ {
3842
+ "aria-label": `${t("subscriptions.quantity")}: ${p.title}`,
3843
+ type: "number",
3844
+ min: 0,
3845
+ max: maxQuantity,
3846
+ value: value[p.variant_id] ?? 0,
3847
+ onChange: (e) => onChange({ ...value, [p.variant_id]: Number(e.target.value) }),
3848
+ style: {
3849
+ font: "inherit",
3850
+ width: "4.5rem",
3851
+ padding: ".5rem",
3852
+ border: "1px solid var(--nv-border)",
3853
+ borderRadius: "var(--nv-radius)",
3854
+ color: "var(--nv-fg)",
3855
+ background: "var(--nv-bg)"
3856
+ }
3857
+ }
3858
+ )
3859
+ ]
3860
+ },
3861
+ p.variant_id
3862
+ )),
3863
+ /* @__PURE__ */ jsx(
3864
+ "input",
3865
+ {
3866
+ "aria-label": t("subscriptions.searchProducts"),
3867
+ placeholder: t("subscriptions.searchProducts"),
3868
+ value: query,
3869
+ onChange: (e) => {
3870
+ setQuery(e.target.value);
3871
+ setPage(0);
3872
+ setItems([]);
3873
+ },
3874
+ style: {
3875
+ font: "inherit",
3876
+ padding: ".65rem",
3877
+ border: "1px solid var(--nv-border)",
3878
+ borderRadius: "var(--nv-radius)",
3879
+ background: "var(--nv-bg)",
3880
+ color: "var(--nv-fg)"
3881
+ }
3882
+ }
3883
+ ),
3884
+ /* @__PURE__ */ jsx(
3885
+ "ul",
3886
+ {
3887
+ style: {
3888
+ listStyle: "none",
3889
+ padding: 0,
3890
+ margin: 0,
3891
+ maxHeight: "18rem",
3892
+ overflowY: "auto"
3893
+ },
3894
+ children: items.filter((p) => !value[p.variant_id]).map((p) => /* @__PURE__ */ jsxs(
3895
+ "li",
3896
+ {
3897
+ style: {
3898
+ display: "flex",
3899
+ alignItems: "center",
3900
+ justifyContent: "space-between",
3901
+ gap: "1rem",
3902
+ padding: ".5rem 0",
3903
+ borderBottom: "1px solid var(--nv-border)"
3904
+ },
3905
+ children: [
3906
+ /* @__PURE__ */ jsxs("span", { children: [
3907
+ p.title,
3908
+ " ",
3909
+ p.variant_title,
3910
+ /* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: formatCents(p.unit_gross, p.currency, loc) })
3911
+ ] }),
3912
+ /* @__PURE__ */ jsx(
3913
+ SubscriptionButton,
3914
+ {
3915
+ "aria-label": `${t("subscriptions.addProduct")}: ${p.title}`,
3916
+ onClick: () => onChange({ ...value, [p.variant_id]: 1 }),
3917
+ children: "+"
3918
+ }
3919
+ )
3920
+ ]
3921
+ },
3922
+ p.variant_id
3923
+ ))
3924
+ }
3925
+ ),
3926
+ busy ? /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.loadingProducts") }) : more ? /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => setPage((p) => p + 1), children: t("account.loadMore") }) : null,
3927
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
3928
+ ] });
3929
+ }
3930
+ function SubscriptionManager({
3931
+ subscriptionId,
3932
+ locale
3933
+ }) {
3934
+ const state = useCustomerSubscription(subscriptionId), { customer } = useAccount(), t = useT();
3935
+ if (!customer) return null;
3936
+ if (state.error)
3937
+ return /* @__PURE__ */ jsxs("p", { role: "alert", children: [
3938
+ state.error.message,
3939
+ " ",
3940
+ /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => void state.refresh(), children: t("account.retry") })
3941
+ ] });
3942
+ if (!state.subscription)
3943
+ return /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.loading") });
3944
+ return /* @__PURE__ */ jsx(
3945
+ Manager,
3946
+ {
3947
+ state,
3948
+ locale
3949
+ },
3950
+ `${customer.id}:${subscriptionId}:${state.subscription.version}:${state.readRevision}`
3951
+ );
3952
+ }
3953
+ function Manager({
3954
+ state,
3955
+ locale
3956
+ }) {
3957
+ const { subscription: s, api, refresh } = state, t = useT(), loc = useFormatLocale(locale);
3958
+ const [mode, setMode] = React27.useState(null);
3959
+ const [option, setOption] = React27.useState("");
3960
+ const [quantities, setQuantities] = React27.useState(
3961
+ {}
3962
+ );
3963
+ const [quote, setQuote] = React27.useState(null);
3964
+ const [busy, setBusy] = React27.useState(false), [locked, setLocked] = React27.useState(false), [error, setError] = React27.useState("");
3965
+ const [consent, setConsent] = React27.useState(false);
3966
+ const upcoming = s?.upcoming, heading = React27.useRef(null);
3967
+ const loader = React27.useCallback(
3968
+ (q, offset, signal) => api.products(s.id, {
3969
+ q,
3970
+ offset,
3971
+ signal,
3972
+ scope: mode === "mix" ? "mix" : "one_time"
3973
+ }),
3974
+ [api, s?.id, mode]
3975
+ );
3976
+ React27.useEffect(() => {
3977
+ if (mode) heading.current?.focus();
3978
+ }, [mode]);
3979
+ if (!s || !upcoming) return null;
3980
+ if (s.status !== "active")
3981
+ return /* @__PURE__ */ jsx("section", { style: subscriptionPanel, children: /* @__PURE__ */ jsx("h2", { children: t(`subscriptions.status.${s.status}`) }) });
3982
+ const money = (n) => formatCents(n, s.currency, loc);
3983
+ const scope = mode === "mix" ? "recurring" : "one_time";
3984
+ const selected = Object.entries(quantities).filter(([, quantity]) => quantity > 0).map(([variant_id, quantity]) => ({ variant_id, quantity }));
3985
+ const changes = mode === "cadence" ? { cadence_option_id: option } : { lines: selected };
3986
+ const amount = quote ? "recurring_total_cents" in quote ? quote.recurring_total_cents : quote.after.total_cents : upcoming.total_cents;
3987
+ const enter = (next) => {
3988
+ setMode(next);
3989
+ setQuote(null);
3990
+ setConsent(false);
3991
+ setError("");
3992
+ setOption("");
3993
+ setQuantities(
3994
+ Object.fromEntries(
3995
+ upcoming.lines.filter(
3996
+ (li) => li.scope === (next === "mix" ? "recurring" : "one_time")
3997
+ ).map((li) => [li.variant_id, li.quantity])
3998
+ )
3999
+ );
4000
+ };
4001
+ return /* @__PURE__ */ jsxs(
4002
+ "section",
4003
+ {
4004
+ style: { ...subscriptionPanel, display: "grid", gap: "1rem" },
4005
+ "aria-busy": busy,
4006
+ children: [
4007
+ /* @__PURE__ */ jsxs("header", { children: [
4008
+ /* @__PURE__ */ jsx("h2", { style: { margin: 0, fontWeight: 500 }, children: t("subscriptions.nextDelivery") }),
4009
+ /* @__PURE__ */ jsxs("p", { children: [
4010
+ t("subscriptions.chargeDate"),
4011
+ ":",
4012
+ " ",
4013
+ /* @__PURE__ */ jsx("time", { dateTime: upcoming.due_at, children: new Date(upcoming.due_at).toLocaleString(loc) })
4014
+ ] })
4015
+ ] }),
4016
+ /* @__PURE__ */ jsx("ul", { style: { listStyle: "none", margin: 0, padding: 0 }, children: upcoming.lines.map((li) => /* @__PURE__ */ jsxs(
4017
+ "li",
4018
+ {
4019
+ style: {
4020
+ display: "flex",
4021
+ justifyContent: "space-between",
4022
+ gap: "1rem",
4023
+ padding: ".65rem 0",
4024
+ borderBottom: "1px solid var(--nv-border)"
4025
+ },
4026
+ children: [
4027
+ /* @__PURE__ */ jsxs("span", { children: [
4028
+ li.title ?? li.sku,
4029
+ " \xD7 ",
4030
+ li.quantity,
4031
+ li.scope === "one_time" ? /* @__PURE__ */ jsx("small", { style: { display: "block", color: "var(--nv-muted)" }, children: t("subscriptions.onlyNext") }) : null
4032
+ ] }),
4033
+ /* @__PURE__ */ jsx("span", { children: money(li.unit_gross * li.quantity - (li.discount_cents ?? 0)) })
4034
+ ]
4035
+ },
4036
+ li.line_id
4037
+ )) }),
4038
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
4039
+ /* @__PURE__ */ jsx("span", { children: t("totals.shipping") }),
4040
+ /* @__PURE__ */ jsx("span", { children: money(upcoming.shipping_cents) })
4041
+ ] }),
4042
+ /* @__PURE__ */ jsxs(
4043
+ "div",
4044
+ {
4045
+ style: {
4046
+ display: "flex",
4047
+ justifyContent: "space-between",
4048
+ fontWeight: 500
4049
+ },
4050
+ children: [
4051
+ /* @__PURE__ */ jsx("span", { children: t("subscriptions.toCharge") }),
4052
+ /* @__PURE__ */ jsx("span", { children: money(upcoming.total_cents) })
4053
+ ]
4054
+ }
4055
+ ),
4056
+ upcoming.capabilities.reminder?.enabled ? /* @__PURE__ */ jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
4057
+ t("subscriptions.noticeStatus"),
4058
+ ":",
4059
+ " ",
4060
+ t(`subscriptions.notice.${upcoming.reminder?.status ?? "pending"}`)
4061
+ ] }) : null,
4062
+ upcoming.charge_held_for_notice && /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.noticeHeld") }),
4063
+ !upcoming.editable ? /* @__PURE__ */ jsx("p", { role: "status", children: t(
4064
+ `subscriptions.locked.${upcoming.locked_reason_code ?? "in_flight"}`
4065
+ ) }) : /* @__PURE__ */ jsxs("p", { style: { color: "var(--nv-muted)", margin: 0 }, children: [
4066
+ t("subscriptions.editUntil"),
4067
+ ":",
4068
+ " ",
4069
+ new Date(upcoming.edit_cutoff_at).toLocaleString(loc)
4070
+ ] }),
4071
+ !mode ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
4072
+ !!upcoming.capabilities.cadence_options?.length && /* @__PURE__ */ jsx(
4073
+ SubscriptionButton,
4074
+ {
4075
+ disabled: !upcoming.editable,
4076
+ onClick: () => enter("cadence"),
4077
+ children: t("subscriptions.changeInterval")
4078
+ }
4079
+ ),
4080
+ upcoming.capabilities.one_time_addons && /* @__PURE__ */ jsx(
4081
+ SubscriptionButton,
4082
+ {
4083
+ disabled: !upcoming.editable,
4084
+ onClick: () => enter("addons"),
4085
+ children: t("subscriptions.addNext")
4086
+ }
4087
+ ),
4088
+ upcoming.capabilities.mix && /* @__PURE__ */ jsx(
4089
+ SubscriptionButton,
4090
+ {
4091
+ disabled: !upcoming.editable,
4092
+ onClick: () => enter("mix"),
4093
+ children: t("subscriptions.changeMix")
4094
+ }
4095
+ ),
4096
+ s.allowed_actions.skip && /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => enter("skip"), children: t("subscriptions.skip") })
4097
+ ] }) : /* @__PURE__ */ jsx(
4098
+ "form",
4099
+ {
4100
+ noValidate: true,
4101
+ onSubmit: async (event) => {
4102
+ event.preventDefault();
4103
+ if (busy || locked) return;
4104
+ setBusy(true);
4105
+ setError("");
4106
+ try {
4107
+ if (mode === "skip") {
4108
+ if (!consent) return;
4109
+ try {
4110
+ await api.skipNext(s.id, s.version);
4111
+ await refresh();
4112
+ } catch (err) {
4113
+ setLocked(true);
4114
+ throw err;
4115
+ }
4116
+ } else if (!quote) {
4117
+ const q = mode === "addons" ? await api.previewNextItems(s.id, { addons: selected }) : await api.previewChanges(s.id, changes);
4118
+ setQuote(q);
4119
+ setConsent(false);
4120
+ } else {
4121
+ if (!consent) return;
4122
+ try {
4123
+ if (mode === "addons")
4124
+ await api.commitNextItems(
4125
+ s.id,
4126
+ { addons: selected },
4127
+ quote,
4128
+ "Customer explicitly accepted the displayed next-delivery contents and total"
4129
+ );
4130
+ else
4131
+ await api.commitChanges(
4132
+ s.id,
4133
+ changes,
4134
+ quote,
4135
+ "Customer explicitly accepted the displayed future terms, dates and price"
4136
+ );
4137
+ await refresh();
4138
+ } catch (err) {
4139
+ setLocked(true);
4140
+ throw err;
4141
+ }
4142
+ }
4143
+ } catch (err) {
4144
+ setError(err instanceof Error ? err.message : String(err));
4145
+ } finally {
4146
+ setBusy(false);
4147
+ }
4148
+ },
4149
+ style: {
4150
+ borderTop: "1px solid var(--nv-border)",
4151
+ paddingTop: "1rem",
4152
+ display: "grid",
4153
+ gap: "1rem"
4154
+ },
4155
+ children: /* @__PURE__ */ jsxs(
4156
+ "fieldset",
4157
+ {
4158
+ disabled: busy,
4159
+ style: {
4160
+ border: 0,
4161
+ margin: 0,
4162
+ padding: 0,
4163
+ display: "grid",
4164
+ gap: "1rem",
4165
+ minWidth: 0
4166
+ },
4167
+ children: [
4168
+ /* @__PURE__ */ jsx(
4169
+ "h3",
4170
+ {
4171
+ ref: heading,
4172
+ tabIndex: -1,
4173
+ style: { margin: 0, fontWeight: 500 },
4174
+ children: t(
4175
+ mode === "cadence" ? "subscriptions.changeInterval" : mode === "mix" ? "subscriptions.changeMix" : mode === "skip" ? "subscriptions.skip" : "subscriptions.addNext"
4176
+ )
4177
+ }
4178
+ ),
4179
+ mode === "skip" ? /* @__PURE__ */ jsx("p", { children: t("subscriptions.skipWarning") }) : quote ? /* @__PURE__ */ jsxs("div", { role: "status", children: [
4180
+ /* @__PURE__ */ jsx("p", { children: t(
4181
+ mode === "addons" ? "subscriptions.onlyNext" : "subscriptions.futureOnly"
4182
+ ) }),
4183
+ mode === "cadence" ? /* @__PURE__ */ jsxs("p", { children: [
4184
+ t("subscriptions.nextUnchanged"),
4185
+ ":",
4186
+ " ",
4187
+ new Date(
4188
+ String(quote.after.next_at)
4189
+ ).toLocaleString(loc)
4190
+ ] }) : null,
4191
+ "upcoming_dates" in quote && quote.upcoming_dates?.map((date) => /* @__PURE__ */ jsx("p", { children: new Date(date).toLocaleString(loc) }, date)),
4192
+ mode === "addons" && "addons" in quote.after && quote.after.addons.map(
4193
+ (li) => /* @__PURE__ */ jsxs("p", { children: [
4194
+ li.title ?? li.sku ?? li.variant_id,
4195
+ " \xD7 ",
4196
+ li.quantity
4197
+ ] }, li.variant_id)
4198
+ ),
4199
+ "display_items" in quote && quote.display_items?.map((li) => /* @__PURE__ */ jsxs("p", { children: [
4200
+ li.title,
4201
+ " \xD7 ",
4202
+ li.quantity
4203
+ ] }, li.variant_id)),
4204
+ /* @__PURE__ */ jsxs("strong", { children: [
4205
+ t("subscriptions.reviewTotal"),
4206
+ ": ",
4207
+ money(amount)
4208
+ ] })
4209
+ ] }) : mode === "cadence" ? /* @__PURE__ */ jsx(
4210
+ "div",
4211
+ {
4212
+ role: "group",
4213
+ "aria-label": t("subscriptions.changeInterval"),
4214
+ style: { display: "flex", gap: ".5rem", flexWrap: "wrap" },
4215
+ children: upcoming.capabilities.cadence_options?.map((o) => /* @__PURE__ */ jsx(
4216
+ SubscriptionButton,
4217
+ {
4218
+ "aria-pressed": option === o.id,
4219
+ onClick: () => setOption(o.id),
4220
+ children: t(`subscriptions.unit.${o.unit}`, { count: o.count })
4221
+ },
4222
+ o.id
4223
+ ))
4224
+ }
4225
+ ) : /* @__PURE__ */ jsx(
4226
+ ProductSelection,
4227
+ {
4228
+ maxQuantity: mode === "mix" ? upcoming.capabilities.mix?.per_sku_max ?? 99 : 99,
4229
+ load: loader,
4230
+ value: quantities,
4231
+ onChange: setQuantities,
4232
+ initialItems: upcoming.lines.filter((li) => li.scope === scope).map((li) => ({
4233
+ ...li,
4234
+ title: li.title ?? li.sku ?? li.variant_id,
4235
+ currency: s.currency,
4236
+ variant_title: li.variant_title ?? null,
4237
+ sku: li.sku ?? null
4238
+ })),
4239
+ locale: loc
4240
+ }
4241
+ ),
4242
+ (quote || mode === "skip") && /* @__PURE__ */ jsxs(
4243
+ "label",
4244
+ {
4245
+ style: {
4246
+ display: "flex",
4247
+ gap: ".5rem",
4248
+ alignItems: "flex-start"
4249
+ },
4250
+ children: [
4251
+ /* @__PURE__ */ jsx(
4252
+ "input",
4253
+ {
4254
+ type: "checkbox",
4255
+ checked: consent,
4256
+ onChange: (e) => setConsent(e.target.checked)
4257
+ }
4258
+ ),
4259
+ t("subscriptions.acceptChange")
4260
+ ]
4261
+ }
4262
+ ),
4263
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
4264
+ locked ? /* @__PURE__ */ jsx(SubscriptionButton, { onClick: () => void refresh(), children: t("subscriptions.reloadBeforeRetry") }) : /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: ".5rem", flexWrap: "wrap" }, children: [
4265
+ /* @__PURE__ */ jsx(
4266
+ SubscriptionButton,
4267
+ {
4268
+ disabled: busy,
4269
+ onClick: () => quote ? (setQuote(null), setConsent(false)) : enter(null),
4270
+ children: t(quote ? "subscriptions.back" : "account.cancel")
4271
+ }
4272
+ ),
4273
+ /* @__PURE__ */ jsx(
4274
+ SubscriptionButton,
4275
+ {
4276
+ type: "submit",
4277
+ primary: true,
4278
+ disabled: busy || (quote || mode === "skip" ? !consent : mode === "cadence" ? !option : mode === "mix" ? !selected.length : false),
4279
+ children: busy ? t("subscriptions.loading") : t(
4280
+ quote || mode === "skip" ? "subscriptions.confirmChange" : "subscriptions.reviewChange"
4281
+ )
4282
+ }
4283
+ )
4284
+ ] })
4285
+ ]
4286
+ }
4287
+ )
4288
+ }
4289
+ )
4290
+ ]
4291
+ }
4292
+ );
4293
+ }
3650
4294
  function serializeJsonLd(data) {
3651
4295
  return JSON.stringify(data).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/&/g, "\\u0026");
3652
4296
  }
@@ -3750,7 +4394,119 @@ function OrganizationJsonLd({
3750
4394
  });
3751
4395
  return /* @__PURE__ */ jsx(JsonLd, { data });
3752
4396
  }
4397
+ function Mix({
4398
+ plan,
4399
+ marketId,
4400
+ locale,
4401
+ onAdded
4402
+ }) {
4403
+ const client = useStorefront(), cart = useCart(), t = useT(), [value, setValue] = React27.useState({}), [busy, setBusy] = React27.useState(false), [error, setError] = React27.useState(""), [locked, setLocked] = React27.useState(false);
4404
+ const load = React27.useCallback(
4405
+ (q, offset, signal) => client.subscriptions.mixProducts(plan.id, {
4406
+ q,
4407
+ offset,
4408
+ marketId,
4409
+ signal
4410
+ }),
4411
+ [client, plan.id, marketId]
4412
+ );
4413
+ const loc = useFormatLocale(locale);
4414
+ const mix = plan.capabilities?.mix;
4415
+ if (!mix) return null;
4416
+ const units = Object.values(value).reduce((a, b) => a + b, 0), lines = Object.entries(value).filter(([, q]) => q > 0).map(([variant_id, quantity]) => ({ variant_id, quantity }));
4417
+ const valid = units >= mix.min_units && units <= mix.max_units && lines.every(
4418
+ (li) => Number.isInteger(li.quantity) && li.quantity <= (mix.per_sku_max ?? 99)
4419
+ ) && (mix.groups ?? []).every((g) => {
4420
+ const n = lines.filter((li) => g.variant_ids.includes(li.variant_id)).reduce((a, li) => a + li.quantity, 0);
4421
+ return n >= g.min_units && n <= g.max_units;
4422
+ });
4423
+ return /* @__PURE__ */ jsxs("section", { style: { ...subscriptionPanel, display: "grid", gap: "1rem" }, children: [
4424
+ /* @__PURE__ */ jsx("h2", { style: { fontWeight: 500, margin: 0 }, children: plan.name }),
4425
+ plan.price && /* @__PURE__ */ jsxs("p", { children: [
4426
+ formatCents(plan.price.amount_cents, plan.price.currency, loc),
4427
+ " \xB7",
4428
+ " ",
4429
+ t("subscriptions.mixPrice")
4430
+ ] }),
4431
+ /* @__PURE__ */ jsx("p", { role: "status", children: t("subscriptions.mixCount", {
4432
+ count: units,
4433
+ min: mix.min_units,
4434
+ max: mix.max_units
4435
+ }) }),
4436
+ mix.groups?.map((g) => /* @__PURE__ */ jsxs("p", { children: [
4437
+ g.name,
4438
+ ": ",
4439
+ g.min_units,
4440
+ "\u2013",
4441
+ g.max_units
4442
+ ] }, g.name)),
4443
+ /* @__PURE__ */ jsx(
4444
+ "fieldset",
4445
+ {
4446
+ disabled: busy || locked,
4447
+ style: { border: 0, padding: 0, margin: 0, minWidth: 0 },
4448
+ children: /* @__PURE__ */ jsx(
4449
+ ProductSelection,
4450
+ {
4451
+ maxQuantity: mix.per_sku_max ?? 99,
4452
+ load,
4453
+ value,
4454
+ onChange: (v) => {
4455
+ setValue(v);
4456
+ setError("");
4457
+ },
4458
+ locale
4459
+ }
4460
+ )
4461
+ }
4462
+ ),
4463
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error }),
4464
+ locked ? /* @__PURE__ */ jsx(
4465
+ SubscriptionButton,
4466
+ {
4467
+ onClick: async () => {
4468
+ await cart.refresh();
4469
+ setLocked(false);
4470
+ },
4471
+ children: t("subscriptions.reloadBeforeRetry")
4472
+ }
4473
+ ) : /* @__PURE__ */ jsx(
4474
+ SubscriptionButton,
4475
+ {
4476
+ primary: true,
4477
+ disabled: !valid || busy || cart.loading,
4478
+ onClick: async () => {
4479
+ setBusy(true);
4480
+ setError("");
4481
+ try {
4482
+ await cart.setSubscriptionMix(
4483
+ plan.id,
4484
+ lines,
4485
+ cart.cart?.line_items.filter((li) => li.subscription_plan_id).map((li) => li.id) ?? []
4486
+ );
4487
+ onAdded?.();
4488
+ } catch (err) {
4489
+ setLocked(true);
4490
+ setError(err instanceof Error ? err.message : String(err));
4491
+ } finally {
4492
+ setBusy(false);
4493
+ }
4494
+ },
4495
+ children: t("subscriptions.addMix")
4496
+ }
4497
+ )
4498
+ ] });
4499
+ }
4500
+ function SubscriptionMix(props) {
4501
+ return /* @__PURE__ */ jsx(
4502
+ Mix,
4503
+ {
4504
+ ...props
4505
+ },
4506
+ `${props.plan.id}:${props.plan.version}:${props.marketId ?? ""}`
4507
+ );
4508
+ }
3753
4509
 
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 };
4510
+ export { AccountDashboard, AccountProvider, AccountSecurity, AddToCart, AddressBook, AddressForm, AvailabilityFilter, BackInStockForm, BreadcrumbJsonLd, CartDrawer, CartLineItem, CartProvider, CartSummary, CheckoutFlow, CheckoutProvider, CollectionPage, I18nProvider, JsonLd, LoginForm, MarketSwitcher, NeviosProvider, OrderConfirmation, OrderHistory, OrderLines, OrderStatusPage, OrderSummary, OrderTotals, OrganizationJsonLd, PaymentMethodPicker, PaymentPage, PaymentSection, PhaseTimeline, PriceMoney, PriceRangeFilter, ProductCard, ProductDetailPage, ProductGallery, ProductJsonLd, ProfileEditor, PurchaseOptions, ShippingPicker, SortSelect, StatusBanner, StockBadge, SubscriptionList, SubscriptionManager, SubscriptionMix, SubscriptionTerms, TagFilter, ThemeProvider, TransferInstructions, VariantPicker, absoluteUrl, bundledMessages, cs, en, formatCents, formatMoney, isTerminal, serializeJsonLd, stripHtml, themeToCssVars, useAccount, useCart, useCheckout, useCustomerSubscription, useCustomerSubscriptions, useI18n, useMagicLinkVerify, useOrderStatus, usePayment, useStorefront, useStorefrontOptional, useT };
3755
4511
  //# sourceMappingURL=index.js.map
3756
4512
  //# sourceMappingURL=index.js.map