@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.
- package/dist/{AssistantMessageInterface-CubxuBIJ.d.ts → AssistantMessageInterface-DHJJO_Z_.d.ts} +1 -1
- package/dist/{AssistantMessageInterface-sHjI2zPN.d.mts → AssistantMessageInterface-S7oyERTT.d.mts} +1 -1
- package/dist/{BlockNoteEditor-VOYKZOP5.mjs → BlockNoteEditor-HXAHDJA7.mjs} +34 -27
- package/dist/BlockNoteEditor-HXAHDJA7.mjs.map +1 -0
- package/dist/{BlockNoteEditor-BENYTTCZ.js → BlockNoteEditor-LLCZDFLZ.js} +50 -43
- package/dist/BlockNoteEditor-LLCZDFLZ.js.map +1 -0
- package/dist/{BlockNoteViewer-USLR4Y7C.js → BlockNoteViewer-77CCTC2U.js} +3 -3
- package/dist/{BlockNoteViewer-USLR4Y7C.js.map → BlockNoteViewer-77CCTC2U.js.map} +1 -1
- package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs → BlockNoteViewer-MDPO6BXN.mjs} +2 -2
- package/dist/{auth.interface-Df4vXbNz.d.mts → auth.interface-CeFCz9NU.d.mts} +1 -1
- package/dist/{auth.interface-BzI4onI_.d.ts → auth.interface-D1R7VVky.d.ts} +1 -1
- package/dist/billing/index.d.mts +40 -6
- package/dist/billing/index.d.ts +40 -6
- package/dist/billing/index.js +806 -774
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +494 -462
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-6ISF5KNQ.mjs → chunk-2VXDZRYY.mjs} +1103 -816
- package/dist/chunk-2VXDZRYY.mjs.map +1 -0
- package/dist/{chunk-4BZJ4LAB.js → chunk-4ORLPEK7.js} +136 -104
- package/dist/chunk-4ORLPEK7.js.map +1 -0
- package/dist/{chunk-ZZ6E33FO.mjs → chunk-6DRSSW5O.mjs} +36 -4
- package/dist/chunk-6DRSSW5O.mjs.map +1 -0
- package/dist/{chunk-KS6WZP2E.js → chunk-6SMJRAE3.js} +4 -4
- package/dist/{chunk-KS6WZP2E.js.map → chunk-6SMJRAE3.js.map} +1 -1
- package/dist/{chunk-WQ77CUUZ.js → chunk-GPXYQMOZ.js} +7 -7
- package/dist/{chunk-WQ77CUUZ.js.map → chunk-GPXYQMOZ.js.map} +1 -1
- package/dist/{chunk-46S3KPLU.mjs → chunk-ILFFC6BG.mjs} +2 -2
- package/dist/{chunk-HZVVRIJQ.mjs → chunk-IUS2RHCX.mjs} +2 -2
- package/dist/{chunk-F7RNBGHS.js → chunk-KJMMBUOA.js} +3 -3
- package/dist/{chunk-F7RNBGHS.js.map → chunk-KJMMBUOA.js.map} +1 -1
- package/dist/{chunk-ZO3QQ5CQ.mjs → chunk-MQDR6MEE.mjs} +2 -2
- package/dist/{chunk-ORU7KLCY.js → chunk-VDVIUDY5.js} +1445 -1158
- package/dist/chunk-VDVIUDY5.js.map +1 -0
- package/dist/client/index.d.mts +20 -6
- package/dist/client/index.d.ts +20 -6
- package/dist/client/index.js +6 -4
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +5 -3
- package/dist/components/index.d.mts +59 -21
- package/dist/components/index.d.ts +59 -21
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/{config-CEAq4Ozy.d.mts → config-BP2vyddw.d.mts} +5 -0
- package/dist/{config-DydDHCfb.d.ts → config-BhW2QrRD.d.ts} +5 -0
- package/dist/contexts/index.d.mts +4 -4
- package/dist/contexts/index.d.ts +4 -4
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +17 -9
- package/dist/core/index.d.ts +17 -9
- package/dist/core/index.js +4 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +3 -1
- package/dist/features/analytics/index.d.mts +1 -1
- package/dist/features/analytics/index.d.ts +1 -1
- package/dist/features/analytics/index.js +122 -122
- package/dist/features/analytics/index.mjs +4 -4
- package/dist/features/analytics/server.js +4 -4
- package/dist/features/analytics/server.mjs +2 -2
- package/dist/features/help/index.d.mts +1 -1
- package/dist/features/help/index.d.ts +1 -1
- package/dist/features/help/index.js +42 -40
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +8 -6
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/tokenusage/index.js +125 -125
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/index.d.mts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +4 -2
- package/dist/{notification.interface-P_pNfzZQ.d.mts → notification.interface-BB6GPpZY.d.mts} +1 -1
- package/dist/{notification.interface-BNCvnVCW.d.ts → notification.interface-CmAGrijJ.d.ts} +1 -1
- package/dist/{s3.service-CbUa6a-8.d.ts → s3.service-C-4YpEMu.d.ts} +7 -3
- package/dist/{s3.service-DAp8FbuG.d.mts → s3.service-dVj-nxMl.d.mts} +7 -3
- package/dist/server/index.d.mts +4 -4
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/dist/{useSocket-BHVIyigI.d.ts → useSocket-B0H9pmmH.d.ts} +1 -1
- package/dist/{useSocket-B9gTbuCj.d.mts → useSocket-B5RQb6v2.d.mts} +1 -1
- package/dist/{user.interface-CrmwJLYW.d.ts → user.interface-C0ZdsBZj.d.ts} +2 -0
- package/dist/{user.interface-CSaGWFbg.d.mts → user.interface-CIWGlbqM.d.mts} +2 -0
- package/package.json +1 -1
- package/src/components/containers/RoundPageContainer.tsx +3 -1
- package/src/components/containers/RoundPageContainerTitle.tsx +5 -2
- package/src/components/editors/BlockNoteEditor.tsx +31 -15
- package/src/components/editors/BlockNoteEditorAiActions.tsx +11 -7
- package/src/components/editors/__tests__/{BlockNoteEditorAiActions.test.ts → BlockNoteEditorAiActions.spec.ts} +10 -3
- package/src/components/errors/ErrorDetails.tsx +5 -2
- package/src/components/forms/CommonAssociationForm.tsx +1 -1
- package/src/components/forms/EntityMultiSelector.tsx +6 -2
- package/src/components/forms/EntitySelector.tsx +4 -1
- package/src/components/forms/FileUploader.tsx +3 -2
- package/src/components/forms/MultiFileUploader.tsx +3 -1
- package/src/components/navigations/Breadcrumb.tsx +3 -2
- package/src/components/navigations/Header.tsx +3 -1
- package/src/components/navigations/MobileNavigationBar.tsx +3 -1
- package/src/components/navigations/__tests__/Header.spec.tsx +2 -2
- package/src/components/tables/__tests__/ContentListTable.spec.tsx +1 -1
- package/src/components/tables/cells/cell.id.tsx +13 -2
- package/src/core/abstracts/AbstractService.ts +3 -3
- package/src/core/abstracts/ClientAbstractService.ts +3 -2
- package/src/features/assistant-action/components/ApprovalActionCard.tsx +74 -5
- package/src/features/assistant-action/components/__tests__/ApprovalActionCard.spec.tsx +81 -0
- package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +4 -1
- package/src/features/assistant-message/components/parts/tabs/UsersTab.tsx +2 -1
- package/src/features/auth/components/buttons/GoogleSignInButton.tsx +4 -1
- package/src/features/auth/components/details/LandingComponent.tsx +1 -1
- package/src/features/auth/components/forms/ForgotPassword.tsx +1 -1
- package/src/features/auth/components/forms/Login.tsx +1 -1
- package/src/features/auth/components/forms/Register.tsx +1 -1
- package/src/features/auth/components/forms/ResetPassword.tsx +2 -2
- package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +1 -1
- package/src/features/auth/components/two-factor/PasskeyButton.tsx +1 -1
- package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +21 -11
- package/src/features/billing/components/cards/CustomerInfoCard.tsx +16 -13
- package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +33 -21
- package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +20 -10
- package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +35 -22
- package/src/features/billing/components/containers/BillingDashboardContainer.tsx +24 -24
- package/src/features/billing/components/utils/__tests__/billing-formatters.spec.ts +63 -0
- package/src/features/billing/components/utils/currency.ts +67 -10
- package/src/features/billing/components/utils/date.ts +9 -4
- package/src/features/billing/components/utils/format-options.ts +20 -0
- package/src/features/billing/components/utils/index.ts +3 -1
- package/src/features/billing/components/utils/useBillingFormatOptions.ts +11 -0
- package/src/features/billing/components/widgets/BillingAlertBanner.tsx +8 -9
- package/src/features/billing/contexts/PriceContext.tsx +18 -6
- package/src/features/billing/i18n-keys.ts +212 -0
- package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +8 -8
- package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +11 -9
- package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +4 -4
- package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +9 -7
- package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +10 -8
- package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +34 -29
- package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +12 -9
- package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +9 -6
- package/src/features/billing/stripe-price/components/details/PriceDetails.tsx +4 -3
- package/src/features/billing/stripe-price/components/forms/PriceArchiver.tsx +3 -1
- package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +3 -1
- package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +6 -5
- package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +12 -7
- package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.service.ts +3 -1
- package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +10 -6
- package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +44 -29
- package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +14 -11
- package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +20 -17
- package/src/features/billing/stripe-subscription/components/widgets/IntervalToggle.tsx +4 -2
- package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +16 -7
- package/src/features/billing/stripe-subscription/components/widgets/ProductPricingList.tsx +3 -1
- package/src/features/billing/stripe-subscription/components/widgets/ProductPricingRow.tsx +3 -1
- package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +12 -7
- package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +11 -8
- package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +13 -10
- package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +8 -6
- package/src/features/billing/stripe-subscription/components/wizards/WizardProgressIndicator.tsx +7 -4
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +4 -2
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepPlanSelection.tsx +3 -1
- package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +37 -28
- package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +9 -7
- package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +20 -9
- package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +6 -6
- package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +24 -9
- package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +16 -12
- package/src/features/company/components/details/TokenStatusIndicator.tsx +7 -5
- package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
- package/src/features/help/components/HelpArticleBody.tsx +1 -1
- package/src/features/help/components/HelpHeader.tsx +1 -1
- package/src/features/help/components/HelpSideNav.tsx +1 -1
- package/src/features/help/i18n-keys.ts +2 -0
- package/src/features/notification/components/common/NotificationErrorBoundary.tsx +17 -12
- package/src/features/notification/components/containers/NotificationsListContainer.tsx +4 -2
- package/src/features/notification/components/notifications/Notification.tsx +6 -3
- package/src/features/notification/contexts/NotificationContext.tsx +9 -4
- package/src/features/notification/contexts/__tests__/NotificationContext.spec.tsx +13 -0
- package/src/features/notification/hooks/useNotificationTableStructure.tsx +4 -2
- package/src/features/oauth/components/OAuthClientCard.tsx +13 -7
- package/src/features/oauth/components/OAuthClientDetail.tsx +34 -30
- package/src/features/oauth/components/OAuthClientForm.tsx +24 -24
- package/src/features/oauth/components/OAuthClientList.tsx +10 -6
- package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +10 -8
- package/src/features/oauth/components/OAuthRedirectUriInput.tsx +9 -13
- package/src/features/oauth/components/OAuthScopeSelector.tsx +15 -6
- package/src/features/oauth/components/__tests__/OAuthClientForm.spec.tsx +11 -9
- package/src/features/oauth/components/consent/OAuthScopeList.tsx +4 -2
- package/src/features/oauth/hooks/__tests__/useOAuthScopeText.spec.ts +30 -0
- package/src/features/oauth/hooks/index.ts +1 -0
- package/src/features/oauth/hooks/useOAuthClient.ts +11 -5
- package/src/features/oauth/hooks/useOAuthClients.ts +9 -3
- package/src/features/oauth/hooks/useOAuthConsent.ts +11 -5
- package/src/features/oauth/hooks/useOAuthScopeText.ts +33 -0
- package/src/features/rbac/components/RbacByRoleContainer.tsx +7 -6
- package/src/features/rbac/components/RbacContainer.tsx +7 -6
- package/src/features/rbac/contexts/RbacContext.tsx +3 -3
- package/src/features/user/components/containers/UserContainer.tsx +3 -1
- package/src/features/user/data/__tests__/user.spec.ts +66 -0
- package/src/features/user/data/user.interface.ts +2 -0
- package/src/features/user/data/user.service.ts +9 -0
- package/src/features/user/data/user.ts +7 -0
- package/src/features/waitlist/components/forms/WaitlistForm.tsx +1 -1
- package/src/features/waitlist/components/lists/WaitlistList.tsx +1 -1
- package/src/i18n/__tests__/{config.test.ts → config.spec.ts} +21 -1
- package/src/i18n/config.ts +13 -0
- package/src/i18n/index.ts +1 -0
- package/src/shadcnui/custom/multi-select.tsx +47 -14
- package/src/shadcnui/custom/multiple-selector.tsx +14 -2
- package/src/shadcnui/ui/__tests__/{confirm-dialog.test.tsx → confirm-dialog.spec.tsx} +1 -1
- package/src/shadcnui/ui/__tests__/{cursor-controls.test.tsx → cursor-controls.spec.tsx} +1 -1
- package/src/shadcnui/ui/__tests__/{cursor-menu-items.test.tsx → cursor-menu-items.spec.tsx} +1 -1
- package/src/shadcnui/ui/__tests__/{empty-state.test.tsx → empty-state.spec.tsx} +1 -1
- package/src/shadcnui/ui/__tests__/{spinner.test.tsx → spinner.spec.tsx} +1 -1
- package/src/shadcnui/ui/__tests__/use-ui-text.spec.tsx +76 -0
- package/src/shadcnui/ui/breadcrumb.tsx +9 -2
- package/src/shadcnui/ui/carousel.tsx +15 -4
- package/src/shadcnui/ui/dialog.tsx +15 -2
- package/src/shadcnui/ui/sheet.tsx +6 -1
- package/src/shadcnui/ui/sidebar.tsx +33 -7
- package/src/shadcnui/ui/spinner.tsx +6 -1
- package/src/shadcnui/ui/use-ui-text.ts +49 -0
- package/src/utils/__tests__/{date-formatter.test.ts → date-formatter.spec.ts} +24 -1
- package/src/utils/date-formatter.ts +16 -0
- package/src/utils/index.ts +1 -1
- package/dist/BlockNoteEditor-BENYTTCZ.js.map +0 -1
- package/dist/BlockNoteEditor-VOYKZOP5.mjs.map +0 -1
- package/dist/chunk-4BZJ4LAB.js.map +0 -1
- package/dist/chunk-6ISF5KNQ.mjs.map +0 -1
- package/dist/chunk-ORU7KLCY.js.map +0 -1
- package/dist/chunk-ZZ6E33FO.mjs.map +0 -1
- /package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs.map → BlockNoteViewer-MDPO6BXN.mjs.map} +0 -0
- /package/dist/{chunk-46S3KPLU.mjs.map → chunk-ILFFC6BG.mjs.map} +0 -0
- /package/dist/{chunk-HZVVRIJQ.mjs.map → chunk-IUS2RHCX.mjs.map} +0 -0
- /package/dist/{chunk-ZO3QQ5CQ.mjs.map → chunk-MQDR6MEE.mjs.map} +0 -0
- /package/scripts/{check-rtl-classes.test.ts → check-rtl-classes.spec.ts} +0 -0
- /package/src/client/__tests__/{JsonApiClient.test.ts → JsonApiClient.spec.ts} +0 -0
- /package/src/client/__tests__/{request.test.ts → request.spec.ts} +0 -0
- /package/src/client/hooks/__tests__/{useJsonApiGet.test.tsx → useJsonApiGet.spec.tsx} +0 -0
- /package/src/client/hooks/__tests__/{useJsonApiMutation.test.tsx → useJsonApiMutation.spec.tsx} +0 -0
- /package/src/client/hooks/__tests__/{useRehydration.test.ts → useRehydration.spec.ts} +0 -0
- /package/src/components/editors/__tests__/{BlockNoteEditorMentionInlineContent.test.ts → BlockNoteEditorMentionInlineContent.spec.ts} +0 -0
- /package/src/components/forms/__tests__/{CommonEditorDiscardDialog.test.tsx → CommonEditorDiscardDialog.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{EditorSheet.test.tsx → EditorSheet.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{FormCheckbox.test.tsx → FormCheckbox.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{FormDate.test.tsx → FormDate.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{FormInput.test.tsx → FormInput.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{FormSelect.test.tsx → FormSelect.spec.tsx} +0 -0
- /package/src/components/forms/__tests__/{useEditorDialog.test.ts → useEditorDialog.spec.ts} +0 -0
- /package/src/components/grids/__tests__/{ContentListGrid.test.tsx → ContentListGrid.spec.tsx} +0 -0
- /package/src/components/tables/__tests__/{ContentListTable.test.tsx → ContentListTable.rendering.spec.tsx} +0 -0
- /package/src/contexts/__tests__/{DirectionContext.test.tsx → DirectionContext.spec.tsx} +0 -0
- /package/src/contexts/__tests__/{SessionContext.test.tsx → SessionContext.spec.tsx} +0 -0
- /package/src/contexts/__tests__/{SocketContext.session.test.tsx → SocketContext.session.spec.tsx} +0 -0
- /package/src/core/abstracts/__tests__/{AbstractService.locale.test.ts → AbstractService.locale.spec.ts} +0 -0
- /package/src/core/endpoint/__tests__/{EndpointCreator.test.ts → EndpointCreator.spec.ts} +0 -0
- /package/src/core/factories/__tests__/{JsonApiDataFactory.test.ts → JsonApiDataFactory.spec.ts} +0 -0
- /package/src/core/factories/__tests__/{RehydrationFactory.test.ts → RehydrationFactory.spec.ts} +0 -0
- /package/src/core/registry/__tests__/{DataClassRegistry.test.ts → DataClassRegistry.spec.ts} +0 -0
- /package/src/core/registry/__tests__/{ModuleRegistrar.test.ts → ModuleRegistrar.spec.ts} +0 -0
- /package/src/core/registry/__tests__/{viewportStore.test.ts → viewportStore.spec.ts} +0 -0
- /package/src/core/utils/__tests__/{translateResponse.test.ts → translateResponse.spec.ts} +0 -0
- /package/src/features/ai-connection/data/__tests__/{ai-connection.test.ts → ai-connection.spec.ts} +0 -0
- /package/src/features/company/data/{company.test.ts → company.spec.ts} +0 -0
- /package/src/features/company/hooks/{useSubscriptionStatus.test.ts → useSubscriptionStatus.spec.ts} +0 -0
- /package/src/hooks/__tests__/{useDataListRetriever.test.ts → useDataListRetriever.spec.ts} +0 -0
- /package/src/hooks/__tests__/{useDebounce.test.ts → useDebounce.spec.ts} +0 -0
- /package/src/hooks/__tests__/{useSocket.render.test.ts → useSocket.render.spec.ts} +0 -0
- /package/src/lib/__tests__/{direction.test.ts → direction.spec.ts} +0 -0
- /package/src/next-config/__tests__/{createNextConfig.test.ts → createNextConfig.spec.ts} +0 -0
- /package/src/server/__tests__/{JsonApiServer.test.ts → JsonApiServer.spec.ts} +0 -0
- /package/src/server/__tests__/{ServerSession.read.test.ts → ServerSession.read.spec.ts} +0 -0
- /package/src/server/__tests__/{cache.test.ts → cache.spec.ts} +0 -0
- /package/src/server/__tests__/{cachedGet.test.ts → cachedGet.spec.ts} +0 -0
- /package/src/server/__tests__/{request.test.ts → request.spec.ts} +0 -0
- /package/src/shadcnui/ui/__tests__/{sidebar.menu-skeleton.test.tsx → sidebar.menu-skeleton.spec.tsx} +0 -0
- /package/src/shadcnui/ui/__tests__/{sidebar.session.test.tsx → sidebar.session.spec.tsx} +0 -0
- /package/src/unified/__tests__/{JsonApiGet.cache.test.ts → JsonApiGet.cache.spec.ts} +0 -0
- /package/src/unified/__tests__/{JsonApiRequest.test.ts → JsonApiRequest.spec.ts} +0 -0
- /package/src/utils/__tests__/{exists.test.ts → exists.spec.ts} +0 -0
- /package/src/utils/__tests__/{italian-validators.test.ts → italian-validators.spec.ts} +0 -0
- /package/src/utils/__tests__/{map-thenable.test.ts → map-thenable.spec.ts} +0 -0
- /package/src/utils/__tests__/{use-mobile.test.tsx → use-mobile.spec.tsx} +0 -0
- /package/src/utils/{cn.test.ts → cn.spec.ts} +0 -0
- /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 "
|
|
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(
|
|
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 "
|
|
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 &&
|
|
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">
|
|
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">
|
|
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
|
-
|
|
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 "
|
|
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(
|
|
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 "
|
|
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">
|
|
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}>
|
|
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>
|
|
49
|
-
<TableHead>
|
|
50
|
-
<TableHead className="text-end">
|
|
51
|
-
<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("
|
|
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="
|
|
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" })}>
|
|
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 ?? "
|
|
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="
|
|
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>
|
|
24
|
-
<p className="text-muted-foreground mt-2">
|
|
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 {
|
|
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 : "
|
|
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 : "
|
|
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 {
|
|
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
|
-
: "
|
|
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"}>
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
98
|
-
<span>{client.isConfidential ? "
|
|
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>
|