@fluid-app/portal-sdk 0.1.306 → 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.
- package/dist/{FluidProvider-BiULiWJY.cjs → FluidProvider-B1UsSiKO.cjs} +2 -2
- package/dist/{FluidProvider-BiULiWJY.cjs.map → FluidProvider-B1UsSiKO.cjs.map} +1 -1
- package/dist/{FluidProvider-CLIlEKSg.mjs → FluidProvider-D7BEpqDQ.mjs} +2 -2
- package/dist/{FluidProvider-CLIlEKSg.mjs.map → FluidProvider-D7BEpqDQ.mjs.map} +1 -1
- package/dist/{MessagingScreen-CYTxBDtC.cjs → MessagingScreen-0qwUGWGf.cjs} +3 -3
- package/dist/{MessagingScreen-CYTxBDtC.cjs.map → MessagingScreen-0qwUGWGf.cjs.map} +1 -1
- package/dist/{MessagingScreen-Cd2iUd_B.cjs → MessagingScreen-DCHGSAA7.cjs} +3 -3
- package/dist/{MessagingScreen-CZTploEf.mjs → MessagingScreen-DyFLJsQ-.mjs} +3 -3
- package/dist/{MessagingScreen-CZTploEf.mjs.map → MessagingScreen-DyFLJsQ-.mjs.map} +1 -1
- package/dist/{MessagingScreen-C_jBMCSt.mjs → MessagingScreen-L6vyOqgm.mjs} +3 -4
- package/dist/{OrdersScreen-CIxlNlYf.cjs → OrdersScreen-3WRoz4fR.cjs} +3 -3
- package/dist/{OrdersScreen-mHhOjrMk.mjs → OrdersScreen-BiB5eMA4.mjs} +3 -3
- package/dist/{OrdersScreen-mHhOjrMk.mjs.map → OrdersScreen-BiB5eMA4.mjs.map} +1 -1
- package/dist/{OrdersScreen-DHqx_gbA.mjs → OrdersScreen-UsFG95zY.mjs} +3 -5
- package/dist/{OrdersScreen-BVOQup59.cjs → OrdersScreen-rjcGVfyT.cjs} +3 -3
- package/dist/{OrdersScreen-BVOQup59.cjs.map → OrdersScreen-rjcGVfyT.cjs.map} +1 -1
- package/dist/{ProfileScreen-BiO036lk.cjs → ProfileScreen-Bm9gayW5.cjs} +3 -3
- package/dist/{ProfileScreen-7yjZmDoH.mjs → ProfileScreen-CIDEmZg9.mjs} +4 -4
- package/dist/{ProfileScreen-7yjZmDoH.mjs.map → ProfileScreen-CIDEmZg9.mjs.map} +1 -1
- package/dist/{ProfileScreen-BH7sSw_t.mjs → ProfileScreen-DioyTFVO.mjs} +3 -4
- package/dist/{ProfileScreen-Dsm-0XQY.cjs → ProfileScreen-xTVmveXS.cjs} +4 -4
- package/dist/{ProfileScreen-Dsm-0XQY.cjs.map → ProfileScreen-xTVmveXS.cjs.map} +1 -1
- package/dist/{ShareablesScreen-C8zQjj9l.mjs → ShareablesScreen-BXMbz5Fn.mjs} +3 -3
- package/dist/{ShareablesScreen-C8zQjj9l.mjs.map → ShareablesScreen-BXMbz5Fn.mjs.map} +1 -1
- package/dist/{ShareablesScreen-DFTD6x4A.cjs → ShareablesScreen-BsXn5-sp.cjs} +3 -3
- package/dist/{ShareablesScreen-DFTD6x4A.cjs.map → ShareablesScreen-BsXn5-sp.cjs.map} +1 -1
- package/dist/{ShareablesScreen-CH643xDj.mjs → ShareablesScreen-CetzfpS7.mjs} +2 -4
- package/dist/{ShareablesScreen-B1h2JbtB.cjs → ShareablesScreen-JAN0oKF9.cjs} +2 -2
- package/dist/{ShopScreen-C3pEQzV_.cjs → ShopScreen-BM0aj7oV.cjs} +2 -2
- package/dist/{ShopScreen-C3pEQzV_.cjs.map → ShopScreen-BM0aj7oV.cjs.map} +1 -1
- package/dist/{ShopScreen-B-lu9pT5.cjs → ShopScreen-BsTTHFit.cjs} +3 -3
- package/dist/{ShopScreen-NzcM2H-M.mjs → ShopScreen-DwyZ8pqV.mjs} +3 -5
- package/dist/{ShopScreen-H8OlaM3P.mjs → ShopScreen-u8MPbT1t.mjs} +2 -2
- package/dist/{ShopScreen-H8OlaM3P.mjs.map → ShopScreen-u8MPbT1t.mjs.map} +1 -1
- package/dist/{SubscriptionsScreen-kCkz_1xD.cjs → SubscriptionsScreen-D_HUnXQb.cjs} +2 -2
- package/dist/{SubscriptionsScreen-1hP0t23a.cjs → SubscriptionsScreen-Dfmqn6LY.cjs} +1344 -37
- package/dist/SubscriptionsScreen-Dfmqn6LY.cjs.map +1 -0
- package/dist/{SubscriptionsScreen-Ck3-dcqq.mjs → SubscriptionsScreen-HIIrxH3a.mjs} +1346 -39
- package/dist/SubscriptionsScreen-HIIrxH3a.mjs.map +1 -0
- package/dist/index.cjs +21 -21
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +21 -21
- package/dist/{portal_tenant-l4a-eX2B.mjs → portal_tenant-BLdhIM81.mjs} +22 -2
- package/dist/portal_tenant-BLdhIM81.mjs.map +1 -0
- package/dist/{portal_tenant-DG4gKXnu.cjs → portal_tenant-ywAHQ1k3.cjs} +33 -1
- package/dist/portal_tenant-ywAHQ1k3.cjs.map +1 -0
- package/dist/{use-account-BgustWSP.mjs → use-account-BOE1yhAH.mjs} +2 -2
- package/dist/{use-account-BgustWSP.mjs.map → use-account-BOE1yhAH.mjs.map} +1 -1
- package/dist/{use-account-EicAlO8K.cjs → use-account-DeRJHrd3.cjs} +2 -2
- package/dist/{use-account-EicAlO8K.cjs.map → use-account-DeRJHrd3.cjs.map} +1 -1
- package/package.json +18 -18
- package/dist/SubscriptionsScreen-1hP0t23a.cjs.map +0 -1
- package/dist/SubscriptionsScreen-Ck3-dcqq.mjs.map +0 -1
- package/dist/portal_tenant-DG4gKXnu.cjs.map +0 -1
- 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-
|
|
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-
|
|
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.
|
|
2636
|
-
shipping_interval: 1,
|
|
2637
|
-
shipping_interval_unit: bff.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
}, [
|
|
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.
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
|
3452
|
-
|
|
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
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
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:
|
|
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
|
-
})
|
|
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-
|
|
7121
|
+
//# sourceMappingURL=SubscriptionsScreen-Dfmqn6LY.cjs.map
|