@fluid-app/portal-sdk 0.1.307 → 0.1.308

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.
Files changed (56) hide show
  1. package/dist/{FluidProvider-BiULiWJY.cjs → FluidProvider-B1UsSiKO.cjs} +2 -2
  2. package/dist/{FluidProvider-BiULiWJY.cjs.map → FluidProvider-B1UsSiKO.cjs.map} +1 -1
  3. package/dist/{FluidProvider-CLIlEKSg.mjs → FluidProvider-D7BEpqDQ.mjs} +2 -2
  4. package/dist/{FluidProvider-CLIlEKSg.mjs.map → FluidProvider-D7BEpqDQ.mjs.map} +1 -1
  5. package/dist/{MessagingScreen-CYTxBDtC.cjs → MessagingScreen-0qwUGWGf.cjs} +3 -3
  6. package/dist/{MessagingScreen-CYTxBDtC.cjs.map → MessagingScreen-0qwUGWGf.cjs.map} +1 -1
  7. package/dist/{MessagingScreen-Cd2iUd_B.cjs → MessagingScreen-DCHGSAA7.cjs} +3 -3
  8. package/dist/{MessagingScreen-CZTploEf.mjs → MessagingScreen-DyFLJsQ-.mjs} +3 -3
  9. package/dist/{MessagingScreen-CZTploEf.mjs.map → MessagingScreen-DyFLJsQ-.mjs.map} +1 -1
  10. package/dist/{MessagingScreen-CKNGfS56.mjs → MessagingScreen-L6vyOqgm.mjs} +3 -3
  11. package/dist/{OrdersScreen-CIxlNlYf.cjs → OrdersScreen-3WRoz4fR.cjs} +3 -3
  12. package/dist/{OrdersScreen-mHhOjrMk.mjs → OrdersScreen-BiB5eMA4.mjs} +3 -3
  13. package/dist/{OrdersScreen-mHhOjrMk.mjs.map → OrdersScreen-BiB5eMA4.mjs.map} +1 -1
  14. package/dist/{OrdersScreen-BB63IDwz.mjs → OrdersScreen-UsFG95zY.mjs} +3 -3
  15. package/dist/{OrdersScreen-BVOQup59.cjs → OrdersScreen-rjcGVfyT.cjs} +3 -3
  16. package/dist/{OrdersScreen-BVOQup59.cjs.map → OrdersScreen-rjcGVfyT.cjs.map} +1 -1
  17. package/dist/{ProfileScreen-BiO036lk.cjs → ProfileScreen-Bm9gayW5.cjs} +3 -3
  18. package/dist/{ProfileScreen-7yjZmDoH.mjs → ProfileScreen-CIDEmZg9.mjs} +4 -4
  19. package/dist/{ProfileScreen-7yjZmDoH.mjs.map → ProfileScreen-CIDEmZg9.mjs.map} +1 -1
  20. package/dist/{ProfileScreen-DLWOaN4I.mjs → ProfileScreen-DioyTFVO.mjs} +3 -3
  21. package/dist/{ProfileScreen-Dsm-0XQY.cjs → ProfileScreen-xTVmveXS.cjs} +4 -4
  22. package/dist/{ProfileScreen-Dsm-0XQY.cjs.map → ProfileScreen-xTVmveXS.cjs.map} +1 -1
  23. package/dist/{ShareablesScreen-C8zQjj9l.mjs → ShareablesScreen-BXMbz5Fn.mjs} +3 -3
  24. package/dist/{ShareablesScreen-C8zQjj9l.mjs.map → ShareablesScreen-BXMbz5Fn.mjs.map} +1 -1
  25. package/dist/{ShareablesScreen-DFTD6x4A.cjs → ShareablesScreen-BsXn5-sp.cjs} +3 -3
  26. package/dist/{ShareablesScreen-DFTD6x4A.cjs.map → ShareablesScreen-BsXn5-sp.cjs.map} +1 -1
  27. package/dist/{ShareablesScreen-6c24GuTW.mjs → ShareablesScreen-CetzfpS7.mjs} +2 -2
  28. package/dist/{ShareablesScreen-B1h2JbtB.cjs → ShareablesScreen-JAN0oKF9.cjs} +2 -2
  29. package/dist/{ShopScreen-C3pEQzV_.cjs → ShopScreen-BM0aj7oV.cjs} +2 -2
  30. package/dist/{ShopScreen-C3pEQzV_.cjs.map → ShopScreen-BM0aj7oV.cjs.map} +1 -1
  31. package/dist/{ShopScreen-B-lu9pT5.cjs → ShopScreen-BsTTHFit.cjs} +3 -3
  32. package/dist/{ShopScreen-CuXcm9mj.mjs → ShopScreen-DwyZ8pqV.mjs} +3 -3
  33. package/dist/{ShopScreen-H8OlaM3P.mjs → ShopScreen-u8MPbT1t.mjs} +2 -2
  34. package/dist/{ShopScreen-H8OlaM3P.mjs.map → ShopScreen-u8MPbT1t.mjs.map} +1 -1
  35. package/dist/{SubscriptionsScreen-kCkz_1xD.cjs → SubscriptionsScreen-D_HUnXQb.cjs} +2 -2
  36. package/dist/{SubscriptionsScreen-1hP0t23a.cjs → SubscriptionsScreen-Dfmqn6LY.cjs} +1344 -37
  37. package/dist/SubscriptionsScreen-Dfmqn6LY.cjs.map +1 -0
  38. package/dist/{SubscriptionsScreen-Ck3-dcqq.mjs → SubscriptionsScreen-HIIrxH3a.mjs} +1346 -39
  39. package/dist/SubscriptionsScreen-HIIrxH3a.mjs.map +1 -0
  40. package/dist/index.cjs +21 -21
  41. package/dist/index.d.cts.map +1 -1
  42. package/dist/index.d.mts.map +1 -1
  43. package/dist/index.mjs +21 -21
  44. package/dist/{portal_tenant-l4a-eX2B.mjs → portal_tenant-BLdhIM81.mjs} +22 -2
  45. package/dist/portal_tenant-BLdhIM81.mjs.map +1 -0
  46. package/dist/{portal_tenant-DG4gKXnu.cjs → portal_tenant-ywAHQ1k3.cjs} +33 -1
  47. package/dist/portal_tenant-ywAHQ1k3.cjs.map +1 -0
  48. package/dist/{use-account-BgustWSP.mjs → use-account-BOE1yhAH.mjs} +2 -2
  49. package/dist/{use-account-BgustWSP.mjs.map → use-account-BOE1yhAH.mjs.map} +1 -1
  50. package/dist/{use-account-EicAlO8K.cjs → use-account-DeRJHrd3.cjs} +2 -2
  51. package/dist/{use-account-EicAlO8K.cjs.map → use-account-DeRJHrd3.cjs.map} +1 -1
  52. package/package.json +16 -16
  53. package/dist/SubscriptionsScreen-1hP0t23a.cjs.map +0 -1
  54. package/dist/SubscriptionsScreen-Ck3-dcqq.mjs.map +0 -1
  55. package/dist/portal_tenant-DG4gKXnu.cjs.map +0 -1
  56. package/dist/portal_tenant-l4a-eX2B.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  require("./chunk-9hOWP6kD.cjs");
2
- const require_portal_tenant = require("./portal_tenant-DG4gKXnu.cjs");
2
+ const require_portal_tenant = require("./portal_tenant-ywAHQ1k3.cjs");
3
3
  const require_api_context = require("./api-context-BkBvyFVK.cjs");
4
4
  const require_countries_api_context = require("./countries-api-context-C0C0K9gJ.cjs");
5
5
  const require_PortalTenantClientProvider = require("./PortalTenantClientProvider-CVv-4rQ9.cjs");
@@ -8,7 +8,7 @@ const require_src = require("./src-D4b32Kh_.cjs");
8
8
  const require_ScreenHeaderContext = require("./ScreenHeaderContext-DWDN0-mb.cjs");
9
9
  const require_translation_api_context_factory = require("./translation-api-context-factory-Cv42jpKn.cjs");
10
10
  const require_query_keys = require("./query-keys-e9EEoWxN.cjs");
11
- const require_use_account = require("./use-account-EicAlO8K.cjs");
11
+ const require_use_account = require("./use-account-DeRJHrd3.cjs");
12
12
  const require_AppNavigationContext = require("./AppNavigationContext-BDs1cOuG.cjs");
13
13
  const require_static_dict_adapter = require("./static-dict-adapter-DIeL3Npb.cjs");
14
14
  const require_AddressAutocompleteInput = require("./AddressAutocompleteInput-Bchlq8ch.cjs");
@@ -2169,6 +2169,27 @@ const subscriptionsKeys = {
2169
2169
  token
2170
2170
  ]
2171
2171
  };
2172
+ const bundlesKeys = {
2173
+ all: ["subscription_bundles"],
2174
+ list: () => [...bundlesKeys.all, "list"]
2175
+ };
2176
+ //#endregion
2177
+ //#region ../../subscriptions/core/src/bundles-api-context.ts
2178
+ const BundlesApiContext = (0, react.createContext)(null);
2179
+ const BundlesApiProvider = BundlesApiContext.Provider;
2180
+ function useBundlesApi() {
2181
+ const api = (0, react.useContext)(BundlesApiContext);
2182
+ if (!api) throw new Error("useBundlesApi must be used within a BundlesCoreProvider");
2183
+ return api;
2184
+ }
2185
+ //#endregion
2186
+ //#region ../../subscriptions/core/src/bundles-provider.tsx
2187
+ function BundlesCoreProvider({ api, children }) {
2188
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundlesApiProvider, {
2189
+ value: api,
2190
+ children
2191
+ });
2192
+ }
2172
2193
  //#endregion
2173
2194
  //#region ../../subscriptions/core/src/hooks/use-subscriptions.ts
2174
2195
  function useSubscriptions(params, options) {
@@ -2294,6 +2315,31 @@ function useUpdateSubscriptionInfo(options) {
2294
2315
  });
2295
2316
  }
2296
2317
  //#endregion
2318
+ //#region ../../subscriptions/core/src/hooks/use-bundles.ts
2319
+ function useBundles(options) {
2320
+ const api = useBundlesApi();
2321
+ return (0, _tanstack_react_query.useQuery)({
2322
+ queryKey: bundlesKeys.list(),
2323
+ queryFn: () => api.fetchBundles(),
2324
+ enabled: options?.enabled ?? true
2325
+ });
2326
+ }
2327
+ //#endregion
2328
+ //#region ../../subscriptions/core/src/hooks/use-create-bundle.ts
2329
+ function useCreateBundle(options) {
2330
+ const api = useBundlesApi();
2331
+ const queryClient = (0, _tanstack_react_query.useQueryClient)();
2332
+ return (0, _tanstack_react_query.useMutation)({
2333
+ mutationFn: (params) => api.createBundle(params),
2334
+ onSuccess: (bundle) => {
2335
+ queryClient.invalidateQueries({ queryKey: bundlesKeys.all });
2336
+ queryClient.invalidateQueries({ queryKey: subscriptionsKeys.all });
2337
+ options?.onSuccess?.(bundle);
2338
+ },
2339
+ onError: options?.onError
2340
+ });
2341
+ }
2342
+ //#endregion
2297
2343
  //#region ../../subscriptions/core/src/utils/subscription-helpers.ts
2298
2344
  function formatSubscriptionFrequency(frequency) {
2299
2345
  if (frequency === "day") return "Daily";
@@ -2585,7 +2631,23 @@ function mapDetailSubscription(bff) {
2585
2631
  name: ""
2586
2632
  },
2587
2633
  subscription_skips: [],
2588
- orders: []
2634
+ orders: [],
2635
+ bundled_items: bff.bundled_items ? bff.bundled_items.map((item) => ({
2636
+ quantity: item.quantity ?? 0,
2637
+ product_id: item.product_id ?? 0,
2638
+ variant_id: item.variant_id ?? 0,
2639
+ option_titles: item.option_titles ?? [],
2640
+ product_title: item.product_title ?? "",
2641
+ variant_title: item.variant_title ?? "",
2642
+ display_to_customer: item.display_to_customer ?? true
2643
+ })) : void 0,
2644
+ bundled_subscriptions: bff.bundled_subscriptions ? bff.bundled_subscriptions.map((sib) => ({
2645
+ subscription_token: sib.subscription_token ?? "",
2646
+ product_title: sib.product_title ?? "",
2647
+ variant_title: sib.variant_title ?? null,
2648
+ price: sib.price ?? 0,
2649
+ image_url: sib.image_url ?? null
2650
+ })) : void 0
2589
2651
  };
2590
2652
  }
2591
2653
  function mapSubscriptionDetail(response) {
@@ -2629,12 +2691,12 @@ function mapListItem(bff) {
2629
2691
  quantity: bff.quantity ?? 1,
2630
2692
  status: bff.status ?? "",
2631
2693
  subscription_plan: {
2632
- id: 0,
2694
+ id: bff.plan?.id ?? 0,
2633
2695
  active: true,
2634
- billing_interval: 1,
2635
- billing_interval_unit: bff.interval ?? "",
2636
- shipping_interval: 1,
2637
- shipping_interval_unit: bff.interval ?? ""
2696
+ billing_interval: bff.plan?.billing_interval ?? 1,
2697
+ billing_interval_unit: bff.plan?.billing_interval_unit ?? "",
2698
+ shipping_interval: bff.plan?.billing_interval ?? 1,
2699
+ shipping_interval_unit: bff.plan?.billing_interval_unit ?? ""
2638
2700
  },
2639
2701
  variant: {
2640
2702
  id: bff.product?.id ?? 0,
@@ -2645,7 +2707,14 @@ function mapListItem(bff) {
2645
2707
  image_url: bff.product?.image_url ?? "",
2646
2708
  price_in_currency: bff.total ?? "0"
2647
2709
  }
2648
- }
2710
+ },
2711
+ bundled_subscriptions: (bff.bundled_subscriptions ?? []).map((b) => ({
2712
+ subscription_token: b.subscription_token ?? "",
2713
+ product_title: b.product_title ?? "",
2714
+ variant_title: b.variant_title ?? null,
2715
+ price: b.price ?? 0,
2716
+ image_url: b.image_url ?? null
2717
+ }))
2649
2718
  };
2650
2719
  }
2651
2720
  function mapSubscriptionList(response) {
@@ -2723,6 +2792,68 @@ function PortalSubscriptionsApiProvider({ children }) {
2723
2792
  });
2724
2793
  }
2725
2794
  //#endregion
2795
+ //#region src/adapters/bundles-api-adapter.ts
2796
+ function mapAddress(bff) {
2797
+ if (!bff) return null;
2798
+ return {
2799
+ id: bff.id ?? 0,
2800
+ name: bff.name ?? null,
2801
+ address1: bff.address1 ?? "",
2802
+ address2: bff.address2 ?? null,
2803
+ city: bff.city ?? "",
2804
+ state: bff.state ?? "",
2805
+ subdivision_code: null,
2806
+ postal_code: bff.postal_code ?? "",
2807
+ country_code: bff.country_code ?? null,
2808
+ default: false
2809
+ };
2810
+ }
2811
+ function mapVolumeRep(bff) {
2812
+ if (!bff) return null;
2813
+ return {
2814
+ id: bff.id ?? 0,
2815
+ email: bff.email ?? null,
2816
+ full_name: bff.full_name ?? null,
2817
+ image_url: bff.image_url ?? null
2818
+ };
2819
+ }
2820
+ function mapBundle(bff) {
2821
+ return {
2822
+ next_bill_date: bff.next_bill_date ?? null,
2823
+ subscription_count: bff.subscription_count ?? 0,
2824
+ address: mapAddress(bff.address),
2825
+ volume_rep: mapVolumeRep(bff.volume_rep),
2826
+ subscriptions: (bff.subscriptions ?? []).map((s) => mapDetailSubscription(s))
2827
+ };
2828
+ }
2829
+ function createPortalBundlesAdapter(client) {
2830
+ return {
2831
+ fetchBundles: async () => {
2832
+ return ((await require_portal_tenant.subscription_bundles_list(client)).subscription_bundles ?? []).map(mapBundle);
2833
+ },
2834
+ createBundle: async (params) => {
2835
+ const bundle = {
2836
+ subscription_tokens: params.subscription_tokens,
2837
+ next_bill_date: params.next_bill_date
2838
+ };
2839
+ if (params.address_id !== void 0) bundle.address_id = params.address_id;
2840
+ if (params.volume_rep_id !== void 0) bundle.volume_rep_id = params.volume_rep_id;
2841
+ const bff = (await require_portal_tenant.subscription_bundles_create(client, { bundle })).subscription_bundle;
2842
+ if (!bff) throw new Error("BFF returned a successful response but bundle data was missing");
2843
+ return mapBundle(bff);
2844
+ }
2845
+ };
2846
+ }
2847
+ //#endregion
2848
+ //#region src/subscriptions/PortalBundlesApiProvider.tsx
2849
+ function PortalBundlesApiProvider({ children }) {
2850
+ const client = require_PortalTenantClientProvider.usePortalTenantClient();
2851
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundlesCoreProvider, {
2852
+ api: (0, react.useMemo)(() => createPortalBundlesAdapter(client), [client]),
2853
+ children
2854
+ });
2855
+ }
2856
+ //#endregion
2726
2857
  //#region ../../subscriptions/core/src/translation-api-context.ts
2727
2858
  const { Provider: SubscriptionsProvider, useTranslation } = require_translation_api_context_factory.createTranslationContext("Subscriptions");
2728
2859
  const SubscriptionsTranslationProvider = SubscriptionsProvider;
@@ -2898,7 +3029,43 @@ const subscriptionsDomain = require_static_dict_adapter.createDomainTranslations
2898
3029
  list_empty_hint: "When you subscribe to a product, it'll show up here.",
2899
3030
  list_load_failed_title: "Couldn't load your subscriptions",
2900
3031
  list_load_failed_hint: "Something went wrong on our end. Please try again.",
2901
- list_try_again: "Try again"
3032
+ list_try_again: "Try again",
3033
+ list_bundle_cta: "Bundle",
3034
+ list_bundle_card_label: "Bundle · {{n}} subscriptions",
3035
+ list_bundle_more_count: "+{{n}} more",
3036
+ list_bundle_members_title: "Bundle members",
3037
+ bundle_subscriptions: "Bundle subscriptions",
3038
+ bundle_back: "Back",
3039
+ bundle_done: "Done",
3040
+ bundle_create: "Create bundle",
3041
+ bundle_step_select: "Select",
3042
+ bundle_step_date: "Date",
3043
+ bundle_step_address: "Address",
3044
+ bundle_step_payment: "Payment",
3045
+ bundle_and_save: "Bundle and save",
3046
+ bundle_select_subs: "Select 2 or more subscriptions to bundle them onto one bill date and shipment.",
3047
+ bundle_step1_message: "✨ Bundle and save on shipping",
3048
+ bundle_select_new_bill_date: "Select a new bill date",
3049
+ bundle_select_new_bill_date_description: "All bundled subscriptions will share this bill date.",
3050
+ bundle_year: "Year",
3051
+ bundle_month: "Month",
3052
+ bundle_day: "Day",
3053
+ bundle_step2_message: "📅 Choose when they ship together",
3054
+ bundle_select_address: "Select shipping address",
3055
+ bundle_select_address_description: "All bundled subscriptions will ship to this address.",
3056
+ bundle_selected_bill_date: "Selected bill date:",
3057
+ bundle_add_new_address: "Add new address",
3058
+ bundle_default_badge: "Default",
3059
+ bundle_step3_message: "🎁 Almost there — pick delivery",
3060
+ bundle_select_payment: "Select payment method",
3061
+ bundle_select_payment_description: "All bundled subscriptions will be charged to this payment method.",
3062
+ bundle_no_usable_payment_methods: "No usable payment methods. Add one in your profile.",
3063
+ bundle_card_expires: "Expires",
3064
+ bundle_add_new_card: "Add new card",
3065
+ bundle_step4_message: "💳 Last step — choose payment method",
3066
+ bundle_succeeded: "Subscriptions bundled successfully.",
3067
+ bundle_partial_payment_failure: "Bundle created but payment method failed for {{n}} subscription(s).",
3068
+ bundle_failed: "Couldn't bundle subscriptions. Please try again."
2902
3069
  },
2903
3070
  loaders: {
2904
3071
  de: () => Promise.resolve().then(() => require("./de-CfNYQbnG.cjs")).then((m) => m.default),
@@ -3057,10 +3224,17 @@ function pricingSuffix(plan, t) {
3057
3224
  }
3058
3225
  function formatLongDate(iso) {
3059
3226
  try {
3060
- return new Date(iso).toLocaleDateString(void 0, {
3227
+ const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(iso);
3228
+ if (!match) return iso;
3229
+ const y = Number(match[1]);
3230
+ const m = Number(match[2]);
3231
+ const d = Number(match[3]);
3232
+ if (!y || !m || !d) return iso;
3233
+ return new Date(Date.UTC(y, m - 1, d)).toLocaleDateString(void 0, {
3061
3234
  month: "long",
3062
3235
  day: "numeric",
3063
- year: "numeric"
3236
+ year: "numeric",
3237
+ timeZone: "UTC"
3064
3238
  });
3065
3239
  } catch {
3066
3240
  return iso;
@@ -3077,6 +3251,17 @@ function formatPrice(sub) {
3077
3251
  return sub.price.toFixed(2);
3078
3252
  }
3079
3253
  /**
3254
+ * Mirror of `formatPrice` for the detail subscription shape used inside a
3255
+ * `Bundle.subscriptions[]`. The detail variant carries `price_in_currency`
3256
+ * directly (no nested `product` like the list variant), so this peels off
3257
+ * one less layer.
3258
+ */
3259
+ function formatDetailPrice(sub) {
3260
+ const fromBff = sub.variant?.price_in_currency;
3261
+ if (fromBff && fromBff.trim().length > 0) return fromBff;
3262
+ return sub.price.toFixed(2);
3263
+ }
3264
+ /**
3080
3265
  * Compute progress through a billing cycle. Returns `null` when either
3081
3266
  * date is missing, unparseable, or `end <= start`. `now` is injectable
3082
3267
  * for tests.
@@ -3108,7 +3293,8 @@ function friendlyDaysAhead(end, t, now = Date.now()) {
3108
3293
  }
3109
3294
  //#endregion
3110
3295
  //#region src/screens/subscriptions/PortalSubscriptionsList.tsx
3111
- function PortalSubscriptionsList({ customerId, isLoadingCustomer, onSubscriptionClick, onResume }) {
3296
+ const EMPTY_BUNDLES = [];
3297
+ function PortalSubscriptionsList({ customerId, isLoadingCustomer, onSubscriptionClick, onResume, onBundleClick, bundles = EMPTY_BUNDLES }) {
3112
3298
  const { t } = useSubscriptionsTranslation();
3113
3299
  const [filter, setFilter] = (0, react.useState)("all");
3114
3300
  const { data, isLoading, isError, refetch, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteSubscriptions({
@@ -3118,13 +3304,26 @@ function PortalSubscriptionsList({ customerId, isLoadingCustomer, onSubscription
3118
3304
  const list = (0, react.useMemo)(() => data?.pages.flatMap((p) => p.subscriptions) ?? [], [data]);
3119
3305
  const counts = (0, react.useMemo)(() => countByFilter(list), [list]);
3120
3306
  const filteredList = (0, react.useMemo)(() => list.filter((s) => filterPredicate(filter, s)), [list, filter]);
3307
+ const relevantBundles = (0, react.useMemo)(() => bundles.filter((b) => b.subscription_count > 1), [bundles]);
3308
+ const bundledTokens = (0, react.useMemo)(() => {
3309
+ const tokens = /* @__PURE__ */ new Set();
3310
+ for (const bundle of relevantBundles) for (const sub of bundle.subscriptions) if (sub.subscription_token) tokens.add(sub.subscription_token);
3311
+ return tokens;
3312
+ }, [relevantBundles]);
3313
+ const showBundleSection = filter === "all" && relevantBundles.length > 0;
3121
3314
  const { activeSubs, inactiveSubs } = (0, react.useMemo)(() => {
3122
3315
  const sorted = filteredList.slice().sort((a, b) => STATUS_PRIORITY[mapStatus(a.status, t).kind] - STATUS_PRIORITY[mapStatus(b.status, t).kind]);
3316
+ const activeCandidates = sorted.filter((s) => isActiveKind(mapStatus(s.status, t).kind));
3123
3317
  return {
3124
- activeSubs: sorted.filter((s) => isActiveKind(mapStatus(s.status, t).kind)),
3318
+ activeSubs: showBundleSection ? activeCandidates.filter((s) => !bundledTokens.has(s.subscription_token)) : activeCandidates,
3125
3319
  inactiveSubs: sorted.filter((s) => !isActiveKind(mapStatus(s.status, t).kind))
3126
3320
  };
3127
- }, [filteredList, t]);
3321
+ }, [
3322
+ filteredList,
3323
+ t,
3324
+ showBundleSection,
3325
+ bundledTokens
3326
+ ]);
3128
3327
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ScreenHeaderContext.ScreenHeaderBreadcrumbs, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Breadcrumb, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbList, {
3129
3328
  className: "text-lg",
3130
3329
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbItem, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.BreadcrumbPage, {
@@ -3144,6 +3343,7 @@ function PortalSubscriptionsList({ customerId, isLoadingCustomer, onSubscription
3144
3343
  active: filter,
3145
3344
  counts,
3146
3345
  onChange: setFilter,
3346
+ onBundleClick,
3147
3347
  t
3148
3348
  }),
3149
3349
  isLoading || isLoadingCustomer ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -3155,14 +3355,18 @@ function PortalSubscriptionsList({ customerId, isLoadingCustomer, onSubscription
3155
3355
  }) : filteredList.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {
3156
3356
  filtered: filter !== "all" && list.length > 0,
3157
3357
  t
3158
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [activeSubs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3358
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [(showBundleSection || activeSubs.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3159
3359
  className: "mt-6 grid grid-cols-1 gap-5 md:grid-cols-2",
3160
- children: activeSubs.map((sub) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionCard, {
3360
+ children: [showBundleSection && relevantBundles.map((bundle, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundleCard, {
3361
+ bundle,
3362
+ onSubscriptionClick,
3363
+ t
3364
+ }, `bundle-${i}-${bundle.next_bill_date}`)), activeSubs.map((sub) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionCard, {
3161
3365
  sub,
3162
3366
  onClick: () => onSubscriptionClick(sub.subscription_token),
3163
3367
  onResume: onResume ? () => onResume(sub) : void 0,
3164
3368
  t
3165
- }, sub.id))
3369
+ }, sub.id))]
3166
3370
  }), inactiveSubs.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3167
3371
  className: activeSubs.length > 0 ? "mt-10" : "mt-6",
3168
3372
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -3209,10 +3413,10 @@ const FILTER_KEYS = [
3209
3413
  labelKey: "list_filter_cancelled"
3210
3414
  }
3211
3415
  ];
3212
- function FilterPills({ active, counts, onChange, t }) {
3213
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3214
- className: "-mx-1 mt-5 flex flex-wrap gap-1.5 px-1",
3215
- children: FILTER_KEYS.map((f) => {
3416
+ function FilterPills({ active, counts, onChange, onBundleClick, t }) {
3417
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3418
+ className: "-mx-1 mt-5 flex flex-wrap items-center gap-1.5 px-1",
3419
+ children: [FILTER_KEYS.map((f) => {
3216
3420
  const isActive = f.key === active;
3217
3421
  const count = counts[f.key];
3218
3422
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -3224,8 +3428,153 @@ function FilterPills({ active, counts, onChange, t }) {
3224
3428
  children: count
3225
3429
  })]
3226
3430
  }, f.key);
3227
- })
3431
+ }), counts.all >= 2 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3432
+ type: "button",
3433
+ onClick: onBundleClick,
3434
+ className: "bg-primary text-primary-foreground hover:bg-primary/90 ml-auto inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-bold transition-all",
3435
+ children: t("list_bundle_cta")
3436
+ })]
3437
+ });
3438
+ }
3439
+ const BUNDLE_INLINE_LIMIT = 2;
3440
+ function BundleCard({ bundle, onSubscriptionClick, t }) {
3441
+ const [popoverOpen, setPopoverOpen] = (0, react.useState)(false);
3442
+ const total = bundle.subscriptions.reduce((sum, s) => sum + s.price, 0);
3443
+ const nextBillLong = bundle.next_bill_date ? formatLongDate(bundle.next_bill_date) : null;
3444
+ const inlineMembers = bundle.subscriptions.slice(0, BUNDLE_INLINE_LIMIT);
3445
+ const overflowCount = bundle.subscriptions.length - inlineMembers.length;
3446
+ const handleMemberClick = (token) => {
3447
+ onSubscriptionClick(token);
3448
+ setPopoverOpen(false);
3449
+ };
3450
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3451
+ className: "ring-border hover:ring-foreground/10 relative flex flex-col gap-4 rounded-2xl bg-transparent p-5 shadow-sm ring-1 transition-all duration-300 hover:shadow-lg",
3452
+ children: [
3453
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3454
+ className: "flex items-center gap-2",
3455
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3456
+ className: "bg-muted flex size-7 items-center justify-center rounded-full",
3457
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Package, {
3458
+ className: "text-foreground size-4",
3459
+ strokeWidth: 2
3460
+ })
3461
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3462
+ className: "text-foreground text-xs font-bold",
3463
+ children: t("list_bundle_card_label", { n: String(bundle.subscription_count) })
3464
+ })]
3465
+ }),
3466
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3467
+ className: "flex flex-col",
3468
+ children: [inlineMembers.map((sub) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundleMemberRow, {
3469
+ sub,
3470
+ onClick: () => handleMemberClick(sub.subscription_token),
3471
+ t
3472
+ }, sub.subscription_token)), overflowCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.Popover, {
3473
+ open: popoverOpen,
3474
+ onOpenChange: setPopoverOpen,
3475
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.PopoverTrigger, {
3476
+ asChild: true,
3477
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3478
+ type: "button",
3479
+ className: "text-muted-foreground hover:text-foreground focus-visible:ring-foreground/30 mt-1 inline-flex w-fit cursor-pointer items-center rounded-md px-2 py-1 text-xs font-bold transition-colors focus-visible:ring-2 focus-visible:outline-none",
3480
+ children: t("list_bundle_more_count", { n: String(overflowCount) })
3481
+ })
3482
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.PopoverContent, {
3483
+ align: "start",
3484
+ className: "bg-background ring-border w-80 rounded-2xl p-3 shadow-lg ring-1",
3485
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3486
+ className: "text-muted-foreground px-2 pt-1 pb-2 text-xs font-bold uppercase",
3487
+ children: t("list_bundle_members_title")
3488
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3489
+ className: "flex max-h-80 flex-col overflow-y-auto",
3490
+ children: bundle.subscriptions.map((sub) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundleMemberRow, {
3491
+ sub,
3492
+ onClick: () => handleMemberClick(sub.subscription_token),
3493
+ t
3494
+ }, sub.subscription_token))
3495
+ })]
3496
+ })]
3497
+ })]
3498
+ }),
3499
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3500
+ className: "bg-muted mt-auto rounded-xl px-4 py-3.5",
3501
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3502
+ className: "text-foreground text-sm font-bold",
3503
+ children: t("list_next_bill_label")
3504
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3505
+ className: "mt-3 grid grid-cols-2",
3506
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3507
+ className: "pr-4",
3508
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3509
+ className: "text-muted-foreground text-xs",
3510
+ children: t("list_bill_date")
3511
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3512
+ className: "text-foreground mt-1 text-base font-bold tracking-tight tabular-nums",
3513
+ children: nextBillLong ?? "—"
3514
+ })]
3515
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3516
+ className: "border-border border-l pl-4",
3517
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3518
+ className: "text-muted-foreground text-xs",
3519
+ children: t("list_bill_amount")
3520
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3521
+ className: "text-foreground mt-1 text-base font-bold tracking-tight tabular-nums",
3522
+ children: total.toFixed(2)
3523
+ })]
3524
+ })]
3525
+ })]
3526
+ })
3527
+ ]
3528
+ });
3529
+ }
3530
+ function BundleMemberRow({ sub, onClick, t }) {
3531
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3532
+ type: "button",
3533
+ onClick,
3534
+ className: "hover:bg-muted/60 focus-visible:ring-foreground/30 -mx-2 flex cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none",
3535
+ children: [
3536
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3537
+ className: "relative size-12 shrink-0",
3538
+ children: sub.variant.product.image_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
3539
+ src: sub.variant.product.image_url,
3540
+ alt: "",
3541
+ className: "ring-background size-12 rounded-lg bg-transparent object-cover ring-2"
3542
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3543
+ className: "bg-muted ring-background text-muted-foreground flex size-12 items-center justify-center rounded-lg ring-2",
3544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ImageOff, {
3545
+ className: "size-4",
3546
+ strokeWidth: 1.75
3547
+ })
3548
+ })
3549
+ }),
3550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3551
+ className: "min-w-0 flex-1",
3552
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3553
+ className: "text-foreground truncate text-xs font-bold",
3554
+ children: sub.variant.product.title || t("list_product_fallback")
3555
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3556
+ className: "text-muted-foreground truncate text-xs",
3557
+ children: bundleMemberCadence(sub.subscription_plan, t)
3558
+ })]
3559
+ }),
3560
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3561
+ className: "text-foreground shrink-0 text-xs font-bold tabular-nums",
3562
+ children: formatDetailPrice(sub)
3563
+ })
3564
+ ]
3565
+ });
3566
+ }
3567
+ function bundleMemberCadence(plan, t) {
3568
+ const { billing_interval: interval, billing_interval_unit: unit } = plan;
3569
+ if (interval === 1 && unit === "month") return t("list_cadence_monthly");
3570
+ if (interval === 1 && unit === "week") return t("list_cadence_weekly");
3571
+ if (interval === 1 && unit === "year") return t("list_cadence_yearly");
3572
+ if (interval === 3 && unit === "month") return t("list_cadence_quarterly");
3573
+ if (interval > 1) return t("list_cadence_every_n_units", {
3574
+ n: String(interval),
3575
+ unit: unit + "s"
3228
3576
  });
3577
+ return t("list_cadence_every_unit", { unit });
3229
3578
  }
3230
3579
  function SubscriptionCard({ sub, onClick, onResume, t }) {
3231
3580
  const status = mapStatus(sub.status, t);
@@ -3243,7 +3592,7 @@ function SubscriptionCard({ sub, onClick, onResume, t }) {
3243
3592
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
3244
3593
  type: "button",
3245
3594
  onClick,
3246
- className: "flex cursor-pointer flex-col gap-5 text-left focus:outline-none",
3595
+ className: "flex flex-1 cursor-pointer flex-col gap-5 text-left focus:outline-none",
3247
3596
  children: [
3248
3597
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3249
3598
  className: "flex items-start gap-4",
@@ -3270,10 +3619,10 @@ function SubscriptionCard({ sub, onClick, onResume, t }) {
3270
3619
  })]
3271
3620
  }),
3272
3621
  isCancelled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3273
- className: "bg-muted text-muted-foreground rounded-xl px-4 py-3 text-sm font-semibold",
3622
+ className: "bg-muted text-muted-foreground mt-auto rounded-xl px-4 py-3 text-sm font-semibold",
3274
3623
  children: status.label
3275
3624
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3276
- className: "bg-muted rounded-xl px-4 py-3.5",
3625
+ className: "bg-muted mt-auto rounded-xl px-4 py-3.5",
3277
3626
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3278
3627
  className: `text-sm font-bold ${isPastDue ? "text-destructive" : "text-foreground"}`,
3279
3628
  children: isPastDue ? daysOverdue === 1 ? t("list_bill_due_1_day_ago") : daysOverdue > 0 ? t("list_bill_due_n_days_ago", { n: String(daysOverdue) }) : t("list_bill_overdue") : isPaused ? t("list_last_bill_label") : t("list_next_bill_label")
@@ -3445,15 +3794,972 @@ function ErrorState({ onRetry, t }) {
3445
3794
  });
3446
3795
  }
3447
3796
  //#endregion
3797
+ //#region src/screens/subscriptions/bundle-drawer/payment-utils.ts
3798
+ /**
3799
+ * Mirrors fluid-checkout's `isPaymentMethodUsable`. A payment method is only
3800
+ * eligible for bundle attachment when it has a complete billing address.
3801
+ *
3802
+ * Note: the portal's `fluidPay.CustomerPaymentMethod` does not surface the
3803
+ * checkout-only `au_status` field, so we only check billing-address completeness.
3804
+ */
3805
+ function isPaymentMethodUsable(pm) {
3806
+ const addr = pm.billing_address;
3807
+ if (!addr) return false;
3808
+ return Boolean(addr.address1 && addr.city && addr.state && addr.zip && addr.country_code);
3809
+ }
3810
+ //#endregion
3811
+ //#region src/screens/subscriptions/bundle-drawer/use-bundle-form.ts
3812
+ function pickDefaultAddressId(addresses, defaultAddressId) {
3813
+ if (defaultAddressId != null) {
3814
+ const match = addresses.find((a) => a.id === defaultAddressId);
3815
+ if (match) return match.id;
3816
+ }
3817
+ const flagged = addresses.find((a) => a.default);
3818
+ if (flagged) return flagged.id;
3819
+ return addresses[0]?.id ?? null;
3820
+ }
3821
+ function pickDefaultPaymentMethodId(paymentMethods, defaultPaymentMethodId) {
3822
+ const usable = paymentMethods.filter(isPaymentMethodUsable);
3823
+ if (defaultPaymentMethodId != null) {
3824
+ const match = usable.find((pm) => pm.id === defaultPaymentMethodId);
3825
+ if (match) return match.id;
3826
+ }
3827
+ const flagged = usable.find((pm) => pm.default);
3828
+ if (flagged) return flagged.id;
3829
+ return usable[0]?.id ?? null;
3830
+ }
3831
+ function useBundleForm({ addresses, paymentMethods, defaultAddressId, defaultPaymentMethodId }) {
3832
+ const [step, setStep] = (0, react.useState)(1);
3833
+ const [selectedTokens, setSelectedTokens] = (0, react.useState)(() => /* @__PURE__ */ new Set());
3834
+ const today = (0, react.useMemo)(() => /* @__PURE__ */ new Date(), []);
3835
+ const [selectedYear, setYearState] = (0, react.useState)(today.getUTCFullYear());
3836
+ const [selectedMonth, setMonthState] = (0, react.useState)(null);
3837
+ const [selectedDay, setDayState] = (0, react.useState)(null);
3838
+ const [selectedAddressId, setSelectedAddressId] = (0, react.useState)(() => pickDefaultAddressId(addresses, defaultAddressId));
3839
+ const [selectedPaymentMethodId, setSelectedPaymentMethodId] = (0, react.useState)(() => pickDefaultPaymentMethodId(paymentMethods, defaultPaymentMethodId));
3840
+ (0, react.useEffect)(() => {
3841
+ if (selectedAddressId == null && addresses.length > 0) setSelectedAddressId(pickDefaultAddressId(addresses, defaultAddressId));
3842
+ }, [
3843
+ selectedAddressId,
3844
+ addresses,
3845
+ defaultAddressId
3846
+ ]);
3847
+ (0, react.useEffect)(() => {
3848
+ if (selectedPaymentMethodId == null && paymentMethods.some(isPaymentMethodUsable)) setSelectedPaymentMethodId(pickDefaultPaymentMethodId(paymentMethods, defaultPaymentMethodId));
3849
+ }, [
3850
+ selectedPaymentMethodId,
3851
+ paymentMethods,
3852
+ defaultPaymentMethodId
3853
+ ]);
3854
+ return {
3855
+ step,
3856
+ setStep,
3857
+ selectedTokens,
3858
+ toggleToken: (0, react.useCallback)((token) => {
3859
+ setSelectedTokens((prev) => {
3860
+ const next = new Set(prev);
3861
+ if (next.has(token)) next.delete(token);
3862
+ else next.add(token);
3863
+ return next;
3864
+ });
3865
+ }, []),
3866
+ selectedYear,
3867
+ selectedMonth,
3868
+ selectedDay,
3869
+ setYear: (0, react.useCallback)((y) => {
3870
+ setYearState(y);
3871
+ if (selectedMonth != null) {
3872
+ const isPastMonth = y < today.getUTCFullYear() || y === today.getUTCFullYear() && selectedMonth < today.getUTCMonth();
3873
+ const isPastDay = y === today.getUTCFullYear() && selectedMonth === today.getUTCMonth() && selectedDay != null && selectedDay < today.getUTCDate();
3874
+ if (isPastMonth) {
3875
+ setMonthState(null);
3876
+ setDayState(null);
3877
+ } else if (isPastDay) setDayState(null);
3878
+ }
3879
+ }, [
3880
+ selectedMonth,
3881
+ selectedDay,
3882
+ today
3883
+ ]),
3884
+ setMonth: (0, react.useCallback)((m) => {
3885
+ setMonthState(m);
3886
+ if (selectedDay != null) {
3887
+ const daysInNewMonth = new Date(Date.UTC(selectedYear, m + 1, 0)).getUTCDate();
3888
+ const isPastDay = selectedYear === today.getUTCFullYear() && m === today.getUTCMonth() && selectedDay < today.getUTCDate();
3889
+ if (selectedDay > daysInNewMonth || isPastDay) setDayState(null);
3890
+ }
3891
+ }, [
3892
+ selectedDay,
3893
+ selectedYear,
3894
+ today
3895
+ ]),
3896
+ setDay: (0, react.useCallback)((d) => setDayState(d), []),
3897
+ selectedAddressId,
3898
+ setSelectedAddressId,
3899
+ selectedPaymentMethodId,
3900
+ setSelectedPaymentMethodId,
3901
+ canProceedFromStep1: selectedTokens.size >= 2,
3902
+ canProceedFromStep2: selectedMonth !== null && selectedDay !== null,
3903
+ canProceedFromStep3: selectedAddressId != null,
3904
+ canProceedFromStep4: selectedPaymentMethodId != null,
3905
+ formattedDate: (0, react.useMemo)(() => {
3906
+ if (selectedMonth == null || selectedDay == null) return null;
3907
+ return `${selectedYear}-${String(selectedMonth + 1).padStart(2, "0")}-${String(selectedDay).padStart(2, "0")}`;
3908
+ }, [
3909
+ selectedYear,
3910
+ selectedMonth,
3911
+ selectedDay
3912
+ ])
3913
+ };
3914
+ }
3915
+ //#endregion
3916
+ //#region src/screens/subscriptions/bundle-drawer/DrawerStepTabs.tsx
3917
+ const TABS = [
3918
+ {
3919
+ id: 1,
3920
+ labelKey: "bundle_step_select"
3921
+ },
3922
+ {
3923
+ id: 2,
3924
+ labelKey: "bundle_step_date"
3925
+ },
3926
+ {
3927
+ id: 3,
3928
+ labelKey: "bundle_step_address"
3929
+ },
3930
+ {
3931
+ id: 4,
3932
+ labelKey: "bundle_step_payment"
3933
+ }
3934
+ ];
3935
+ const PROGRESS_BY_STEP = {
3936
+ 1: "0%",
3937
+ 2: "33%",
3938
+ 3: "66%",
3939
+ 4: "100%"
3940
+ };
3941
+ function DrawerStepTabs({ step, onStepClick, canProceedFromStep1, canProceedFromStep2, canProceedFromStep3 }) {
3942
+ const { t } = useSubscriptionsTranslation();
3943
+ const handleClick = (target) => {
3944
+ if (target < step) onStepClick(target);
3945
+ else if (target === 2 && canProceedFromStep1) onStepClick(2);
3946
+ else if (target === 3 && canProceedFromStep1 && canProceedFromStep2) onStepClick(3);
3947
+ else if (target === 4 && canProceedFromStep1 && canProceedFromStep2 && canProceedFromStep3) onStepClick(4);
3948
+ };
3949
+ const messageKey = step === 1 ? "bundle_step1_message" : step === 2 ? "bundle_step2_message" : step === 3 ? "bundle_step3_message" : "bundle_step4_message";
3950
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3951
+ className: "px-6 pt-4",
3952
+ children: [
3953
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3954
+ className: "mb-2 flex items-center justify-between",
3955
+ children: TABS.map((tab) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
3956
+ type: "button",
3957
+ onClick: () => handleClick(tab.id),
3958
+ className: `text-xs font-bold transition-colors ${step === tab.id ? "text-foreground" : step > tab.id ? "text-muted-foreground cursor-pointer" : "text-muted-foreground/75"}`,
3959
+ children: t(tab.labelKey)
3960
+ }, tab.id))
3961
+ }),
3962
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3963
+ className: "bg-muted relative h-1 rounded-full",
3964
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3965
+ className: "bg-primary absolute h-1 rounded-full transition-all duration-300",
3966
+ style: { width: PROGRESS_BY_STEP[step] }
3967
+ }), TABS.map((tab, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3968
+ className: `absolute top-1/2 size-3 rounded-full transition-colors ${step >= tab.id ? "bg-primary" : "bg-muted ring-border ring-1"}`,
3969
+ style: index === 0 ? {
3970
+ left: 0,
3971
+ transform: "translateY(-50%)"
3972
+ } : index === TABS.length - 1 ? {
3973
+ right: 0,
3974
+ transform: "translateY(-50%)"
3975
+ } : {
3976
+ left: `${index / (TABS.length - 1) * 100}%`,
3977
+ transform: "translate(-50%, -50%)"
3978
+ }
3979
+ }, tab.id))]
3980
+ }),
3981
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3982
+ className: "text-muted-foreground mt-3 flex items-center justify-center text-xs",
3983
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(messageKey) })
3984
+ })
3985
+ ]
3986
+ });
3987
+ }
3988
+ //#endregion
3989
+ //#region src/screens/subscriptions/bundle-drawer/DrawerStepSelect.tsx
3990
+ function formatBillDate(value) {
3991
+ if (!value) return "—";
3992
+ try {
3993
+ const match = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
3994
+ if (!match) return "—";
3995
+ const y = Number(match[1]);
3996
+ const m = Number(match[2]);
3997
+ const d = Number(match[3]);
3998
+ if (!y || !m || !d) return "—";
3999
+ return new Date(Date.UTC(y, m - 1, d)).toLocaleDateString(void 0, {
4000
+ month: "short",
4001
+ day: "numeric",
4002
+ timeZone: "UTC"
4003
+ });
4004
+ } catch {
4005
+ return "—";
4006
+ }
4007
+ }
4008
+ function DrawerStepSelect({ subscriptions, selectedTokens, onToggle }) {
4009
+ const { t } = useSubscriptionsTranslation();
4010
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4011
+ className: "flex-1 overflow-y-auto px-6 py-4",
4012
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4013
+ className: "bg-muted mt-2 rounded-2xl p-6 text-center",
4014
+ children: [
4015
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4016
+ className: "mx-auto mb-3 flex size-14 items-center justify-center rounded-2xl bg-transparent shadow-sm",
4017
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Truck, {
4018
+ className: "text-foreground size-7",
4019
+ strokeWidth: 2
4020
+ })
4021
+ }),
4022
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4023
+ className: "text-foreground text-sm font-bold",
4024
+ children: t("bundle_and_save")
4025
+ }),
4026
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4027
+ className: "text-muted-foreground mt-1 text-xs",
4028
+ children: t("bundle_select_subs")
4029
+ })
4030
+ ]
4031
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4032
+ className: "mt-4 space-y-2",
4033
+ children: subscriptions.map((sub) => {
4034
+ const isSelected = selectedTokens.has(sub.subscription_token);
4035
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4036
+ role: "checkbox",
4037
+ "aria-checked": isSelected,
4038
+ tabIndex: 0,
4039
+ onClick: () => onToggle(sub.subscription_token),
4040
+ onKeyDown: (e) => {
4041
+ if (e.key === "Enter" || e.key === " ") {
4042
+ e.preventDefault();
4043
+ onToggle(sub.subscription_token);
4044
+ }
4045
+ },
4046
+ className: `flex cursor-pointer items-start gap-3 rounded-2xl p-4 transition-all ${isSelected ? "ring-primary bg-muted ring-2" : "ring-border hover:ring-foreground/20 ring-1"}`,
4047
+ children: [
4048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4049
+ className: "pt-1",
4050
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4051
+ className: `flex size-5 items-center justify-center rounded-full transition-colors ${isSelected ? "bg-primary" : "ring-foreground/30 ring-2"}`,
4052
+ children: isSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
4053
+ className: "text-primary-foreground size-3",
4054
+ strokeWidth: 3
4055
+ })
4056
+ })
4057
+ }),
4058
+ sub.variant?.product?.image_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4059
+ src: sub.variant.product.image_url,
4060
+ alt: sub.variant.product.title ?? "",
4061
+ className: "size-12 shrink-0 rounded-lg object-cover"
4062
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4063
+ className: "bg-muted flex size-12 shrink-0 items-center justify-center rounded-lg",
4064
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Package, {
4065
+ className: "text-muted-foreground size-6",
4066
+ strokeWidth: 1.75
4067
+ })
4068
+ }),
4069
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4070
+ className: "min-w-0 flex-1",
4071
+ children: [
4072
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4073
+ className: "text-foreground truncate text-xs font-bold",
4074
+ children: sub.variant?.product?.title ?? t("list_product_fallback")
4075
+ }),
4076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4077
+ className: "text-foreground mt-0.5 text-xs",
4078
+ children: formatPrice(sub)
4079
+ }),
4080
+ sub.customer?.full_name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4081
+ className: "text-muted-foreground mt-0.5 truncate text-xs",
4082
+ children: sub.customer.full_name
4083
+ })
4084
+ ]
4085
+ }),
4086
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4087
+ className: "shrink-0 text-right",
4088
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4089
+ className: "text-muted-foreground inline-flex items-center gap-1 text-xs",
4090
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Repeat, {
4091
+ className: "size-3",
4092
+ strokeWidth: 2
4093
+ }), formatCadence(sub.subscription_plan, t)]
4094
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4095
+ className: "text-muted-foreground/75 mt-1 text-xs",
4096
+ children: formatBillDate(sub.next_bill_date)
4097
+ })]
4098
+ })
4099
+ ]
4100
+ }, sub.subscription_token);
4101
+ })
4102
+ })]
4103
+ });
4104
+ }
4105
+ //#endregion
4106
+ //#region src/screens/subscriptions/bundle-drawer/DrawerStepDate.tsx
4107
+ const MONTHS = [
4108
+ "Jan",
4109
+ "Feb",
4110
+ "Mar",
4111
+ "Apr",
4112
+ "May",
4113
+ "Jun",
4114
+ "Jul",
4115
+ "Aug",
4116
+ "Sep",
4117
+ "Oct",
4118
+ "Nov",
4119
+ "Dec"
4120
+ ];
4121
+ function getDaysInMonth(year, month) {
4122
+ return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
4123
+ }
4124
+ function DrawerStepDate({ selectedYear, selectedMonth, selectedDay, onYearChange, onMonthChange, onDayChange }) {
4125
+ const { t } = useSubscriptionsTranslation();
4126
+ const today = (0, react.useMemo)(() => /* @__PURE__ */ new Date(), []);
4127
+ const currentYear = today.getUTCFullYear();
4128
+ const currentMonth = today.getUTCMonth();
4129
+ const currentDay = today.getUTCDate();
4130
+ const availableYears = (0, react.useMemo)(() => [
4131
+ currentYear,
4132
+ currentYear + 1,
4133
+ currentYear + 2
4134
+ ], [currentYear]);
4135
+ const isMonthDisabled = (monthIndex) => {
4136
+ if (selectedYear < currentYear) return true;
4137
+ if (selectedYear > currentYear) return false;
4138
+ return monthIndex < currentMonth;
4139
+ };
4140
+ const daysInSelectedMonth = selectedMonth == null ? 31 : getDaysInMonth(selectedYear, selectedMonth);
4141
+ const isDayDisabled = (day) => {
4142
+ if (selectedMonth == null) return true;
4143
+ if (day > getDaysInMonth(selectedYear, selectedMonth)) return true;
4144
+ if (selectedYear < currentYear) return true;
4145
+ if (selectedYear > currentYear) return false;
4146
+ if (selectedMonth > currentMonth) return false;
4147
+ if (selectedMonth < currentMonth) return true;
4148
+ return day < currentDay;
4149
+ };
4150
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4151
+ className: "flex-1 overflow-y-auto px-6 py-4",
4152
+ children: [
4153
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4154
+ className: "mt-2",
4155
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4156
+ className: "text-foreground text-sm font-bold",
4157
+ children: t("bundle_select_new_bill_date")
4158
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4159
+ className: "text-muted-foreground mt-1 text-xs",
4160
+ children: t("bundle_select_new_bill_date_description")
4161
+ })]
4162
+ }),
4163
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4164
+ className: "mt-6",
4165
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
4166
+ className: "text-foreground mb-3 text-xs font-bold",
4167
+ children: t("bundle_year")
4168
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4169
+ className: "grid grid-cols-3 gap-2",
4170
+ children: availableYears.map((year) => {
4171
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4172
+ type: "button",
4173
+ onClick: () => onYearChange(year),
4174
+ className: `rounded-lg px-3 py-2.5 text-xs font-semibold transition-all ${selectedYear === year ? "bg-primary text-primary-foreground" : "text-foreground ring-border hover:ring-foreground/20 bg-transparent ring-1"}`,
4175
+ children: year
4176
+ }, year);
4177
+ })
4178
+ })]
4179
+ }),
4180
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4181
+ className: "mt-6",
4182
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
4183
+ className: "text-foreground mb-3 text-xs font-bold",
4184
+ children: t("bundle_month")
4185
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4186
+ className: "grid grid-cols-4 gap-2",
4187
+ children: MONTHS.map((month, index) => {
4188
+ const isActive = selectedMonth === index;
4189
+ const disabled = isMonthDisabled(index);
4190
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4191
+ type: "button",
4192
+ disabled,
4193
+ onClick: () => onMonthChange(index),
4194
+ className: `rounded-lg px-3 py-2.5 text-xs font-semibold transition-all ${isActive ? "bg-primary text-primary-foreground" : "text-foreground ring-border hover:ring-foreground/20 bg-transparent ring-1"} ${disabled ? "cursor-not-allowed opacity-40" : ""}`,
4195
+ children: month
4196
+ }, month);
4197
+ })
4198
+ })]
4199
+ }),
4200
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4201
+ className: "mt-6",
4202
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
4203
+ className: "text-foreground mb-3 text-xs font-bold",
4204
+ children: t("bundle_day")
4205
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4206
+ className: "grid grid-cols-8 gap-1.5",
4207
+ children: Array.from({ length: daysInSelectedMonth }, (_, i) => i + 1).map((day) => {
4208
+ const isActive = selectedDay === day;
4209
+ const disabled = isDayDisabled(day);
4210
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4211
+ type: "button",
4212
+ disabled,
4213
+ onClick: () => onDayChange(day),
4214
+ className: `rounded-lg py-2 text-xs font-semibold transition-all ${isActive ? "bg-primary text-primary-foreground" : "text-foreground ring-border hover:ring-foreground/20 bg-transparent ring-1"} ${disabled ? "cursor-not-allowed opacity-40" : ""}`,
4215
+ children: day
4216
+ }, day);
4217
+ })
4218
+ })]
4219
+ })
4220
+ ]
4221
+ });
4222
+ }
4223
+ //#endregion
4224
+ //#region src/screens/subscriptions/bundle-drawer/DrawerStepAddress.tsx
4225
+ function DrawerStepAddress({ addresses, selectedAddressId, onAddressSelect, onAddAddress, formattedSelectedDate }) {
4226
+ const { t } = useSubscriptionsTranslation();
4227
+ const hasFlaggedDefault = addresses.some((a) => a.default);
4228
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4229
+ className: "flex-1 overflow-y-auto px-6 py-4",
4230
+ children: [
4231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4232
+ className: "mt-2",
4233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4234
+ className: "text-foreground text-sm font-bold",
4235
+ children: t("bundle_select_address")
4236
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4237
+ className: "text-muted-foreground mt-1 text-xs",
4238
+ children: t("bundle_select_address_description")
4239
+ })]
4240
+ }),
4241
+ formattedSelectedDate && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4242
+ className: "bg-muted text-muted-foreground mt-4 rounded-lg px-4 py-2.5 text-xs",
4243
+ children: [
4244
+ t("bundle_selected_bill_date"),
4245
+ " ",
4246
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4247
+ className: "text-foreground font-semibold",
4248
+ children: formatLongDate(formattedSelectedDate)
4249
+ })
4250
+ ]
4251
+ }),
4252
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4253
+ className: "mt-4 space-y-2",
4254
+ role: "radiogroup",
4255
+ children: addresses.map((address, index) => {
4256
+ const isSelected = selectedAddressId === address.id;
4257
+ const isDefault = address.default || !hasFlaggedDefault && index === 0;
4258
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4259
+ type: "button",
4260
+ role: "radio",
4261
+ "aria-checked": isSelected,
4262
+ onClick: () => onAddressSelect(address.id),
4263
+ className: `flex h-auto w-full cursor-pointer items-start gap-3 rounded-2xl p-4 text-left transition-all ${isSelected ? "ring-primary bg-muted ring-2" : "ring-border hover:ring-foreground/20 ring-1"}`,
4264
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4265
+ className: "pt-0.5",
4266
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4267
+ className: `flex size-5 items-center justify-center rounded-full transition-colors ${isSelected ? "bg-primary" : "ring-foreground/30 ring-2"}`,
4268
+ children: isSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
4269
+ className: "text-primary-foreground size-3",
4270
+ strokeWidth: 3
4271
+ })
4272
+ })
4273
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4274
+ className: "min-w-0 flex-1",
4275
+ children: [
4276
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4277
+ className: "flex flex-wrap items-center gap-2",
4278
+ children: [address.name && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4279
+ className: "text-foreground truncate text-xs font-bold",
4280
+ children: address.name
4281
+ }), isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4282
+ className: "text-foreground ring-border rounded-full bg-transparent px-2 py-0.5 text-xs font-bold ring-1",
4283
+ children: t("bundle_default_badge")
4284
+ })]
4285
+ }),
4286
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4287
+ className: "text-muted-foreground mt-0.5 truncate text-xs",
4288
+ children: address.address1
4289
+ }),
4290
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4291
+ className: "text-muted-foreground truncate text-xs",
4292
+ children: [
4293
+ [address.city, address.state].filter(Boolean).join(", "),
4294
+ " ",
4295
+ address.postal_code
4296
+ ]
4297
+ })
4298
+ ]
4299
+ })]
4300
+ }, address.id);
4301
+ })
4302
+ }),
4303
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4304
+ className: "mt-4 text-right",
4305
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4306
+ type: "button",
4307
+ onClick: onAddAddress,
4308
+ className: "text-foreground inline-flex items-center text-xs font-bold underline-offset-2 hover:underline",
4309
+ children: t("bundle_add_new_address")
4310
+ })
4311
+ })
4312
+ ]
4313
+ });
4314
+ }
4315
+ //#endregion
4316
+ //#region src/screens/subscriptions/bundle-drawer/DrawerStepPayment.tsx
4317
+ function DrawerStepPayment({ paymentMethods, selectedPaymentMethodId, onPaymentMethodSelect, onAddPaymentMethod }) {
4318
+ const { t } = useSubscriptionsTranslation();
4319
+ const usable = (0, react.useMemo)(() => {
4320
+ const filtered = paymentMethods.filter(isPaymentMethodUsable);
4321
+ const defaultIdx = filtered.findIndex((pm) => pm.default);
4322
+ if (defaultIdx <= 0) return filtered;
4323
+ const defaultCard = filtered[defaultIdx];
4324
+ if (!defaultCard) return filtered;
4325
+ return [defaultCard, ...filtered.filter((_, i) => i !== defaultIdx)];
4326
+ }, [paymentMethods]);
4327
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4328
+ className: "flex-1 overflow-y-auto px-6 py-4",
4329
+ children: [
4330
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4331
+ className: "mt-2",
4332
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
4333
+ className: "text-foreground text-sm font-bold",
4334
+ children: t("bundle_select_payment")
4335
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4336
+ className: "text-muted-foreground mt-1 text-xs",
4337
+ children: t("bundle_select_payment_description")
4338
+ })]
4339
+ }),
4340
+ usable.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4341
+ className: "bg-muted text-muted-foreground mt-4 rounded-lg px-4 py-3 text-xs",
4342
+ children: t("bundle_no_usable_payment_methods")
4343
+ }),
4344
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4345
+ className: "mt-4 space-y-2",
4346
+ role: "radiogroup",
4347
+ children: usable.map((pm) => {
4348
+ const isSelected = selectedPaymentMethodId === pm.id;
4349
+ const brand = pm.details.card_brand ?? pm.details.card_type ?? pm.payment_type ?? "Card";
4350
+ const lastFour = pm.details.last_four ?? "••••";
4351
+ const expiry = pm.details.exp_month != null && pm.details.exp_year != null ? `${t("bundle_card_expires")} ${pm.details.exp_month}/${pm.details.exp_year}` : null;
4352
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4353
+ type: "button",
4354
+ role: "radio",
4355
+ "aria-checked": isSelected,
4356
+ onClick: () => onPaymentMethodSelect(pm.id),
4357
+ className: `flex h-auto w-full cursor-pointer items-start gap-3 rounded-2xl p-4 text-left transition-all ${isSelected ? "ring-primary bg-muted ring-2" : "ring-border hover:ring-foreground/20 ring-1"}`,
4358
+ children: [
4359
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4360
+ className: "pt-0.5",
4361
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4362
+ className: `flex size-5 items-center justify-center rounded-full transition-colors ${isSelected ? "bg-primary" : "ring-foreground/30 ring-2"}`,
4363
+ children: isSelected && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Check, {
4364
+ className: "text-primary-foreground size-3",
4365
+ strokeWidth: 3
4366
+ })
4367
+ })
4368
+ }),
4369
+ pm.details.logo_url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
4370
+ src: pm.details.logo_url,
4371
+ alt: brand,
4372
+ className: "h-6 w-10 shrink-0 object-contain"
4373
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4374
+ className: "bg-muted text-muted-foreground flex h-6 w-10 shrink-0 items-center justify-center rounded",
4375
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.CreditCard, {
4376
+ className: "size-4",
4377
+ strokeWidth: 1.75
4378
+ })
4379
+ }),
4380
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4381
+ className: "min-w-0 flex-1",
4382
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4383
+ className: "flex flex-wrap items-center gap-2",
4384
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
4385
+ className: "text-foreground text-xs font-bold",
4386
+ children: [
4387
+ brand,
4388
+ " •••• ",
4389
+ lastFour
4390
+ ]
4391
+ }), pm.default && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4392
+ className: "text-foreground ring-border rounded-full bg-transparent px-2 py-0.5 text-xs font-bold ring-1",
4393
+ children: t("bundle_default_badge")
4394
+ })]
4395
+ }), expiry && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4396
+ className: "text-muted-foreground mt-0.5 text-xs",
4397
+ children: expiry
4398
+ })]
4399
+ })
4400
+ ]
4401
+ }, pm.id);
4402
+ })
4403
+ }),
4404
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4405
+ className: "mt-4 text-right",
4406
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4407
+ type: "button",
4408
+ onClick: onAddPaymentMethod,
4409
+ className: "text-foreground inline-flex items-center text-xs font-bold underline-offset-2 hover:underline",
4410
+ children: t("bundle_add_new_card")
4411
+ })
4412
+ })
4413
+ ]
4414
+ });
4415
+ }
4416
+ //#endregion
4417
+ //#region src/screens/subscriptions/bundle-drawer/BundleSubscriptionsDrawer.tsx
4418
+ function BundleSubscriptionsDrawer({ open, onOpenChange, subscriptions: subscriptionList, addresses, paymentMethods, defaultAddressId, defaultPaymentMethodId, onAddAddress, onAddPaymentMethod, onSubmit, isSubmitting }) {
4419
+ const { t } = useSubscriptionsTranslation();
4420
+ const form = useBundleForm({
4421
+ addresses,
4422
+ paymentMethods,
4423
+ defaultAddressId,
4424
+ defaultPaymentMethodId
4425
+ });
4426
+ const handleClose = () => onOpenChange(false);
4427
+ const handleNext = () => {
4428
+ if (form.step < 4) form.setStep(form.step + 1);
4429
+ };
4430
+ const handleBack = () => {
4431
+ if (form.step === 1) handleClose();
4432
+ else form.setStep(form.step - 1);
4433
+ };
4434
+ const handleSubmit = async () => {
4435
+ if (!form.canProceedFromStep1 || !form.canProceedFromStep2 || !form.canProceedFromStep3 || !form.canProceedFromStep4 || form.formattedDate == null || form.selectedAddressId == null || form.selectedPaymentMethodId == null) return;
4436
+ await onSubmit({
4437
+ subscription_tokens: Array.from(form.selectedTokens),
4438
+ next_bill_date: form.formattedDate,
4439
+ address_id: form.selectedAddressId,
4440
+ payment_method_id: form.selectedPaymentMethodId
4441
+ });
4442
+ };
4443
+ const nextDisabled = form.step === 1 && !form.canProceedFromStep1 || form.step === 2 && !form.canProceedFromStep2 || form.step === 3 && !form.canProceedFromStep3;
4444
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.Sheet, {
4445
+ open,
4446
+ onOpenChange,
4447
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_src.SheetContent, {
4448
+ side: "right",
4449
+ className: "bg-background flex h-full w-full flex-col gap-0 p-0 sm:max-w-md",
4450
+ children: [
4451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4452
+ className: "border-border flex items-center justify-between border-b px-6 py-4",
4453
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_src.SheetTitle, {
4454
+ className: "text-foreground text-base font-bold",
4455
+ children: t("bundle_subscriptions")
4456
+ })
4457
+ }),
4458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepTabs, {
4459
+ step: form.step,
4460
+ onStepClick: form.setStep,
4461
+ canProceedFromStep1: form.canProceedFromStep1,
4462
+ canProceedFromStep2: form.canProceedFromStep2,
4463
+ canProceedFromStep3: form.canProceedFromStep3
4464
+ }),
4465
+ form.step === 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepSelect, {
4466
+ subscriptions: subscriptionList,
4467
+ selectedTokens: form.selectedTokens,
4468
+ onToggle: form.toggleToken
4469
+ }),
4470
+ form.step === 2 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepDate, {
4471
+ selectedYear: form.selectedYear,
4472
+ selectedMonth: form.selectedMonth,
4473
+ selectedDay: form.selectedDay,
4474
+ onYearChange: form.setYear,
4475
+ onMonthChange: form.setMonth,
4476
+ onDayChange: form.setDay
4477
+ }),
4478
+ form.step === 3 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepAddress, {
4479
+ addresses,
4480
+ selectedAddressId: form.selectedAddressId,
4481
+ onAddressSelect: form.setSelectedAddressId,
4482
+ onAddAddress,
4483
+ formattedSelectedDate: form.formattedDate
4484
+ }),
4485
+ form.step === 4 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DrawerStepPayment, {
4486
+ paymentMethods,
4487
+ selectedPaymentMethodId: form.selectedPaymentMethodId,
4488
+ onPaymentMethodSelect: form.setSelectedPaymentMethodId,
4489
+ onAddPaymentMethod
4490
+ }),
4491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4492
+ className: "border-border flex items-center justify-between gap-3 border-t px-6 py-4",
4493
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4494
+ type: "button",
4495
+ onClick: handleBack,
4496
+ className: "text-foreground ring-border hover:bg-muted rounded-lg bg-transparent px-6 py-2.5 text-xs font-bold ring-1 transition-all",
4497
+ children: form.step === 1 ? t("bundle_done") : t("bundle_back")
4498
+ }), form.step < 4 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4499
+ type: "button",
4500
+ onClick: handleNext,
4501
+ disabled: nextDisabled,
4502
+ className: "bg-primary text-primary-foreground hover:bg-primary/90 rounded-lg px-6 py-2.5 text-xs font-bold transition-all disabled:cursor-not-allowed disabled:opacity-40",
4503
+ children: t("next")
4504
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4505
+ type: "button",
4506
+ onClick: handleSubmit,
4507
+ disabled: !form.canProceedFromStep4 || isSubmitting,
4508
+ className: "bg-primary text-primary-foreground hover:bg-primary/90 inline-flex min-w-[140px] items-center justify-center rounded-lg px-6 py-2.5 text-xs font-bold transition-all disabled:cursor-not-allowed disabled:opacity-40",
4509
+ children: isSubmitting ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "border-primary-foreground size-5 animate-spin rounded-full border-2 border-t-transparent" }) : t("bundle_create")
4510
+ })]
4511
+ })
4512
+ ]
4513
+ })
4514
+ });
4515
+ }
4516
+ //#endregion
3448
4517
  //#region src/screens/SubscriptionsListScreen.tsx
3449
- function SubscriptionsListScreen({ customerId, isLoadingCustomer }) {
4518
+ const ADDRESS_DIALOG_LABELS = {
4519
+ add_an_address: "Add an Address",
4520
+ add_address: "Add Address",
4521
+ edit_address: "Edit Address",
4522
+ delete_address: "Delete Address",
4523
+ delete_address_message: "Are you sure you want to delete this address? This action cannot be undone.",
4524
+ delete: "Delete",
4525
+ cancel: "Cancel",
4526
+ country: "Country",
4527
+ name: "Name",
4528
+ address_line_1: "Address Line 1",
4529
+ address_line_2: "Address Line 2",
4530
+ city: "City",
4531
+ state: "State",
4532
+ province: "Province",
4533
+ zip_code: "Zip Code",
4534
+ postal_code: "Postal Code",
4535
+ select_state: "Select state",
4536
+ select_an_option: "Select an option",
4537
+ set_as_default_address: "Set as default address",
4538
+ save_address: "Save Address",
4539
+ first_name: "First Name",
4540
+ last_name: "Last Name",
4541
+ deleting: "Deleting…",
4542
+ tap_to_confirm: "Tap to confirm"
4543
+ };
4544
+ function addressDialogT(key) {
4545
+ return ADDRESS_DIALOG_LABELS[key] ?? key.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4546
+ }
4547
+ function mapToFluidPayAddress$1(raw) {
4548
+ return {
4549
+ id: raw.id,
4550
+ address1: raw.street1,
4551
+ address2: raw.street2,
4552
+ city: raw.city,
4553
+ country_code: raw.country,
4554
+ default: raw.default,
4555
+ name: raw.name,
4556
+ postal_code: raw.zip,
4557
+ state: raw.state,
4558
+ subdivision_code: null
4559
+ };
4560
+ }
4561
+ function composeAddressName$1(firstName, lastName) {
4562
+ const combined = `${firstName} ${lastName}`.trim();
4563
+ return combined.length > 0 ? combined : null;
4564
+ }
4565
+ function SubscriptionsListScreenInner({ customerId, isLoadingCustomer, onToast }) {
3450
4566
  const { navigate } = require_AppNavigationContext.useAppNavigation();
3451
- const goToDetail = (token) => navigate(`subscriptions/${token}`);
3452
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalSubscriptionsList, {
4567
+ const queryClient = (0, _tanstack_react_query.useQueryClient)();
4568
+ const payApi = require_api_context.usePayApi();
4569
+ const countriesApi = require_countries_api_context.useCountriesApi();
4570
+ const { t } = useSubscriptionsTranslation();
4571
+ const { data: addresses = [] } = (0, _tanstack_react_query.useQuery)({
4572
+ queryKey: require_query_keys.payKeys.addresses.list(),
4573
+ queryFn: async () => {
4574
+ return (await payApi.fetchAddresses()).addresses.map(mapToFluidPayAddress$1);
4575
+ },
4576
+ enabled: !!customerId && !isLoadingCustomer
4577
+ });
4578
+ const { data: paymentMethods = [] } = (0, _tanstack_react_query.useQuery)({
4579
+ queryKey: require_query_keys.payKeys.paymentMethods.list(),
4580
+ queryFn: async () => {
4581
+ return (await payApi.fetchPaymentMethods()).payment_methods.map(require_AddressAutocompleteInput.mapToFluidPayPaymentMethod);
4582
+ },
4583
+ enabled: !!customerId && !isLoadingCustomer
4584
+ });
4585
+ const { data: subscriptionsData } = useInfiniteSubscriptions({
3453
4586
  customerId,
3454
- isLoadingCustomer,
3455
- onSubscriptionClick: goToDetail,
3456
- onResume: (sub) => goToDetail(sub.subscription_token)
4587
+ limit: 12
4588
+ }, { enabled: !!customerId && !isLoadingCustomer });
4589
+ const subscriptionsList = (0, react.useMemo)(() => subscriptionsData?.pages.flatMap((p) => p.subscriptions) ?? [], [subscriptionsData]);
4590
+ const { data: bundlesData } = useBundles({ enabled: !!customerId && !isLoadingCustomer });
4591
+ const [bundleDrawerOpen, setBundleDrawerOpen] = (0, react.useState)(false);
4592
+ const [bundleDrawerKey, setBundleDrawerKey] = (0, react.useState)(0);
4593
+ const [addressDialog, setAddressDialog] = (0, react.useState)({
4594
+ open: false,
4595
+ selected: null
4596
+ });
4597
+ const [isAddCardOpen, setIsAddCardOpen] = (0, react.useState)(false);
4598
+ const [isBundling, setIsBundling] = (0, react.useState)(false);
4599
+ const { data: countriesData } = (0, _tanstack_react_query.useQuery)({
4600
+ queryKey: require_query_keys.storeKeys.countries(),
4601
+ queryFn: () => countriesApi.listCountries(),
4602
+ enabled: addressDialog.open || isAddCardOpen
4603
+ });
4604
+ const countryOptions = (0, react.useMemo)(() => [...countriesData?.countries ?? []].map((c) => ({
4605
+ iso: c.code,
4606
+ name: c.name
4607
+ })).sort((a, b) => a.name.localeCompare(b.name)), [countriesData]);
4608
+ const statesByCountry = (0, react.useMemo)(() => {
4609
+ const map = /* @__PURE__ */ new Map();
4610
+ for (const c of countriesData?.countries ?? []) map.set(c.code, c.states.map((s) => ({
4611
+ name: s.name,
4612
+ isoCode: s.code
4613
+ })));
4614
+ return map;
4615
+ }, [countriesData]);
4616
+ const fetchStatesFromCountries = (0, react.useCallback)((countryCode) => Promise.resolve(statesByCountry.get(countryCode) ?? []), [statesByCountry]);
4617
+ const createAddressMutation = (0, _tanstack_react_query.useMutation)({
4618
+ mutationFn: (body) => payApi.createAddress({ address: {
4619
+ name: composeAddressName$1(body.address.first_name, body.address.last_name),
4620
+ street1: body.address.address1,
4621
+ street2: body.address.address2,
4622
+ city: body.address.city,
4623
+ state: body.address.state,
4624
+ zip: body.address.postal_code,
4625
+ country: body.address.country_code,
4626
+ default: body.address.default
4627
+ } }),
4628
+ onSuccess: () => {
4629
+ queryClient.invalidateQueries({ queryKey: require_query_keys.payKeys.addresses.all });
4630
+ onToast?.("Address added", "success");
4631
+ },
4632
+ onError: () => onToast?.("Failed to add address", "error")
4633
+ });
4634
+ const addCreditCardMutation = (0, _tanstack_react_query.useMutation)({
4635
+ mutationFn: (data) => payApi.createPaymentMethod({ payment_method: {
4636
+ type: "card",
4637
+ token: data.payment_method.token,
4638
+ default: data.default_payment_method
4639
+ } }),
4640
+ onSuccess: () => {
4641
+ queryClient.invalidateQueries({ queryKey: require_query_keys.payKeys.paymentMethods.all });
4642
+ onToast?.("Payment method added", "success");
4643
+ },
4644
+ onError: () => onToast?.("Failed to add payment method", "error")
4645
+ });
4646
+ const createBundle = useCreateBundle();
4647
+ const updateSubscription = useUpdateSubscriptionInfo();
4648
+ const handleBundleClick = () => {
4649
+ setBundleDrawerKey((k) => k + 1);
4650
+ setBundleDrawerOpen(true);
4651
+ };
4652
+ const handleBundle = async (params) => {
4653
+ if (isBundling) return;
4654
+ setIsBundling(true);
4655
+ try {
4656
+ await createBundle.mutateAsync({
4657
+ subscription_tokens: params.subscription_tokens,
4658
+ next_bill_date: params.next_bill_date,
4659
+ address_id: params.address_id
4660
+ });
4661
+ } catch {
4662
+ onToast?.(t("bundle_failed"), "error");
4663
+ setIsBundling(false);
4664
+ return;
4665
+ }
4666
+ const failedCount = (await Promise.allSettled(params.subscription_tokens.map((token) => updateSubscription.mutateAsync({
4667
+ subscriptionToken: token,
4668
+ body: { payment_method_id: params.payment_method_id }
4669
+ })))).filter((r) => r.status === "rejected").length;
4670
+ if (failedCount === 0) onToast?.(t("bundle_succeeded"), "success");
4671
+ else onToast?.(t("bundle_partial_payment_failure", { n: String(failedCount) }), "warning");
4672
+ setBundleDrawerOpen(false);
4673
+ setIsBundling(false);
4674
+ };
4675
+ const goToDetail = (token) => navigate(`subscriptions/${token}`);
4676
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4677
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalSubscriptionsList, {
4678
+ customerId,
4679
+ isLoadingCustomer,
4680
+ onSubscriptionClick: goToDetail,
4681
+ onResume: (sub) => goToDetail(sub.subscription_token),
4682
+ onBundleClick: handleBundleClick,
4683
+ bundles: bundlesData
4684
+ }),
4685
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BundleSubscriptionsDrawer, {
4686
+ open: bundleDrawerOpen,
4687
+ onOpenChange: setBundleDrawerOpen,
4688
+ subscriptions: subscriptionsList,
4689
+ addresses,
4690
+ paymentMethods,
4691
+ defaultAddressId: addresses.find((a) => a.default)?.id ?? addresses[0]?.id ?? null,
4692
+ defaultPaymentMethodId: paymentMethods.find((pm) => pm.default)?.id ?? paymentMethods[0]?.id ?? null,
4693
+ onAddAddress: () => setAddressDialog({
4694
+ open: true,
4695
+ selected: null
4696
+ }),
4697
+ onAddPaymentMethod: () => setIsAddCardOpen(true),
4698
+ onSubmit: handleBundle,
4699
+ isSubmitting: isBundling
4700
+ }, bundleDrawerKey),
4701
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AddressAutocompleteInput.AddressFormDialog, {
4702
+ isOpen: addressDialog.open,
4703
+ onClose: () => setAddressDialog({
4704
+ open: false,
4705
+ selected: null
4706
+ }),
4707
+ selectedAddress: addressDialog.selected,
4708
+ t: addressDialogT,
4709
+ onSubmit: async (formData) => {
4710
+ try {
4711
+ await createAddressMutation.mutateAsync(formData);
4712
+ setAddressDialog({
4713
+ open: false,
4714
+ selected: null
4715
+ });
4716
+ } catch {}
4717
+ },
4718
+ isSubmitting: createAddressMutation.isPending,
4719
+ countries: countryOptions,
4720
+ fetchStates: fetchStatesFromCountries,
4721
+ renderAddressAutocomplete: ({ control, setValue, countryCode }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AddressAutocompleteInput.AddressAutocompleteInput, {
4722
+ control,
4723
+ setValue,
4724
+ countryIso: countryCode,
4725
+ addressLineField: "address1",
4726
+ cityField: "city",
4727
+ stateField: "state",
4728
+ postalCodeField: "postal_code",
4729
+ placeholder: addressDialogT("address_line_1")
4730
+ })
4731
+ }),
4732
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AddressAutocompleteInput.CreditCardFormDialog, {
4733
+ isOpen: isAddCardOpen,
4734
+ onClose: () => setIsAddCardOpen(false),
4735
+ t: addressDialogT,
4736
+ onSubmit: async (data) => {
4737
+ try {
4738
+ await addCreditCardMutation.mutateAsync(data);
4739
+ setIsAddCardOpen(false);
4740
+ } catch {}
4741
+ },
4742
+ isSubmitting: addCreditCardMutation.isPending,
4743
+ countries: countryOptions,
4744
+ jwt: "",
4745
+ renderAddressAutocomplete: ({ control, setValue, countryCode }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AddressAutocompleteInput.AddressAutocompleteInput, {
4746
+ control,
4747
+ setValue,
4748
+ countryIso: countryCode,
4749
+ addressLineField: "address1",
4750
+ cityField: "city",
4751
+ stateField: "state",
4752
+ postalCodeField: "zip",
4753
+ placeholder: addressDialogT("address_line_1")
4754
+ })
4755
+ })
4756
+ ] });
4757
+ }
4758
+ function SubscriptionsListScreen(props) {
4759
+ const payApi = require_api_context.usePayApi();
4760
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_AddressAutocompleteInput.FluidPayCoreProvider, {
4761
+ api: (0, react.useMemo)(() => require_AddressAutocompleteInput.createFluidPayApiAdapter(payApi), [payApi]),
4762
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsListScreenInner, { ...props })
3457
4763
  });
3458
4764
  }
3459
4765
  //#endregion
@@ -5779,13 +7085,14 @@ function SubscriptionsScreen({ onToast, background, textColor, accentColor, padd
5779
7085
  const isLoadingCustomer = account === void 0;
5780
7086
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5781
7087
  ...divProps,
5782
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsTranslationBridge, { children: isDetailView ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalSubscriptionsApiProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionDetailScreen, {
7088
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsTranslationBridge, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalSubscriptionsApiProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalBundlesApiProvider, { children: isDetailView ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionDetailScreen, {
5783
7089
  token: detailToken,
5784
7090
  onToast: effectiveToast
5785
- }) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PortalSubscriptionsApiProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsListScreen, {
7091
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubscriptionsListScreen, {
5786
7092
  customerId,
5787
- isLoadingCustomer
5788
- }) }) })
7093
+ isLoadingCustomer,
7094
+ onToast: effectiveToast
7095
+ }) }) }) })
5789
7096
  });
5790
7097
  }
5791
7098
  const subscriptionsScreenPropertySchema = {
@@ -5811,4 +7118,4 @@ Object.defineProperty(exports, "subscriptionsScreenPropertySchema", {
5811
7118
  }
5812
7119
  });
5813
7120
 
5814
- //# sourceMappingURL=SubscriptionsScreen-1hP0t23a.cjs.map
7121
+ //# sourceMappingURL=SubscriptionsScreen-Dfmqn6LY.cjs.map