@carlonicora/nextjs-jsonapi 4.1.1 → 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 (230) 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/scripts/vitest-slow-tests.reporter.spec.ts +54 -0
  88. package/src/components/containers/RoundPageContainer.tsx +3 -1
  89. package/src/components/containers/RoundPageContainerTitle.tsx +5 -2
  90. package/src/components/editors/BlockNoteEditor.tsx +31 -15
  91. package/src/components/editors/BlockNoteEditorAiActions.tsx +11 -7
  92. package/src/components/editors/__tests__/BlockNoteEditorAiActions.test.ts +10 -3
  93. package/src/components/errors/ErrorDetails.tsx +5 -2
  94. package/src/components/forms/CommonAssociationForm.tsx +1 -1
  95. package/src/components/forms/EntityMultiSelector.tsx +6 -2
  96. package/src/components/forms/EntitySelector.tsx +4 -1
  97. package/src/components/forms/FileUploader.tsx +3 -2
  98. package/src/components/forms/MultiFileUploader.tsx +3 -1
  99. package/src/components/navigations/Breadcrumb.tsx +3 -2
  100. package/src/components/navigations/Header.tsx +3 -1
  101. package/src/components/navigations/MobileNavigationBar.tsx +3 -1
  102. package/src/components/navigations/__tests__/Header.spec.tsx +2 -2
  103. package/src/components/tables/cells/cell.id.tsx +13 -2
  104. package/src/core/abstracts/AbstractService.ts +3 -3
  105. package/src/core/abstracts/ClientAbstractService.ts +3 -2
  106. package/src/features/assistant-action/components/ApprovalActionCard.tsx +74 -5
  107. package/src/features/assistant-action/components/__tests__/ApprovalActionCard.spec.tsx +81 -0
  108. package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +4 -1
  109. package/src/features/assistant-message/components/parts/tabs/UsersTab.tsx +2 -1
  110. package/src/features/auth/components/buttons/GoogleSignInButton.tsx +4 -1
  111. package/src/features/auth/components/details/LandingComponent.tsx +1 -1
  112. package/src/features/auth/components/forms/ForgotPassword.tsx +1 -1
  113. package/src/features/auth/components/forms/Login.tsx +1 -1
  114. package/src/features/auth/components/forms/Register.tsx +1 -1
  115. package/src/features/auth/components/forms/ResetPassword.tsx +2 -2
  116. package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +1 -1
  117. package/src/features/auth/components/two-factor/PasskeyButton.tsx +1 -1
  118. package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +21 -11
  119. package/src/features/billing/components/cards/CustomerInfoCard.tsx +16 -13
  120. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +33 -21
  121. package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +20 -10
  122. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +35 -22
  123. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +24 -24
  124. package/src/features/billing/components/utils/__tests__/billing-formatters.spec.ts +63 -0
  125. package/src/features/billing/components/utils/currency.ts +67 -10
  126. package/src/features/billing/components/utils/date.ts +9 -4
  127. package/src/features/billing/components/utils/format-options.ts +20 -0
  128. package/src/features/billing/components/utils/index.ts +3 -1
  129. package/src/features/billing/components/utils/useBillingFormatOptions.ts +11 -0
  130. package/src/features/billing/components/widgets/BillingAlertBanner.tsx +8 -9
  131. package/src/features/billing/contexts/PriceContext.tsx +18 -6
  132. package/src/features/billing/i18n-keys.ts +212 -0
  133. package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +8 -8
  134. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +11 -9
  135. package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +4 -4
  136. package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +9 -7
  137. package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +10 -8
  138. package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +34 -29
  139. package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +12 -9
  140. package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +9 -6
  141. package/src/features/billing/stripe-price/components/details/PriceDetails.tsx +4 -3
  142. package/src/features/billing/stripe-price/components/forms/PriceArchiver.tsx +3 -1
  143. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +3 -1
  144. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +6 -5
  145. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +12 -7
  146. package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.service.ts +3 -1
  147. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +10 -6
  148. package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +44 -29
  149. package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +14 -11
  150. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +20 -17
  151. package/src/features/billing/stripe-subscription/components/widgets/IntervalToggle.tsx +4 -2
  152. package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +16 -7
  153. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingList.tsx +3 -1
  154. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingRow.tsx +3 -1
  155. package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +12 -7
  156. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +11 -8
  157. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +13 -10
  158. package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +8 -6
  159. package/src/features/billing/stripe-subscription/components/wizards/WizardProgressIndicator.tsx +7 -4
  160. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +4 -2
  161. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPlanSelection.tsx +3 -1
  162. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +37 -28
  163. package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +9 -7
  164. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +20 -9
  165. package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +6 -6
  166. package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +24 -9
  167. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +16 -12
  168. package/src/features/company/components/details/TokenStatusIndicator.tsx +7 -5
  169. package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
  170. package/src/features/help/components/HelpArticleBody.tsx +1 -1
  171. package/src/features/help/components/HelpHeader.tsx +1 -1
  172. package/src/features/help/components/HelpSideNav.tsx +1 -1
  173. package/src/features/help/i18n-keys.ts +2 -0
  174. package/src/features/notification/components/common/NotificationErrorBoundary.tsx +17 -12
  175. package/src/features/notification/components/containers/NotificationsListContainer.tsx +4 -2
  176. package/src/features/notification/components/notifications/Notification.tsx +6 -3
  177. package/src/features/notification/contexts/NotificationContext.tsx +9 -4
  178. package/src/features/notification/contexts/__tests__/NotificationContext.spec.tsx +13 -0
  179. package/src/features/notification/hooks/useNotificationTableStructure.tsx +4 -2
  180. package/src/features/oauth/components/OAuthClientCard.tsx +13 -7
  181. package/src/features/oauth/components/OAuthClientDetail.tsx +34 -30
  182. package/src/features/oauth/components/OAuthClientForm.tsx +24 -24
  183. package/src/features/oauth/components/OAuthClientList.tsx +10 -6
  184. package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +10 -8
  185. package/src/features/oauth/components/OAuthRedirectUriInput.tsx +9 -13
  186. package/src/features/oauth/components/OAuthScopeSelector.tsx +15 -6
  187. package/src/features/oauth/components/__tests__/OAuthClientForm.spec.tsx +11 -9
  188. package/src/features/oauth/components/consent/OAuthScopeList.tsx +4 -2
  189. package/src/features/oauth/hooks/__tests__/useOAuthScopeText.spec.ts +30 -0
  190. package/src/features/oauth/hooks/index.ts +1 -0
  191. package/src/features/oauth/hooks/useOAuthClient.ts +11 -5
  192. package/src/features/oauth/hooks/useOAuthClients.ts +9 -3
  193. package/src/features/oauth/hooks/useOAuthConsent.ts +11 -5
  194. package/src/features/oauth/hooks/useOAuthScopeText.ts +33 -0
  195. package/src/features/rbac/components/RbacByRoleContainer.tsx +7 -6
  196. package/src/features/rbac/components/RbacContainer.tsx +7 -6
  197. package/src/features/rbac/contexts/RbacContext.tsx +3 -3
  198. package/src/features/user/components/containers/UserContainer.tsx +3 -1
  199. package/src/features/user/data/__tests__/user.test.ts +66 -0
  200. package/src/features/user/data/user.interface.ts +2 -0
  201. package/src/features/user/data/user.service.ts +9 -0
  202. package/src/features/user/data/user.ts +7 -0
  203. package/src/features/waitlist/components/forms/WaitlistForm.tsx +1 -1
  204. package/src/features/waitlist/components/lists/WaitlistList.tsx +1 -1
  205. package/src/i18n/__tests__/config.test.ts +21 -1
  206. package/src/i18n/config.ts +13 -0
  207. package/src/i18n/index.ts +1 -0
  208. package/src/shadcnui/custom/multi-select.tsx +47 -14
  209. package/src/shadcnui/custom/multiple-selector.tsx +14 -2
  210. package/src/shadcnui/ui/__tests__/use-ui-text.test.tsx +76 -0
  211. package/src/shadcnui/ui/breadcrumb.tsx +9 -2
  212. package/src/shadcnui/ui/carousel.tsx +15 -4
  213. package/src/shadcnui/ui/dialog.tsx +15 -2
  214. package/src/shadcnui/ui/sheet.tsx +6 -1
  215. package/src/shadcnui/ui/sidebar.tsx +33 -7
  216. package/src/shadcnui/ui/spinner.tsx +6 -1
  217. package/src/shadcnui/ui/use-ui-text.ts +49 -0
  218. package/src/utils/__tests__/date-formatter.test.ts +24 -1
  219. package/src/utils/date-formatter.ts +16 -0
  220. package/src/utils/index.ts +1 -1
  221. package/dist/BlockNoteEditor-BENYTTCZ.js.map +0 -1
  222. package/dist/BlockNoteEditor-VOYKZOP5.mjs.map +0 -1
  223. package/dist/chunk-4BZJ4LAB.js.map +0 -1
  224. package/dist/chunk-6ISF5KNQ.mjs.map +0 -1
  225. package/dist/chunk-ORU7KLCY.js.map +0 -1
  226. package/dist/chunk-ZZ6E33FO.mjs.map +0 -1
  227. /package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs.map → BlockNoteViewer-MDPO6BXN.mjs.map} +0 -0
  228. /package/dist/{chunk-46S3KPLU.mjs.map → chunk-ILFFC6BG.mjs.map} +0 -0
  229. /package/dist/{chunk-HZVVRIJQ.mjs.map → chunk-IUS2RHCX.mjs.map} +0 -0
  230. /package/dist/{chunk-ZO3QQ5CQ.mjs.map → chunk-MQDR6MEE.mjs.map} +0 -0
@@ -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 { SubscriptionStatus } from "../../data";
5
6
 
@@ -9,55 +10,57 @@ type SubscriptionStatusBadgeProps = {
9
10
  };
10
11
 
11
12
  type StatusConfig = {
13
+ /** Message key, translated at render time. */
12
14
  label: string;
13
15
  variant: BadgeProps["variant"];
14
16
  };
15
17
 
16
18
  const statusConfig: Record<SubscriptionStatus, StatusConfig> = {
17
19
  [SubscriptionStatus.ACTIVE]: {
18
- label: "Active",
20
+ label: "billing.ui.subscription_status.active",
19
21
  variant: "softGreen",
20
22
  },
21
23
  [SubscriptionStatus.TRIALING]: {
22
- label: "Trial",
24
+ label: "billing.ui.subscription_status.trialing",
23
25
  variant: "softBlue",
24
26
  },
25
27
  [SubscriptionStatus.PAST_DUE]: {
26
- label: "Past Due",
28
+ label: "billing.ui.subscription_status.past_due",
27
29
  variant: "softRed",
28
30
  },
29
31
  [SubscriptionStatus.CANCELED]: {
30
- label: "Canceled",
32
+ label: "billing.ui.subscription_status.canceled",
31
33
  variant: "softGray",
32
34
  },
33
35
  [SubscriptionStatus.PAUSED]: {
34
- label: "Paused",
36
+ label: "billing.ui.subscription_status.paused",
35
37
  variant: "softYellow",
36
38
  },
37
39
  [SubscriptionStatus.UNPAID]: {
38
- label: "Unpaid",
40
+ label: "billing.ui.subscription_status.unpaid",
39
41
  variant: "softOrange",
40
42
  },
41
43
  [SubscriptionStatus.INCOMPLETE]: {
42
- label: "Incomplete",
44
+ label: "billing.ui.subscription_status.incomplete",
43
45
  variant: "softGray",
44
46
  },
45
47
  [SubscriptionStatus.INCOMPLETE_EXPIRED]: {
46
- label: "Expired",
48
+ label: "billing.ui.subscription_status.incomplete_expired",
47
49
  variant: "softGray",
48
50
  },
49
51
  };
50
52
 
51
53
  const cancelingConfig: StatusConfig = {
52
- label: "Canceling",
54
+ label: "billing.ui.cards.subscriptions.canceling",
53
55
  variant: "softAmber",
54
56
  };
55
57
 
56
58
  export function SubscriptionStatusBadge({ status, cancelAtPeriodEnd }: SubscriptionStatusBadgeProps) {
59
+ const t = useTranslations();
57
60
  // Show "Canceling" when subscription is set to cancel at period end
58
61
  const config = cancelAtPeriodEnd
59
62
  ? cancelingConfig
60
63
  : statusConfig[status] || statusConfig[SubscriptionStatus.CANCELED];
61
64
 
62
- return <Badge variant={config.variant}>{config.label}</Badge>;
65
+ return <Badge variant={config.variant}>{t(config.label)}</Badge>;
63
66
  }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useCallback, useEffect, useRef } from "react";
4
5
  import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "../../../../../shadcnui";
5
6
  import { StripeSubscriptionInterface } from "../../data";
@@ -24,6 +25,7 @@ export function SubscriptionWizard({
24
25
  hasActiveRecurringSubscription,
25
26
  subscription,
26
27
  }: SubscriptionWizardProps) {
28
+ const t = useTranslations();
27
29
  const handleClose = useCallback(() => onOpenChange(false), [onOpenChange]);
28
30
 
29
31
  const { state, actions } = useSubscriptionWizard({
@@ -57,16 +59,16 @@ export function SubscriptionWizard({
57
59
  const isChangePlanMode = !!subscription;
58
60
 
59
61
  const dialogTitle = subscription
60
- ? "Change Subscription Plan"
62
+ ? t("billing.ui.wizard.change_title")
61
63
  : isPurchasingAddons
62
- ? "Purchase Add-ons"
63
- : "Subscribe to a Plan";
64
+ ? t("billing.ui.subscriptions.purchase_addons")
65
+ : t("billing.ui.subscriptions.subscribe");
64
66
 
65
67
  const dialogDescription = subscription
66
- ? "Select a new plan for your subscription"
68
+ ? t("billing.ui.wizard.change_description")
67
69
  : isPurchasingAddons
68
- ? "Select one-time products to purchase"
69
- : "Choose a subscription plan to get started";
70
+ ? t("billing.ui.wizard.addons_description")
71
+ : t("billing.ui.wizard.subscribe_description");
70
72
 
71
73
  return (
72
74
  <Dialog open={open} onOpenChange={onOpenChange}>
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Check } from "lucide-react";
4
5
  import { WizardStep } from "../../hooks/useSubscriptionWizard";
5
6
 
@@ -7,10 +8,11 @@ type WizardProgressIndicatorProps = {
7
8
  currentStep: WizardStep;
8
9
  };
9
10
 
11
+ // `label` is a message key, translated at render time.
10
12
  const STEPS: { key: WizardStep; label: string }[] = [
11
- { key: "plan-selection", label: "Select Plan" },
12
- { key: "review", label: "Review" },
13
- { key: "payment-method", label: "Payment" },
13
+ { key: "plan-selection", label: "billing.ui.wizard.steps.select_plan" },
14
+ { key: "review", label: "billing.ui.wizard.steps.review" },
15
+ { key: "payment-method", label: "billing.ui.wizard.steps.payment" },
14
16
  ];
15
17
 
16
18
  function getStepIndex(step: WizardStep): number {
@@ -18,6 +20,7 @@ function getStepIndex(step: WizardStep): number {
18
20
  }
19
21
 
20
22
  export function WizardProgressIndicator({ currentStep }: WizardProgressIndicatorProps) {
23
+ const t = useTranslations();
21
24
  const currentIndex = getStepIndex(currentStep);
22
25
 
23
26
  return (
@@ -43,7 +46,7 @@ export function WizardProgressIndicator({ currentStep }: WizardProgressIndicator
43
46
 
44
47
  {/* Step Label */}
45
48
  <span className={`text-sm ${isCurrent ? "font-medium text-foreground" : "text-muted-foreground"}`}>
46
- {step.label}
49
+ {t(step.label)}
47
50
  </span>
48
51
 
49
52
  {/* Connector */}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { PaymentMethodForm } from "../../../stripe-customer/components/forms/PaymentMethodForm";
4
5
  import { SectionHeader } from "../../../../../components/typography";
5
6
 
@@ -10,11 +11,12 @@ type WizardStepPaymentMethodProps = {
10
11
  };
11
12
 
12
13
  export function WizardStepPaymentMethod({ onBack, onSuccess, isProcessing }: WizardStepPaymentMethodProps) {
14
+ const t = useTranslations();
13
15
  return (
14
16
  <div className="space-y-6">
15
17
  <div className="text-center">
16
- <SectionHeader>Add Payment Method</SectionHeader>
17
- <p className="text-sm text-muted-foreground">Enter your card details to complete your subscription</p>
18
+ <SectionHeader>{t("billing.ui.payment_methods.add")}</SectionHeader>
19
+ <p className="text-sm text-muted-foreground">{t("billing.ui.wizard.payment_description")}</p>
18
20
  </div>
19
21
 
20
22
  <PaymentMethodForm onSuccess={onSuccess} onCancel={onBack} isLoading={isProcessing} />
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useEffect, useMemo, useState } from "react";
4
5
  import { Button } from "../../../../../shadcnui";
5
6
  import { StripeProductInterface, StripeProductService } from "../../../stripe-product";
@@ -30,6 +31,7 @@ export function WizardStepPlanSelection({
30
31
  onNext,
31
32
  isProcessing,
32
33
  }: WizardStepPlanSelectionProps) {
34
+ const t = useTranslations();
33
35
  const [products, setProducts] = useState<StripeProductInterface[]>([]);
34
36
  const [loading, setLoading] = useState(true);
35
37
 
@@ -100,7 +102,7 @@ export function WizardStepPlanSelection({
100
102
  {/* Action Buttons */}
101
103
  <div className="flex justify-end pt-4 border-t">
102
104
  <Button onClick={onNext} disabled={!selectedPrice || isProcessing}>
103
- {isProcessing ? "Loading..." : "Next: Review"}
105
+ {isProcessing ? t("common.loading") : t("billing.ui.wizard.next_review")}
104
106
  </Button>
105
107
  </div>
106
108
  </div>
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { AlertCircle } from "lucide-react";
4
5
  import { Alert, AlertDescription, Badge, Button } from "../../../../../shadcnui";
5
6
  import { SectionHeader } from "../../../../../components/typography";
6
- import { formatCurrency } from "../../../components/utils/currency";
7
+ import { formatCurrency, useBillingFormatOptions } from "../../../components/utils";
7
8
  import { StripePriceInterface } from "../../../stripe-price/data/stripe-price.interface";
8
9
  import { ProrationPreviewInterface } from "../../../stripe-invoice/data/stripe-invoice.interface";
9
10
  import { PromotionCodeValidationResult } from "../../../stripe-promotion-code";
@@ -47,18 +48,20 @@ export function WizardStepReview({
47
48
  onRemovePromoCode,
48
49
  isTrialUpgrade,
49
50
  }: WizardStepReviewProps) {
51
+ const t = useTranslations();
52
+ const fmt = useBillingFormatOptions();
50
53
  if (!selectedPrice) {
51
- return (
52
- <div className="text-center py-8 text-muted-foreground">No plan selected. Please go back and select a plan.</div>
53
- );
54
+ return <div className="text-center py-8 text-muted-foreground">{t("billing.ui.wizard.review.no_plan")}</div>;
54
55
  }
55
56
 
56
57
  const isChangingPlan = subscription && subscription.price?.id !== selectedPrice.id;
57
58
 
58
59
  const formatInterval = (price: StripePriceInterface) => {
59
- if (price.priceType === "one_time") return "one-time";
60
+ if (price.priceType === "one_time") return t("billing.ui.wizard.review.interval_one_time");
60
61
  const interval = price.recurring?.interval || "month";
61
- return interval === "year" ? "yearly" : "monthly";
62
+ return interval === "year"
63
+ ? t("billing.ui.wizard.review.interval_yearly")
64
+ : t("billing.ui.wizard.review.interval_monthly");
62
65
  };
63
66
 
64
67
  // Calculate discounted price if promotion code is applied
@@ -105,11 +108,13 @@ export function WizardStepReview({
105
108
  if (!promotionCode?.valid) return null;
106
109
 
107
110
  if (promotionCode.discountType === "percent_off" && promotionCode.discountValue) {
108
- return `${promotionCode.discountValue}% off`;
111
+ return t("billing.ui.wizard.review.percent_off", { value: promotionCode.discountValue });
109
112
  }
110
113
 
111
114
  if (promotionCode.discountType === "amount_off" && promotionCode.discountValue) {
112
- return `${formatCurrency(promotionCode.discountValue, promotionCode.currency || selectedPrice.currency)} off`;
115
+ return t("billing.ui.wizard.review.amount_off", {
116
+ amount: formatCurrency(promotionCode.discountValue, promotionCode.currency || selectedPrice.currency, fmt),
117
+ });
113
118
  }
114
119
 
115
120
  return null;
@@ -121,7 +126,7 @@ export function WizardStepReview({
121
126
  <div className="space-y-6">
122
127
  {/* Selected Plan Summary */}
123
128
  <div className="bg-muted/50 rounded-lg p-4 space-y-3">
124
- <SectionHeader>Selected Plan</SectionHeader>
129
+ <SectionHeader>{t("billing.ui.wizard.review.selected_plan")}</SectionHeader>
125
130
  <div className="flex justify-between items-center">
126
131
  <div>
127
132
  <p className="font-medium">{selectedPrice.product?.name}</p>
@@ -131,16 +136,16 @@ export function WizardStepReview({
131
136
  {discountedPrice !== null ? (
132
137
  <>
133
138
  <p className="text-sm text-muted-foreground line-through">
134
- {formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency)}
139
+ {formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency, fmt)}
135
140
  </p>
136
141
  <p className="text-success text-lg font-semibold">
137
- {formatCurrency(discountedPrice, selectedPrice.currency)}
142
+ {formatCurrency(discountedPrice, selectedPrice.currency, fmt)}
138
143
  </p>
139
144
  {discountDescription && <Badge variant="softGreen">{discountDescription}</Badge>}
140
145
  </>
141
146
  ) : (
142
147
  <p className="font-semibold text-lg">
143
- {formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency)}
148
+ {formatCurrency(selectedPrice.unitAmount || 0, selectedPrice.currency, fmt)}
144
149
  </p>
145
150
  )}
146
151
  <p className="text-sm text-muted-foreground">{formatInterval(selectedPrice)}</p>
@@ -154,29 +159,31 @@ export function WizardStepReview({
154
159
  className={`${isTrialUpgrade ? "bg-warning/15 border-warning/30" : "bg-primary/10 border-primary/30"} space-y-2 rounded-lg border p-4`}
155
160
  >
156
161
  <h4 className={`font-medium ${isTrialUpgrade ? "text-warning" : "text-primary"}`}>
157
- {isTrialUpgrade ? "Trial Upgrade" : "Proration Summary"}
162
+ {isTrialUpgrade
163
+ ? t("billing.ui.wizard.review.trial_upgrade")
164
+ : t("billing.ui.wizard.review.proration_summary")}
158
165
  </h4>
159
166
  <p className={`text-sm ${isTrialUpgrade ? "text-warning" : "text-foreground"}`}>
160
167
  {isTrialUpgrade
161
- ? "Your trial will end immediately and you will be charged the full price."
162
- : "Your next charge will be adjusted to account for the plan change."}
168
+ ? t("billing.ui.wizard.review.trial_upgrade_description")
169
+ : t("billing.ui.wizard.review.proration_description")}
163
170
  </p>
164
171
  <div className="flex justify-between text-sm">
165
172
  <span className={`${isTrialUpgrade ? "text-warning" : "text-muted-foreground"}`}>
166
- {isTrialUpgrade ? "Amount to charge now:" : "Amount due now:"}
173
+ {isTrialUpgrade ? t("billing.ui.wizard.review.charge_now") : t("billing.ui.wizard.review.due_now")}
167
174
  </span>
168
175
  <span className={`font-medium ${isTrialUpgrade ? "text-warning" : "text-foreground"}`}>
169
176
  {discountedImmediateCharge !== null ? (
170
177
  <>
171
178
  <span className="line-through text-muted-foreground me-2">
172
- {formatCurrency(prorationPreview.immediateCharge, prorationPreview.currency)}
179
+ {formatCurrency(prorationPreview.immediateCharge, prorationPreview.currency, fmt)}
173
180
  </span>
174
181
  <span className="text-success">
175
- {formatCurrency(discountedImmediateCharge, prorationPreview.currency)}
182
+ {formatCurrency(discountedImmediateCharge, prorationPreview.currency, fmt)}
176
183
  </span>
177
184
  </>
178
185
  ) : (
179
- formatCurrency(prorationPreview.immediateCharge, prorationPreview.currency)
186
+ formatCurrency(prorationPreview.immediateCharge, prorationPreview.currency, fmt)
180
187
  )}
181
188
  </span>
182
189
  </div>
@@ -185,7 +192,7 @@ export function WizardStepReview({
185
192
 
186
193
  {/* Promotion Code */}
187
194
  <div className="border rounded-lg p-4 space-y-3">
188
- <h4 className="font-medium">Promotion Code</h4>
195
+ <h4 className="font-medium">{t("billing.ui.wizard.review.promotion_code")}</h4>
189
196
  <PromoCodeInput
190
197
  appliedCode={promotionCode}
191
198
  isValidating={isValidatingPromoCode}
@@ -200,14 +207,16 @@ export function WizardStepReview({
200
207
  <div className="border rounded-lg p-4">
201
208
  <div className="flex justify-between items-center">
202
209
  <div>
203
- <h4 className="font-medium">Payment Method</h4>
210
+ <h4 className="font-medium">{t("billing.ui.payment_method.title")}</h4>
204
211
  <p className="text-sm text-muted-foreground">
205
- {hasPaymentMethod ? "A payment method is on file" : "No payment method on file"}
212
+ {hasPaymentMethod
213
+ ? t("billing.ui.wizard.review.payment_on_file")
214
+ : t("billing.ui.wizard.review.no_payment_on_file")}
206
215
  </p>
207
216
  </div>
208
217
  {!hasPaymentMethod && (
209
218
  <Button variant="outline" onClick={onAddPaymentMethod}>
210
- Add Payment Method
219
+ {t("billing.ui.payment_methods.add")}
211
220
  </Button>
212
221
  )}
213
222
  </div>
@@ -224,16 +233,16 @@ export function WizardStepReview({
224
233
  {/* Action Buttons */}
225
234
  <div className="flex justify-between pt-4 border-t">
226
235
  <Button variant="outline" onClick={onBack} disabled={isProcessing}>
227
- Back
236
+ {t("billing.ui.wizard.back")}
228
237
  </Button>
229
238
  <Button onClick={hasPaymentMethod ? onConfirm : onAddPaymentMethod} disabled={isProcessing}>
230
239
  {isProcessing
231
- ? "Processing..."
240
+ ? t("billing.ui.wizard.review.processing")
232
241
  : hasPaymentMethod
233
242
  ? isChangingPlan
234
- ? "Confirm Plan Change"
235
- : "Subscribe Now"
236
- : "Add Payment Method"}
243
+ ? t("billing.ui.wizard.review.confirm_change")
244
+ : t("billing.ui.wizard.review.subscribe_now")
245
+ : t("billing.ui.payment_methods.add")}
237
246
  </Button>
238
247
  </div>
239
248
  </div>
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useStripe } from "@stripe/react-stripe-js";
4
+ import { useTranslations } from "next-intl";
4
5
  import { useCallback, useState } from "react";
5
6
 
6
7
  export interface ConfirmPaymentResult {
@@ -42,6 +43,7 @@ export interface UseConfirmSubscriptionPaymentReturn {
42
43
  */
43
44
  export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentReturn {
44
45
  const stripe = useStripe();
46
+ const t = useTranslations();
45
47
  const [isConfirming, setIsConfirming] = useState(false);
46
48
 
47
49
  const confirmPayment = useCallback(
@@ -50,7 +52,7 @@ export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentRe
50
52
  console.error("[useConfirmSubscriptionPayment] Stripe not initialized");
51
53
  return {
52
54
  success: false,
53
- error: "Payment system not initialized. Please refresh the page and try again.",
55
+ error: t("billing.ui.payment_confirm.errors.not_initialized"),
54
56
  };
55
57
  }
56
58
 
@@ -58,7 +60,7 @@ export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentRe
58
60
  console.error("[useConfirmSubscriptionPayment] No client secret provided");
59
61
  return {
60
62
  success: false,
61
- error: "Payment confirmation failed. Missing payment details.",
63
+ error: t("billing.ui.payment_confirm.errors.missing_details"),
62
64
  };
63
65
  }
64
66
 
@@ -71,7 +73,7 @@ export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentRe
71
73
  console.error("[useConfirmSubscriptionPayment] Stripe error:", stripeError);
72
74
  return {
73
75
  success: false,
74
- error: stripeError.message || "Payment confirmation failed. Please try again.",
76
+ error: stripeError.message || t("billing.ui.payment_confirm.errors.failed"),
75
77
  };
76
78
  }
77
79
 
@@ -83,25 +85,25 @@ export function useConfirmSubscriptionPayment(): UseConfirmSubscriptionPaymentRe
83
85
  // This shouldn't happen as Stripe handles 3DS inline, but handle it just in case
84
86
  return {
85
87
  success: false,
86
- error: "Additional authentication required. Please complete the verification.",
88
+ error: t("billing.ui.payment_confirm.errors.requires_action"),
87
89
  };
88
90
  }
89
91
 
90
92
  return {
91
93
  success: false,
92
- error: "Payment could not be completed. Please try again.",
94
+ error: t("billing.ui.payment_confirm.errors.not_completed"),
93
95
  };
94
96
  } catch (err: any) {
95
97
  console.error("[useConfirmSubscriptionPayment] Unexpected error:", err);
96
98
  return {
97
99
  success: false,
98
- error: err.message || "An unexpected error occurred during payment confirmation.",
100
+ error: err.message || t("billing.ui.payment_confirm.errors.unexpected"),
99
101
  };
100
102
  } finally {
101
103
  setIsConfirming(false);
102
104
  }
103
105
  },
104
- [stripe],
106
+ [stripe, t],
105
107
  );
106
108
 
107
109
  return {
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useCallback, useMemo, useReducer, useRef } from "react";
4
5
  import { v4 } from "uuid";
6
+ import { useI18nLocale } from "../../../../i18n";
5
7
  import { StripeCustomerService } from "../../stripe-customer/data/stripe-customer.service";
6
8
  import { ProrationPreviewInterface } from "../../stripe-invoice/data/stripe-invoice.interface";
7
9
  import { StripePriceInterface } from "../../stripe-price/data/stripe-price.interface";
@@ -93,6 +95,8 @@ export type UseSubscriptionWizardOptions = {
93
95
  };
94
96
 
95
97
  export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseSubscriptionWizardOptions) {
98
+ const t = useTranslations();
99
+ const locale = useI18nLocale();
96
100
  const [state, dispatch] = useReducer(wizardReducer, {
97
101
  ...initialState,
98
102
  selectedPrice: subscription?.price || null,
@@ -146,7 +150,7 @@ export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseS
146
150
  dispatch({ type: "SET_STEP", step: "payment-method" });
147
151
  dispatch({
148
152
  type: "SET_ERROR",
149
- error: "A payment method is required to upgrade from your trial.",
153
+ error: t("billing.ui.wizard.errors.payment_method_required"),
150
154
  });
151
155
  return;
152
156
  }
@@ -164,11 +168,11 @@ export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseS
164
168
  dispatch({ type: "SET_STEP", step: "review" });
165
169
  } catch (error: any) {
166
170
  console.error("[useSubscriptionWizard] Error preparing review:", error);
167
- dispatch({ type: "SET_ERROR", error: error?.message || "Failed to prepare review" });
171
+ dispatch({ type: "SET_ERROR", error: error?.message || t("billing.ui.wizard.errors.prepare_review") });
168
172
  } finally {
169
173
  dispatch({ type: "SET_PROCESSING", isProcessing: false });
170
174
  }
171
- }, [state.selectedPrice, state.hasPaymentMethod, subscription, checkPaymentMethod]);
175
+ }, [state.selectedPrice, state.hasPaymentMethod, subscription, checkPaymentMethod, t]);
172
176
 
173
177
  const confirmSubscription = useCallback(async () => {
174
178
  if (!state.selectedPrice) return;
@@ -202,7 +206,7 @@ export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseS
202
206
  if (error?.status === 409 || error?.response?.status === 409) {
203
207
  dispatch({
204
208
  type: "SET_ERROR",
205
- error: "You already have an active subscription. Please change your existing plan instead.",
209
+ error: t("billing.ui.wizard.errors.already_subscribed"),
206
210
  });
207
211
  return;
208
212
  }
@@ -214,11 +218,11 @@ export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseS
214
218
  return;
215
219
  }
216
220
 
217
- dispatch({ type: "SET_ERROR", error: error?.message || "Failed to process subscription" });
221
+ dispatch({ type: "SET_ERROR", error: error?.message || t("billing.ui.wizard.errors.process_subscription") });
218
222
  } finally {
219
223
  dispatch({ type: "SET_PROCESSING", isProcessing: false });
220
224
  }
221
- }, [state.selectedPrice, state.promotionCode, subscription]);
225
+ }, [state.selectedPrice, state.promotionCode, subscription, t]);
222
226
 
223
227
  const handlePaymentMethodSuccess = useCallback(async () => {
224
228
  dispatch({ type: "SET_HAS_PAYMENT_METHOD", hasMethod: true });
@@ -239,21 +243,28 @@ export function useSubscriptionWizard({ subscription, onSuccess, onClose }: UseS
239
243
  const result = await StripePromotionCodeService.validatePromotionCode({
240
244
  code,
241
245
  stripePriceId: state.selectedPrice?.id,
246
+ language: locale,
242
247
  });
243
248
 
244
249
  if (result.valid) {
245
250
  dispatch({ type: "SET_PROMOTION_CODE", code: result });
246
251
  } else {
247
- dispatch({ type: "SET_PROMO_CODE_ERROR", error: result.errorMessage || "Invalid promotion code" });
252
+ dispatch({
253
+ type: "SET_PROMO_CODE_ERROR",
254
+ error: result.errorMessage || t("billing.ui.wizard.errors.invalid_promo_code"),
255
+ });
248
256
  }
249
257
  } catch (error: any) {
250
258
  console.error("[useSubscriptionWizard] Promo code validation error:", error);
251
- dispatch({ type: "SET_PROMO_CODE_ERROR", error: error?.message || "Failed to validate promotion code" });
259
+ dispatch({
260
+ type: "SET_PROMO_CODE_ERROR",
261
+ error: error?.message || t("billing.ui.wizard.errors.validate_promo_code"),
262
+ });
252
263
  } finally {
253
264
  dispatch({ type: "SET_VALIDATING_PROMO_CODE", isValidating: false });
254
265
  }
255
266
  },
256
- [state.selectedPrice?.id],
267
+ [state.selectedPrice?.id, t, locale],
257
268
  );
258
269
 
259
270
  const clearPromoCode = useCallback(() => {
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Activity } from "lucide-react";
4
5
  import { useEffect, useState } from "react";
5
6
  import { StripeSubscriptionInterface, StripeSubscriptionService } from "../../../stripe-subscription";
@@ -8,6 +9,7 @@ import { StripeUsageService } from "../../data/stripe-usage.service";
8
9
  import { UsageSummaryCards } from "../widgets/UsageSummaryCards";
9
10
 
10
11
  export function UsageContainer() {
12
+ const t = useTranslations();
11
13
  const [meters, setMeters] = useState<MeterInterface[]>([]);
12
14
  const [summaries, setSummaries] = useState<Record<string, MeterSummaryInterface | null>>({});
13
15
  const [loading, setLoading] = useState<boolean>(true);
@@ -76,7 +78,7 @@ export function UsageContainer() {
76
78
  if (loading) {
77
79
  return (
78
80
  <div className="flex h-64 items-center justify-center">
79
- <p className="text-muted-foreground">Loading usage data...</p>
81
+ <p className="text-muted-foreground">{t("billing.ui.usage.loading")}</p>
80
82
  </div>
81
83
  );
82
84
  }
@@ -86,7 +88,7 @@ export function UsageContainer() {
86
88
  {/* Header */}
87
89
  <div className="flex items-center gap-x-3">
88
90
  <Activity className="h-8 w-8" />
89
- <h1 className="text-primary text-3xl font-semibold">Usage Tracking</h1>
91
+ <h1 className="text-primary text-3xl font-semibold">{t("billing.ui.usage.title")}</h1>
90
92
  </div>
91
93
 
92
94
  {/* Empty State */}
@@ -94,10 +96,8 @@ export function UsageContainer() {
94
96
  <div className="bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed border-gray-300 p-12">
95
97
  <Activity className="text-muted-foreground h-16 w-16" />
96
98
  <div className="text-center">
97
- <h3 className="mb-2 text-sm font-medium">No usage meters configured</h3>
98
- <p className="text-muted-foreground">
99
- Usage tracking will appear here when you have metered subscriptions with configured meters.
100
- </p>
99
+ <h3 className="mb-2 text-sm font-medium">{t("billing.ui.usage.empty_title")}</h3>
100
+ <p className="text-muted-foreground">{t("billing.ui.usage.empty_description")}</p>
101
101
  </div>
102
102
  </div>
103
103
  )}