@carlonicora/nextjs-jsonapi 5.5.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/{BlockNoteEditor-GVQCKRMU.mjs → BlockNoteEditor-R3WXUFHX.mjs} +4 -4
  2. package/dist/{BlockNoteEditor-THBJH2UR.js → BlockNoteEditor-ZHXXFWUJ.js} +19 -19
  3. package/dist/{BlockNoteEditor-THBJH2UR.js.map → BlockNoteEditor-ZHXXFWUJ.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-U6E3DS4F.mjs → BlockNoteViewer-BVVN6D3T.mjs} +2 -2
  5. package/dist/{BlockNoteViewer-VZOVKTFO.js → BlockNoteViewer-SD3ZFIXD.js} +3 -3
  6. package/dist/{BlockNoteViewer-VZOVKTFO.js.map → BlockNoteViewer-SD3ZFIXD.js.map} +1 -1
  7. package/dist/billing/index.d.mts +197 -46
  8. package/dist/billing/index.d.ts +197 -46
  9. package/dist/billing/index.js +1166 -589
  10. package/dist/billing/index.js.map +1 -1
  11. package/dist/billing/index.mjs +803 -226
  12. package/dist/billing/index.mjs.map +1 -1
  13. package/dist/{chunk-WPYT4UKJ.js → chunk-2TXE57LE.js} +3 -3
  14. package/dist/{chunk-WPYT4UKJ.js.map → chunk-2TXE57LE.js.map} +1 -1
  15. package/dist/{chunk-G6UE6MEP.js → chunk-5TH2DSAH.js} +901 -204
  16. package/dist/chunk-5TH2DSAH.js.map +1 -0
  17. package/dist/{chunk-X5WIUUNG.mjs → chunk-CFB6FHIY.mjs} +3342 -2377
  18. package/dist/chunk-CFB6FHIY.mjs.map +1 -0
  19. package/dist/{chunk-VC5A3AUZ.mjs → chunk-FK26NX3S.mjs} +701 -4
  20. package/dist/chunk-FK26NX3S.mjs.map +1 -0
  21. package/dist/{chunk-YDYVJM4I.js → chunk-IJME5SAM.js} +4 -4
  22. package/dist/{chunk-YDYVJM4I.js.map → chunk-IJME5SAM.js.map} +1 -1
  23. package/dist/{chunk-W7F5BDSV.mjs → chunk-OKCV4GPJ.mjs} +2 -2
  24. package/dist/{chunk-3CGROPF3.mjs → chunk-RGQYFSJF.mjs} +2 -2
  25. package/dist/{chunk-6SQYXUVP.mjs → chunk-WCHLCOZG.mjs} +2 -2
  26. package/dist/{chunk-S4YJ5WCW.js → chunk-XOSC3FFP.js} +8 -8
  27. package/dist/{chunk-S4YJ5WCW.js.map → chunk-XOSC3FFP.js.map} +1 -1
  28. package/dist/{chunk-TY5FYP3G.js → chunk-ZCOPOO7T.js} +2024 -1059
  29. package/dist/chunk-ZCOPOO7T.js.map +1 -0
  30. package/dist/client/index.js +4 -4
  31. package/dist/client/index.mjs +3 -3
  32. package/dist/components/index.js +4 -4
  33. package/dist/components/index.mjs +3 -3
  34. package/dist/contexts/index.js +4 -4
  35. package/dist/contexts/index.mjs +3 -3
  36. package/dist/core/index.d.mts +248 -3
  37. package/dist/core/index.d.ts +248 -3
  38. package/dist/core/index.js +42 -2
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/index.mjs +41 -1
  41. package/dist/{stripe-promotion-code.interface-ClZ7DxS9.d.mts → currency-DT_dveBx.d.mts} +137 -1
  42. package/dist/{stripe-promotion-code.interface-C3qqh3mi.d.ts → currency-pK9eRYRC.d.ts} +137 -1
  43. package/dist/email-automations/index.js +92 -92
  44. package/dist/email-automations/index.mjs +3 -3
  45. package/dist/features/analytics/index.js +121 -121
  46. package/dist/features/analytics/index.mjs +4 -4
  47. package/dist/features/analytics/server.js +4 -4
  48. package/dist/features/analytics/server.mjs +2 -2
  49. package/dist/features/blog/index.js +150 -150
  50. package/dist/features/blog/index.mjs +3 -3
  51. package/dist/features/blog/server.js +16 -16
  52. package/dist/features/blog/server.mjs +1 -1
  53. package/dist/features/help/index.js +37 -37
  54. package/dist/features/help/index.mjs +3 -3
  55. package/dist/features/tokenusage/index.js +124 -124
  56. package/dist/features/tokenusage/index.mjs +4 -4
  57. package/dist/index.d.mts +2 -2
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.js +43 -3
  60. package/dist/index.js.map +1 -1
  61. package/dist/index.mjs +42 -2
  62. package/dist/server/index.js +3 -3
  63. package/dist/server/index.mjs +2 -2
  64. package/messages/en.json +125 -3
  65. package/messages/it.json +125 -3
  66. package/package.json +1 -1
  67. package/scripts/validate-messages.mjs +1 -0
  68. package/src/billing/index.ts +1 -0
  69. package/src/client/index.ts +4 -0
  70. package/src/core/index.ts +4 -0
  71. package/src/core/registry/ModuleRegistry.ts +3 -0
  72. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +3 -2
  73. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +4 -3
  74. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +22 -5
  75. package/src/features/billing/components/utils/__tests__/discount.spec.ts +59 -0
  76. package/src/features/billing/components/utils/__tests__/url.spec.ts +32 -0
  77. package/src/features/billing/components/utils/discount.ts +38 -0
  78. package/src/features/billing/components/utils/index.ts +2 -0
  79. package/src/features/billing/components/utils/url.ts +12 -0
  80. package/src/features/billing/contexts/CouponContext.tsx +209 -0
  81. package/src/features/billing/contexts/index.ts +1 -0
  82. package/src/features/billing/i18n-keys.ts +79 -0
  83. package/src/features/billing/stripe-coupon/components/containers/CouponContainer.tsx +38 -0
  84. package/src/features/billing/stripe-coupon/components/containers/index.ts +1 -0
  85. package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +72 -0
  86. package/src/features/billing/stripe-coupon/components/details/index.ts +1 -0
  87. package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +80 -0
  88. package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +214 -0
  89. package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +425 -0
  90. package/src/features/billing/stripe-coupon/components/forms/CouponRefresher.tsx +45 -0
  91. package/src/features/billing/stripe-coupon/components/forms/index.ts +6 -0
  92. package/src/features/billing/stripe-coupon/components/index.ts +5 -0
  93. package/src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx +61 -0
  94. package/src/features/billing/stripe-coupon/components/lists/CouponsList.tsx +67 -0
  95. package/src/features/billing/stripe-coupon/components/lists/index.ts +2 -0
  96. package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +81 -0
  97. package/src/features/billing/stripe-coupon/components/widgets/index.ts +1 -0
  98. package/src/features/billing/stripe-coupon/data/__tests__/stripe-coupon-models.spec.ts +114 -0
  99. package/src/features/billing/stripe-coupon/data/index.ts +10 -0
  100. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.fields.ts +8 -0
  101. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.interface.ts +44 -0
  102. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.service.ts +92 -0
  103. package/src/features/billing/stripe-coupon/data/stripe-coupon-code.ts +132 -0
  104. package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.interface.ts +17 -0
  105. package/src/features/billing/stripe-coupon/data/stripe-coupon-offer.ts +70 -0
  106. package/src/features/billing/stripe-coupon/data/stripe-coupon.fields.ts +7 -0
  107. package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +55 -0
  108. package/src/features/billing/stripe-coupon/data/stripe-coupon.service.ts +93 -0
  109. package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +167 -0
  110. package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +161 -0
  111. package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +113 -0
  112. package/src/features/billing/stripe-coupon/index.ts +5 -0
  113. package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +15 -0
  114. package/src/features/billing/stripe-coupon/stripe-coupon-offer.module.ts +10 -0
  115. package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +25 -0
  116. package/src/features/billing/stripe-coupon/utils/__tests__/coupon-places.spec.ts +19 -0
  117. package/src/features/billing/stripe-coupon/utils/__tests__/to-promotion-code-result.spec.ts +41 -0
  118. package/src/features/billing/stripe-coupon/utils/coupon-places.ts +23 -0
  119. package/src/features/billing/stripe-coupon/utils/format-coupon-discount.ts +36 -0
  120. package/src/features/billing/stripe-coupon/utils/index.ts +3 -0
  121. package/src/features/billing/stripe-coupon/utils/to-promotion-code-result.ts +23 -0
  122. package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +24 -8
  123. package/src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx +21 -6
  124. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +60 -19
  125. package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.interface.ts +2 -0
  126. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +24 -5
  127. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +21 -2
  128. package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +1 -1
  129. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +1 -1
  130. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +60 -49
  131. package/src/features/billing/stripe-subscription/components/wizards/__tests__/review-card.spec.ts +34 -0
  132. package/src/features/billing/stripe-subscription/components/wizards/review-card.ts +24 -0
  133. package/src/features/billing/stripe-subscription/data/__tests__/stripe-subscription.discount.spec.ts +67 -0
  134. package/src/features/billing/stripe-subscription/data/stripe-subscription.interface.ts +14 -0
  135. package/src/features/billing/stripe-subscription/data/stripe-subscription.service.ts +39 -0
  136. package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +21 -0
  137. package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-payment.spec.ts +48 -0
  138. package/src/features/billing/stripe-subscription/hooks/__tests__/wizard-promotion.spec.ts +132 -0
  139. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +138 -12
  140. package/src/features/billing/stripe-subscription/hooks/wizard-payment.ts +30 -0
  141. package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +61 -0
  142. package/src/features/user/contexts/CurrentUserContext.tsx +25 -0
  143. package/src/features/user/contexts/__tests__/CurrentUserContext.spec.tsx +121 -2
  144. package/dist/chunk-G6UE6MEP.js.map +0 -1
  145. package/dist/chunk-TY5FYP3G.js.map +0 -1
  146. package/dist/chunk-VC5A3AUZ.mjs.map +0 -1
  147. package/dist/chunk-X5WIUUNG.mjs.map +0 -1
  148. /package/dist/{BlockNoteEditor-GVQCKRMU.mjs.map → BlockNoteEditor-R3WXUFHX.mjs.map} +0 -0
  149. /package/dist/{BlockNoteViewer-U6E3DS4F.mjs.map → BlockNoteViewer-BVVN6D3T.mjs.map} +0 -0
  150. /package/dist/{chunk-W7F5BDSV.mjs.map → chunk-OKCV4GPJ.mjs.map} +0 -0
  151. /package/dist/{chunk-3CGROPF3.mjs.map → chunk-RGQYFSJF.mjs.map} +0 -0
  152. /package/dist/{chunk-6SQYXUVP.mjs.map → chunk-WCHLCOZG.mjs.map} +0 -0
@@ -22,6 +22,11 @@ import {
22
22
  Checkbox,
23
23
  ConfirmDialog,
24
24
  ContentListTable,
25
+ CouponCodeActions,
26
+ CouponCodeFields,
27
+ CouponEditor,
28
+ CouponProvider,
29
+ CouponRefresher,
25
30
  DetailField,
26
31
  Dialog,
27
32
  DialogContent,
@@ -56,19 +61,29 @@ import {
56
61
  Tabs,
57
62
  TabsList,
58
63
  TabsTrigger,
64
+ applyDiscount,
65
+ couponCodeDefaults,
66
+ couponCodeSchema,
67
+ couponCurrencyOptions,
59
68
  errorToast,
60
- formatCurrency,
61
69
  formatDate,
62
- formatFixed,
63
- formatInterval,
70
+ optionalDate,
71
+ optionalDecimal,
72
+ optionalPositiveInt,
73
+ parseOptionalDecimal,
74
+ parseOptionalPositiveInt,
75
+ promotionCodeDiscount,
76
+ subscriptionDiscount,
77
+ toCouponCodeInput,
64
78
  useBillingFormatOptions,
79
+ useCouponContext,
65
80
  useCurrentUserContext,
66
81
  useDataListRetriever,
67
82
  usePageUrlGenerator
68
- } from "../chunk-X5WIUUNG.mjs";
83
+ } from "../chunk-CFB6FHIY.mjs";
69
84
  import {
70
85
  getStripePublishableKey
71
- } from "../chunk-6SQYXUVP.mjs";
86
+ } from "../chunk-WCHLCOZG.mjs";
72
87
  import {
73
88
  getRoleId
74
89
  } from "../chunk-PW42LRSL.mjs";
@@ -76,6 +91,8 @@ import {
76
91
  FeatureService,
77
92
  InvoiceStatus,
78
93
  Modules,
94
+ StripeCouponCodeService,
95
+ StripeCouponService,
79
96
  StripeCustomerService,
80
97
  StripeInvoiceService,
81
98
  StripePriceService,
@@ -85,11 +102,16 @@ import {
85
102
  StripeUsageService,
86
103
  SubscriptionStatus,
87
104
  cn,
105
+ formatCouponDiscount,
106
+ formatCurrency,
107
+ formatFixed,
108
+ formatInterval,
88
109
  rehydrate,
89
110
  showToast,
111
+ toPromotionCodeResult,
90
112
  useI18nLocale,
91
113
  useI18nRouter
92
- } from "../chunk-VC5A3AUZ.mjs";
114
+ } from "../chunk-FK26NX3S.mjs";
93
115
  import "../chunk-V5MI7RNB.mjs";
94
116
  import "../chunk-AUXK7QSA.mjs";
95
117
  import "../chunk-PI56A4DG.mjs";
@@ -166,7 +188,7 @@ function SubscriptionSummaryCard({
166
188
  /* @__PURE__ */ jsx(CardTitle, { children: t("billing.ui.subscriptions.title") }),
167
189
  /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" })
168
190
  ] }),
169
- /* @__PURE__ */ jsx(CardContent, { children: subscriptions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
191
+ /* @__PURE__ */ jsx(CardContent, { children: !primarySubscription ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
170
192
  /* @__PURE__ */ jsx("p", { className: "text-xl font-bold text-muted-foreground", children: t("billing.ui.cards.subscriptions.empty_title") }),
171
193
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("billing.ui.cards.subscriptions.empty_description") }),
172
194
  /* @__PURE__ */ jsxs(
@@ -185,7 +207,7 @@ function SubscriptionSummaryCard({
185
207
  ]
186
208
  }
187
209
  )
188
- ] }) : primarySubscription ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
210
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
189
211
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
190
212
  /* @__PURE__ */ jsx("p", { className: "text-xl font-bold", children: formatPlanName(primarySubscription, t("billing.ui.cards.subscriptions.fallback_name")) }),
191
213
  /* @__PURE__ */ jsx(
@@ -204,7 +226,7 @@ function SubscriptionSummaryCard({
204
226
  date: formatDate(primarySubscription.currentPeriodEnd, fmt)
205
227
  }) }),
206
228
  activeSubscriptions.length > 1 && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("billing.ui.cards.subscriptions.more", { count: activeSubscriptions.length - 1 }) })
207
- ] }) : null })
229
+ ] }) })
208
230
  ] });
209
231
  }
210
232
  __name(SubscriptionSummaryCard, "SubscriptionSummaryCard");
@@ -438,7 +460,7 @@ function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }) {
438
460
  /* @__PURE__ */ jsx4(CardContent, { children: /* @__PURE__ */ jsx4("p", { className: "text-destructive text-xs/relaxed", children: error }) })
439
461
  ] });
440
462
  }
441
- const latestInvoice = invoices[0];
463
+ const latestInvoice = invoices.find((inv) => inv.status !== "void" /* VOID */) ?? invoices[0];
442
464
  const paidInvoices = invoices.filter((inv) => inv.status === "paid" /* PAID */);
443
465
  const openInvoices = invoices.filter((inv) => inv.status === "open" /* OPEN */);
444
466
  return /* @__PURE__ */ jsxs4(Card, { className: "cursor-pointer hover:bg-accent/50 transition-colors", onClick: onViewAllClick, children: [
@@ -540,10 +562,10 @@ function BillingUsageSummaryCard({
540
562
  __name(BillingUsageSummaryCard, "BillingUsageSummaryCard");
541
563
 
542
564
  // src/features/billing/components/containers/BillingDashboardContainer.tsx
543
- import { useTranslations as useTranslations36 } from "next-intl";
565
+ import { useTranslations as useTranslations37 } from "next-intl";
544
566
  import { CreditCard as CreditCard4, Loader2 as Loader23, Wallet as Wallet2 } from "lucide-react";
545
567
  import { useSearchParams } from "next/navigation";
546
- import { useCallback as useCallback4, useEffect as useEffect9, useMemo as useMemo3, useState as useState14 } from "react";
568
+ import { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo3, useState as useState16 } from "react";
547
569
 
548
570
  // src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx
549
571
  import { useTranslations as useTranslations9 } from "next-intl";
@@ -558,7 +580,12 @@ import { useTranslations as useTranslations6 } from "next-intl";
558
580
  import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
559
581
  import { useEffect, useState as useState2 } from "react";
560
582
  import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
561
- function PaymentMethodForm({ onSuccess, onCancel, isLoading = false }) {
583
+ function PaymentMethodForm({
584
+ onSuccess,
585
+ onCancel,
586
+ isLoading = false,
587
+ alwaysSetDefault = false
588
+ }) {
562
589
  const t = useTranslations6();
563
590
  const stripe = useStripe();
564
591
  const elements = useElements();
@@ -608,7 +635,7 @@ function PaymentMethodForm({ onSuccess, onCancel, isLoading = false }) {
608
635
  setIsSubmitting(false);
609
636
  return;
610
637
  }
611
- if (setAsDefault && confirmedSetupIntent?.payment_method) {
638
+ if ((alwaysSetDefault || setAsDefault) && confirmedSetupIntent?.payment_method) {
612
639
  await StripeCustomerService.setDefaultPaymentMethod({
613
640
  paymentMethodId: typeof confirmedSetupIntent.payment_method === "string" ? confirmedSetupIntent.payment_method : confirmedSetupIntent.payment_method.id
614
641
  });
@@ -647,8 +674,15 @@ function PaymentMethodForm({ onSuccess, onCancel, isLoading = false }) {
647
674
  }
648
675
  }
649
676
  ) }),
650
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-x-2", children: [
651
- /* @__PURE__ */ jsx6(Checkbox, { id: "setAsDefault", checked: setAsDefault, onCheckedChange: (checked) => setSetAsDefault(!!checked) }),
677
+ !alwaysSetDefault && /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-x-2", children: [
678
+ /* @__PURE__ */ jsx6(
679
+ Checkbox,
680
+ {
681
+ id: "setAsDefault",
682
+ checked: setAsDefault,
683
+ onCheckedChange: (checked) => setSetAsDefault(!!checked)
684
+ }
685
+ ),
652
686
  /* @__PURE__ */ jsx6(Label, { htmlFor: "setAsDefault", className: "cursor-pointer font-normal", children: t("billing.ui.payment_form.set_default") })
653
687
  ] }),
654
688
  error && /* @__PURE__ */ jsx6(Alert, { variant: "destructive", className: "bg-red-50 border-red-200", children: /* @__PURE__ */ jsx6(AlertDescription, { children: error }) }),
@@ -1426,6 +1460,7 @@ function SubscriptionsList({ subscriptions, onSubscriptionsChange, onChangePlan
1426
1460
  /* @__PURE__ */ jsx18(TableBody, { children: subscriptions.map((subscription) => {
1427
1461
  const price = subscription.price;
1428
1462
  const amount = price?.unitAmount ? formatCurrency(price.unitAmount, price.currency, fmt) : "0";
1463
+ const discountedAmount = price ? applyDiscount(price.unitAmount, subscriptionDiscount(subscription)) : null;
1429
1464
  const period = `${formatDate(subscription.currentPeriodStart, fmt)} - ${formatDate(subscription.currentPeriodEnd, fmt)}`;
1430
1465
  return /* @__PURE__ */ jsxs15(
1431
1466
  TableRow,
@@ -1442,7 +1477,10 @@ function SubscriptionsList({ subscriptions, onSubscriptionsChange, onChangePlan
1442
1477
  ) }),
1443
1478
  /* @__PURE__ */ jsx18(TableCell, { className: "font-medium", children: formatPlanName3(price, t) }),
1444
1479
  /* @__PURE__ */ jsx18(TableCell, { className: "text-muted-foreground text-sm", children: period }),
1445
- /* @__PURE__ */ jsx18(TableCell, { className: "text-end font-medium", children: amount }),
1480
+ /* @__PURE__ */ jsx18(TableCell, { className: "text-end font-medium", children: discountedAmount !== null && price ? /* @__PURE__ */ jsxs15(Fragment4, { children: [
1481
+ /* @__PURE__ */ jsx18("span", { className: "line-through text-muted-foreground me-2", children: amount }),
1482
+ /* @__PURE__ */ jsx18("span", { className: "text-success", children: formatCurrency(discountedAmount, price.currency, fmt) })
1483
+ ] }) : amount }),
1446
1484
  /* @__PURE__ */ jsx18(TableCell, { className: "text-end", children: (subscription.status === "active" /* ACTIVE */ || subscription.status === "trialing" /* TRIALING */) && onChangePlan && /* @__PURE__ */ jsx18(
1447
1485
  Button,
1448
1486
  {
@@ -1483,11 +1521,23 @@ __name(SubscriptionsList, "SubscriptionsList");
1483
1521
 
1484
1522
  // src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx
1485
1523
  import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1486
- function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscription }) {
1524
+ function SubscriptionsContainer({
1525
+ onOpenWizard,
1526
+ hasActiveRecurringSubscription,
1527
+ subscriptions: parentSubscriptions,
1528
+ onSubscriptionsChange
1529
+ }) {
1487
1530
  const t = useTranslations18();
1488
- const [subscriptions, setSubscriptions] = useState10([]);
1489
- const [loading, setLoading] = useState10(true);
1531
+ const isControlled = parentSubscriptions !== void 0;
1532
+ const [ownSubscriptions, setSubscriptions] = useState10([]);
1533
+ const [ownLoading, setLoading] = useState10(!isControlled);
1534
+ const subscriptions = parentSubscriptions ?? ownSubscriptions;
1535
+ const loading = isControlled ? false : ownLoading;
1490
1536
  const loadSubscriptions = useCallback(async () => {
1537
+ if (isControlled) {
1538
+ onSubscriptionsChange?.();
1539
+ return;
1540
+ }
1491
1541
  setLoading(true);
1492
1542
  try {
1493
1543
  const fetchedSubscriptions = await StripeSubscriptionService.listSubscriptions();
@@ -1497,9 +1547,9 @@ function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscription }
1497
1547
  } finally {
1498
1548
  setLoading(false);
1499
1549
  }
1500
- }, []);
1550
+ }, [isControlled, onSubscriptionsChange]);
1501
1551
  useEffect5(() => {
1502
- loadSubscriptions();
1552
+ if (!isControlled) loadSubscriptions();
1503
1553
  }, []);
1504
1554
  const criticalSubscriptions = subscriptions.filter(
1505
1555
  (sub) => sub.status === "past_due" /* PAST_DUE */ || sub.status === "trialing" /* TRIALING */ && sub.trialEnd && new Date(sub.trialEnd).getTime() - (/* @__PURE__ */ new Date()).getTime() <= 7 * 24 * 60 * 60 * 1e3
@@ -1824,13 +1874,128 @@ function SubscriptionConfirmation({ price, isLoading, onConfirm, onCancel }) {
1824
1874
  __name(SubscriptionConfirmation, "SubscriptionConfirmation");
1825
1875
 
1826
1876
  // src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx
1827
- import { useTranslations as useTranslations31 } from "next-intl";
1828
- import { useCallback as useCallback3, useEffect as useEffect7, useRef as useRef2 } from "react";
1877
+ import { useTranslations as useTranslations32 } from "next-intl";
1878
+ import { useCallback as useCallback4, useEffect as useEffect8, useRef as useRef2 } from "react";
1829
1879
 
1830
1880
  // src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts
1831
- import { useTranslations as useTranslations25 } from "next-intl";
1832
- import { useCallback as useCallback2, useMemo, useReducer, useRef } from "react";
1881
+ import { useTranslations as useTranslations26 } from "next-intl";
1882
+ import { useCallback as useCallback3, useMemo, useReducer, useRef } from "react";
1833
1883
  import { v4 } from "uuid";
1884
+
1885
+ // src/features/billing/stripe-subscription/hooks/wizard-promotion.ts
1886
+ function promotionCodeToSend(code) {
1887
+ if (!code) return void 0;
1888
+ return code.promotionCodeId;
1889
+ }
1890
+ __name(promotionCodeToSend, "promotionCodeToSend");
1891
+ function shouldLookUpAutomatic(code) {
1892
+ return !code || code.automatic === true;
1893
+ }
1894
+ __name(shouldLookUpAutomatic, "shouldLookUpAutomatic");
1895
+ function read(value, key) {
1896
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : void 0;
1897
+ }
1898
+ __name(read, "read");
1899
+ function errorStatus(error) {
1900
+ const status = read(error, "status") ?? read(read(error, "response"), "status");
1901
+ return typeof status === "number" ? status : void 0;
1902
+ }
1903
+ __name(errorStatus, "errorStatus");
1904
+ function errorCode(error) {
1905
+ const body = read(error, "body");
1906
+ const errors = read(body, "errors");
1907
+ const first = Array.isArray(errors) ? errors[0] : void 0;
1908
+ const code = read(first, "code") ?? read(read(read(first, "meta"), "context"), "code") ?? read(body, "code");
1909
+ return typeof code === "string" ? code : void 0;
1910
+ }
1911
+ __name(errorCode, "errorCode");
1912
+ function isDiscountSoldOut(error) {
1913
+ return errorStatus(error) === 410 && errorCode(error) === "discount_sold_out";
1914
+ }
1915
+ __name(isDiscountSoldOut, "isDiscountSoldOut");
1916
+ function isDiscountUnavailable(error) {
1917
+ return errorStatus(error) === 422 && errorCode(error) === "discount_unavailable";
1918
+ }
1919
+ __name(isDiscountUnavailable, "isDiscountUnavailable");
1920
+
1921
+ // src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts
1922
+ import { useStripe as useStripe2 } from "@stripe/react-stripe-js";
1923
+ import { useTranslations as useTranslations25 } from "next-intl";
1924
+ import { useCallback as useCallback2, useState as useState11 } from "react";
1925
+ function useConfirmSubscriptionPayment() {
1926
+ const stripe = useStripe2();
1927
+ const t = useTranslations25();
1928
+ const [isConfirming, setIsConfirming] = useState11(false);
1929
+ const confirmPayment = useCallback2(
1930
+ async (clientSecret) => {
1931
+ if (!stripe) {
1932
+ console.error("[useConfirmSubscriptionPayment] Stripe not initialized");
1933
+ return {
1934
+ success: false,
1935
+ error: t("billing.ui.payment_confirm.errors.not_initialized")
1936
+ };
1937
+ }
1938
+ if (!clientSecret) {
1939
+ console.error("[useConfirmSubscriptionPayment] No client secret provided");
1940
+ return {
1941
+ success: false,
1942
+ error: t("billing.ui.payment_confirm.errors.missing_details")
1943
+ };
1944
+ }
1945
+ setIsConfirming(true);
1946
+ try {
1947
+ const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(clientSecret);
1948
+ if (stripeError) {
1949
+ console.error("[useConfirmSubscriptionPayment] Stripe error:", stripeError);
1950
+ return {
1951
+ success: false,
1952
+ error: stripeError.message || t("billing.ui.payment_confirm.errors.failed")
1953
+ };
1954
+ }
1955
+ if (paymentIntent?.status === "succeeded") {
1956
+ return { success: true };
1957
+ }
1958
+ if (paymentIntent?.status === "requires_action") {
1959
+ return {
1960
+ success: false,
1961
+ error: t("billing.ui.payment_confirm.errors.requires_action")
1962
+ };
1963
+ }
1964
+ return {
1965
+ success: false,
1966
+ error: t("billing.ui.payment_confirm.errors.not_completed")
1967
+ };
1968
+ } catch (err) {
1969
+ console.error("[useConfirmSubscriptionPayment] Unexpected error:", err);
1970
+ return {
1971
+ success: false,
1972
+ error: err.message || t("billing.ui.payment_confirm.errors.unexpected")
1973
+ };
1974
+ } finally {
1975
+ setIsConfirming(false);
1976
+ }
1977
+ },
1978
+ [stripe, t]
1979
+ );
1980
+ return {
1981
+ confirmPayment,
1982
+ isConfirming
1983
+ };
1984
+ }
1985
+ __name(useConfirmSubscriptionPayment, "useConfirmSubscriptionPayment");
1986
+
1987
+ // src/features/billing/stripe-subscription/hooks/wizard-payment.ts
1988
+ function clientSecretToConfirm(meta) {
1989
+ if (!meta?.requiresAction) return null;
1990
+ return meta.clientSecret || null;
1991
+ }
1992
+ __name(clientSecretToConfirm, "clientSecretToConfirm");
1993
+ function subscriptionIdToSync(answered, opened) {
1994
+ return answered?.id || opened?.id || null;
1995
+ }
1996
+ __name(subscriptionIdToSync, "subscriptionIdToSync");
1997
+
1998
+ // src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts
1834
1999
  var initialState = {
1835
2000
  step: "plan-selection",
1836
2001
  selectedPrice: null,
@@ -1876,8 +2041,9 @@ function wizardReducer(state, action) {
1876
2041
  }
1877
2042
  __name(wizardReducer, "wizardReducer");
1878
2043
  function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1879
- const t = useTranslations25();
2044
+ const t = useTranslations26();
1880
2045
  const locale = useI18nLocale();
2046
+ const { confirmPayment } = useConfirmSubscriptionPayment();
1881
2047
  const [state, dispatch] = useReducer(wizardReducer, {
1882
2048
  ...initialState,
1883
2049
  selectedPrice: subscription?.price || null
@@ -1886,7 +2052,7 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1886
2052
  const onCloseRef = useRef(onClose);
1887
2053
  onSuccessRef.current = onSuccess;
1888
2054
  onCloseRef.current = onClose;
1889
- const checkPaymentMethod = useCallback2(async () => {
2055
+ const checkPaymentMethod = useCallback3(async () => {
1890
2056
  try {
1891
2057
  const methods = await StripeCustomerService.listPaymentMethods();
1892
2058
  dispatch({ type: "SET_HAS_PAYMENT_METHOD", hasMethod: methods.length > 0 });
@@ -1895,16 +2061,40 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1895
2061
  dispatch({ type: "SET_HAS_PAYMENT_METHOD", hasMethod: false });
1896
2062
  }
1897
2063
  }, []);
1898
- const selectPrice = useCallback2((price) => {
2064
+ const selectPrice = useCallback3((price) => {
1899
2065
  dispatch({ type: "SELECT_PRICE", price });
1900
2066
  }, []);
1901
- const setInterval = useCallback2((interval) => {
2067
+ const setInterval = useCallback3((interval) => {
1902
2068
  dispatch({ type: "SET_INTERVAL", interval });
1903
2069
  }, []);
1904
- const goToStep = useCallback2((step) => {
2070
+ const goToStep = useCallback3((step) => {
1905
2071
  dispatch({ type: "SET_STEP", step });
1906
2072
  }, []);
1907
- const goToReview = useCallback2(async () => {
2073
+ const lookUpAutomaticCode = useCallback3(async (priceId) => {
2074
+ try {
2075
+ const automaticCode = await StripeCouponCodeService.findAutomatic({ priceId });
2076
+ dispatch({
2077
+ type: "SET_PROMOTION_CODE",
2078
+ code: automaticCode ? toPromotionCodeResult(automaticCode) : null
2079
+ });
2080
+ return true;
2081
+ } catch (error) {
2082
+ console.error("[useSubscriptionWizard] Failed to look up the automatic promotion code:", error);
2083
+ dispatch({ type: "SET_PROMOTION_CODE", code: null });
2084
+ return false;
2085
+ }
2086
+ }, []);
2087
+ const refreshAutomaticCodeOrLeaveReview = useCallback3(
2088
+ async (priceId) => {
2089
+ const found = await lookUpAutomaticCode(priceId);
2090
+ if (!found) {
2091
+ dispatch({ type: "SET_STEP", step: "plan-selection" });
2092
+ dispatch({ type: "SET_ERROR", error: t("billing.ui.wizard.errors.prepare_review") });
2093
+ }
2094
+ },
2095
+ [lookUpAutomaticCode, t]
2096
+ );
2097
+ const goToReview = useCallback3(async () => {
1908
2098
  if (!state.selectedPrice) return;
1909
2099
  dispatch({ type: "SET_PROCESSING", isProcessing: true });
1910
2100
  try {
@@ -1922,6 +2112,13 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1922
2112
  return;
1923
2113
  }
1924
2114
  }
2115
+ if (shouldLookUpAutomatic(state.promotionCode)) {
2116
+ const found = await lookUpAutomaticCode(state.selectedPrice.id);
2117
+ if (!found) {
2118
+ dispatch({ type: "SET_ERROR", error: t("billing.ui.wizard.errors.prepare_review") });
2119
+ return;
2120
+ }
2121
+ }
1925
2122
  if (subscription && state.selectedPrice.id !== subscription.price?.id) {
1926
2123
  const preview = await StripeSubscriptionService.getProrationPreview({
1927
2124
  subscriptionId: subscription.id,
@@ -1936,30 +2133,69 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1936
2133
  } finally {
1937
2134
  dispatch({ type: "SET_PROCESSING", isProcessing: false });
1938
2135
  }
1939
- }, [state.selectedPrice, state.hasPaymentMethod, subscription, checkPaymentMethod, t]);
1940
- const confirmSubscription = useCallback2(async () => {
2136
+ }, [
2137
+ state.selectedPrice,
2138
+ state.hasPaymentMethod,
2139
+ state.promotionCode,
2140
+ subscription,
2141
+ checkPaymentMethod,
2142
+ lookUpAutomaticCode,
2143
+ t
2144
+ ]);
2145
+ const confirmSubscription = useCallback3(async () => {
1941
2146
  if (!state.selectedPrice) return;
1942
2147
  dispatch({ type: "SET_PROCESSING", isProcessing: true });
1943
2148
  dispatch({ type: "SET_ERROR", error: null });
1944
2149
  try {
2150
+ let result;
1945
2151
  if (subscription) {
1946
2152
  const changePlanParams = {
1947
2153
  id: subscription.id,
1948
2154
  newPriceId: state.selectedPrice.id,
1949
- promotionCode: state.promotionCode?.promotionCodeId
2155
+ promotionCode: promotionCodeToSend(state.promotionCode)
1950
2156
  };
1951
- await StripeSubscriptionService.changePlan(changePlanParams);
2157
+ result = await StripeSubscriptionService.changePlanWithMeta(changePlanParams);
1952
2158
  } else {
1953
2159
  const createParams = {
1954
2160
  id: v4(),
1955
2161
  priceId: state.selectedPrice.id,
1956
- promotionCode: state.promotionCode?.promotionCodeId
2162
+ promotionCode: promotionCodeToSend(state.promotionCode)
1957
2163
  };
1958
- await StripeSubscriptionService.createSubscription(createParams);
2164
+ result = await StripeSubscriptionService.createSubscription(createParams);
2165
+ }
2166
+ const clientSecret = clientSecretToConfirm(result.meta);
2167
+ if (clientSecret) {
2168
+ const confirmation = await confirmPayment(clientSecret);
2169
+ if (!confirmation.success) {
2170
+ console.error("[useSubscriptionWizard] Payment not confirmed:", confirmation.error);
2171
+ dispatch({
2172
+ type: "SET_ERROR",
2173
+ error: t("billing.ui.wizard.errors.payment_not_confirmed", { reason: confirmation.error ?? "" })
2174
+ });
2175
+ return;
2176
+ }
2177
+ const subscriptionId = subscriptionIdToSync(result.subscription, subscription);
2178
+ if (subscriptionId) {
2179
+ try {
2180
+ await StripeSubscriptionService.syncSubscription({ subscriptionId });
2181
+ } catch (syncError) {
2182
+ console.error("[useSubscriptionWizard] Subscription sync after payment failed:", syncError);
2183
+ }
2184
+ }
1959
2185
  }
1960
2186
  onSuccessRef.current();
1961
2187
  onCloseRef.current();
1962
2188
  } catch (error) {
2189
+ const soldOut = isDiscountSoldOut(error);
2190
+ if (soldOut || isDiscountUnavailable(error)) {
2191
+ dispatch({
2192
+ type: "SET_ERROR",
2193
+ error: soldOut ? t("billing.ui.wizard.errors.discount_sold_out") : t("billing.ui.wizard.errors.discount_unavailable")
2194
+ });
2195
+ dispatch({ type: "SET_PROMOTION_CODE", code: null });
2196
+ await refreshAutomaticCodeOrLeaveReview(state.selectedPrice.id);
2197
+ return;
2198
+ }
1963
2199
  if (error?.status === 409 || error?.response?.status === 409) {
1964
2200
  dispatch({
1965
2201
  type: "SET_ERROR",
@@ -1976,15 +2212,15 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
1976
2212
  } finally {
1977
2213
  dispatch({ type: "SET_PROCESSING", isProcessing: false });
1978
2214
  }
1979
- }, [state.selectedPrice, state.promotionCode, subscription, t]);
1980
- const handlePaymentMethodSuccess = useCallback2(async () => {
2215
+ }, [state.selectedPrice, state.promotionCode, subscription, refreshAutomaticCodeOrLeaveReview, confirmPayment, t]);
2216
+ const handlePaymentMethodSuccess = useCallback3(async () => {
1981
2217
  dispatch({ type: "SET_HAS_PAYMENT_METHOD", hasMethod: true });
1982
- dispatch({ type: "SET_STEP", step: "review" });
1983
- }, []);
1984
- const reset = useCallback2(() => {
2218
+ await goToReview();
2219
+ }, [goToReview]);
2220
+ const reset = useCallback3(() => {
1985
2221
  dispatch({ type: "RESET" });
1986
2222
  }, []);
1987
- const validatePromoCode = useCallback2(
2223
+ const validatePromoCode = useCallback3(
1988
2224
  async (code) => {
1989
2225
  dispatch({ type: "SET_VALIDATING_PROMO_CODE", isValidating: true });
1990
2226
  dispatch({ type: "SET_PROMO_CODE_ERROR", error: null });
@@ -2014,11 +2250,18 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
2014
2250
  },
2015
2251
  [state.selectedPrice?.id, t, locale]
2016
2252
  );
2017
- const clearPromoCode = useCallback2(() => {
2253
+ const clearPromoCode = useCallback3(async () => {
2018
2254
  dispatch({ type: "SET_PROMOTION_CODE", code: null });
2019
2255
  dispatch({ type: "SET_PROMO_CODE_ERROR", error: null });
2020
2256
  dispatch({ type: "SET_ERROR", error: null });
2021
- }, []);
2257
+ if (!state.selectedPrice) return;
2258
+ dispatch({ type: "SET_PROCESSING", isProcessing: true });
2259
+ try {
2260
+ await refreshAutomaticCodeOrLeaveReview(state.selectedPrice.id);
2261
+ } finally {
2262
+ dispatch({ type: "SET_PROCESSING", isProcessing: false });
2263
+ }
2264
+ }, [state.selectedPrice, refreshAutomaticCodeOrLeaveReview]);
2022
2265
  const actions = useMemo(
2023
2266
  () => ({
2024
2267
  selectPrice,
@@ -2053,7 +2296,7 @@ function useSubscriptionWizard({ subscription, onSuccess, onClose }) {
2053
2296
  __name(useSubscriptionWizard, "useSubscriptionWizard");
2054
2297
 
2055
2298
  // src/features/billing/stripe-subscription/components/wizards/WizardProgressIndicator.tsx
2056
- import { useTranslations as useTranslations26 } from "next-intl";
2299
+ import { useTranslations as useTranslations27 } from "next-intl";
2057
2300
  import { Check as Check3 } from "lucide-react";
2058
2301
  import { jsx as jsx26, jsxs as jsxs23 } from "react/jsx-runtime";
2059
2302
  var STEPS = [
@@ -2066,7 +2309,7 @@ function getStepIndex(step) {
2066
2309
  }
2067
2310
  __name(getStepIndex, "getStepIndex");
2068
2311
  function WizardProgressIndicator({ currentStep }) {
2069
- const t = useTranslations26();
2312
+ const t = useTranslations27();
2070
2313
  const currentIndex = getStepIndex(currentStep);
2071
2314
  return /* @__PURE__ */ jsx26("div", { className: "flex items-center justify-center gap-x-2 py-4", children: STEPS.map((step, index) => {
2072
2315
  const isCompleted = index < currentIndex;
@@ -2087,8 +2330,8 @@ function WizardProgressIndicator({ currentStep }) {
2087
2330
  __name(WizardProgressIndicator, "WizardProgressIndicator");
2088
2331
 
2089
2332
  // src/features/billing/stripe-subscription/components/wizards/WizardStepPlanSelection.tsx
2090
- import { useTranslations as useTranslations27 } from "next-intl";
2091
- import { useEffect as useEffect6, useMemo as useMemo2, useState as useState11 } from "react";
2333
+ import { useTranslations as useTranslations28 } from "next-intl";
2334
+ import { useEffect as useEffect6, useMemo as useMemo2, useState as useState12 } from "react";
2092
2335
  import { jsx as jsx27, jsxs as jsxs24 } from "react/jsx-runtime";
2093
2336
  function WizardStepPlanSelection({
2094
2337
  selectedPrice,
@@ -2101,9 +2344,9 @@ function WizardStepPlanSelection({
2101
2344
  onNext,
2102
2345
  isProcessing
2103
2346
  }) {
2104
- const t = useTranslations27();
2105
- const [products, setProducts] = useState11([]);
2106
- const [loading, setLoading] = useState11(true);
2347
+ const t = useTranslations28();
2348
+ const [products, setProducts] = useState12([]);
2349
+ const [loading, setLoading] = useState12(true);
2107
2350
  useEffect6(() => {
2108
2351
  const loadProducts = /* @__PURE__ */ __name(async () => {
2109
2352
  try {
@@ -2164,12 +2407,13 @@ function WizardStepPlanSelection({
2164
2407
  __name(WizardStepPlanSelection, "WizardStepPlanSelection");
2165
2408
 
2166
2409
  // src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx
2167
- import { useTranslations as useTranslations29 } from "next-intl";
2410
+ import { useTranslations as useTranslations30 } from "next-intl";
2168
2411
  import { AlertCircle } from "lucide-react";
2412
+ import { useEffect as useEffect7, useState as useState14 } from "react";
2169
2413
 
2170
2414
  // src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx
2171
- import { useTranslations as useTranslations28 } from "next-intl";
2172
- import { useState as useState12 } from "react";
2415
+ import { useTranslations as useTranslations29 } from "next-intl";
2416
+ import { useState as useState13 } from "react";
2173
2417
  import { Check as Check4, X, Loader2 as Loader22 } from "lucide-react";
2174
2418
  import { jsx as jsx28, jsxs as jsxs25 } from "react/jsx-runtime";
2175
2419
  function PromoCodeInput({
@@ -2180,9 +2424,19 @@ function PromoCodeInput({
2180
2424
  onRemove,
2181
2425
  disabled = false
2182
2426
  }) {
2183
- const t = useTranslations28();
2427
+ const t = useTranslations29();
2184
2428
  const locale = useI18nLocale();
2185
- const [code, setCode] = useState12("");
2429
+ const [code, setCode] = useState13("");
2430
+ const appliedKey = appliedCode?.valid ? `${appliedCode.automatic ? "auto" : "typed"}:${appliedCode.code ?? ""}` : null;
2431
+ const [previousAppliedKey, setPreviousAppliedKey] = useState13(appliedKey);
2432
+ if (appliedKey !== previousAppliedKey) {
2433
+ setPreviousAppliedKey(appliedKey);
2434
+ if (appliedKey !== null) setCode("");
2435
+ }
2436
+ const handleRemove = /* @__PURE__ */ __name(() => {
2437
+ setCode("");
2438
+ onRemove();
2439
+ }, "handleRemove");
2186
2440
  const handleApply = /* @__PURE__ */ __name(() => {
2187
2441
  if (code.trim()) {
2188
2442
  onApply(code.trim().toUpperCase());
@@ -2214,6 +2468,39 @@ function PromoCodeInput({
2214
2468
  return "";
2215
2469
  }
2216
2470
  }, "formatDuration");
2471
+ const codeInput = /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2472
+ /* @__PURE__ */ jsxs25("div", { className: "flex gap-2", children: [
2473
+ /* @__PURE__ */ jsx28(
2474
+ Input,
2475
+ {
2476
+ placeholder: t("billing.ui.promo_code.placeholder"),
2477
+ value: code,
2478
+ onChange: (e) => setCode(e.target.value.toUpperCase()),
2479
+ onKeyDown: handleKeyDown,
2480
+ disabled: disabled || isValidating,
2481
+ className: "flex-1"
2482
+ }
2483
+ ),
2484
+ /* @__PURE__ */ jsx28(Button, { variant: "outline", onClick: handleApply, disabled: disabled || isValidating || !code.trim(), children: isValidating ? /* @__PURE__ */ jsx28(Loader22, { className: "h-4 w-4 animate-spin" }) : t("billing.ui.promo_code.apply") })
2485
+ ] }),
2486
+ error && /* @__PURE__ */ jsx28("p", { className: "text-destructive text-xs/relaxed", children: error })
2487
+ ] });
2488
+ if (appliedCode?.valid && appliedCode.automatic) {
2489
+ return /* @__PURE__ */ jsxs25("div", { className: "space-y-3", children: [
2490
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2491
+ /* @__PURE__ */ jsxs25("div", { className: "bg-success/15 border-success/30 flex items-center gap-2 rounded-lg border p-3", children: [
2492
+ /* @__PURE__ */ jsx28(Check4, { className: "text-success h-4 w-4" }),
2493
+ /* @__PURE__ */ jsx28("span", { className: "text-success font-medium", children: t("billing.ui.promo_code.automatic_label") }),
2494
+ /* @__PURE__ */ jsx28("span", { className: "text-success text-sm", children: formatDiscount(appliedCode) })
2495
+ ] }),
2496
+ /* @__PURE__ */ jsx28("p", { className: "text-success text-sm", children: formatDuration(appliedCode) })
2497
+ ] }),
2498
+ /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2499
+ /* @__PURE__ */ jsx28("p", { className: "text-muted-foreground text-xs/relaxed", children: t("billing.ui.promo_code.use_different") }),
2500
+ codeInput
2501
+ ] })
2502
+ ] });
2503
+ }
2217
2504
  if (appliedCode?.valid) {
2218
2505
  return /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2219
2506
  /* @__PURE__ */ jsxs25("div", { className: "bg-success/15 border-success/30 flex items-center justify-between rounded-lg border p-3", children: [
@@ -2227,7 +2514,7 @@ function PromoCodeInput({
2227
2514
  {
2228
2515
  variant: "ghost",
2229
2516
  size: "sm",
2230
- onClick: onRemove,
2517
+ onClick: handleRemove,
2231
2518
  disabled,
2232
2519
  className: "text-success hover:bg-success/25 hover:text-success",
2233
2520
  children: /* @__PURE__ */ jsx28(X, { className: "h-4 w-4" })
@@ -2237,26 +2524,20 @@ function PromoCodeInput({
2237
2524
  /* @__PURE__ */ jsx28("p", { className: "text-success text-sm", children: formatDuration(appliedCode) })
2238
2525
  ] });
2239
2526
  }
2240
- return /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2241
- /* @__PURE__ */ jsxs25("div", { className: "flex gap-2", children: [
2242
- /* @__PURE__ */ jsx28(
2243
- Input,
2244
- {
2245
- placeholder: t("billing.ui.promo_code.placeholder"),
2246
- value: code,
2247
- onChange: (e) => setCode(e.target.value.toUpperCase()),
2248
- onKeyDown: handleKeyDown,
2249
- disabled: disabled || isValidating,
2250
- className: "flex-1"
2251
- }
2252
- ),
2253
- /* @__PURE__ */ jsx28(Button, { variant: "outline", onClick: handleApply, disabled: disabled || isValidating || !code.trim(), children: isValidating ? /* @__PURE__ */ jsx28(Loader22, { className: "h-4 w-4 animate-spin" }) : t("billing.ui.promo_code.apply") })
2254
- ] }),
2255
- error && /* @__PURE__ */ jsx28("p", { className: "text-destructive text-xs/relaxed", children: error })
2256
- ] });
2527
+ return codeInput;
2257
2528
  }
2258
2529
  __name(PromoCodeInput, "PromoCodeInput");
2259
2530
 
2531
+ // src/features/billing/stripe-subscription/components/wizards/review-card.ts
2532
+ function reviewCard(methods, defaultPaymentMethodId) {
2533
+ if (!methods || methods.length === 0) return null;
2534
+ const method = defaultPaymentMethodId && methods.find((m) => m.id === defaultPaymentMethodId) || methods[0];
2535
+ if (!method.card?.last4) return null;
2536
+ const brand = method.card.brand ? method.card.brand.charAt(0).toUpperCase() + method.card.brand.slice(1) : method.type.charAt(0).toUpperCase() + method.type.slice(1);
2537
+ return { brand, last4: method.card.last4 };
2538
+ }
2539
+ __name(reviewCard, "reviewCard");
2540
+
2260
2541
  // src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx
2261
2542
  import { Fragment as Fragment6, jsx as jsx29, jsxs as jsxs26 } from "react/jsx-runtime";
2262
2543
  function WizardStepReview({
@@ -2276,8 +2557,25 @@ function WizardStepReview({
2276
2557
  onRemovePromoCode,
2277
2558
  isTrialUpgrade
2278
2559
  }) {
2279
- const t = useTranslations29();
2560
+ const t = useTranslations30();
2280
2561
  const fmt = useBillingFormatOptions();
2562
+ const [card, setCard] = useState14(null);
2563
+ useEffect7(() => {
2564
+ if (!hasPaymentMethod) {
2565
+ setCard(null);
2566
+ return;
2567
+ }
2568
+ let cancelled = false;
2569
+ Promise.all([StripeCustomerService.listPaymentMethods(), StripeCustomerService.getCustomer()]).then(([methods, customer]) => {
2570
+ if (!cancelled) setCard(reviewCard(methods, customer?.defaultPaymentMethodId));
2571
+ }).catch((err) => {
2572
+ console.error("[WizardStepReview] Failed to load the card on file:", err);
2573
+ if (!cancelled) setCard(null);
2574
+ });
2575
+ return () => {
2576
+ cancelled = true;
2577
+ };
2578
+ }, [hasPaymentMethod]);
2281
2579
  if (!selectedPrice) {
2282
2580
  return /* @__PURE__ */ jsx29("div", { className: "text-center py-8 text-muted-foreground", children: t("billing.ui.wizard.review.no_plan") });
2283
2581
  }
@@ -2287,41 +2585,19 @@ function WizardStepReview({
2287
2585
  const interval = price.recurring?.interval || "month";
2288
2586
  return interval === "year" ? t("billing.ui.wizard.review.interval_yearly") : t("billing.ui.wizard.review.interval_monthly");
2289
2587
  }, "formatInterval");
2290
- const calculateDiscountedPrice = /* @__PURE__ */ __name(() => {
2291
- if (!promotionCode?.valid || !selectedPrice.unitAmount) return null;
2292
- const originalPrice = selectedPrice.unitAmount;
2293
- if (promotionCode.discountType === "percent_off" && promotionCode.discountValue) {
2294
- return originalPrice * (1 - promotionCode.discountValue / 100);
2295
- }
2296
- if (promotionCode.discountType === "amount_off" && promotionCode.discountValue) {
2297
- return Math.max(0, originalPrice - promotionCode.discountValue);
2298
- }
2299
- return null;
2300
- }, "calculateDiscountedPrice");
2301
- const discountedPrice = calculateDiscountedPrice();
2302
- const calculateDiscountedImmediateCharge = /* @__PURE__ */ __name(() => {
2303
- if (!promotionCode?.valid || !prorationPreview?.immediateCharge) return null;
2304
- const originalCharge = prorationPreview.immediateCharge;
2305
- if (promotionCode.discountType === "percent_off" && promotionCode.discountValue) {
2306
- return originalCharge * (1 - promotionCode.discountValue / 100);
2307
- }
2308
- if (promotionCode.discountType === "amount_off" && promotionCode.discountValue) {
2309
- return Math.max(0, originalCharge - promotionCode.discountValue);
2310
- }
2311
- return null;
2312
- }, "calculateDiscountedImmediateCharge");
2313
- const discountedImmediateCharge = calculateDiscountedImmediateCharge();
2588
+ const codeDiscount = promotionCodeDiscount(promotionCode);
2589
+ const carriedDiscount = isChangingPlan && !codeDiscount ? subscriptionDiscount(subscription) : null;
2590
+ const discount = codeDiscount ?? carriedDiscount;
2591
+ const discountedPrice = applyDiscount(selectedPrice.unitAmount, discount);
2592
+ const discountedImmediateCharge = applyDiscount(prorationPreview?.immediateCharge, discount);
2314
2593
  const getDiscountDescription = /* @__PURE__ */ __name(() => {
2315
- if (!promotionCode?.valid) return null;
2316
- if (promotionCode.discountType === "percent_off" && promotionCode.discountValue) {
2317
- return t("billing.ui.wizard.review.percent_off", { value: promotionCode.discountValue });
2318
- }
2319
- if (promotionCode.discountType === "amount_off" && promotionCode.discountValue) {
2320
- return t("billing.ui.wizard.review.amount_off", {
2321
- amount: formatCurrency(promotionCode.discountValue, promotionCode.currency || selectedPrice.currency, fmt)
2322
- });
2594
+ if (!discount) return null;
2595
+ if (discount.discountType === "percent_off") {
2596
+ return t("billing.ui.wizard.review.percent_off", { value: discount.discountValue });
2323
2597
  }
2324
- return null;
2598
+ return t("billing.ui.wizard.review.amount_off", {
2599
+ amount: formatCurrency(discount.discountValue, discount.currency || selectedPrice.currency, fmt)
2600
+ });
2325
2601
  }, "getDiscountDescription");
2326
2602
  const discountDescription = getDiscountDescription();
2327
2603
  return /* @__PURE__ */ jsxs26("div", { className: "space-y-6", children: [
@@ -2336,7 +2612,8 @@ function WizardStepReview({
2336
2612
  discountedPrice !== null ? /* @__PURE__ */ jsxs26(Fragment6, { children: [
2337
2613
  /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground line-through", children: formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency, fmt) }),
2338
2614
  /* @__PURE__ */ jsx29("p", { className: "text-success text-lg font-semibold", children: formatCurrency(discountedPrice, selectedPrice.currency, fmt) }),
2339
- discountDescription && /* @__PURE__ */ jsx29(Badge, { variant: "softGreen", children: discountDescription })
2615
+ discountDescription && /* @__PURE__ */ jsx29(Badge, { variant: "softGreen", children: discountDescription }),
2616
+ carriedDiscount && /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: t("billing.ui.wizard.review.discount_carries_over") })
2340
2617
  ] }) : /* @__PURE__ */ jsx29("p", { className: "font-semibold text-lg", children: formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency, fmt) }),
2341
2618
  /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: formatInterval2(selectedPrice) })
2342
2619
  ] })
@@ -2348,7 +2625,7 @@ function WizardStepReview({
2348
2625
  className: `${isTrialUpgrade ? "bg-warning/15 border-warning/30" : "bg-primary/10 border-primary/30"} space-y-2 rounded-lg border p-4`,
2349
2626
  children: [
2350
2627
  /* @__PURE__ */ jsx29("h4", { className: `font-medium ${isTrialUpgrade ? "text-warning" : "text-primary"}`, children: isTrialUpgrade ? t("billing.ui.wizard.review.trial_upgrade") : t("billing.ui.wizard.review.proration_summary") }),
2351
- /* @__PURE__ */ jsx29("p", { className: `text-sm ${isTrialUpgrade ? "text-warning" : "text-foreground"}`, children: isTrialUpgrade ? t("billing.ui.wizard.review.trial_upgrade_description") : t("billing.ui.wizard.review.proration_description") }),
2628
+ /* @__PURE__ */ jsx29("p", { className: `text-sm ${isTrialUpgrade ? "text-warning" : "text-foreground"}`, children: isTrialUpgrade ? discountedImmediateCharge !== null ? t("billing.ui.wizard.review.trial_upgrade_description_discounted") : t("billing.ui.wizard.review.trial_upgrade_description") : t("billing.ui.wizard.review.proration_description") }),
2352
2629
  /* @__PURE__ */ jsxs26("div", { className: "flex justify-between text-sm", children: [
2353
2630
  /* @__PURE__ */ jsx29("span", { className: `${isTrialUpgrade ? "text-warning" : "text-muted-foreground"}`, children: isTrialUpgrade ? t("billing.ui.wizard.review.charge_now") : t("billing.ui.wizard.review.due_now") }),
2354
2631
  /* @__PURE__ */ jsx29("span", { className: `font-medium ${isTrialUpgrade ? "text-warning" : "text-foreground"}`, children: discountedImmediateCharge !== null ? /* @__PURE__ */ jsxs26(Fragment6, { children: [
@@ -2376,9 +2653,9 @@ function WizardStepReview({
2376
2653
  /* @__PURE__ */ jsx29("div", { className: "border rounded-lg p-4", children: /* @__PURE__ */ jsxs26("div", { className: "flex justify-between items-center", children: [
2377
2654
  /* @__PURE__ */ jsxs26("div", { children: [
2378
2655
  /* @__PURE__ */ jsx29("h4", { className: "font-medium", children: t("billing.ui.payment_method.title") }),
2379
- /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: hasPaymentMethod ? t("billing.ui.wizard.review.payment_on_file") : t("billing.ui.wizard.review.no_payment_on_file") })
2656
+ /* @__PURE__ */ jsx29("p", { className: "text-sm text-muted-foreground", children: hasPaymentMethod ? card ? t("billing.ui.wizard.review.card_on_file", { brand: card.brand, last4: card.last4 }) : t("billing.ui.wizard.review.payment_on_file") : t("billing.ui.wizard.review.no_payment_on_file") })
2380
2657
  ] }),
2381
- !hasPaymentMethod && /* @__PURE__ */ jsx29(Button, { variant: "outline", onClick: onAddPaymentMethod, children: t("billing.ui.payment_methods.add") })
2658
+ hasPaymentMethod ? /* @__PURE__ */ jsx29(Button, { variant: "outline", onClick: onAddPaymentMethod, disabled: isProcessing, children: t("billing.ui.wizard.review.use_another_card") }) : /* @__PURE__ */ jsx29(Button, { variant: "outline", onClick: onAddPaymentMethod, children: t("billing.ui.payment_methods.add") })
2382
2659
  ] }) }),
2383
2660
  error && /* @__PURE__ */ jsxs26(Alert, { variant: "destructive", children: [
2384
2661
  /* @__PURE__ */ jsx29(AlertCircle, { className: "h-4 w-4" }),
@@ -2393,16 +2670,16 @@ function WizardStepReview({
2393
2670
  __name(WizardStepReview, "WizardStepReview");
2394
2671
 
2395
2672
  // src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx
2396
- import { useTranslations as useTranslations30 } from "next-intl";
2673
+ import { useTranslations as useTranslations31 } from "next-intl";
2397
2674
  import { jsx as jsx30, jsxs as jsxs27 } from "react/jsx-runtime";
2398
2675
  function WizardStepPaymentMethod({ onBack, onSuccess, isProcessing }) {
2399
- const t = useTranslations30();
2676
+ const t = useTranslations31();
2400
2677
  return /* @__PURE__ */ jsxs27("div", { className: "space-y-6", children: [
2401
2678
  /* @__PURE__ */ jsxs27("div", { className: "text-center", children: [
2402
2679
  /* @__PURE__ */ jsx30(SectionHeader, { children: t("billing.ui.payment_methods.add") }),
2403
2680
  /* @__PURE__ */ jsx30("p", { className: "text-sm text-muted-foreground", children: t("billing.ui.wizard.payment_description") })
2404
2681
  ] }),
2405
- /* @__PURE__ */ jsx30(PaymentMethodForm, { onSuccess, onCancel: onBack, isLoading: isProcessing })
2682
+ /* @__PURE__ */ jsx30(PaymentMethodForm, { onSuccess, onCancel: onBack, isLoading: isProcessing, alwaysSetDefault: true })
2406
2683
  ] });
2407
2684
  }
2408
2685
  __name(WizardStepPaymentMethod, "WizardStepPaymentMethod");
@@ -2416,15 +2693,15 @@ function SubscriptionWizard({
2416
2693
  hasActiveRecurringSubscription,
2417
2694
  subscription
2418
2695
  }) {
2419
- const t = useTranslations31();
2420
- const handleClose = useCallback3(() => onOpenChange(false), [onOpenChange]);
2696
+ const t = useTranslations32();
2697
+ const handleClose = useCallback4(() => onOpenChange(false), [onOpenChange]);
2421
2698
  const { state, actions } = useSubscriptionWizard({
2422
2699
  subscription,
2423
2700
  onSuccess,
2424
2701
  onClose: handleClose
2425
2702
  });
2426
2703
  const hasCheckedRef = useRef2(false);
2427
- useEffect7(() => {
2704
+ useEffect8(() => {
2428
2705
  if (open && !hasCheckedRef.current) {
2429
2706
  hasCheckedRef.current = true;
2430
2707
  actions.checkPaymentMethod();
@@ -2433,7 +2710,7 @@ function SubscriptionWizard({
2433
2710
  hasCheckedRef.current = false;
2434
2711
  }
2435
2712
  }, [open, actions.checkPaymentMethod]);
2436
- useEffect7(() => {
2713
+ useEffect8(() => {
2437
2714
  if (!open) {
2438
2715
  actions.reset();
2439
2716
  }
@@ -2442,7 +2719,7 @@ function SubscriptionWizard({
2442
2719
  const isChangePlanMode = !!subscription;
2443
2720
  const dialogTitle = subscription ? t("billing.ui.wizard.change_title") : isPurchasingAddons ? t("billing.ui.subscriptions.purchase_addons") : t("billing.ui.subscriptions.subscribe");
2444
2721
  const dialogDescription = subscription ? t("billing.ui.wizard.change_description") : isPurchasingAddons ? t("billing.ui.wizard.addons_description") : t("billing.ui.wizard.subscribe_description");
2445
- return /* @__PURE__ */ jsx31(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs28(DialogContent, { className: "max-w-2xl", children: [
2722
+ return /* @__PURE__ */ jsx31(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs28(DialogContent, { className: "max-w-2xl max-h-[90vh] overflow-y-auto", children: [
2446
2723
  /* @__PURE__ */ jsxs28(DialogHeader, { children: [
2447
2724
  /* @__PURE__ */ jsx31(DialogTitle, { children: dialogTitle }),
2448
2725
  /* @__PURE__ */ jsx31(DialogDescription, { children: dialogDescription })
@@ -2495,12 +2772,12 @@ function SubscriptionWizard({
2495
2772
  __name(SubscriptionWizard, "SubscriptionWizard");
2496
2773
 
2497
2774
  // src/features/billing/stripe-usage/components/containers/UsageContainer.tsx
2498
- import { useTranslations as useTranslations33 } from "next-intl";
2775
+ import { useTranslations as useTranslations34 } from "next-intl";
2499
2776
  import { Activity as Activity3 } from "lucide-react";
2500
- import { useEffect as useEffect8, useState as useState13 } from "react";
2777
+ import { useEffect as useEffect9, useState as useState15 } from "react";
2501
2778
 
2502
2779
  // src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx
2503
- import { useTranslations as useTranslations32 } from "next-intl";
2780
+ import { useTranslations as useTranslations33 } from "next-intl";
2504
2781
  import { Activity as Activity2 } from "lucide-react";
2505
2782
  import { jsx as jsx32, jsxs as jsxs29 } from "react/jsx-runtime";
2506
2783
  function getProgressColor(percentage) {
@@ -2523,7 +2800,7 @@ function formatDate2(date, { locale, t }) {
2523
2800
  }
2524
2801
  __name(formatDate2, "formatDate");
2525
2802
  function UsageSummaryCard({ meter, summary }) {
2526
- const t = useTranslations32();
2803
+ const t = useTranslations33();
2527
2804
  const fmt = useBillingFormatOptions();
2528
2805
  const currentUsage = summary?.aggregatedValue ?? 0;
2529
2806
  const limit = meter.limit;
@@ -2570,12 +2847,12 @@ __name(UsageSummaryCards, "UsageSummaryCards");
2570
2847
  // src/features/billing/stripe-usage/components/containers/UsageContainer.tsx
2571
2848
  import { jsx as jsx34, jsxs as jsxs30 } from "react/jsx-runtime";
2572
2849
  function UsageContainer() {
2573
- const t = useTranslations33();
2574
- const [meters, setMeters] = useState13([]);
2575
- const [summaries, setSummaries] = useState13({});
2576
- const [loading, setLoading] = useState13(true);
2577
- const [subscriptions, setSubscriptions] = useState13([]);
2578
- useEffect8(() => {
2850
+ const t = useTranslations34();
2851
+ const [meters, setMeters] = useState15([]);
2852
+ const [summaries, setSummaries] = useState15({});
2853
+ const [loading, setLoading] = useState15(true);
2854
+ const [subscriptions, setSubscriptions] = useState15([]);
2855
+ useEffect9(() => {
2579
2856
  loadUsageData();
2580
2857
  }, []);
2581
2858
  const loadUsageData = /* @__PURE__ */ __name(async () => {
@@ -2639,7 +2916,7 @@ function UsageContainer() {
2639
2916
  __name(UsageContainer, "UsageContainer");
2640
2917
 
2641
2918
  // src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx
2642
- import { useTranslations as useTranslations34 } from "next-intl";
2919
+ import { useTranslations as useTranslations35 } from "next-intl";
2643
2920
  import { jsx as jsx35, jsxs as jsxs31 } from "react/jsx-runtime";
2644
2921
  function formatDateTime(date, { locale, t }) {
2645
2922
  if (!date) return t("billing.ui.not_available");
@@ -2658,7 +2935,7 @@ function formatDateTime(date, { locale, t }) {
2658
2935
  }
2659
2936
  __name(formatDateTime, "formatDateTime");
2660
2937
  function UsageHistoryTable({ usageRecords }) {
2661
- const t = useTranslations34();
2938
+ const t = useTranslations35();
2662
2939
  const fmt = useBillingFormatOptions();
2663
2940
  if (usageRecords.length === 0) {
2664
2941
  return /* @__PURE__ */ jsx35("div", { className: "rounded-lg border p-8 text-center", children: /* @__PURE__ */ jsx35("p", { className: "text-muted-foreground", children: t("billing.ui.usage.history_empty") }) });
@@ -2697,12 +2974,21 @@ function BillingDetailModal({ open, onOpenChange, title, children, className })
2697
2974
  }
2698
2975
  __name(BillingDetailModal, "BillingDetailModal");
2699
2976
 
2977
+ // src/features/billing/components/utils/url.ts
2978
+ function urlWithoutParam(params) {
2979
+ const query = new URLSearchParams(params.search);
2980
+ query.delete(params.param);
2981
+ const remaining = query.toString();
2982
+ return `${params.pathname}${remaining ? `?${remaining}` : ""}${params.hash ?? ""}`;
2983
+ }
2984
+ __name(urlWithoutParam, "urlWithoutParam");
2985
+
2700
2986
  // src/features/billing/components/widgets/BillingAlertBanner.tsx
2701
- import { useTranslations as useTranslations35 } from "next-intl";
2987
+ import { useTranslations as useTranslations36 } from "next-intl";
2702
2988
  import { AlertCircle as AlertCircle2 } from "lucide-react";
2703
2989
  import { jsx as jsx37, jsxs as jsxs33 } from "react/jsx-runtime";
2704
2990
  function BillingAlertBanner({ subscription, onUpdatePayment, onAddPayment }) {
2705
- const t = useTranslations35();
2991
+ const t = useTranslations36();
2706
2992
  if (subscription.status === "past_due" /* PAST_DUE */) {
2707
2993
  return /* @__PURE__ */ jsxs33("div", { className: "bg-red-50 border border-red-200 rounded-lg p-4 flex items-start gap-x-3", children: [
2708
2994
  /* @__PURE__ */ jsx37(AlertCircle2, { className: "text-destructive mt-0.5 h-5 w-5" }),
@@ -2735,8 +3021,8 @@ __name(BillingAlertBanner, "BillingAlertBanner");
2735
3021
  // src/features/billing/components/containers/BillingDashboardContainer.tsx
2736
3022
  import { Fragment as Fragment7, jsx as jsx38, jsxs as jsxs34 } from "react/jsx-runtime";
2737
3023
  function BillingDashboardContainer() {
2738
- const t = useTranslations36();
2739
- const [data, setData] = useState14({
3024
+ const t = useTranslations37();
3025
+ const [data, setData] = useState16({
2740
3026
  customer: null,
2741
3027
  subscriptions: [],
2742
3028
  paymentMethods: [],
@@ -2744,27 +3030,27 @@ function BillingDashboardContainer() {
2744
3030
  meters: [],
2745
3031
  meterSummaries: {}
2746
3032
  });
2747
- const [loading, setLoading] = useState14({
3033
+ const [loading, setLoading] = useState16({
2748
3034
  customer: true,
2749
3035
  subscriptions: true,
2750
3036
  paymentMethods: true,
2751
3037
  invoices: true,
2752
3038
  usage: true
2753
3039
  });
2754
- const [errors, setErrors] = useState14({
3040
+ const [errors, setErrors] = useState16({
2755
3041
  customer: null,
2756
3042
  subscriptions: null,
2757
3043
  paymentMethods: null,
2758
3044
  invoices: null,
2759
3045
  usage: null
2760
3046
  });
2761
- const [activeModal, setActiveModal] = useState14(null);
2762
- const [noCustomerExists, setNoCustomerExists] = useState14(false);
2763
- const [creatingCustomer, setCreatingCustomer] = useState14(false);
3047
+ const [activeModal, setActiveModal] = useState16(null);
3048
+ const [noCustomerExists, setNoCustomerExists] = useState16(false);
3049
+ const [creatingCustomer, setCreatingCustomer] = useState16(false);
2764
3050
  const searchParams = useSearchParams();
2765
- const [showWizard, setShowWizard] = useState14(false);
2766
- const [editingSubscription, setEditingSubscription] = useState14(null);
2767
- const hasMeteredSubscriptions = useCallback4(() => {
3051
+ const [showWizard, setShowWizard] = useState16(false);
3052
+ const [editingSubscription, setEditingSubscription] = useState16(null);
3053
+ const hasMeteredSubscriptions = useCallback5(() => {
2768
3054
  return data.subscriptions.some((sub) => sub.price?.recurring?.usageType === "metered");
2769
3055
  }, [data.subscriptions]);
2770
3056
  const hasActiveRecurringSubscription = useMemo3(() => {
@@ -2772,7 +3058,7 @@ function BillingDashboardContainer() {
2772
3058
  (sub) => (sub.status === "active" /* ACTIVE */ || sub.status === "trialing" /* TRIALING */) && sub.price?.priceType === "recurring"
2773
3059
  );
2774
3060
  }, [data.subscriptions]);
2775
- const fetchAllData = useCallback4(async () => {
3061
+ const fetchAllData = useCallback5(async () => {
2776
3062
  setNoCustomerExists(false);
2777
3063
  let customer = null;
2778
3064
  try {
@@ -2891,7 +3177,7 @@ function BillingDashboardContainer() {
2891
3177
  setLoading((prev) => ({ ...prev, usage: false }));
2892
3178
  }
2893
3179
  }, "fetchUsageData");
2894
- const refreshData = useCallback4(async () => {
3180
+ const refreshData = useCallback5(async () => {
2895
3181
  setLoading({
2896
3182
  customer: true,
2897
3183
  subscriptions: true,
@@ -2901,23 +3187,32 @@ function BillingDashboardContainer() {
2901
3187
  });
2902
3188
  await fetchAllData();
2903
3189
  }, [fetchAllData]);
2904
- const handleOpenWizard = useCallback4((subscription) => {
3190
+ const handleOpenWizard = useCallback5((subscription) => {
2905
3191
  setEditingSubscription(subscription || null);
2906
3192
  setShowWizard(true);
2907
3193
  }, []);
2908
- const handleWizardClose = useCallback4(() => {
3194
+ const handleWizardClose = useCallback5(() => {
2909
3195
  setShowWizard(false);
2910
3196
  setEditingSubscription(null);
2911
3197
  refreshData();
2912
3198
  }, [refreshData]);
2913
- useEffect9(() => {
3199
+ useEffect10(() => {
2914
3200
  fetchAllData();
2915
3201
  }, [fetchAllData]);
2916
- useEffect9(() => {
3202
+ useEffect10(() => {
2917
3203
  const action = searchParams.get("action");
2918
3204
  if (action === "subscribe") {
2919
3205
  setShowWizard(true);
2920
- window.history.replaceState({}, "", window.location.pathname);
3206
+ window.history.replaceState(
3207
+ window.history.state,
3208
+ "",
3209
+ urlWithoutParam({
3210
+ pathname: window.location.pathname,
3211
+ search: window.location.search,
3212
+ hash: window.location.hash,
3213
+ param: "action"
3214
+ })
3215
+ );
2921
3216
  }
2922
3217
  }, [searchParams]);
2923
3218
  const criticalSubscriptions = data.subscriptions.filter(
@@ -3036,7 +3331,9 @@ function BillingDashboardContainer() {
3036
3331
  SubscriptionsContainer,
3037
3332
  {
3038
3333
  onOpenWizard: handleOpenWizard,
3039
- hasActiveRecurringSubscription
3334
+ hasActiveRecurringSubscription,
3335
+ subscriptions: data.subscriptions,
3336
+ onSubscriptionsChange: refreshData
3040
3337
  }
3041
3338
  )
3042
3339
  }
@@ -3071,7 +3368,7 @@ function BillingDashboardContainer() {
3071
3368
  /* @__PURE__ */ jsx38(
3072
3369
  SubscriptionWizard,
3073
3370
  {
3074
- open: showWizard,
3371
+ open: showWizard && !!data.customer,
3075
3372
  onOpenChange: (open) => !open && handleWizardClose(),
3076
3373
  onSuccess: refreshData,
3077
3374
  hasActiveRecurringSubscription,
@@ -3117,19 +3414,19 @@ function isStripeConfigured() {
3117
3414
  __name(isStripeConfigured, "isStripeConfigured");
3118
3415
 
3119
3416
  // src/features/billing/contexts/PriceContext.tsx
3120
- import { useTranslations as useTranslations39 } from "next-intl";
3121
- import { createContext, useCallback as useCallback6, useContext, useMemo as useMemo6, useState as useState17 } from "react";
3417
+ import { useTranslations as useTranslations40 } from "next-intl";
3418
+ import { createContext, useCallback as useCallback7, useContext, useMemo as useMemo6, useState as useState19 } from "react";
3122
3419
 
3123
3420
  // src/features/billing/stripe-price/components/forms/PriceArchiver.tsx
3124
3421
  import { ArchiveIcon, RotateCcwIcon } from "lucide-react";
3125
- import { useTranslations as useTranslations37 } from "next-intl";
3126
- import { useState as useState15 } from "react";
3422
+ import { useTranslations as useTranslations38 } from "next-intl";
3423
+ import { useState as useState17 } from "react";
3127
3424
  import { Fragment as Fragment9, jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
3128
3425
  function PriceArchiver({ price }) {
3129
- const t = useTranslations37();
3426
+ const t = useTranslations38();
3130
3427
  const fmt = useBillingFormatOptions();
3131
3428
  const { archivePrice, restorePrice } = usePriceContext();
3132
- const [open, setOpen] = useState15(false);
3429
+ const [open, setOpen] = useState17(false);
3133
3430
  const isArchived = !price.active;
3134
3431
  const scope = isArchived ? "restore" : "archive";
3135
3432
  return /* @__PURE__ */ jsxs35(Fragment9, { children: [
@@ -3165,8 +3462,8 @@ __name(PriceArchiver, "PriceArchiver");
3165
3462
  // src/features/billing/stripe-price/components/forms/PriceEditor.tsx
3166
3463
  import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
3167
3464
  import { PlusIcon, XIcon } from "lucide-react";
3168
- import { useTranslations as useTranslations38 } from "next-intl";
3169
- import { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo5, useRef as useRef3, useState as useState16 } from "react";
3465
+ import { useTranslations as useTranslations39 } from "next-intl";
3466
+ import { useCallback as useCallback6, useEffect as useEffect11, useMemo as useMemo5, useRef as useRef3, useState as useState18 } from "react";
3170
3467
  import { useForm as useForm2 } from "react-hook-form";
3171
3468
  import { v4 as v42 } from "uuid";
3172
3469
  import { z as z2 } from "zod";
@@ -3183,14 +3480,14 @@ function PriceEditorInternal({
3183
3480
  dialogOpen,
3184
3481
  onDialogOpenChange
3185
3482
  }) {
3186
- const t = useTranslations38();
3483
+ const t = useTranslations39();
3187
3484
  const fmt = useBillingFormatOptions();
3188
3485
  const router = useI18nRouter();
3189
3486
  const { createPrice, updatePrice, productId: contextProductId } = usePriceContext();
3190
- const [allFeatures, setAllFeatures] = useState16([]);
3487
+ const [allFeatures, setAllFeatures] = useState18([]);
3191
3488
  const isEdit = !!price;
3192
3489
  const targetProductId = productId ?? price?.product?.id ?? contextProductId;
3193
- useEffect10(() => {
3490
+ useEffect11(() => {
3194
3491
  const fetchFeatures = /* @__PURE__ */ __name(async () => {
3195
3492
  setAllFeatures(await FeatureService.findMany({}));
3196
3493
  }, "fetchFeatures");
@@ -3239,7 +3536,7 @@ function PriceEditorInternal({
3239
3536
  }),
3240
3537
  [t]
3241
3538
  );
3242
- const resolvePlatformFeatures = useCallback5(
3539
+ const resolvePlatformFeatures = useCallback6(
3243
3540
  (names) => {
3244
3541
  const ids = [];
3245
3542
  const unknown = [];
@@ -3252,7 +3549,7 @@ function PriceEditorInternal({
3252
3549
  },
3253
3550
  [allFeatures]
3254
3551
  );
3255
- const getDefaultValues = useCallback5(() => {
3552
+ const getDefaultValues = useCallback6(() => {
3256
3553
  const coreFeatureIds = allFeatures.filter((feature) => feature.isCore).map((feature) => feature.id);
3257
3554
  if (!price && seed) {
3258
3555
  return {
@@ -3291,14 +3588,14 @@ function PriceEditorInternal({
3291
3588
  defaultValues: getDefaultValues()
3292
3589
  });
3293
3590
  const hasCaughtUpWithFeatures = useRef3(false);
3294
- useEffect10(() => {
3591
+ useEffect11(() => {
3295
3592
  if (isEdit || hasCaughtUpWithFeatures.current || allFeatures.length === 0) return;
3296
3593
  hasCaughtUpWithFeatures.current = true;
3297
3594
  if (form.formState.isDirty) return;
3298
3595
  form.reset(getDefaultValues());
3299
3596
  }, [allFeatures, isEdit, form, getDefaultValues]);
3300
3597
  const warnedUnknownFor = useRef3(void 0);
3301
- useEffect10(() => {
3598
+ useEffect11(() => {
3302
3599
  if (isEdit || !seed || allFeatures.length === 0) return;
3303
3600
  const fingerprint = JSON.stringify(seed.platformFeatures ?? []);
3304
3601
  if (warnedUnknownFor.current === fingerprint) return;
@@ -3592,21 +3889,21 @@ var PriceProvider = /* @__PURE__ */ __name(({
3592
3889
  publishChrome = true,
3593
3890
  productId: productIdProp
3594
3891
  }) => {
3595
- const t = useTranslations39();
3892
+ const t = useTranslations40();
3596
3893
  const fmt = useBillingFormatOptions();
3597
3894
  const generateUrl = usePageUrlGenerator();
3598
3895
  const { hasRole } = useCurrentUserContext();
3599
- const [price, setPrice] = useState17(
3896
+ const [price, setPrice] = useState19(
3600
3897
  dehydratedPrice ? rehydrate(Modules.StripePrice, dehydratedPrice) : void 0
3601
3898
  );
3602
- const [priceVersion, setPriceVersion] = useState17(0);
3603
- const refreshPrices = useCallback6(() => setPriceVersion((value2) => value2 + 1), []);
3899
+ const [priceVersion, setPriceVersion] = useState19(0);
3900
+ const refreshPrices = useCallback7(() => setPriceVersion((value2) => value2 + 1), []);
3604
3901
  const productId = productIdProp ?? price?.product?.id;
3605
- const reloadPrice = useCallback6(async () => {
3902
+ const reloadPrice = useCallback7(async () => {
3606
3903
  if (!price?.id) return;
3607
3904
  setPrice(await StripePriceService.getPrice({ id: price.id }));
3608
3905
  }, [price?.id]);
3609
- const createPrice = useCallback6(
3906
+ const createPrice = useCallback7(
3610
3907
  async (input) => {
3611
3908
  const saved = await StripePriceService.createPrice(input);
3612
3909
  refreshPrices();
@@ -3614,7 +3911,7 @@ var PriceProvider = /* @__PURE__ */ __name(({
3614
3911
  },
3615
3912
  [refreshPrices]
3616
3913
  );
3617
- const updatePrice = useCallback6(
3914
+ const updatePrice = useCallback7(
3618
3915
  async (input) => {
3619
3916
  const saved = await StripePriceService.updatePrice(input);
3620
3917
  setPrice(saved);
@@ -3623,7 +3920,7 @@ var PriceProvider = /* @__PURE__ */ __name(({
3623
3920
  },
3624
3921
  [refreshPrices]
3625
3922
  );
3626
- const archivePrice = useCallback6(
3923
+ const archivePrice = useCallback7(
3627
3924
  async (id) => {
3628
3925
  const target = id ?? price?.id;
3629
3926
  if (!target) return;
@@ -3633,7 +3930,7 @@ var PriceProvider = /* @__PURE__ */ __name(({
3633
3930
  },
3634
3931
  [price?.id, reloadPrice, refreshPrices]
3635
3932
  );
3636
- const restorePrice = useCallback6(
3933
+ const restorePrice = useCallback7(
3637
3934
  async (id) => {
3638
3935
  const target = id ?? price?.id;
3639
3936
  if (!target) return;
@@ -3688,18 +3985,18 @@ var PriceProvider = /* @__PURE__ */ __name(({
3688
3985
  var usePriceContext = /* @__PURE__ */ __name(() => useContext(PriceContext) ?? defaultContextValue, "usePriceContext");
3689
3986
 
3690
3987
  // src/features/billing/contexts/ProductContext.tsx
3691
- import { useTranslations as useTranslations42 } from "next-intl";
3692
- import { createContext as createContext2, useCallback as useCallback8, useContext as useContext2, useMemo as useMemo8, useState as useState19 } from "react";
3988
+ import { useTranslations as useTranslations43 } from "next-intl";
3989
+ import { createContext as createContext2, useCallback as useCallback9, useContext as useContext2, useMemo as useMemo8, useState as useState21 } from "react";
3693
3990
 
3694
3991
  // src/features/billing/stripe-product/components/forms/ProductArchiver.tsx
3695
3992
  import { ArchiveIcon as ArchiveIcon2, RotateCcwIcon as RotateCcwIcon2 } from "lucide-react";
3696
- import { useTranslations as useTranslations40 } from "next-intl";
3697
- import { useState as useState18 } from "react";
3993
+ import { useTranslations as useTranslations41 } from "next-intl";
3994
+ import { useState as useState20 } from "react";
3698
3995
  import { Fragment as Fragment10, jsx as jsx43, jsxs as jsxs37 } from "react/jsx-runtime";
3699
3996
  function ProductArchiver({ product }) {
3700
- const t = useTranslations40();
3997
+ const t = useTranslations41();
3701
3998
  const { archiveProduct, restoreProduct } = useProductContext();
3702
- const [open, setOpen] = useState18(false);
3999
+ const [open, setOpen] = useState20(false);
3703
4000
  const isArchived = !product.active;
3704
4001
  const scope = isArchived ? "restore" : "archive";
3705
4002
  return /* @__PURE__ */ jsxs37(Fragment10, { children: [
@@ -3734,8 +4031,8 @@ __name(ProductArchiver, "ProductArchiver");
3734
4031
 
3735
4032
  // src/features/billing/stripe-product/components/forms/ProductEditor.tsx
3736
4033
  import { zodResolver as zodResolver3 } from "@hookform/resolvers/zod";
3737
- import { useTranslations as useTranslations41 } from "next-intl";
3738
- import { useCallback as useCallback7, useMemo as useMemo7 } from "react";
4034
+ import { useTranslations as useTranslations42 } from "next-intl";
4035
+ import { useCallback as useCallback8, useMemo as useMemo7 } from "react";
3739
4036
  import { useForm as useForm3 } from "react-hook-form";
3740
4037
  import { v4 as v43 } from "uuid";
3741
4038
  import { z as z3 } from "zod";
@@ -3750,7 +4047,7 @@ function ProductEditorInternal({
3750
4047
  dialogOpen,
3751
4048
  onDialogOpenChange
3752
4049
  }) {
3753
- const t = useTranslations41();
4050
+ const t = useTranslations42();
3754
4051
  const router = useI18nRouter();
3755
4052
  const { createProduct, updateProduct } = useProductContext();
3756
4053
  const isEdit = !!product;
@@ -3762,7 +4059,7 @@ function ProductEditorInternal({
3762
4059
  }),
3763
4060
  [t]
3764
4061
  );
3765
- const getDefaultValues = useCallback7(
4062
+ const getDefaultValues = useCallback8(
3766
4063
  () => ({
3767
4064
  id: product?.id ?? v43(),
3768
4065
  name: product?.name ?? "",
@@ -3851,19 +4148,19 @@ var defaultContextValue2 = {
3851
4148
  }, "refreshCatalogue")
3852
4149
  };
3853
4150
  var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, publishChrome = true }) => {
3854
- const t = useTranslations42();
4151
+ const t = useTranslations43();
3855
4152
  const generateUrl = usePageUrlGenerator();
3856
4153
  const { hasRole } = useCurrentUserContext();
3857
- const [product, setProduct] = useState19(
4154
+ const [product, setProduct] = useState21(
3858
4155
  dehydratedProduct ? rehydrate(Modules.StripeProduct, dehydratedProduct) : void 0
3859
4156
  );
3860
- const [catalogueVersion, setCatalogueVersion] = useState19(0);
3861
- const refreshCatalogue = useCallback8(() => setCatalogueVersion((value2) => value2 + 1), []);
3862
- const reloadProduct = useCallback8(async () => {
4157
+ const [catalogueVersion, setCatalogueVersion] = useState21(0);
4158
+ const refreshCatalogue = useCallback9(() => setCatalogueVersion((value2) => value2 + 1), []);
4159
+ const reloadProduct = useCallback9(async () => {
3863
4160
  if (!product?.id) return;
3864
4161
  setProduct(await StripeProductService.getProduct({ id: product.id }));
3865
4162
  }, [product?.id]);
3866
- const createProduct = useCallback8(
4163
+ const createProduct = useCallback9(
3867
4164
  async (input) => {
3868
4165
  const saved = await StripeProductService.createProduct(input);
3869
4166
  refreshCatalogue();
@@ -3871,7 +4168,7 @@ var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, pub
3871
4168
  },
3872
4169
  [refreshCatalogue]
3873
4170
  );
3874
- const updateProduct = useCallback8(
4171
+ const updateProduct = useCallback9(
3875
4172
  async (input) => {
3876
4173
  const saved = await StripeProductService.updateProduct(input);
3877
4174
  setProduct(saved);
@@ -3880,7 +4177,7 @@ var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, pub
3880
4177
  },
3881
4178
  [refreshCatalogue]
3882
4179
  );
3883
- const archiveProduct = useCallback8(
4180
+ const archiveProduct = useCallback9(
3884
4181
  async (id) => {
3885
4182
  const target = id ?? product?.id;
3886
4183
  if (!target) return;
@@ -3890,7 +4187,7 @@ var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, pub
3890
4187
  },
3891
4188
  [product?.id, reloadProduct, refreshCatalogue]
3892
4189
  );
3893
- const restoreProduct = useCallback8(
4190
+ const restoreProduct = useCallback9(
3894
4191
  async (id) => {
3895
4192
  const target = id ?? product?.id;
3896
4193
  if (!target) return;
@@ -3952,10 +4249,10 @@ var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, pub
3952
4249
  var useProductContext = /* @__PURE__ */ __name(() => useContext2(ProductContext) ?? defaultContextValue2, "useProductContext");
3953
4250
 
3954
4251
  // src/features/billing/stripe-price/components/details/PriceDetails.tsx
3955
- import { useTranslations as useTranslations43 } from "next-intl";
4252
+ import { useTranslations as useTranslations44 } from "next-intl";
3956
4253
  import { jsx as jsx46, jsxs as jsxs39 } from "react/jsx-runtime";
3957
4254
  function PriceDetails() {
3958
- const t = useTranslations43();
4255
+ const t = useTranslations44();
3959
4256
  const fmt = useBillingFormatOptions();
3960
4257
  const generateUrl = usePageUrlGenerator();
3961
4258
  const { price } = usePriceContext();
@@ -4072,8 +4369,8 @@ __name(PriceContainer, "PriceContainer");
4072
4369
 
4073
4370
  // src/features/billing/stripe-price/components/lists/PricesList.tsx
4074
4371
  import { DollarSignIcon, UploadIcon } from "lucide-react";
4075
- import { useTranslations as useTranslations44 } from "next-intl";
4076
- import { Fragment as Fragment11, useCallback as useCallback9, useEffect as useEffect11, useMemo as useMemo9, useRef as useRef4, useState as useState20 } from "react";
4372
+ import { useTranslations as useTranslations45 } from "next-intl";
4373
+ import { Fragment as Fragment11, useCallback as useCallback10, useEffect as useEffect12, useMemo as useMemo9, useRef as useRef4, useState as useState22 } from "react";
4077
4374
 
4078
4375
  // src/features/billing/stripe-price/data/stripe-price.transfer.ts
4079
4376
  import { z as z4 } from "zod";
@@ -4093,9 +4390,9 @@ var seedSchema = z4.object({
4093
4390
  isTrial: z4.boolean().optional(),
4094
4391
  platformFeatures: z4.array(z4.string()).optional()
4095
4392
  });
4096
- function readOptional(read) {
4393
+ function readOptional(read2) {
4097
4394
  try {
4098
- return read();
4395
+ return read2();
4099
4396
  } catch {
4100
4397
  return void 0;
4101
4398
  }
@@ -4186,28 +4483,28 @@ __name(parseStripePriceTransferDocument, "parseStripePriceTransferDocument");
4186
4483
  // src/features/billing/stripe-price/components/lists/PricesList.tsx
4187
4484
  import { Fragment as Fragment12, jsx as jsx48, jsxs as jsxs40 } from "react/jsx-runtime";
4188
4485
  function PricesList({ productId, fullWidth }) {
4189
- const t = useTranslations44();
4486
+ const t = useTranslations45();
4190
4487
  const { priceVersion } = usePriceContext();
4191
4488
  const importInputRef = useRef4(null);
4192
- const [seed, setSeed] = useState20(void 0);
4193
- const [seedNonce, setSeedNonce] = useState20(0);
4489
+ const [seed, setSeed] = useState22(void 0);
4490
+ const [seedNonce, setSeedNonce] = useState22(0);
4194
4491
  const data = useDataListRetriever({
4195
4492
  retriever: /* @__PURE__ */ __name((params) => StripePriceService.listPrices({ ...params, productId }), "retriever"),
4196
4493
  retrieverParams: {},
4197
4494
  module: Modules.StripePrice
4198
4495
  });
4199
- useEffect11(() => {
4496
+ useEffect12(() => {
4200
4497
  if (priceVersion > 0) void data.refresh();
4201
4498
  }, [priceVersion]);
4202
- const openSeededEditor = useCallback9((next) => {
4499
+ const openSeededEditor = useCallback10((next) => {
4203
4500
  setSeed(next);
4204
4501
  setSeedNonce((nonce) => nonce + 1);
4205
4502
  }, []);
4206
- const onClone = useCallback9(
4503
+ const onClone = useCallback10(
4207
4504
  (price) => openSeededEditor(priceToSeed(price)),
4208
4505
  [openSeededEditor]
4209
4506
  );
4210
- const onExport = useCallback9((price) => {
4507
+ const onExport = useCallback10((price) => {
4211
4508
  const text = JSON.stringify(buildStripePriceTransferDocument(price), null, 2);
4212
4509
  const blob = new Blob([text], { type: "application/json" });
4213
4510
  const url = URL.createObjectURL(blob);
@@ -4218,7 +4515,7 @@ function PricesList({ productId, fullWidth }) {
4218
4515
  URL.revokeObjectURL(url);
4219
4516
  }, []);
4220
4517
  const tableContext = useMemo9(() => ({ onClone, onExport }), [onClone, onExport]);
4221
- const onImportFileChange = useCallback9(
4518
+ const onImportFileChange = useCallback10(
4222
4519
  async (event) => {
4223
4520
  const input = event.target;
4224
4521
  const file = input.files?.[0];
@@ -4313,13 +4610,13 @@ function PricesList({ productId, fullWidth }) {
4313
4610
  __name(PricesList, "PricesList");
4314
4611
 
4315
4612
  // src/features/billing/stripe-product/components/containers/ProductContainer.tsx
4316
- import { useTranslations as useTranslations46 } from "next-intl";
4613
+ import { useTranslations as useTranslations47 } from "next-intl";
4317
4614
 
4318
4615
  // src/features/billing/stripe-product/components/details/ProductDetails.tsx
4319
- import { useTranslations as useTranslations45 } from "next-intl";
4616
+ import { useTranslations as useTranslations46 } from "next-intl";
4320
4617
  import { jsx as jsx49, jsxs as jsxs41 } from "react/jsx-runtime";
4321
4618
  function ProductDetails() {
4322
- const t = useTranslations45();
4619
+ const t = useTranslations46();
4323
4620
  const { product } = useProductContext();
4324
4621
  if (!product) return null;
4325
4622
  return /* @__PURE__ */ jsxs41("div", { className: "flex w-full flex-col gap-y-4", children: [
@@ -4363,7 +4660,7 @@ __name(ProductDetails, "ProductDetails");
4363
4660
  // src/features/billing/stripe-product/components/containers/ProductContainer.tsx
4364
4661
  import { jsx as jsx50 } from "react/jsx-runtime";
4365
4662
  function ProductContainer() {
4366
- const t = useTranslations46();
4663
+ const t = useTranslations47();
4367
4664
  const { product } = useProductContext();
4368
4665
  if (!product) return null;
4369
4666
  return /* @__PURE__ */ jsx50(
@@ -4394,24 +4691,72 @@ function ProductContainer() {
4394
4691
  }
4395
4692
  __name(ProductContainer, "ProductContainer");
4396
4693
 
4694
+ // src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx
4695
+ import { useTranslations as useTranslations50 } from "next-intl";
4696
+
4697
+ // src/features/billing/stripe-coupon/components/lists/CouponsList.tsx
4698
+ import { TicketPercentIcon } from "lucide-react";
4699
+ import { useTranslations as useTranslations48 } from "next-intl";
4700
+ import { useEffect as useEffect13 } from "react";
4701
+ import { jsx as jsx51 } from "react/jsx-runtime";
4702
+ function CouponsList({ fullWidth } = {}) {
4703
+ const t = useTranslations48();
4704
+ const { catalogueVersion } = useCouponContext();
4705
+ const data = useDataListRetriever({
4706
+ retriever: /* @__PURE__ */ __name((params) => StripeCouponService.listCoupons(params), "retriever"),
4707
+ retrieverParams: {},
4708
+ module: Modules.StripeCoupon
4709
+ });
4710
+ useEffect13(() => {
4711
+ if (catalogueVersion > 0) void data.refresh();
4712
+ }, [catalogueVersion]);
4713
+ const functions = [/* @__PURE__ */ jsx51(CouponEditor, {}, "create-coupon")];
4714
+ return /* @__PURE__ */ jsx51(
4715
+ ContentListTable,
4716
+ {
4717
+ data,
4718
+ fields: [
4719
+ "name" /* name */,
4720
+ "discount" /* discount */,
4721
+ "places" /* places */,
4722
+ "codes" /* codes */,
4723
+ "status" /* status */
4724
+ ],
4725
+ tableGeneratorType: Modules.StripeCoupon,
4726
+ functions,
4727
+ fullWidth,
4728
+ title: t("billing.admin.coupons.title"),
4729
+ emptyState: /* @__PURE__ */ jsx51(
4730
+ EmptyState,
4731
+ {
4732
+ icon: TicketPercentIcon,
4733
+ title: t("billing.admin.coupons.empty.title"),
4734
+ description: t("billing.admin.coupons.empty.description")
4735
+ }
4736
+ )
4737
+ }
4738
+ );
4739
+ }
4740
+ __name(CouponsList, "CouponsList");
4741
+
4397
4742
  // src/features/billing/stripe-product/components/lists/ProductsList.tsx
4398
4743
  import { PackageIcon } from "lucide-react";
4399
- import { useTranslations as useTranslations47 } from "next-intl";
4400
- import { useEffect as useEffect12 } from "react";
4401
- import { jsx as jsx51 } from "react/jsx-runtime";
4744
+ import { useTranslations as useTranslations49 } from "next-intl";
4745
+ import { useEffect as useEffect14 } from "react";
4746
+ import { jsx as jsx52 } from "react/jsx-runtime";
4402
4747
  function ProductsList({ fullWidth } = {}) {
4403
- const t = useTranslations47();
4748
+ const t = useTranslations49();
4404
4749
  const { catalogueVersion } = useProductContext();
4405
4750
  const data = useDataListRetriever({
4406
4751
  retriever: /* @__PURE__ */ __name((params) => StripeProductService.listProducts(params), "retriever"),
4407
4752
  retrieverParams: {},
4408
4753
  module: Modules.StripeProduct
4409
4754
  });
4410
- useEffect12(() => {
4755
+ useEffect14(() => {
4411
4756
  if (catalogueVersion > 0) void data.refresh();
4412
4757
  }, [catalogueVersion]);
4413
- const functions = [/* @__PURE__ */ jsx51(ProductEditor, {}, "create-product")];
4414
- return /* @__PURE__ */ jsx51(
4758
+ const functions = [/* @__PURE__ */ jsx52(ProductEditor, {}, "create-product")];
4759
+ return /* @__PURE__ */ jsx52(
4415
4760
  ContentListTable,
4416
4761
  {
4417
4762
  data,
@@ -4420,7 +4765,7 @@ function ProductsList({ fullWidth } = {}) {
4420
4765
  functions,
4421
4766
  fullWidth,
4422
4767
  title: t("billing.admin.products.title"),
4423
- emptyState: /* @__PURE__ */ jsx51(
4768
+ emptyState: /* @__PURE__ */ jsx52(
4424
4769
  EmptyState,
4425
4770
  {
4426
4771
  icon: PackageIcon,
@@ -4434,17 +4779,236 @@ function ProductsList({ fullWidth } = {}) {
4434
4779
  __name(ProductsList, "ProductsList");
4435
4780
 
4436
4781
  // src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx
4437
- import { jsx as jsx52 } from "react/jsx-runtime";
4782
+ import { jsx as jsx53 } from "react/jsx-runtime";
4438
4783
  function ProductsListContainer() {
4439
- return /* @__PURE__ */ jsx52(ProductProvider, { children: /* @__PURE__ */ jsx52(RoundPageContainer, { module: Modules.StripeProduct, fullWidth: true, children: /* @__PURE__ */ jsx52(ProductsList, { fullWidth: true }) }) });
4784
+ const t = useTranslations50();
4785
+ return /* @__PURE__ */ jsx53(ProductProvider, { children: /* @__PURE__ */ jsx53(CouponProvider, { publishChrome: false, children: /* @__PURE__ */ jsx53(
4786
+ RoundPageContainer,
4787
+ {
4788
+ module: Modules.StripeProduct,
4789
+ tabs: [
4790
+ { sectionKey: "products", label: t("billing.admin.products.tabs.products"), content: /* @__PURE__ */ jsx53(ProductsList, {}) },
4791
+ { sectionKey: "codes", label: t("billing.admin.products.tabs.codes"), content: /* @__PURE__ */ jsx53(CouponsList, {}) }
4792
+ ]
4793
+ }
4794
+ ) }) });
4440
4795
  }
4441
4796
  __name(ProductsListContainer, "ProductsListContainer");
4797
+
4798
+ // src/features/billing/stripe-coupon/components/containers/CouponContainer.tsx
4799
+ import { useTranslations as useTranslations54 } from "next-intl";
4800
+
4801
+ // src/features/billing/stripe-coupon/components/details/CouponDetails.tsx
4802
+ import { useTranslations as useTranslations51 } from "next-intl";
4803
+ import { jsx as jsx54, jsxs as jsxs42 } from "react/jsx-runtime";
4804
+ function CouponDetails() {
4805
+ const t = useTranslations51();
4806
+ const fmt = useBillingFormatOptions();
4807
+ const { coupon } = useCouponContext();
4808
+ if (!coupon) return null;
4809
+ return /* @__PURE__ */ jsxs42("div", { className: "flex w-full flex-col gap-y-4", children: [
4810
+ /* @__PURE__ */ jsx54(SectionHeader, { children: t("billing.admin.products.tabs.details") }),
4811
+ /* @__PURE__ */ jsx54("p", { className: "text-muted-foreground text-xs/relaxed", children: t("billing.admin.coupons.fixed_notice") }),
4812
+ /* @__PURE__ */ jsxs42("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
4813
+ /* @__PURE__ */ jsx54(DetailField, { label: t("billing.admin.coupons.fields.name"), value: coupon.name }),
4814
+ /* @__PURE__ */ jsx54(
4815
+ DetailField,
4816
+ {
4817
+ label: t("billing.admin.coupons.fields.status"),
4818
+ value: coupon.valid ? /* @__PURE__ */ jsx54(Badge, { variant: "softGreen", children: t("billing.admin.coupons.status.active") }) : /* @__PURE__ */ jsx54(Badge, { variant: "softGray", children: t("billing.admin.coupons.status.ended") })
4819
+ }
4820
+ ),
4821
+ /* @__PURE__ */ jsx54(
4822
+ DetailField,
4823
+ {
4824
+ label: t("billing.admin.coupons.fields.discount"),
4825
+ value: /* @__PURE__ */ jsx54("span", { className: "tabular-nums", children: formatCouponDiscount(coupon, t, fmt) })
4826
+ }
4827
+ ),
4828
+ /* @__PURE__ */ jsx54(
4829
+ DetailField,
4830
+ {
4831
+ label: t("billing.admin.coupons.fields.places"),
4832
+ value: /* @__PURE__ */ jsx54("span", { className: "tabular-nums", children: coupon.maxRedemptions !== void 0 ? t("billing.admin.coupons.places.limited", {
4833
+ used: String(coupon.timesRedeemed),
4834
+ limit: String(coupon.maxRedemptions)
4835
+ }) : t("billing.admin.coupons.places.unlimited", { used: String(coupon.timesRedeemed) }) })
4836
+ }
4837
+ ),
4838
+ /* @__PURE__ */ jsx54(
4839
+ DetailField,
4840
+ {
4841
+ label: t("billing.admin.coupons.fields.products"),
4842
+ value: coupon.stripeProductIds.length > 0 ? /* @__PURE__ */ jsx54("span", { className: "tabular-nums", children: coupon.stripeProductIds.join(", ") }) : t("billing.admin.coupons.all_products")
4843
+ }
4844
+ ),
4845
+ /* @__PURE__ */ jsx54(
4846
+ DetailField,
4847
+ {
4848
+ label: t("billing.admin.coupons.fields.redeemBy"),
4849
+ value: /* @__PURE__ */ jsx54("span", { className: "tabular-nums", children: coupon.redeemBy ? formatDate(coupon.redeemBy, fmt) : "\u2014" })
4850
+ }
4851
+ ),
4852
+ /* @__PURE__ */ jsx54(
4853
+ DetailField,
4854
+ {
4855
+ label: t("billing.admin.coupons.fields.stripeId"),
4856
+ value: /* @__PURE__ */ jsx54("span", { className: "tabular-nums", children: coupon.stripeCouponId })
4857
+ }
4858
+ )
4859
+ ] })
4860
+ ] });
4861
+ }
4862
+ __name(CouponDetails, "CouponDetails");
4863
+
4864
+ // src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx
4865
+ import { TicketIcon } from "lucide-react";
4866
+ import { useTranslations as useTranslations53 } from "next-intl";
4867
+ import { useEffect as useEffect15 } from "react";
4868
+
4869
+ // src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx
4870
+ import { zodResolver as zodResolver4 } from "@hookform/resolvers/zod";
4871
+ import { useTranslations as useTranslations52 } from "next-intl";
4872
+ import { useCallback as useCallback11, useMemo as useMemo10 } from "react";
4873
+ import { useForm as useForm4 } from "react-hook-form";
4874
+ import { v4 as v44 } from "uuid";
4875
+ import { z as z5 } from "zod";
4876
+ import { jsx as jsx55 } from "react/jsx-runtime";
4877
+ function CouponCodeEditorInternal({
4878
+ couponId,
4879
+ onSuccess,
4880
+ trigger,
4881
+ forceShow,
4882
+ onClose,
4883
+ dialogOpen,
4884
+ onDialogOpenChange
4885
+ }) {
4886
+ const t = useTranslations52();
4887
+ const router = useI18nRouter();
4888
+ const { createCode } = useCouponContext();
4889
+ const formSchema2 = useMemo10(() => z5.object({ id: z5.uuidv4() }).and(couponCodeSchema(t)), [t]);
4890
+ const getDefaultValues = useCallback11(() => ({ id: v44(), ...couponCodeDefaults() }), []);
4891
+ const form = useForm4({
4892
+ resolver: zodResolver4(formSchema2),
4893
+ defaultValues: getDefaultValues()
4894
+ });
4895
+ return /* @__PURE__ */ jsx55(
4896
+ EditorSheet,
4897
+ {
4898
+ form,
4899
+ entityType: t("billing.admin.coupons.codes.entity"),
4900
+ isEdit: false,
4901
+ module: Modules.StripeCouponCode,
4902
+ size: "md",
4903
+ onSuccess: onSuccess ?? (() => {
4904
+ }),
4905
+ onNavigate: (url) => router.push(url),
4906
+ onSubmit: async (values) => await createCode({ id: values.id, couponId, ...toCouponCodeInput(values) }),
4907
+ onReset: getDefaultValues,
4908
+ trigger,
4909
+ forceShow,
4910
+ onClose,
4911
+ dialogOpen,
4912
+ onDialogOpenChange,
4913
+ children: /* @__PURE__ */ jsx55(CouponCodeFields, { form })
4914
+ }
4915
+ );
4916
+ }
4917
+ __name(CouponCodeEditorInternal, "CouponCodeEditorInternal");
4918
+ function CouponCodeEditor(props) {
4919
+ return /* @__PURE__ */ jsx55(CouponCodeEditorInternal, { ...props });
4920
+ }
4921
+ __name(CouponCodeEditor, "CouponCodeEditor");
4922
+
4923
+ // src/features/billing/stripe-coupon/components/lists/CouponCodesList.tsx
4924
+ import { jsx as jsx56 } from "react/jsx-runtime";
4925
+ function CouponCodesList({ couponId }) {
4926
+ const t = useTranslations53();
4927
+ const { catalogueVersion } = useCouponContext();
4928
+ const data = useDataListRetriever({
4929
+ retriever: /* @__PURE__ */ __name((params) => StripeCouponCodeService.listByCoupon({ ...params, couponId }), "retriever"),
4930
+ retrieverParams: {},
4931
+ module: Modules.StripeCouponCode
4932
+ });
4933
+ useEffect15(() => {
4934
+ if (catalogueVersion > 0) void data.refresh();
4935
+ }, [catalogueVersion]);
4936
+ const functions = [/* @__PURE__ */ jsx56(CouponCodeEditor, { couponId }, "create-code")];
4937
+ return /* @__PURE__ */ jsx56(
4938
+ ContentListTable,
4939
+ {
4940
+ data,
4941
+ fields: [
4942
+ "code" /* code */,
4943
+ "places" /* places */,
4944
+ "expiresAt" /* expiresAt */,
4945
+ "automatic" /* automatic */,
4946
+ "status" /* status */,
4947
+ "actions" /* actions */
4948
+ ],
4949
+ tableGeneratorType: Modules.StripeCouponCode,
4950
+ functions,
4951
+ title: t("billing.admin.coupons.codes.title"),
4952
+ emptyState: /* @__PURE__ */ jsx56(
4953
+ EmptyState,
4954
+ {
4955
+ icon: TicketIcon,
4956
+ title: t("billing.admin.coupons.codes.empty.title"),
4957
+ description: t("billing.admin.coupons.codes.empty.description")
4958
+ }
4959
+ )
4960
+ }
4961
+ );
4962
+ }
4963
+ __name(CouponCodesList, "CouponCodesList");
4964
+
4965
+ // src/features/billing/stripe-coupon/components/containers/CouponContainer.tsx
4966
+ import { jsx as jsx57 } from "react/jsx-runtime";
4967
+ function CouponContainer() {
4968
+ const t = useTranslations54();
4969
+ const { coupon } = useCouponContext();
4970
+ if (!coupon) return null;
4971
+ return /* @__PURE__ */ jsx57(
4972
+ RoundPageContainer,
4973
+ {
4974
+ module: Modules.StripeCoupon,
4975
+ id: coupon.id,
4976
+ tabs: [
4977
+ {
4978
+ sectionKey: "details",
4979
+ label: t("billing.admin.products.tabs.details"),
4980
+ content: /* @__PURE__ */ jsx57(CouponDetails, {})
4981
+ },
4982
+ {
4983
+ sectionKey: "codes",
4984
+ label: t("billing.admin.coupons.codes.title"),
4985
+ // No `fillHeight` and no `fullWidth` on the list: a ContentListTable in
4986
+ // a tab keeps its own bordered card and flows with the panel, exactly
4987
+ // as PricesList does inside ProductContainer. The codes list reads the
4988
+ // page's own CouponProvider, so no nested provider is needed.
4989
+ content: /* @__PURE__ */ jsx57(CouponCodesList, { couponId: coupon.id })
4990
+ }
4991
+ ]
4992
+ }
4993
+ );
4994
+ }
4995
+ __name(CouponContainer, "CouponContainer");
4442
4996
  export {
4443
4997
  BillingAlertBanner,
4444
4998
  BillingDashboardContainer,
4445
4999
  BillingDetailModal,
4446
5000
  BillingUsageSummaryCard,
4447
5001
  CancelSubscriptionDialog,
5002
+ CouponCodeActions,
5003
+ CouponCodeEditor,
5004
+ CouponCodeFields,
5005
+ CouponCodesList,
5006
+ CouponContainer,
5007
+ CouponDetails,
5008
+ CouponEditor,
5009
+ CouponProvider,
5010
+ CouponRefresher,
5011
+ CouponsList,
4448
5012
  CustomerInfoCard,
4449
5013
  IntervalToggle,
4450
5014
  InvoiceDetails,
@@ -4492,13 +5056,26 @@ export {
4492
5056
  WizardStepPaymentMethod,
4493
5057
  WizardStepPlanSelection,
4494
5058
  WizardStepReview,
5059
+ applyDiscount,
5060
+ couponCodeDefaults,
5061
+ couponCodeSchema,
5062
+ couponCurrencyOptions,
4495
5063
  formatCurrency,
4496
5064
  formatDate,
4497
5065
  formatFixed,
4498
5066
  formatInterval,
4499
5067
  isStripeConfigured,
5068
+ optionalDate,
5069
+ optionalDecimal,
5070
+ optionalPositiveInt,
5071
+ parseOptionalDecimal,
5072
+ parseOptionalPositiveInt,
4500
5073
  priceLabel,
5074
+ promotionCodeDiscount,
5075
+ subscriptionDiscount,
5076
+ toCouponCodeInput,
4501
5077
  useBillingFormatOptions,
5078
+ useCouponContext,
4502
5079
  usePriceContext,
4503
5080
  useProductContext
4504
5081
  };