@carlonicora/nextjs-jsonapi 4.1.2 → 4.2.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 (229) hide show
  1. package/dist/{AssistantMessageInterface-CubxuBIJ.d.ts → AssistantMessageInterface-DHJJO_Z_.d.ts} +1 -1
  2. package/dist/{AssistantMessageInterface-sHjI2zPN.d.mts → AssistantMessageInterface-S7oyERTT.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-VOYKZOP5.mjs → BlockNoteEditor-HXAHDJA7.mjs} +34 -27
  4. package/dist/BlockNoteEditor-HXAHDJA7.mjs.map +1 -0
  5. package/dist/{BlockNoteEditor-BENYTTCZ.js → BlockNoteEditor-LLCZDFLZ.js} +50 -43
  6. package/dist/BlockNoteEditor-LLCZDFLZ.js.map +1 -0
  7. package/dist/{BlockNoteViewer-USLR4Y7C.js → BlockNoteViewer-77CCTC2U.js} +3 -3
  8. package/dist/{BlockNoteViewer-USLR4Y7C.js.map → BlockNoteViewer-77CCTC2U.js.map} +1 -1
  9. package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs → BlockNoteViewer-MDPO6BXN.mjs} +2 -2
  10. package/dist/{auth.interface-Df4vXbNz.d.mts → auth.interface-CeFCz9NU.d.mts} +1 -1
  11. package/dist/{auth.interface-BzI4onI_.d.ts → auth.interface-D1R7VVky.d.ts} +1 -1
  12. package/dist/billing/index.d.mts +40 -6
  13. package/dist/billing/index.d.ts +40 -6
  14. package/dist/billing/index.js +806 -774
  15. package/dist/billing/index.js.map +1 -1
  16. package/dist/billing/index.mjs +494 -462
  17. package/dist/billing/index.mjs.map +1 -1
  18. package/dist/{chunk-6ISF5KNQ.mjs → chunk-2VXDZRYY.mjs} +1103 -816
  19. package/dist/chunk-2VXDZRYY.mjs.map +1 -0
  20. package/dist/{chunk-4BZJ4LAB.js → chunk-4ORLPEK7.js} +136 -104
  21. package/dist/chunk-4ORLPEK7.js.map +1 -0
  22. package/dist/{chunk-ZZ6E33FO.mjs → chunk-6DRSSW5O.mjs} +36 -4
  23. package/dist/chunk-6DRSSW5O.mjs.map +1 -0
  24. package/dist/{chunk-KS6WZP2E.js → chunk-6SMJRAE3.js} +4 -4
  25. package/dist/{chunk-KS6WZP2E.js.map → chunk-6SMJRAE3.js.map} +1 -1
  26. package/dist/{chunk-WQ77CUUZ.js → chunk-GPXYQMOZ.js} +7 -7
  27. package/dist/{chunk-WQ77CUUZ.js.map → chunk-GPXYQMOZ.js.map} +1 -1
  28. package/dist/{chunk-46S3KPLU.mjs → chunk-ILFFC6BG.mjs} +2 -2
  29. package/dist/{chunk-HZVVRIJQ.mjs → chunk-IUS2RHCX.mjs} +2 -2
  30. package/dist/{chunk-F7RNBGHS.js → chunk-KJMMBUOA.js} +3 -3
  31. package/dist/{chunk-F7RNBGHS.js.map → chunk-KJMMBUOA.js.map} +1 -1
  32. package/dist/{chunk-ZO3QQ5CQ.mjs → chunk-MQDR6MEE.mjs} +2 -2
  33. package/dist/{chunk-ORU7KLCY.js → chunk-VDVIUDY5.js} +1445 -1158
  34. package/dist/chunk-VDVIUDY5.js.map +1 -0
  35. package/dist/client/index.d.mts +20 -6
  36. package/dist/client/index.d.ts +20 -6
  37. package/dist/client/index.js +6 -4
  38. package/dist/client/index.js.map +1 -1
  39. package/dist/client/index.mjs +5 -3
  40. package/dist/components/index.d.mts +59 -21
  41. package/dist/components/index.d.ts +59 -21
  42. package/dist/components/index.js +4 -4
  43. package/dist/components/index.mjs +3 -3
  44. package/dist/{config-CEAq4Ozy.d.mts → config-BP2vyddw.d.mts} +5 -0
  45. package/dist/{config-DydDHCfb.d.ts → config-BhW2QrRD.d.ts} +5 -0
  46. package/dist/contexts/index.d.mts +4 -4
  47. package/dist/contexts/index.d.ts +4 -4
  48. package/dist/contexts/index.js +4 -4
  49. package/dist/contexts/index.mjs +3 -3
  50. package/dist/core/index.d.mts +17 -9
  51. package/dist/core/index.d.ts +17 -9
  52. package/dist/core/index.js +4 -2
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/index.mjs +3 -1
  55. package/dist/features/analytics/index.d.mts +1 -1
  56. package/dist/features/analytics/index.d.ts +1 -1
  57. package/dist/features/analytics/index.js +122 -122
  58. package/dist/features/analytics/index.mjs +4 -4
  59. package/dist/features/analytics/server.js +4 -4
  60. package/dist/features/analytics/server.mjs +2 -2
  61. package/dist/features/help/index.d.mts +1 -1
  62. package/dist/features/help/index.d.ts +1 -1
  63. package/dist/features/help/index.js +42 -40
  64. package/dist/features/help/index.js.map +1 -1
  65. package/dist/features/help/index.mjs +8 -6
  66. package/dist/features/help/index.mjs.map +1 -1
  67. package/dist/features/tokenusage/index.js +125 -125
  68. package/dist/features/tokenusage/index.mjs +4 -4
  69. package/dist/index.d.mts +7 -7
  70. package/dist/index.d.ts +7 -7
  71. package/dist/index.js +5 -3
  72. package/dist/index.js.map +1 -1
  73. package/dist/index.mjs +4 -2
  74. package/dist/{notification.interface-P_pNfzZQ.d.mts → notification.interface-BB6GPpZY.d.mts} +1 -1
  75. package/dist/{notification.interface-BNCvnVCW.d.ts → notification.interface-CmAGrijJ.d.ts} +1 -1
  76. package/dist/{s3.service-CbUa6a-8.d.ts → s3.service-C-4YpEMu.d.ts} +7 -3
  77. package/dist/{s3.service-DAp8FbuG.d.mts → s3.service-dVj-nxMl.d.mts} +7 -3
  78. package/dist/server/index.d.mts +4 -4
  79. package/dist/server/index.d.ts +4 -4
  80. package/dist/server/index.js +3 -3
  81. package/dist/server/index.mjs +2 -2
  82. package/dist/{useSocket-BHVIyigI.d.ts → useSocket-B0H9pmmH.d.ts} +1 -1
  83. package/dist/{useSocket-B9gTbuCj.d.mts → useSocket-B5RQb6v2.d.mts} +1 -1
  84. package/dist/{user.interface-CrmwJLYW.d.ts → user.interface-C0ZdsBZj.d.ts} +2 -0
  85. package/dist/{user.interface-CSaGWFbg.d.mts → user.interface-CIWGlbqM.d.mts} +2 -0
  86. package/package.json +1 -1
  87. package/src/components/containers/RoundPageContainer.tsx +3 -1
  88. package/src/components/containers/RoundPageContainerTitle.tsx +5 -2
  89. package/src/components/editors/BlockNoteEditor.tsx +31 -15
  90. package/src/components/editors/BlockNoteEditorAiActions.tsx +11 -7
  91. package/src/components/editors/__tests__/BlockNoteEditorAiActions.test.ts +10 -3
  92. package/src/components/errors/ErrorDetails.tsx +5 -2
  93. package/src/components/forms/CommonAssociationForm.tsx +1 -1
  94. package/src/components/forms/EntityMultiSelector.tsx +6 -2
  95. package/src/components/forms/EntitySelector.tsx +4 -1
  96. package/src/components/forms/FileUploader.tsx +3 -2
  97. package/src/components/forms/MultiFileUploader.tsx +3 -1
  98. package/src/components/navigations/Breadcrumb.tsx +3 -2
  99. package/src/components/navigations/Header.tsx +3 -1
  100. package/src/components/navigations/MobileNavigationBar.tsx +3 -1
  101. package/src/components/navigations/__tests__/Header.spec.tsx +2 -2
  102. package/src/components/tables/cells/cell.id.tsx +13 -2
  103. package/src/core/abstracts/AbstractService.ts +3 -3
  104. package/src/core/abstracts/ClientAbstractService.ts +3 -2
  105. package/src/features/assistant-action/components/ApprovalActionCard.tsx +74 -5
  106. package/src/features/assistant-action/components/__tests__/ApprovalActionCard.spec.tsx +81 -0
  107. package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +4 -1
  108. package/src/features/assistant-message/components/parts/tabs/UsersTab.tsx +2 -1
  109. package/src/features/auth/components/buttons/GoogleSignInButton.tsx +4 -1
  110. package/src/features/auth/components/details/LandingComponent.tsx +1 -1
  111. package/src/features/auth/components/forms/ForgotPassword.tsx +1 -1
  112. package/src/features/auth/components/forms/Login.tsx +1 -1
  113. package/src/features/auth/components/forms/Register.tsx +1 -1
  114. package/src/features/auth/components/forms/ResetPassword.tsx +2 -2
  115. package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +1 -1
  116. package/src/features/auth/components/two-factor/PasskeyButton.tsx +1 -1
  117. package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +21 -11
  118. package/src/features/billing/components/cards/CustomerInfoCard.tsx +16 -13
  119. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +33 -21
  120. package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +20 -10
  121. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +35 -22
  122. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +24 -24
  123. package/src/features/billing/components/utils/__tests__/billing-formatters.spec.ts +63 -0
  124. package/src/features/billing/components/utils/currency.ts +67 -10
  125. package/src/features/billing/components/utils/date.ts +9 -4
  126. package/src/features/billing/components/utils/format-options.ts +20 -0
  127. package/src/features/billing/components/utils/index.ts +3 -1
  128. package/src/features/billing/components/utils/useBillingFormatOptions.ts +11 -0
  129. package/src/features/billing/components/widgets/BillingAlertBanner.tsx +8 -9
  130. package/src/features/billing/contexts/PriceContext.tsx +18 -6
  131. package/src/features/billing/i18n-keys.ts +212 -0
  132. package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +8 -8
  133. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +11 -9
  134. package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +4 -4
  135. package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +9 -7
  136. package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +10 -8
  137. package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +34 -29
  138. package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +12 -9
  139. package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +9 -6
  140. package/src/features/billing/stripe-price/components/details/PriceDetails.tsx +4 -3
  141. package/src/features/billing/stripe-price/components/forms/PriceArchiver.tsx +3 -1
  142. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +3 -1
  143. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +6 -5
  144. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +12 -7
  145. package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.service.ts +3 -1
  146. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +10 -6
  147. package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +44 -29
  148. package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +14 -11
  149. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +20 -17
  150. package/src/features/billing/stripe-subscription/components/widgets/IntervalToggle.tsx +4 -2
  151. package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +16 -7
  152. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingList.tsx +3 -1
  153. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingRow.tsx +3 -1
  154. package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +12 -7
  155. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +11 -8
  156. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +13 -10
  157. package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +8 -6
  158. package/src/features/billing/stripe-subscription/components/wizards/WizardProgressIndicator.tsx +7 -4
  159. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +4 -2
  160. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPlanSelection.tsx +3 -1
  161. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +37 -28
  162. package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +9 -7
  163. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +20 -9
  164. package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +6 -6
  165. package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +24 -9
  166. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +16 -12
  167. package/src/features/company/components/details/TokenStatusIndicator.tsx +7 -5
  168. package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
  169. package/src/features/help/components/HelpArticleBody.tsx +1 -1
  170. package/src/features/help/components/HelpHeader.tsx +1 -1
  171. package/src/features/help/components/HelpSideNav.tsx +1 -1
  172. package/src/features/help/i18n-keys.ts +2 -0
  173. package/src/features/notification/components/common/NotificationErrorBoundary.tsx +17 -12
  174. package/src/features/notification/components/containers/NotificationsListContainer.tsx +4 -2
  175. package/src/features/notification/components/notifications/Notification.tsx +6 -3
  176. package/src/features/notification/contexts/NotificationContext.tsx +9 -4
  177. package/src/features/notification/contexts/__tests__/NotificationContext.spec.tsx +13 -0
  178. package/src/features/notification/hooks/useNotificationTableStructure.tsx +4 -2
  179. package/src/features/oauth/components/OAuthClientCard.tsx +13 -7
  180. package/src/features/oauth/components/OAuthClientDetail.tsx +34 -30
  181. package/src/features/oauth/components/OAuthClientForm.tsx +24 -24
  182. package/src/features/oauth/components/OAuthClientList.tsx +10 -6
  183. package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +10 -8
  184. package/src/features/oauth/components/OAuthRedirectUriInput.tsx +9 -13
  185. package/src/features/oauth/components/OAuthScopeSelector.tsx +15 -6
  186. package/src/features/oauth/components/__tests__/OAuthClientForm.spec.tsx +11 -9
  187. package/src/features/oauth/components/consent/OAuthScopeList.tsx +4 -2
  188. package/src/features/oauth/hooks/__tests__/useOAuthScopeText.spec.ts +30 -0
  189. package/src/features/oauth/hooks/index.ts +1 -0
  190. package/src/features/oauth/hooks/useOAuthClient.ts +11 -5
  191. package/src/features/oauth/hooks/useOAuthClients.ts +9 -3
  192. package/src/features/oauth/hooks/useOAuthConsent.ts +11 -5
  193. package/src/features/oauth/hooks/useOAuthScopeText.ts +33 -0
  194. package/src/features/rbac/components/RbacByRoleContainer.tsx +7 -6
  195. package/src/features/rbac/components/RbacContainer.tsx +7 -6
  196. package/src/features/rbac/contexts/RbacContext.tsx +3 -3
  197. package/src/features/user/components/containers/UserContainer.tsx +3 -1
  198. package/src/features/user/data/__tests__/user.test.ts +66 -0
  199. package/src/features/user/data/user.interface.ts +2 -0
  200. package/src/features/user/data/user.service.ts +9 -0
  201. package/src/features/user/data/user.ts +7 -0
  202. package/src/features/waitlist/components/forms/WaitlistForm.tsx +1 -1
  203. package/src/features/waitlist/components/lists/WaitlistList.tsx +1 -1
  204. package/src/i18n/__tests__/config.test.ts +21 -1
  205. package/src/i18n/config.ts +13 -0
  206. package/src/i18n/index.ts +1 -0
  207. package/src/shadcnui/custom/multi-select.tsx +47 -14
  208. package/src/shadcnui/custom/multiple-selector.tsx +14 -2
  209. package/src/shadcnui/ui/__tests__/use-ui-text.test.tsx +76 -0
  210. package/src/shadcnui/ui/breadcrumb.tsx +9 -2
  211. package/src/shadcnui/ui/carousel.tsx +15 -4
  212. package/src/shadcnui/ui/dialog.tsx +15 -2
  213. package/src/shadcnui/ui/sheet.tsx +6 -1
  214. package/src/shadcnui/ui/sidebar.tsx +33 -7
  215. package/src/shadcnui/ui/spinner.tsx +6 -1
  216. package/src/shadcnui/ui/use-ui-text.ts +49 -0
  217. package/src/utils/__tests__/date-formatter.test.ts +24 -1
  218. package/src/utils/date-formatter.ts +16 -0
  219. package/src/utils/index.ts +1 -1
  220. package/dist/BlockNoteEditor-BENYTTCZ.js.map +0 -1
  221. package/dist/BlockNoteEditor-VOYKZOP5.mjs.map +0 -1
  222. package/dist/chunk-4BZJ4LAB.js.map +0 -1
  223. package/dist/chunk-6ISF5KNQ.mjs.map +0 -1
  224. package/dist/chunk-ORU7KLCY.js.map +0 -1
  225. package/dist/chunk-ZZ6E33FO.mjs.map +0 -1
  226. /package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs.map → BlockNoteViewer-MDPO6BXN.mjs.map} +0 -0
  227. /package/dist/{chunk-46S3KPLU.mjs.map → chunk-ILFFC6BG.mjs.map} +0 -0
  228. /package/dist/{chunk-HZVVRIJQ.mjs.map → chunk-IUS2RHCX.mjs.map} +0 -0
  229. /package/dist/{chunk-ZO3QQ5CQ.mjs.map → chunk-MQDR6MEE.mjs.map} +0 -0
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Download, ExternalLink, RefreshCw } from "lucide-react";
4
5
  import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../../../../../shadcnui";
5
- import { formatCurrency, formatDate } from "../../../components/utils";
6
+ import { formatCurrency, formatDate, useBillingFormatOptions } from "../../../components/utils";
6
7
  import { InvoiceStatus, StripeInvoiceInterface } from "../../data/stripe-invoice.interface";
7
8
  import { InvoiceStatusBadge } from "../widgets/InvoiceStatusBadge";
8
9
 
@@ -19,6 +20,8 @@ export function InvoiceDetails({
19
20
  onOpenChange,
20
21
  onInvoiceChange: _onInvoiceChange,
21
22
  }: InvoiceDetailsProps) {
23
+ const t = useTranslations();
24
+ const fmt = useBillingFormatOptions();
22
25
  const handleDownloadPDF = () => {
23
26
  if (invoice.stripePdfUrl) {
24
27
  window.open(invoice.stripePdfUrl, "_blank");
@@ -42,67 +45,67 @@ export function InvoiceDetails({
42
45
  return invoice.stripeInvoiceId.slice(-8);
43
46
  };
44
47
 
45
- const productName = invoice.subscription?.price?.product?.name || "Subscription";
48
+ const productName = invoice.subscription?.price?.product?.name || t("billing.ui.cards.subscriptions.fallback_name");
46
49
 
47
50
  return (
48
51
  <Dialog open={open} onOpenChange={onOpenChange}>
49
52
  <DialogContent className="max-w-2xl">
50
53
  <DialogHeader>
51
- <DialogTitle>Invoice {getInvoiceNumber()}</DialogTitle>
52
- <DialogDescription>{formatDate(invoice.periodStart)}</DialogDescription>
54
+ <DialogTitle>{t("billing.ui.invoice.title", { number: getInvoiceNumber() })}</DialogTitle>
55
+ <DialogDescription>{formatDate(invoice.periodStart, fmt)}</DialogDescription>
53
56
  </DialogHeader>
54
57
 
55
58
  <div className="space-y-6">
56
59
  {/* Status */}
57
60
  <div className="flex items-center gap-x-3">
58
- <span className="text-sm font-medium text-muted-foreground">Status:</span>
61
+ <span className="text-sm font-medium text-muted-foreground">{t("billing.ui.labels.status")}</span>
59
62
  <InvoiceStatusBadge status={invoice.status} />
60
63
  </div>
61
64
 
62
65
  {/* Invoice Info Grid */}
63
66
  <div className="grid grid-cols-2 gap-4">
64
67
  <div>
65
- <span className="text-muted-foreground text-xs">Billing Period:</span>
68
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.billing_period")}</span>
66
69
  <p className="text-sm">
67
- {formatDate(invoice.periodStart)} - {formatDate(invoice.periodEnd)}
70
+ {formatDate(invoice.periodStart, fmt)} - {formatDate(invoice.periodEnd, fmt)}
68
71
  </p>
69
72
  </div>
70
73
 
71
74
  {invoice.dueDate && (
72
75
  <div>
73
- <span className="text-muted-foreground text-xs">Due Date:</span>
74
- <p className="text-sm">{formatDate(invoice.dueDate)}</p>
76
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.due_date")}</span>
77
+ <p className="text-sm">{formatDate(invoice.dueDate, fmt)}</p>
75
78
  </div>
76
79
  )}
77
80
 
78
81
  {invoice.paidAt && (
79
82
  <div>
80
- <span className="text-muted-foreground text-xs">Paid Date:</span>
81
- <p className="text-sm">{formatDate(invoice.paidAt)}</p>
83
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.paid_date")}</span>
84
+ <p className="text-sm">{formatDate(invoice.paidAt, fmt)}</p>
82
85
  </div>
83
86
  )}
84
87
 
85
88
  <div>
86
- <span className="text-muted-foreground text-xs">Attempt Count:</span>
89
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.attempt_count")}</span>
87
90
  <p className="text-sm">{invoice.attemptCount}</p>
88
91
  </div>
89
92
  </div>
90
93
 
91
94
  {/* Line Items */}
92
95
  <div>
93
- <h4 className="text-sm font-medium text-muted-foreground mb-2">Line Items</h4>
96
+ <h4 className="text-sm font-medium text-muted-foreground mb-2">{t("billing.ui.invoice.line_items")}</h4>
94
97
  <div className="border rounded-lg overflow-clip">
95
98
  <table className="w-full">
96
99
  <thead className="bg-muted">
97
100
  <tr>
98
- <th className="text-start p-3 text-sm font-medium">Description</th>
99
- <th className="text-end p-3 text-sm font-medium">Amount</th>
101
+ <th className="text-start p-3 text-sm font-medium">{t("billing.ui.columns.description")}</th>
102
+ <th className="text-end p-3 text-sm font-medium">{t("billing.ui.columns.amount")}</th>
100
103
  </tr>
101
104
  </thead>
102
105
  <tbody>
103
106
  <tr className="border-t">
104
107
  <td className="p-3">{productName}</td>
105
- <td className="p-3 text-end">{formatCurrency(invoice.subtotal, invoice.currency)}</td>
108
+ <td className="p-3 text-end">{formatCurrency(invoice.subtotal, invoice.currency, fmt)}</td>
106
109
  </tr>
107
110
  </tbody>
108
111
  </table>
@@ -112,34 +115,36 @@ export function InvoiceDetails({
112
115
  {/* Amount Breakdown */}
113
116
  <div className="space-y-2 border-t pt-4">
114
117
  <div className="flex justify-between">
115
- <span className="text-muted-foreground text-xs">Subtotal:</span>
116
- <span className="text-sm">{formatCurrency(invoice.subtotal, invoice.currency)}</span>
118
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.subtotal")}</span>
119
+ <span className="text-sm">{formatCurrency(invoice.subtotal, invoice.currency, fmt)}</span>
117
120
  </div>
118
121
 
119
122
  {invoice.tax !== undefined && invoice.tax > 0 && (
120
123
  <div className="flex justify-between">
121
- <span className="text-muted-foreground text-xs">Tax:</span>
122
- <span className="text-sm">{formatCurrency(invoice.tax, invoice.currency)}</span>
124
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.tax")}</span>
125
+ <span className="text-sm">{formatCurrency(invoice.tax, invoice.currency, fmt)}</span>
123
126
  </div>
124
127
  )}
125
128
 
126
129
  <div className="flex justify-between text-lg font-semibold border-t pt-2">
127
- <span>Total:</span>
128
- <span>{formatCurrency(invoice.total, invoice.currency)}</span>
130
+ <span>{t("billing.ui.invoice.total")}</span>
131
+ <span>{formatCurrency(invoice.total, invoice.currency, fmt)}</span>
129
132
  </div>
130
133
 
131
134
  {invoice.amountPaid > 0 && (
132
135
  <div className="flex justify-between">
133
- <span className="text-muted-foreground text-xs">Amount Paid:</span>
134
- <span className="text-sm text-success">{formatCurrency(invoice.amountPaid, invoice.currency)}</span>
136
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.amount_paid")}</span>
137
+ <span className="text-sm text-success">
138
+ {formatCurrency(invoice.amountPaid, invoice.currency, fmt)}
139
+ </span>
135
140
  </div>
136
141
  )}
137
142
 
138
143
  {invoice.amountRemaining > 0 && (
139
144
  <div className="flex justify-between">
140
- <span className="text-muted-foreground text-xs">Amount Due:</span>
145
+ <span className="text-muted-foreground text-xs">{t("billing.ui.invoice.amount_due")}</span>
141
146
  <span className="text-sm text-destructive">
142
- {formatCurrency(invoice.amountRemaining, invoice.currency)}
147
+ {formatCurrency(invoice.amountRemaining, invoice.currency, fmt)}
143
148
  </span>
144
149
  </div>
145
150
  )}
@@ -150,21 +155,21 @@ export function InvoiceDetails({
150
155
  {invoice.stripePdfUrl && (
151
156
  <Button variant="outline" onClick={handleDownloadPDF}>
152
157
  <Download className="me-2 h-4 w-4" />
153
- Download PDF
158
+ {t("billing.ui.invoice.download_pdf")}
154
159
  </Button>
155
160
  )}
156
161
 
157
162
  {invoice.status === InvoiceStatus.OPEN && invoice.attempted && (
158
163
  <Button variant="default" onClick={handleRetryPayment}>
159
164
  <RefreshCw className="me-2 h-4 w-4" />
160
- Retry Payment
165
+ {t("billing.ui.invoice.retry_payment")}
161
166
  </Button>
162
167
  )}
163
168
 
164
169
  {invoice.stripeHostedInvoiceUrl && (
165
170
  <Button variant="outline" onClick={handleViewInStripe}>
166
171
  <ExternalLink className="me-2 h-4 w-4" />
167
- View in Stripe
172
+ {t("billing.ui.invoice.view_in_stripe")}
168
173
  </Button>
169
174
  )}
170
175
  </div>
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useState } from "react";
4
5
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../shadcnui";
5
- import { formatCurrency, formatDate } from "../../../components/utils";
6
+ import { formatCurrency, formatDate, useBillingFormatOptions } from "../../../components/utils";
6
7
  import { StripeInvoiceInterface } from "../../data/stripe-invoice.interface";
7
8
  import { InvoiceDetails } from "../details/InvoiceDetails";
8
9
  import { InvoiceStatusBadge } from "../widgets/InvoiceStatusBadge";
@@ -13,6 +14,8 @@ type InvoicesListProps = {
13
14
  };
14
15
 
15
16
  export function InvoicesList({ invoices, onInvoicesChange }: InvoicesListProps) {
17
+ const t = useTranslations();
18
+ const fmt = useBillingFormatOptions();
16
19
  const [selectedInvoice, setSelectedInvoice] = useState<StripeInvoiceInterface | null>(null);
17
20
 
18
21
  const handleRowClick = (invoice: StripeInvoiceInterface) => {
@@ -33,19 +36,19 @@ export function InvoicesList({ invoices, onInvoicesChange }: InvoicesListProps)
33
36
  <Table>
34
37
  <TableHeader className="bg-muted">
35
38
  <TableRow>
36
- <TableHead>Invoice #</TableHead>
37
- <TableHead>Date</TableHead>
38
- <TableHead>Status</TableHead>
39
- <TableHead className="text-end">Amount</TableHead>
40
- <TableHead>Period</TableHead>
39
+ <TableHead>{t("billing.ui.columns.invoice_number")}</TableHead>
40
+ <TableHead>{t("billing.ui.columns.date")}</TableHead>
41
+ <TableHead>{t("billing.ui.columns.status")}</TableHead>
42
+ <TableHead className="text-end">{t("billing.ui.columns.amount")}</TableHead>
43
+ <TableHead>{t("billing.ui.columns.period")}</TableHead>
41
44
  </TableRow>
42
45
  </TableHeader>
43
46
  <TableBody>
44
47
  {invoices.map((invoice) => {
45
48
  const invoiceNumber = getInvoiceNumber(invoice);
46
- const date = formatDate(invoice.periodStart);
47
- const amount = formatCurrency(invoice.total, invoice.currency);
48
- const period = `${formatDate(invoice.periodStart)} - ${formatDate(invoice.periodEnd)}`;
49
+ const date = formatDate(invoice.periodStart, fmt);
50
+ const amount = formatCurrency(invoice.total, invoice.currency, fmt);
51
+ const period = `${formatDate(invoice.periodStart, fmt)} - ${formatDate(invoice.periodEnd, fmt)}`;
49
52
 
50
53
  return (
51
54
  <TableRow
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Badge, type BadgeProps } from "../../../../../shadcnui";
4
5
  import { InvoiceStatus } from "../../data/stripe-invoice.interface";
5
6
 
@@ -8,35 +9,37 @@ type InvoiceStatusBadgeProps = {
8
9
  };
9
10
 
10
11
  type StatusConfig = {
12
+ /** Message key, translated at render time. */
11
13
  label: string;
12
14
  variant: BadgeProps["variant"];
13
15
  };
14
16
 
15
17
  const statusConfig: Record<InvoiceStatus, StatusConfig> = {
16
18
  [InvoiceStatus.DRAFT]: {
17
- label: "Draft",
19
+ label: "billing.ui.invoice_status.draft",
18
20
  variant: "softGray",
19
21
  },
20
22
  [InvoiceStatus.OPEN]: {
21
- label: "Open",
23
+ label: "billing.ui.invoice_status.open",
22
24
  variant: "softBlue",
23
25
  },
24
26
  [InvoiceStatus.PAID]: {
25
- label: "Paid",
27
+ label: "billing.ui.invoice_status.paid",
26
28
  variant: "softGreen",
27
29
  },
28
30
  [InvoiceStatus.VOID]: {
29
- label: "Void",
31
+ label: "billing.ui.invoice_status.void",
30
32
  variant: "softGray",
31
33
  },
32
34
  [InvoiceStatus.UNCOLLECTIBLE]: {
33
- label: "Uncollectible",
35
+ label: "billing.ui.invoice_status.uncollectible",
34
36
  variant: "softRed",
35
37
  },
36
38
  };
37
39
 
38
40
  export function InvoiceStatusBadge({ status }: InvoiceStatusBadgeProps) {
41
+ const t = useTranslations();
39
42
  const config = statusConfig[status] || statusConfig[InvoiceStatus.DRAFT];
40
43
 
41
- return <Badge variant={config.variant}>{config.label}</Badge>;
44
+ return <Badge variant={config.variant}>{t(config.label)}</Badge>;
42
45
  }
@@ -5,11 +5,12 @@ import { DetailField, SectionHeader } from "../../../../../components";
5
5
  import { Modules } from "../../../../../core";
6
6
  import { usePageUrlGenerator } from "../../../../../hooks";
7
7
  import { Badge, Link } from "../../../../../shadcnui";
8
- import { formatCurrency, formatInterval } from "../../../components/utils/currency";
8
+ import { formatCurrency, formatInterval, useBillingFormatOptions } from "../../../components/utils";
9
9
  import { usePriceContext } from "../../../contexts/PriceContext";
10
10
 
11
11
  export function PriceDetails() {
12
12
  const t = useTranslations();
13
+ const fmt = useBillingFormatOptions();
13
14
  const generateUrl = usePageUrlGenerator();
14
15
  const { price } = usePriceContext();
15
16
 
@@ -49,14 +50,14 @@ export function PriceDetails() {
49
50
  />
50
51
  <DetailField
51
52
  label={t("billing.admin.prices.fields.amount")}
52
- value={<span className="tabular-nums">{formatCurrency(price.unitAmount, price.currency)}</span>}
53
+ value={<span className="tabular-nums">{formatCurrency(price.unitAmount, price.currency, fmt)}</span>}
53
54
  />
54
55
  <DetailField label={t("billing.admin.prices.fields.currency")} value={price.currency.toUpperCase()} />
55
56
  <DetailField
56
57
  label={t("billing.admin.prices.fields.type")}
57
58
  value={isRecurring ? t("billing.admin.prices.type.recurring") : t("billing.admin.prices.type.oneTime")}
58
59
  />
59
- <DetailField label={t("billing.admin.prices.fields.billing")} value={formatInterval(price)} />
60
+ <DetailField label={t("billing.admin.prices.fields.billing")} value={formatInterval(price, fmt)} />
60
61
  {isRecurring && (
61
62
  <DetailField
62
63
  label={t("billing.admin.prices.fields.intervalCount")}
@@ -5,6 +5,7 @@ import { useTranslations } from "next-intl";
5
5
  import { useState } from "react";
6
6
  import { errorToast } from "../../../../../components/errors";
7
7
  import { Button, ConfirmDialog } from "../../../../../shadcnui";
8
+ import { useBillingFormatOptions } from "../../../components/utils";
8
9
  import { priceLabel, usePriceContext } from "../../../contexts/PriceContext";
9
10
  import { StripePriceInterface } from "../../data/stripe-price.interface";
10
11
 
@@ -20,6 +21,7 @@ type PriceArchiverProps = {
20
21
  */
21
22
  export function PriceArchiver({ price }: PriceArchiverProps) {
22
23
  const t = useTranslations();
24
+ const fmt = useBillingFormatOptions();
23
25
  const { archivePrice, restorePrice } = usePriceContext();
24
26
  const [open, setOpen] = useState<boolean>(false);
25
27
 
@@ -36,7 +38,7 @@ export function PriceArchiver({ price }: PriceArchiverProps) {
36
38
  open={open}
37
39
  onOpenChange={setOpen}
38
40
  title={t(`billing.admin.prices.${scope}.title`)}
39
- description={t(`billing.admin.prices.${scope}.description`, { label: priceLabel(price) })}
41
+ description={t(`billing.admin.prices.${scope}.description`, { label: priceLabel(price, fmt) })}
40
42
  confirmLabel={t(`billing.admin.prices.${scope}.confirm`)}
41
43
  cancelLabel={t("ui.buttons.cancel")}
42
44
  destructive={!isArchived}
@@ -13,6 +13,7 @@ import { useI18nRouter } from "../../../../../i18n";
13
13
  import { Alert, AlertDescription, AlertTitle, Button, Checkbox, Input } from "../../../../../shadcnui";
14
14
  import { showToast } from "../../../../../utils/toast";
15
15
  import { FeatureInterface, FeatureService } from "../../../../feature";
16
+ import { useBillingFormatOptions } from "../../../components/utils";
16
17
  import { priceLabel, usePriceContext } from "../../../contexts/PriceContext";
17
18
  import { StripePriceInput, StripePriceInterface } from "../../data/stripe-price.interface";
18
19
  import { StripePriceSeed } from "../../data/stripe-price.transfer";
@@ -54,6 +55,7 @@ function PriceEditorInternal({
54
55
  onDialogOpenChange,
55
56
  }: PriceEditorProps) {
56
57
  const t = useTranslations();
58
+ const fmt = useBillingFormatOptions();
57
59
  const router = useI18nRouter();
58
60
  const { createPrice, updatePrice, productId: contextProductId } = usePriceContext();
59
61
  const [allFeatures, setAllFeatures] = useState<FeatureInterface[]>([]);
@@ -266,7 +268,7 @@ function PriceEditorInternal({
266
268
  <EditorSheet
267
269
  form={form}
268
270
  entityType={t("billing.admin.prices.entity")}
269
- entityName={price ? priceLabel(price) : undefined}
271
+ entityName={price ? priceLabel(price, fmt) : undefined}
270
272
  isEdit={isEdit}
271
273
  module={Modules.StripePrice}
272
274
  size="lg"
@@ -7,7 +7,7 @@ import { useMemo } from "react";
7
7
  import { Modules } from "../../../../core";
8
8
  import { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from "../../../../hooks";
9
9
  import { Badge, Button, Link } from "../../../../shadcnui";
10
- import { formatCurrency, formatInterval } from "../../components/utils/currency";
10
+ import { formatCurrency, formatInterval, useBillingFormatOptions } from "../../components/utils";
11
11
  import { StripePriceFields } from "../data/stripe-price.fields";
12
12
  import { StripePriceInterface } from "../data/stripe-price.interface";
13
13
 
@@ -31,6 +31,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
31
31
  params,
32
32
  ) => {
33
33
  const t = useTranslations();
34
+ const fmt = useBillingFormatOptions();
34
35
  const generateUrl = usePageUrlGenerator();
35
36
 
36
37
  const onClone = params.context?.onClone as StripePriceRowActions["onClone"];
@@ -56,7 +57,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
56
57
  const price: StripePriceInterface = row.original.jsonApiData;
57
58
  return (
58
59
  <Link href={generateUrl({ page: Modules.StripePrice, id: price.id })}>
59
- {price.nickname ?? formatCurrency(price.unitAmount, price.currency)}
60
+ {price.nickname ?? formatCurrency(price.unitAmount, price.currency, fmt)}
60
61
  </Link>
61
62
  );
62
63
  },
@@ -72,7 +73,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
72
73
  // Typography role 15 (numeric): tabular-nums, end-aligned. NEVER font-mono.
73
74
  return (
74
75
  <span className="block text-xs tabular-nums text-end">
75
- {formatCurrency(price.unitAmount, price.currency)}
76
+ {formatCurrency(price.unitAmount, price.currency, fmt)}
76
77
  </span>
77
78
  );
78
79
  },
@@ -86,7 +87,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
86
87
  cell: ({ row }: { row: TableContent<StripePriceInterface> }) => {
87
88
  const price: StripePriceInterface = row.original.jsonApiData;
88
89
  // Typography role 8 (UI chrome): single-line, plain text-xs.
89
- return <span className="text-xs">{formatInterval(price)}</span>;
90
+ return <span className="text-xs">{formatInterval(price, fmt)}</span>;
90
91
  },
91
92
  enableSorting: false,
92
93
  enableHiding: false,
@@ -175,7 +176,7 @@ export const useStripePriceTableStructure: UseTableStructureHook<StripePriceInte
175
176
  return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
176
177
  TableContent<StripePriceInterface>
177
178
  >[];
178
- }, [params.fields, fieldColumnMap, t, generateUrl, onClone, onExport]);
179
+ }, [params.fields, fieldColumnMap, t, fmt, generateUrl, onClone, onExport]);
179
180
 
180
181
  return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
181
182
  };
@@ -1,8 +1,11 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useState } from "react";
4
5
  import { Check, X, Loader2 } from "lucide-react";
5
6
  import { Button, Input } from "../../../../shadcnui";
7
+ import { useI18nLocale } from "../../../../i18n";
8
+ import { formatFixed } from "../../components/utils";
6
9
  import { PromotionCodeValidationResult } from "../data/stripe-promotion-code.interface";
7
10
 
8
11
  type PromoCodeInputProps = {
@@ -22,6 +25,8 @@ export function PromoCodeInput({
22
25
  onRemove,
23
26
  disabled = false,
24
27
  }: PromoCodeInputProps) {
28
+ const t = useTranslations();
29
+ const locale = useI18nLocale();
25
30
  const [code, setCode] = useState("");
26
31
 
27
32
  const handleApply = () => {
@@ -40,23 +45,23 @@ export function PromoCodeInput({
40
45
  // Format discount for display
41
46
  const formatDiscount = (result: PromotionCodeValidationResult): string => {
42
47
  if (result.discountType === "percent_off") {
43
- return `${result.discountValue}% off`;
48
+ return t("billing.ui.wizard.review.percent_off", { value: String(result.discountValue) });
44
49
  }
45
50
  // amount_off is in cents, convert to dollars
46
51
  const amount = (result.discountValue || 0) / 100;
47
52
  const currency = result.currency?.toUpperCase() || "USD";
48
- return `${currency} ${amount.toFixed(2)} off`;
53
+ return t("billing.ui.wizard.review.amount_off", { amount: `${currency} ${formatFixed(amount, locale, 2)}` });
49
54
  };
50
55
 
51
56
  // Format duration for display
52
57
  const formatDuration = (result: PromotionCodeValidationResult): string => {
53
58
  switch (result.duration) {
54
59
  case "forever":
55
- return "Applied to all payments";
60
+ return t("billing.ui.promo_code.duration_forever");
56
61
  case "once":
57
- return "Applied to first payment only";
62
+ return t("billing.ui.promo_code.duration_once");
58
63
  case "repeating":
59
- return `Applied for ${result.durationInMonths} months`;
64
+ return t("billing.ui.promo_code.duration_repeating", { months: String(result.durationInMonths) });
60
65
  default:
61
66
  return "";
62
67
  }
@@ -91,7 +96,7 @@ export function PromoCodeInput({
91
96
  <div className="space-y-2">
92
97
  <div className="flex gap-2">
93
98
  <Input
94
- placeholder="Enter promo code"
99
+ placeholder={t("billing.ui.promo_code.placeholder")}
95
100
  value={code}
96
101
  onChange={(e) => setCode(e.target.value.toUpperCase())}
97
102
  onKeyDown={handleKeyDown}
@@ -99,7 +104,7 @@ export function PromoCodeInput({
99
104
  className="flex-1"
100
105
  />
101
106
  <Button variant="outline" onClick={handleApply} disabled={disabled || isValidating || !code.trim()}>
102
- {isValidating ? <Loader2 className="h-4 w-4 animate-spin" /> : "Apply"}
107
+ {isValidating ? <Loader2 className="h-4 w-4 animate-spin" /> : t("billing.ui.promo_code.apply")}
103
108
  </Button>
104
109
  </div>
105
110
  {error && <p className="text-destructive text-xs/relaxed">{error}</p>}
@@ -42,7 +42,9 @@ export class StripePromotionCodeService {
42
42
  return {
43
43
  valid: false,
44
44
  code: params.code,
45
- errorMessage: response.error || "Failed to validate promotion code",
45
+ // No English fallback here: a service cannot translate, so the caller words
46
+ // the missing message in the active language.
47
+ errorMessage: response.error || undefined,
46
48
  };
47
49
  }
48
50
 
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { CreditCard } from "lucide-react";
4
5
  import { useCallback, useEffect, useState } from "react";
5
6
  import { Button } from "../../../../../shadcnui";
@@ -13,6 +14,7 @@ type SubscriptionsContainerProps = {
13
14
  };
14
15
 
15
16
  export function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscription }: SubscriptionsContainerProps) {
17
+ const t = useTranslations();
16
18
  const [subscriptions, setSubscriptions] = useState<StripeSubscriptionInterface[]>([]);
17
19
  const [loading, setLoading] = useState<boolean>(true);
18
20
 
@@ -44,7 +46,7 @@ export function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscri
44
46
  if (loading) {
45
47
  return (
46
48
  <div className="flex h-64 items-center justify-center">
47
- <p className="text-muted-foreground">Loading subscriptions...</p>
49
+ <p className="text-muted-foreground">{t("billing.ui.subscriptions.loading")}</p>
48
50
  </div>
49
51
  );
50
52
  }
@@ -55,11 +57,13 @@ export function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscri
55
57
  <div className="flex items-center justify-between">
56
58
  <div className="flex items-center gap-x-3">
57
59
  <CreditCard className="h-8 w-8" />
58
- <h1 className="text-primary text-3xl font-semibold">Subscriptions</h1>
60
+ <h1 className="text-primary text-3xl font-semibold">{t("billing.ui.subscriptions.title")}</h1>
59
61
  </div>
60
62
  {subscriptions.length > 0 && (
61
63
  <Button onClick={() => onOpenWizard?.()}>
62
- {hasActiveRecurringSubscription ? "Purchase Add-ons" : "Subscribe to a Plan"}
64
+ {hasActiveRecurringSubscription
65
+ ? t("billing.ui.subscriptions.purchase_addons")
66
+ : t("billing.ui.subscriptions.subscribe")}
63
67
  </Button>
64
68
  )}
65
69
  </div>
@@ -74,9 +78,9 @@ export function SubscriptionsContainer({ onOpenWizard, hasActiveRecurringSubscri
74
78
  <div className="flex flex-col items-center justify-center py-12 space-y-4">
75
79
  <CreditCard className="h-16 w-16 text-muted-foreground" />
76
80
  <div className="text-center">
77
- <h3 className="text-sm font-medium mb-2">No Active Subscriptions</h3>
78
- <p className="text-muted-foreground mb-6">Choose a subscription plan to get started with our services.</p>
79
- <Button onClick={() => onOpenWizard?.()}>Subscribe to a Plan</Button>
81
+ <h3 className="text-sm font-medium mb-2">{t("billing.ui.subscriptions.empty_title")}</h3>
82
+ <p className="text-muted-foreground mb-6">{t("billing.ui.subscriptions.empty_description")}</p>
83
+ <Button onClick={() => onOpenWizard?.()}>{t("billing.ui.subscriptions.subscribe")}</Button>
80
84
  </div>
81
85
  </div>
82
86
  )}