@carlonicora/nextjs-jsonapi 4.1.2 → 4.2.1

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 (285) 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 → BlockNoteEditorAiActions.spec.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/__tests__/ContentListTable.spec.tsx +1 -1
  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.spec.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 → config.spec.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__/{confirm-dialog.test.tsx → confirm-dialog.spec.tsx} +1 -1
  211. package/src/shadcnui/ui/__tests__/{cursor-controls.test.tsx → cursor-controls.spec.tsx} +1 -1
  212. package/src/shadcnui/ui/__tests__/{cursor-menu-items.test.tsx → cursor-menu-items.spec.tsx} +1 -1
  213. package/src/shadcnui/ui/__tests__/{empty-state.test.tsx → empty-state.spec.tsx} +1 -1
  214. package/src/shadcnui/ui/__tests__/{spinner.test.tsx → spinner.spec.tsx} +1 -1
  215. package/src/shadcnui/ui/__tests__/use-ui-text.spec.tsx +76 -0
  216. package/src/shadcnui/ui/breadcrumb.tsx +9 -2
  217. package/src/shadcnui/ui/carousel.tsx +15 -4
  218. package/src/shadcnui/ui/dialog.tsx +15 -2
  219. package/src/shadcnui/ui/sheet.tsx +6 -1
  220. package/src/shadcnui/ui/sidebar.tsx +33 -7
  221. package/src/shadcnui/ui/spinner.tsx +6 -1
  222. package/src/shadcnui/ui/use-ui-text.ts +49 -0
  223. package/src/utils/__tests__/{date-formatter.test.ts → date-formatter.spec.ts} +24 -1
  224. package/src/utils/date-formatter.ts +16 -0
  225. package/src/utils/index.ts +1 -1
  226. package/dist/BlockNoteEditor-BENYTTCZ.js.map +0 -1
  227. package/dist/BlockNoteEditor-VOYKZOP5.mjs.map +0 -1
  228. package/dist/chunk-4BZJ4LAB.js.map +0 -1
  229. package/dist/chunk-6ISF5KNQ.mjs.map +0 -1
  230. package/dist/chunk-ORU7KLCY.js.map +0 -1
  231. package/dist/chunk-ZZ6E33FO.mjs.map +0 -1
  232. /package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs.map → BlockNoteViewer-MDPO6BXN.mjs.map} +0 -0
  233. /package/dist/{chunk-46S3KPLU.mjs.map → chunk-ILFFC6BG.mjs.map} +0 -0
  234. /package/dist/{chunk-HZVVRIJQ.mjs.map → chunk-IUS2RHCX.mjs.map} +0 -0
  235. /package/dist/{chunk-ZO3QQ5CQ.mjs.map → chunk-MQDR6MEE.mjs.map} +0 -0
  236. /package/scripts/{check-rtl-classes.test.ts → check-rtl-classes.spec.ts} +0 -0
  237. /package/src/client/__tests__/{JsonApiClient.test.ts → JsonApiClient.spec.ts} +0 -0
  238. /package/src/client/__tests__/{request.test.ts → request.spec.ts} +0 -0
  239. /package/src/client/hooks/__tests__/{useJsonApiGet.test.tsx → useJsonApiGet.spec.tsx} +0 -0
  240. /package/src/client/hooks/__tests__/{useJsonApiMutation.test.tsx → useJsonApiMutation.spec.tsx} +0 -0
  241. /package/src/client/hooks/__tests__/{useRehydration.test.ts → useRehydration.spec.ts} +0 -0
  242. /package/src/components/editors/__tests__/{BlockNoteEditorMentionInlineContent.test.ts → BlockNoteEditorMentionInlineContent.spec.ts} +0 -0
  243. /package/src/components/forms/__tests__/{CommonEditorDiscardDialog.test.tsx → CommonEditorDiscardDialog.spec.tsx} +0 -0
  244. /package/src/components/forms/__tests__/{EditorSheet.test.tsx → EditorSheet.spec.tsx} +0 -0
  245. /package/src/components/forms/__tests__/{FormCheckbox.test.tsx → FormCheckbox.spec.tsx} +0 -0
  246. /package/src/components/forms/__tests__/{FormDate.test.tsx → FormDate.spec.tsx} +0 -0
  247. /package/src/components/forms/__tests__/{FormInput.test.tsx → FormInput.spec.tsx} +0 -0
  248. /package/src/components/forms/__tests__/{FormSelect.test.tsx → FormSelect.spec.tsx} +0 -0
  249. /package/src/components/forms/__tests__/{useEditorDialog.test.ts → useEditorDialog.spec.ts} +0 -0
  250. /package/src/components/grids/__tests__/{ContentListGrid.test.tsx → ContentListGrid.spec.tsx} +0 -0
  251. /package/src/components/tables/__tests__/{ContentListTable.test.tsx → ContentListTable.rendering.spec.tsx} +0 -0
  252. /package/src/contexts/__tests__/{DirectionContext.test.tsx → DirectionContext.spec.tsx} +0 -0
  253. /package/src/contexts/__tests__/{SessionContext.test.tsx → SessionContext.spec.tsx} +0 -0
  254. /package/src/contexts/__tests__/{SocketContext.session.test.tsx → SocketContext.session.spec.tsx} +0 -0
  255. /package/src/core/abstracts/__tests__/{AbstractService.locale.test.ts → AbstractService.locale.spec.ts} +0 -0
  256. /package/src/core/endpoint/__tests__/{EndpointCreator.test.ts → EndpointCreator.spec.ts} +0 -0
  257. /package/src/core/factories/__tests__/{JsonApiDataFactory.test.ts → JsonApiDataFactory.spec.ts} +0 -0
  258. /package/src/core/factories/__tests__/{RehydrationFactory.test.ts → RehydrationFactory.spec.ts} +0 -0
  259. /package/src/core/registry/__tests__/{DataClassRegistry.test.ts → DataClassRegistry.spec.ts} +0 -0
  260. /package/src/core/registry/__tests__/{ModuleRegistrar.test.ts → ModuleRegistrar.spec.ts} +0 -0
  261. /package/src/core/registry/__tests__/{viewportStore.test.ts → viewportStore.spec.ts} +0 -0
  262. /package/src/core/utils/__tests__/{translateResponse.test.ts → translateResponse.spec.ts} +0 -0
  263. /package/src/features/ai-connection/data/__tests__/{ai-connection.test.ts → ai-connection.spec.ts} +0 -0
  264. /package/src/features/company/data/{company.test.ts → company.spec.ts} +0 -0
  265. /package/src/features/company/hooks/{useSubscriptionStatus.test.ts → useSubscriptionStatus.spec.ts} +0 -0
  266. /package/src/hooks/__tests__/{useDataListRetriever.test.ts → useDataListRetriever.spec.ts} +0 -0
  267. /package/src/hooks/__tests__/{useDebounce.test.ts → useDebounce.spec.ts} +0 -0
  268. /package/src/hooks/__tests__/{useSocket.render.test.ts → useSocket.render.spec.ts} +0 -0
  269. /package/src/lib/__tests__/{direction.test.ts → direction.spec.ts} +0 -0
  270. /package/src/next-config/__tests__/{createNextConfig.test.ts → createNextConfig.spec.ts} +0 -0
  271. /package/src/server/__tests__/{JsonApiServer.test.ts → JsonApiServer.spec.ts} +0 -0
  272. /package/src/server/__tests__/{ServerSession.read.test.ts → ServerSession.read.spec.ts} +0 -0
  273. /package/src/server/__tests__/{cache.test.ts → cache.spec.ts} +0 -0
  274. /package/src/server/__tests__/{cachedGet.test.ts → cachedGet.spec.ts} +0 -0
  275. /package/src/server/__tests__/{request.test.ts → request.spec.ts} +0 -0
  276. /package/src/shadcnui/ui/__tests__/{sidebar.menu-skeleton.test.tsx → sidebar.menu-skeleton.spec.tsx} +0 -0
  277. /package/src/shadcnui/ui/__tests__/{sidebar.session.test.tsx → sidebar.session.spec.tsx} +0 -0
  278. /package/src/unified/__tests__/{JsonApiGet.cache.test.ts → JsonApiGet.cache.spec.ts} +0 -0
  279. /package/src/unified/__tests__/{JsonApiRequest.test.ts → JsonApiRequest.spec.ts} +0 -0
  280. /package/src/utils/__tests__/{exists.test.ts → exists.spec.ts} +0 -0
  281. /package/src/utils/__tests__/{italian-validators.test.ts → italian-validators.spec.ts} +0 -0
  282. /package/src/utils/__tests__/{map-thenable.test.ts → map-thenable.spec.ts} +0 -0
  283. /package/src/utils/__tests__/{use-mobile.test.tsx → use-mobile.spec.tsx} +0 -0
  284. /package/src/utils/{cn.test.ts → cn.spec.ts} +0 -0
  285. /package/src/utils/{isRemoteImageSrc.test.ts → isRemoteImageSrc.spec.ts} +0 -0
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Activity } from "lucide-react";
4
5
  import { Card, CardContent, CardHeader } from "../../../../../shadcnui";
6
+ import { type BillingFormatOptions, formatFixed, useBillingFormatOptions } from "../../../components/utils";
5
7
  import { MeterInterface, MeterSummaryInterface } from "../../data/stripe-usage.interface";
6
8
 
7
9
  type UsageSummaryCardProps = {
@@ -22,20 +24,22 @@ function getProgressColor(percentage: number | null): string {
22
24
  /**
23
25
  * Format a Unix timestamp to readable date
24
26
  */
25
- function formatDate(date: Date | undefined): string {
26
- if (!date) return "N/A";
27
+ function formatDate(date: Date | undefined, { locale, t }: Required<BillingFormatOptions>): string {
28
+ if (!date) return t("billing.ui.not_available");
27
29
 
28
30
  try {
29
- return new Intl.DateTimeFormat("en-US", {
31
+ return new Intl.DateTimeFormat(locale, {
30
32
  month: "short",
31
33
  day: "numeric",
32
34
  }).format(new Date(date));
33
35
  } catch (_error) {
34
- return "Invalid Date";
36
+ return t("billing.ui.invalid_date");
35
37
  }
36
38
  }
37
39
 
38
40
  export function UsageSummaryCard({ meter, summary }: UsageSummaryCardProps) {
41
+ const t = useTranslations();
42
+ const fmt = useBillingFormatOptions();
39
43
  const currentUsage = summary?.aggregatedValue ?? 0;
40
44
  const limit = (meter as any).limit; // Meters may have optional limit field
41
45
  const percentage = limit && limit > 0 ? (currentUsage / limit) * 100 : null;
@@ -60,8 +64,12 @@ export function UsageSummaryCard({ meter, summary }: UsageSummaryCardProps) {
60
64
  <CardContent className="flex flex-col gap-y-4">
61
65
  {/* Current Usage */}
62
66
  <div>
63
- <p className="text-3xl font-bold">{currentUsage.toLocaleString()}</p>
64
- {hasLimit && <p className="text-muted-foreground text-sm">of {limit.toLocaleString()} used</p>}
67
+ <p className="text-3xl font-bold">{currentUsage.toLocaleString(fmt.locale)}</p>
68
+ {hasLimit && (
69
+ <p className="text-muted-foreground text-sm">
70
+ {t("billing.ui.usage.of_limit_used", { limit: limit.toLocaleString(fmt.locale) })}
71
+ </p>
72
+ )}
65
73
  </div>
66
74
 
67
75
  {/* Progress Bar */}
@@ -70,17 +78,24 @@ export function UsageSummaryCard({ meter, summary }: UsageSummaryCardProps) {
70
78
  <div className="h-2 w-full overflow-hidden rounded-full bg-gray-200">
71
79
  <div className={`h-full transition-all ${progressColor}`} style={{ width: `${progressWidth}%` }} />
72
80
  </div>
73
- <p className="text-muted-foreground text-sm">{percentage?.toFixed(1)}% used</p>
81
+ <p className="text-muted-foreground text-sm">
82
+ {t("billing.ui.usage.percent_used", {
83
+ value: percentage !== null ? formatFixed(percentage, fmt.locale, 1) : "",
84
+ })}
85
+ </p>
74
86
  </div>
75
87
  ) : (
76
- <p className="text-muted-foreground text-sm">No limit set</p>
88
+ <p className="text-muted-foreground text-sm">{t("billing.ui.usage.no_limit")}</p>
77
89
  )}
78
90
 
79
91
  {/* Period Information */}
80
92
  {summary && summary.start && summary.end && (
81
93
  <div className="border-t pt-3">
82
94
  <p className="text-muted-foreground text-xs">
83
- Period: {formatDate(summary.start)} - {formatDate(summary.end)}
95
+ {t("billing.ui.usage.period", {
96
+ start: formatDate(summary.start, fmt),
97
+ end: formatDate(summary.end, fmt),
98
+ })}
84
99
  </p>
85
100
  </div>
86
101
  )}
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../../../../shadcnui";
4
5
  import { SectionHeader } from "../../../../../components/typography";
6
+ import { type BillingFormatOptions, useBillingFormatOptions } from "../../../components/utils";
5
7
  import { StripeUsageInterface } from "../../data/stripe-usage.interface";
6
8
 
7
9
  type UsageHistoryTableProps = {
@@ -11,13 +13,13 @@ type UsageHistoryTableProps = {
11
13
  /**
12
14
  * Format a date with time for usage history
13
15
  */
14
- function formatDateTime(date: Date | string | undefined): string {
15
- if (!date) return "N/A";
16
+ function formatDateTime(date: Date | string | undefined, { locale, t }: Required<BillingFormatOptions>): string {
17
+ if (!date) return t("billing.ui.not_available");
16
18
 
17
19
  const dateObj = typeof date === "string" ? new Date(date) : date;
18
20
 
19
21
  try {
20
- return new Intl.DateTimeFormat("en-US", {
22
+ return new Intl.DateTimeFormat(locale, {
21
23
  month: "short",
22
24
  day: "numeric",
23
25
  year: "numeric",
@@ -25,36 +27,38 @@ function formatDateTime(date: Date | string | undefined): string {
25
27
  minute: "2-digit",
26
28
  }).format(dateObj);
27
29
  } catch (_error) {
28
- return "Invalid Date";
30
+ return t("billing.ui.invalid_date");
29
31
  }
30
32
  }
31
33
 
32
34
  export function UsageHistoryTable({ usageRecords }: UsageHistoryTableProps) {
35
+ const t = useTranslations();
36
+ const fmt = useBillingFormatOptions();
33
37
  if (usageRecords.length === 0) {
34
38
  return (
35
39
  <div className="rounded-lg border p-8 text-center">
36
- <p className="text-muted-foreground">No usage history available.</p>
40
+ <p className="text-muted-foreground">{t("billing.ui.usage.history_empty")}</p>
37
41
  </div>
38
42
  );
39
43
  }
40
44
 
41
45
  return (
42
46
  <div className="flex w-full flex-col gap-y-4">
43
- <SectionHeader level={2}>Usage History</SectionHeader>
47
+ <SectionHeader level={2}>{t("billing.ui.usage.history_title")}</SectionHeader>
44
48
  <div className="overflow-clip rounded-lg border">
45
49
  <Table>
46
50
  <TableHeader className="bg-muted">
47
51
  <TableRow>
48
- <TableHead>Date & Time</TableHead>
49
- <TableHead>Meter Event</TableHead>
50
- <TableHead className="text-end">Quantity</TableHead>
51
- <TableHead>Event ID</TableHead>
52
+ <TableHead>{t("billing.ui.columns.date_time")}</TableHead>
53
+ <TableHead>{t("billing.ui.columns.meter_event")}</TableHead>
54
+ <TableHead className="text-end">{t("billing.ui.columns.quantity")}</TableHead>
55
+ <TableHead>{t("billing.ui.columns.event_id")}</TableHead>
52
56
  </TableRow>
53
57
  </TableHeader>
54
58
  <TableBody>
55
59
  {usageRecords.map((record) => {
56
- const dateTime = formatDateTime(record.timestamp);
57
- const quantity = record.quantity.toLocaleString();
60
+ const dateTime = formatDateTime(record.timestamp, fmt);
61
+ const quantity = record.quantity.toLocaleString(fmt.locale);
58
62
 
59
63
  return (
60
64
  <TableRow key={record.id}>
@@ -5,6 +5,7 @@ import Link from "next/link";
5
5
 
6
6
  import { cn } from "@/lib/utils";
7
7
  import { useTranslations } from "next-intl";
8
+ import { useI18nLocale } from "../../../../i18n";
8
9
  import { Button, Separator, Tooltip, TooltipContent, TooltipTrigger } from "../../../../shadcnui";
9
10
  import { useCurrentUserContext } from "../../../user/contexts";
10
11
 
@@ -30,7 +31,7 @@ interface TokenStatusIndicatorProps {
30
31
  * which overstates the debt. trunc keeps it at -1 and matches floor for
31
32
  * non-negative values.
32
33
  */
33
- const asWholeCredits = (value: number): string => Math.trunc(value).toLocaleString();
34
+ const asWholeCredits = (value: number, locale: string): string => Math.trunc(value).toLocaleString(locale);
34
35
 
35
36
  /**
36
37
  * TokenStatusIndicator displays the current status of available monthly and extra pages
@@ -50,6 +51,7 @@ export function TokenStatusIndicator({
50
51
  }: TokenStatusIndicatorProps) {
51
52
  const { company } = useCurrentUserContext();
52
53
  const t = useTranslations();
54
+ const locale = useI18nLocale();
53
55
 
54
56
  // Don't render if no company data
55
57
  if (!company) return null;
@@ -114,7 +116,7 @@ export function TokenStatusIndicator({
114
116
  {t("billing.tokens.monthly", { defaultValue: "Monthly Pages" })}:
115
117
  </span>
116
118
  <span className={cn("font-medium", getStatusColor())}>
117
- {asWholeCredits(availableMonthlyCredits)} / {asWholeCredits(monthlyCredits)}
119
+ {asWholeCredits(availableMonthlyCredits, locale)} / {asWholeCredits(monthlyCredits, locale)}
118
120
  </span>
119
121
  </div>
120
122
  <div className="flex items-center justify-between gap-4">
@@ -131,7 +133,7 @@ export function TokenStatusIndicator({
131
133
  <span className="text-muted-foreground">
132
134
  {t("billing.tokens.available_extra", { defaultValue: "Extra Pages" })}:
133
135
  </span>
134
- <span className="font-medium text-blue-500">{asWholeCredits(availableExtraCredits)}</span>
136
+ <span className="font-medium text-blue-500">{asWholeCredits(availableExtraCredits, locale)}</span>
135
137
  </div>
136
138
  <Link href="/settings/billing?action=subscribe" className="w-full flex justify-end my-4">
137
139
  <Button variant="outline" size="sm">
@@ -146,13 +148,13 @@ export function TokenStatusIndicator({
146
148
  <>
147
149
  {getBatteryIcon()}
148
150
  <span className={cn(textSize, "text-muted-foreground font-medium leading-none")}>
149
- {asWholeCredits(availableMonthlyCredits)}
151
+ {asWholeCredits(availableMonthlyCredits, locale)}
150
152
  </span>
151
153
  {showExtraPages && availableExtraCredits > 0 && (
152
154
  <span className="inline-flex items-center gap-0.5">
153
155
  <PlusCircle className={cn(smallIconSize, "text-blue-500")} />
154
156
  <span className={cn(textSize, "text-blue-500 font-medium leading-none")}>
155
- {asWholeCredits(availableExtraCredits)}
157
+ {asWholeCredits(availableExtraCredits, locale)}
156
158
  </span>
157
159
  </span>
158
160
  )}
@@ -182,7 +182,7 @@ function CompanyEditorInternal({
182
182
  }
183
183
 
184
184
  if (fiscalRef.current && !fiscalRef.current.validate()) {
185
- throw new Error("Fiscal data validation failed");
185
+ throw new Error(t("company.fiscal_data_invalid"));
186
186
  }
187
187
 
188
188
  const payload: CompanyInput = {
@@ -230,7 +230,7 @@ function CompanyEditorInternal({
230
230
  {file || company?.logo ? (
231
231
  <Image
232
232
  src={file ? URL.createObjectURL(file) : company?.logo || ""}
233
- alt="Company Logo"
233
+ alt={t("company.logo_alt")}
234
234
  width={200}
235
235
  height={200}
236
236
  unoptimized={isRemoteImageSrc(file ? undefined : company?.logo)}
@@ -24,7 +24,7 @@ export function HelpArticleBody(props: {
24
24
  return (
25
25
  <article className="prose dark:prose-invert max-w-none">
26
26
  <nav className="text-muted-foreground mb-2 text-xs">
27
- <Link href={generateUrl({ page: "/help" })}>Help</Link>
27
+ <Link href={generateUrl({ page: "/help" })}>{t("help.title")}</Link>
28
28
  {" · "}
29
29
  <Link href={generateUrl({ page: `/help/${howToType}` })}>{t(`help.modes.${howToType}`)}</Link>
30
30
  </nav>
@@ -12,7 +12,7 @@ export function HelpHeader() {
12
12
  const { currentUser } = useCurrentUserContext();
13
13
  const { brand } = useHelp();
14
14
  const logo = brand?.logo;
15
- const label = brand?.label ?? "Help";
15
+ const label = brand?.label ?? t("help.title");
16
16
  const appHref = brand?.appHref ?? "/";
17
17
 
18
18
  return (
@@ -56,7 +56,7 @@ export function HelpSideNav() {
56
56
  const isActive = (mode: string, slug: string) => pathname.endsWith(`/help/${mode}/${slug}`);
57
57
 
58
58
  return (
59
- <nav aria-label="Help navigation" className="flex h-full flex-col gap-3 overflow-y-auto p-4">
59
+ <nav aria-label={t("help.sideNav.label")} className="flex h-full flex-col gap-3 overflow-y-auto p-4">
60
60
  <input
61
61
  type="search"
62
62
  value={query}
@@ -3,12 +3,14 @@
3
3
  * Consuming apps must define each entry in their messages/<locale>.json.
4
4
  */
5
5
  export const HELP_I18N_KEYS = [
6
+ "help.title",
6
7
  "help.modes.tutorial",
7
8
  "help.modes.how-to",
8
9
  "help.modes.reference",
9
10
  "help.modes.explanation",
10
11
  "help.sideNav.filterPlaceholder",
11
12
  "help.sideNav.noMatches",
13
+ "help.sideNav.label",
12
14
  "help.toc.title",
13
15
  "help.article.lastUpdated",
14
16
  "help.article.previous",
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import React, { Component, ReactNode } from "react";
4
5
 
5
6
  interface Props {
@@ -12,6 +13,21 @@ interface State {
12
13
  error?: Error;
13
14
  }
14
15
 
16
+ // Class components cannot call hooks, so the default fallback is a function component.
17
+ function NotificationErrorFallback({ onRetry }: { onRetry: () => void }) {
18
+ const t = useTranslations();
19
+ return (
20
+ <div className="flex items-center justify-center p-4 text-center">
21
+ <div className="text-muted-foreground text-sm">
22
+ <p>{t("notification.ui.error_boundary.message")}</p>
23
+ <button onClick={onRetry} className="text-primary mt-2">
24
+ {t("notification.ui.error_boundary.retry")}
25
+ </button>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
30
+
15
31
  export class NotificationErrorBoundary extends Component<Props, State> {
16
32
  constructor(props: Props) {
17
33
  super(props);
@@ -29,18 +45,7 @@ export class NotificationErrorBoundary extends Component<Props, State> {
29
45
 
30
46
  render() {
31
47
  if (this.state.hasError) {
32
- return (
33
- this.props.fallback || (
34
- <div className="flex items-center justify-center p-4 text-center">
35
- <div className="text-muted-foreground text-sm">
36
- <p>Something went wrong with notifications.</p>
37
- <button onClick={() => this.setState({ hasError: false })} className="text-primary mt-2">
38
- Try again
39
- </button>
40
- </div>
41
- </div>
42
- )
43
- );
48
+ return this.props.fallback || <NotificationErrorFallback onRetry={() => this.setState({ hasError: false })} />;
44
49
  }
45
50
 
46
51
  return this.props.children;
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { useSearchParams } from "next/navigation";
4
5
  import { useState } from "react";
5
6
  import { RoundPageContainer } from "../../../../components/containers/RoundPageContainer";
@@ -10,6 +11,7 @@ import { NotificationsList } from "../lists/NotificationsList";
10
11
  import { Modules } from "../../../../core";
11
12
 
12
13
  function NotificationsListContainerContent() {
14
+ const t = useTranslations();
13
15
  const searchParams = useSearchParams();
14
16
  const { error } = useNotificationContext();
15
17
 
@@ -20,8 +22,8 @@ function NotificationsListContainerContent() {
20
22
  return (
21
23
  <div className="flex items-center justify-center p-8 text-center">
22
24
  <div className="text-destructive text-xs/relaxed">
23
- <p>Error loading notifications: {error}</p>
24
- <p className="text-muted-foreground mt-2">Please try refreshing the page.</p>
25
+ <p>{t("notification.ui.list.error_loading", { error })}</p>
26
+ <p className="text-muted-foreground mt-2">{t("notification.ui.list.refresh_hint")}</p>
25
27
  </div>
26
28
  </div>
27
29
  );
@@ -4,7 +4,8 @@ import { useTranslations } from "next-intl";
4
4
  import { ReactElement, useEffect, useState } from "react";
5
5
  import { usePageUrlGenerator } from "../../../../hooks";
6
6
  import { Link } from "../../../../shadcnui";
7
- import { formatDate } from "../../../../utils";
7
+ import { useI18nLocale } from "../../../../i18n";
8
+ import { dateFormatLocale, formatDate } from "../../../../utils";
8
9
  import { UserInterface } from "../../../user";
9
10
  import { UserAvatar } from "../../../user/components";
10
11
  import { NotificationInterface } from "../../data";
@@ -36,6 +37,7 @@ export function NotificationToast(
36
37
  t: any,
37
38
  generateUrl: any,
38
39
  reouter: any,
40
+ locale?: string,
39
41
  ): {
40
42
  title: string;
41
43
  description: string | ReactElement<any>;
@@ -67,7 +69,7 @@ export function NotificationToast(
67
69
  })}
68
70
  </p>
69
71
  <div className="text-muted-foreground mt-1 w-full text-xs">
70
- {formatDate(new Date(notification.createdAt), "dateTime")}
72
+ {formatDate(new Date(notification.createdAt), "dateTime", dateFormatLocale(locale))}
71
73
  </div>
72
74
  </div>
73
75
  </div>
@@ -87,6 +89,7 @@ export function NotificationMenuItem({ notification, closePopover }: TaskComment
87
89
  const generateUrl = usePageUrlGenerator();
88
90
  const [isRead, setIsRead] = useState<boolean>(false);
89
91
  const t = useTranslations();
92
+ const dateLocale = dateFormatLocale(useI18nLocale());
90
93
 
91
94
  useEffect(() => {
92
95
  setIsRead(notification.isRead);
@@ -112,7 +115,7 @@ export function NotificationMenuItem({ notification, closePopover }: TaskComment
112
115
  })}
113
116
  </p>
114
117
  <div className="text-muted-foreground mt-0.5 text-xs">
115
- {formatDate(new Date(notification.createdAt), "default")}
118
+ {formatDate(new Date(notification.createdAt), "default", dateLocale)}
116
119
  </div>
117
120
  </div>
118
121
  {!isRead && <span className="bg-primary mt-1.5 size-2 shrink-0 rounded-full" aria-hidden />}
@@ -4,7 +4,7 @@ import { useTranslations } from "next-intl";
4
4
  import React, { createContext, ReactElement, useCallback, useContext, useEffect, useRef, useState } from "react";
5
5
  import { SharedProvider } from "../../../contexts";
6
6
  import { Modules } from "../../../core";
7
- import { useI18nRouter } from "../../../i18n";
7
+ import { useI18nLocale, useI18nRouter } from "../../../i18n";
8
8
  import { BreadcrumbItemData } from "../../../interfaces";
9
9
  import { NotificationMenuItem, NotificationToast } from "../components/notifications/Notification";
10
10
  import { getRoleId, isRolesConfigured } from "../../../roles";
@@ -47,6 +47,11 @@ type NotificationContextProviderProps = {
47
47
 
48
48
  export const NotificationContextProvider = ({ children }: NotificationContextProviderProps) => {
49
49
  const t = useTranslations();
50
+ const locale = useI18nLocale();
51
+ // A ref, not a dependency: the translator only words the fallback errors and must
52
+ // not re-create loadNotifications, whose identity drives the initial-load effect.
53
+ const tRef = useRef(t);
54
+ tRef.current = t;
50
55
  const router = useI18nRouter();
51
56
 
52
57
  const [notifications, setNotifications] = useState<NotificationInterface[]>([]);
@@ -105,7 +110,7 @@ export const NotificationContextProvider = ({ children }: NotificationContextPro
105
110
  setNotifications(fetchedNotifications);
106
111
  setLastLoaded(Date.now());
107
112
  } catch (error) {
108
- const errorMessage = error instanceof Error ? error.message : "Failed to load notifications";
113
+ const errorMessage = error instanceof Error ? error.message : tRef.current("notification.ui.errors.load");
109
114
  setError(errorMessage);
110
115
  } finally {
111
116
  setIsLoading(false);
@@ -156,7 +161,7 @@ export const NotificationContextProvider = ({ children }: NotificationContextPro
156
161
  setNotifications(allNotifications);
157
162
  setLastLoaded(Date.now());
158
163
  } catch (error) {
159
- const errorMessage = error instanceof Error ? error.message : "Failed to mark notifications as read";
164
+ const errorMessage = error instanceof Error ? error.message : tRef.current("notification.ui.errors.mark_read");
160
165
  setError(errorMessage);
161
166
  throw error;
162
167
  } finally {
@@ -176,7 +181,7 @@ export const NotificationContextProvider = ({ children }: NotificationContextPro
176
181
  onClick: () => void;
177
182
  };
178
183
  } => {
179
- return NotificationToast(notification, t, generateUrl, router);
184
+ return NotificationToast(notification, t, generateUrl, router, locale);
180
185
  };
181
186
 
182
187
  const generateNotification = (notification: NotificationInterface, closePopover: () => void) => {
@@ -125,6 +125,19 @@ describe("NotificationContextProvider failed load", () => {
125
125
  expect(result.current.notifications).toEqual([]);
126
126
  expect(result.current.error).toBe("500:");
127
127
  });
128
+
129
+ it("words a failure without a message through the translator, not an English literal", async () => {
130
+ vi.mocked(NotificationService.findMany).mockRejectedValue("offline");
131
+
132
+ const { result } = renderHook(() => useNotificationContext(), { wrapper });
133
+
134
+ await act(async () => {
135
+ await result.current.loadNotifications();
136
+ });
137
+
138
+ // The suite's next-intl mock returns the key itself.
139
+ expect(result.current.error).toBe("notification.ui.errors.load");
140
+ });
128
141
  });
129
142
 
130
143
  describe("NotificationContextProvider first render", () => {
@@ -6,7 +6,8 @@ import { useTranslations } from "next-intl";
6
6
  import { useMemo } from "react";
7
7
  import { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from "../../../hooks";
8
8
  import { Button, Tooltip, TooltipContent, TooltipTrigger } from "../../../shadcnui";
9
- import { formatDate } from "../../../utils";
9
+ import { useI18nLocale } from "../../../i18n";
10
+ import { dateFormatLocale, formatDate } from "../../../utils";
10
11
  import { generateNotificationData } from "../components/notifications/Notification";
11
12
  import { useNotificationContext } from "../contexts/NotificationContext";
12
13
  import { NotificationIcon } from "../components/notifications/NotificationIcon";
@@ -32,6 +33,7 @@ export const useNotificationTableStructure: UseTableStructureHook<NotificationIn
32
33
  params,
33
34
  ) => {
34
35
  const t = useTranslations();
36
+ const dateLocale = dateFormatLocale(useI18nLocale());
35
37
  const generateUrl = usePageUrlGenerator();
36
38
  const { loadNotifications } = useNotificationContext();
37
39
 
@@ -123,7 +125,7 @@ export const useNotificationTableStructure: UseTableStructureHook<NotificationIn
123
125
  shows how long ago it arrived. The two must match. */
124
126
  return (
125
127
  <span className="text-muted-foreground text-xs">
126
- {formatDate(new Date(notification.createdAt), "default")}
128
+ {formatDate(new Date(notification.createdAt), "default", dateLocale)}
127
129
  </span>
128
130
  );
129
131
  },
@@ -1,6 +1,8 @@
1
1
  "use client";
2
2
 
3
+ import { useTranslations } from "next-intl";
3
4
  import { formatDistanceToNow } from "date-fns";
5
+ import { useI18nDateFnsLocale } from "../../../i18n";
4
6
  import { Key, MoreVertical, Pencil, Trash2 } from "lucide-react";
5
7
  import {
6
8
  Badge,
@@ -32,13 +34,15 @@ export interface OAuthClientCardProps {
32
34
  * Card component for displaying an OAuth client in a list
33
35
  */
34
36
  export function OAuthClientCard({ client, onClick, onEdit, onDelete }: OAuthClientCardProps) {
37
+ const t = useTranslations();
38
+ const dateFnsLocale = useI18nDateFnsLocale();
35
39
  // Truncate client ID for display
36
40
  const truncatedId =
37
41
  client.clientId.length > 12 ? `${client.clientId.slice(0, 8)}...${client.clientId.slice(-4)}` : client.clientId;
38
42
 
39
43
  const createdAgo = client.createdAt
40
- ? formatDistanceToNow(new Date(client.createdAt), { addSuffix: true })
41
- : "Unknown";
44
+ ? formatDistanceToNow(new Date(client.createdAt), { addSuffix: true, locale: dateFnsLocale })
45
+ : t("oauth.ui.card.unknown");
42
46
 
43
47
  return (
44
48
  <Card
@@ -52,7 +56,9 @@ export function OAuthClientCard({ client, onClick, onEdit, onDelete }: OAuthClie
52
56
  <CardTitle className="text-lg">{client.name}</CardTitle>
53
57
  </div>
54
58
  <div className="flex items-center gap-2">
55
- <Badge variant={client.isActive ? "default" : "secondary"}>{client.isActive ? "Active" : "Inactive"}</Badge>
59
+ <Badge variant={client.isActive ? "default" : "secondary"}>
60
+ {client.isActive ? t("oauth.ui.card.active") : t("oauth.ui.card.inactive")}
61
+ </Badge>
56
62
  {(onEdit || onDelete) && (
57
63
  <DropdownMenu>
58
64
  <DropdownMenuTrigger onClick={(e) => e.stopPropagation()}>
@@ -69,7 +75,7 @@ export function OAuthClientCard({ client, onClick, onEdit, onDelete }: OAuthClie
69
75
  }}
70
76
  >
71
77
  <Pencil className="h-4 w-4 me-2" />
72
- Edit
78
+ {t("ui.buttons.edit")}
73
79
  </DropdownMenuItem>
74
80
  )}
75
81
  {onDelete && (
@@ -81,7 +87,7 @@ export function OAuthClientCard({ client, onClick, onEdit, onDelete }: OAuthClie
81
87
  className="text-destructive"
82
88
  >
83
89
  <Trash2 className="h-4 w-4 me-2" />
84
- Delete
90
+ {t("ui.buttons.delete")}
85
91
  </DropdownMenuItem>
86
92
  )}
87
93
  </DropdownMenuContent>
@@ -94,8 +100,8 @@ export function OAuthClientCard({ client, onClick, onEdit, onDelete }: OAuthClie
94
100
  <CardContent>
95
101
  <div className="flex flex-wrap gap-x-4 gap-y-1 text-sm text-muted-foreground">
96
102
  <span className="tabular-nums">{truncatedId}</span>
97
- <span>Created {createdAgo}</span>
98
- <span>{client.isConfidential ? "Confidential" : "Public"}</span>
103
+ <span>{t("oauth.ui.card.created", { ago: createdAgo })}</span>
104
+ <span>{client.isConfidential ? t("oauth.ui.card.confidential") : t("oauth.ui.card.public")}</span>
99
105
  </div>
100
106
  </CardContent>
101
107
  </Card>