@carlonicora/nextjs-jsonapi 4.1.2 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +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/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.test.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 +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__/use-ui-text.test.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 +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
|
@@ -2,6 +2,25 @@ import { render, screen } from "@testing-library/react";
|
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { ApprovalActionCard } from "../ApprovalActionCard";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* A message catalogue the test fills in. Keys it does not hold behave like a
|
|
7
|
+
* host app without them: `t()` hands back the key path and `t.has()` is false,
|
|
8
|
+
* so the card must fall back to the humanised attribute name.
|
|
9
|
+
*/
|
|
10
|
+
const messages = vi.hoisted(() => ({}) as Record<string, string>);
|
|
11
|
+
|
|
12
|
+
vi.mock("next-intl", () => {
|
|
13
|
+
const t = (key: string) => messages[key] ?? key;
|
|
14
|
+
(t as unknown as { has: (key: string) => boolean }).has = (key: string) => key in messages;
|
|
15
|
+
return {
|
|
16
|
+
useTranslations: () => t,
|
|
17
|
+
useLocale: () => "en",
|
|
18
|
+
useNow: () => new Date(),
|
|
19
|
+
useTimeZone: () => "UTC",
|
|
20
|
+
useFormatter: () => ({ dateTime: vi.fn(), number: vi.fn(), relativeTime: vi.fn() }),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
5
24
|
const findOne = vi.fn();
|
|
6
25
|
const approve = vi.fn();
|
|
7
26
|
const deny = vi.fn();
|
|
@@ -45,6 +64,7 @@ function makeAction(overrides: Record<string, unknown> = {}) {
|
|
|
45
64
|
|
|
46
65
|
describe("ApprovalActionCard", () => {
|
|
47
66
|
beforeEach(() => {
|
|
67
|
+
for (const key of Object.keys(messages)) delete messages[key];
|
|
48
68
|
findOne.mockReset();
|
|
49
69
|
approve.mockReset();
|
|
50
70
|
deny.mockReset();
|
|
@@ -254,6 +274,67 @@ describe("ApprovalActionCard", () => {
|
|
|
254
274
|
expect(screen.queryByText(UUID_LIKE)).toBeNull();
|
|
255
275
|
});
|
|
256
276
|
|
|
277
|
+
it("labels fields with the entity editor's own message keys when the app defines them", async () => {
|
|
278
|
+
Object.assign(messages, {
|
|
279
|
+
"features.npc.fields.name.label": "Nome",
|
|
280
|
+
"features.npc.fields.tldr.label": "Riassunto",
|
|
281
|
+
"entities.npcs": "PNG",
|
|
282
|
+
});
|
|
283
|
+
findOne.mockResolvedValue(makeAction());
|
|
284
|
+
|
|
285
|
+
render(<ApprovalActionCard actionId="act-1" />);
|
|
286
|
+
|
|
287
|
+
expect(await screen.findByText("Nome")).toBeInTheDocument();
|
|
288
|
+
expect(screen.getByText("Riassunto")).toBeInTheDocument();
|
|
289
|
+
// `related` has no editor key and no generic key: humanised, as before
|
|
290
|
+
expect(screen.getByText("Related")).toBeInTheDocument();
|
|
291
|
+
expect(screen.queryByText("Name")).toBeNull();
|
|
292
|
+
expect(screen.queryByText("Tldr")).toBeNull();
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("falls back to the generic ui.fields key, and finds editor keys for irregular plurals", async () => {
|
|
296
|
+
Object.assign(messages, {
|
|
297
|
+
"ui.fields.related": "Collegati",
|
|
298
|
+
"features.knife.fields.name.label": "Nome del gancio",
|
|
299
|
+
});
|
|
300
|
+
findOne.mockResolvedValue(
|
|
301
|
+
makeAction({
|
|
302
|
+
proposal: JSON.stringify({
|
|
303
|
+
type: "knives",
|
|
304
|
+
attributes: { name: "The sister left behind", publicDescription: "Visible" },
|
|
305
|
+
relationships: { related: [{ id: "r1", type: "npcs", label: "Zoe" }] },
|
|
306
|
+
}),
|
|
307
|
+
}),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
render(<ApprovalActionCard actionId="act-1" />);
|
|
311
|
+
|
|
312
|
+
expect(await screen.findByText("Nome del gancio")).toBeInTheDocument();
|
|
313
|
+
expect(screen.getByText("Collegati")).toBeInTheDocument();
|
|
314
|
+
// no key anywhere: the humanised attribute name, unchanged
|
|
315
|
+
expect(screen.getByText("Public Description")).toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("translates the relationship name on a link proposal", async () => {
|
|
319
|
+
Object.assign(messages, { "features.clue.fields.containedInScene.label": "Contenuto nella scena" });
|
|
320
|
+
findOne.mockResolvedValue(
|
|
321
|
+
makeAction({
|
|
322
|
+
toolName: "link_entities",
|
|
323
|
+
proposal: JSON.stringify({
|
|
324
|
+
type: "clues",
|
|
325
|
+
target: { id: "c1", type: "clues", label: "Bloody knife" },
|
|
326
|
+
relationship: "containedInScene",
|
|
327
|
+
targets: [{ id: "s1", type: "scenes", label: "The docks" }],
|
|
328
|
+
}),
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
render(<ApprovalActionCard actionId="act-1" />);
|
|
333
|
+
|
|
334
|
+
expect(await screen.findByText("Contenuto nella scena")).toBeInTheDocument();
|
|
335
|
+
expect(screen.getByText("The docks")).toBeInTheDocument();
|
|
336
|
+
});
|
|
337
|
+
|
|
257
338
|
it("still renders the summary and the buttons when neither payload parses", async () => {
|
|
258
339
|
findOne.mockResolvedValue(makeAction({ proposal: "{not-json", toolArgs: "{not-json" }));
|
|
259
340
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
4
|
+
|
|
3
5
|
interface Props {
|
|
4
6
|
/** 0-1 (decimal) or 0-100 (percent). Auto-detected. */
|
|
5
7
|
value: number;
|
|
@@ -7,6 +9,7 @@ interface Props {
|
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
export function RelevanceMeter({ value, className = "" }: Props) {
|
|
12
|
+
const t = useTranslations();
|
|
10
13
|
const pct = Math.max(0, Math.min(100, value <= 1 ? value * 100 : value));
|
|
11
14
|
const label = `${Math.round(pct)}%`;
|
|
12
15
|
return (
|
|
@@ -15,7 +18,7 @@ export function RelevanceMeter({ value, className = "" }: Props) {
|
|
|
15
18
|
aria-valuenow={Math.round(pct)}
|
|
16
19
|
aria-valuemin={0}
|
|
17
20
|
aria-valuemax={100}
|
|
18
|
-
aria-label={
|
|
21
|
+
aria-label={`${t("common.relevance")} ${label}`}
|
|
19
22
|
className={`bg-muted relative mx-auto flex h-5 w-20 items-center justify-center overflow-hidden rounded border ${className}`}
|
|
20
23
|
>
|
|
21
24
|
<div className="bg-accent absolute top-0 start-0 h-full" style={{ width: `${pct}%` }} />
|
|
@@ -101,7 +101,8 @@ export function UsersTab({ users, citations, sources }: Props) {
|
|
|
101
101
|
return null;
|
|
102
102
|
}
|
|
103
103
|
const href = generate({ page: module, id: user.id });
|
|
104
|
-
const name =
|
|
104
|
+
const name =
|
|
105
|
+
(user as any).name ?? (user as any).fullName ?? user.identifier ?? t("entities.users", { count: 1 });
|
|
105
106
|
const avatarUrl = (user as any).avatar as string | undefined;
|
|
106
107
|
const showCounts = citationCount > 0 || contentCount > 0;
|
|
107
108
|
return (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { getPublicApiUrl } from "../../../../client/config";
|
|
4
5
|
import { isGoogleAuthEnabled } from "../../../../login";
|
|
5
6
|
import { Button, Link } from "../../../../shadcnui";
|
|
@@ -9,6 +10,8 @@ interface GoogleSignInButtonProps {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function GoogleSignInButton({ referralCode }: GoogleSignInButtonProps) {
|
|
13
|
+
const t = useTranslations();
|
|
14
|
+
|
|
12
15
|
if (!isGoogleAuthEnabled()) {
|
|
13
16
|
return null;
|
|
14
17
|
}
|
|
@@ -45,7 +48,7 @@ export function GoogleSignInButton({ referralCode }: GoogleSignInButtonProps) {
|
|
|
45
48
|
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
46
49
|
/>
|
|
47
50
|
</svg>
|
|
48
|
-
|
|
51
|
+
{t("auth.buttons.sign_in_with_google")}
|
|
49
52
|
</Button>
|
|
50
53
|
</Link>
|
|
51
54
|
);
|
|
@@ -55,7 +55,7 @@ export function LandingComponent() {
|
|
|
55
55
|
{isDiscordAuthEnabled() && (
|
|
56
56
|
<Link href={`${getPublicApiUrl()}auth/discord`} className="flex w-full justify-end">
|
|
57
57
|
<Button className="w-full" variant={`outline`} data-testid="page-login-button-initial-login">
|
|
58
|
-
|
|
58
|
+
{t(`auth.buttons.login_with_discord`)}
|
|
59
59
|
</Button>
|
|
60
60
|
</Link>
|
|
61
61
|
)}
|
|
@@ -152,7 +152,7 @@ export function Login() {
|
|
|
152
152
|
{isDiscordAuthEnabled() && (
|
|
153
153
|
<Link href={buildDiscordOAuthUrl()} className="flex w-full justify-end">
|
|
154
154
|
<Button className="w-full" variant={`outline`} data-testid="page-login-button-initial-login">
|
|
155
|
-
|
|
155
|
+
{t(`auth.buttons.login_with_discord`)}
|
|
156
156
|
</Button>
|
|
157
157
|
</Link>
|
|
158
158
|
)}
|
|
@@ -106,8 +106,8 @@ export function ResetPassword() {
|
|
|
106
106
|
) : (
|
|
107
107
|
<Form {...form}>
|
|
108
108
|
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
109
|
-
<FormPassword form={form} id="password" name={
|
|
110
|
-
<FormPassword form={form} id="passwordRetype" name={
|
|
109
|
+
<FormPassword form={form} id="password" name={t(`user.fields.password.label`)} />
|
|
110
|
+
<FormPassword form={form} id="passwordRetype" name={t(`auth.fields.retype_password.retype_label`)} />
|
|
111
111
|
<Button className="mt-4 w-full" type={"submit"}>
|
|
112
112
|
{t(`auth.buttons.reset_password`)}
|
|
113
113
|
</Button>
|
|
@@ -59,7 +59,7 @@ export function BackupCodesDialog({ remainingCodes, onRegenerate, trigger }: Bac
|
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const handleDownload = () => {
|
|
62
|
-
const text =
|
|
62
|
+
const text = `${t("auth.two_factor.backup_codes")}\n\n${codes.join("\n")}\n\n${t("auth.two_factor.backup_codes_file_note")}`;
|
|
63
63
|
const blob = new Blob([text], { type: "text/plain" });
|
|
64
64
|
const url = URL.createObjectURL(blob);
|
|
65
65
|
const a = document.createElement("a");
|
|
@@ -38,7 +38,7 @@ export function PasskeyButton({ pendingToken, onSuccess, onError, disabled = fal
|
|
|
38
38
|
|
|
39
39
|
onSuccess(auth);
|
|
40
40
|
} catch (error) {
|
|
41
|
-
onError(error instanceof Error ? error : new Error("
|
|
41
|
+
onError(error instanceof Error ? error : new Error(t("auth.two_factor.passkey_failed")));
|
|
42
42
|
} finally {
|
|
43
43
|
setIsLoading(false);
|
|
44
44
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { Activity, ChevronRight } from "lucide-react";
|
|
4
5
|
import { Card, CardContent, CardHeader, CardTitle, Skeleton } from "../../../../shadcnui";
|
|
6
|
+
import { useI18nLocale } from "../../../../i18n";
|
|
5
7
|
import { MeterInterface, MeterSummaryInterface } from "../../stripe-usage";
|
|
6
8
|
|
|
7
9
|
type BillingUsageSummaryCardProps = {
|
|
@@ -12,8 +14,8 @@ type BillingUsageSummaryCardProps = {
|
|
|
12
14
|
onViewDetailsClick: () => void;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
|
-
function formatNumber(value: number): string {
|
|
16
|
-
return new Intl.NumberFormat(
|
|
17
|
+
function formatNumber(value: number, locale: string): string {
|
|
18
|
+
return new Intl.NumberFormat(locale, {
|
|
17
19
|
notation: "compact",
|
|
18
20
|
compactDisplay: "short",
|
|
19
21
|
}).format(value);
|
|
@@ -26,11 +28,13 @@ export function BillingUsageSummaryCard({
|
|
|
26
28
|
error,
|
|
27
29
|
onViewDetailsClick,
|
|
28
30
|
}: BillingUsageSummaryCardProps) {
|
|
31
|
+
const t = useTranslations();
|
|
32
|
+
const locale = useI18nLocale();
|
|
29
33
|
if (loading) {
|
|
30
34
|
return (
|
|
31
35
|
<Card>
|
|
32
36
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
33
|
-
<CardTitle>
|
|
37
|
+
<CardTitle>{t("billing.ui.cards.usage.title")}</CardTitle>
|
|
34
38
|
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
35
39
|
</CardHeader>
|
|
36
40
|
<CardContent>
|
|
@@ -45,7 +49,7 @@ export function BillingUsageSummaryCard({
|
|
|
45
49
|
return (
|
|
46
50
|
<Card>
|
|
47
51
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
48
|
-
<CardTitle>
|
|
52
|
+
<CardTitle>{t("billing.ui.cards.usage.title")}</CardTitle>
|
|
49
53
|
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
50
54
|
</CardHeader>
|
|
51
55
|
<CardContent>
|
|
@@ -67,26 +71,32 @@ export function BillingUsageSummaryCard({
|
|
|
67
71
|
return (
|
|
68
72
|
<Card className="cursor-pointer hover:bg-accent/50 transition-colors" onClick={onViewDetailsClick}>
|
|
69
73
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
70
|
-
<CardTitle>
|
|
74
|
+
<CardTitle>{t("billing.ui.cards.usage.title")}</CardTitle>
|
|
71
75
|
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
72
76
|
</CardHeader>
|
|
73
77
|
<CardContent>
|
|
74
78
|
{meters.length === 0 ? (
|
|
75
79
|
<div className="space-y-2">
|
|
76
|
-
<p className="text-xl font-bold text-muted-foreground">
|
|
77
|
-
<p className="text-xs text-muted-foreground">
|
|
80
|
+
<p className="text-xl font-bold text-muted-foreground">{t("billing.ui.cards.usage.no_meters")}</p>
|
|
81
|
+
<p className="text-xs text-muted-foreground">{t("billing.ui.cards.usage.no_meters_description")}</p>
|
|
78
82
|
</div>
|
|
79
83
|
) : (
|
|
80
84
|
<div className="space-y-2">
|
|
81
|
-
<p className="text-xl font-bold">
|
|
85
|
+
<p className="text-xl font-bold">
|
|
86
|
+
{t("billing.ui.cards.usage.units", { value: formatNumber(totalUsage, locale) })}
|
|
87
|
+
</p>
|
|
82
88
|
{primaryMeter && primarySummary && (
|
|
83
89
|
<p className="text-sm text-muted-foreground">
|
|
84
|
-
{primaryMeter.displayName}: {formatNumber(primarySummary.aggregatedValue)}
|
|
90
|
+
{primaryMeter.displayName}: {formatNumber(primarySummary.aggregatedValue, locale)}
|
|
91
|
+
</p>
|
|
92
|
+
)}
|
|
93
|
+
{meters.length > 1 && (
|
|
94
|
+
<p className="text-xs text-muted-foreground">
|
|
95
|
+
{t("billing.ui.cards.usage.across_meters", { count: meters.length })}
|
|
85
96
|
</p>
|
|
86
97
|
)}
|
|
87
|
-
{meters.length > 1 && <p className="text-xs text-muted-foreground">Across {meters.length} meters</p>}
|
|
88
98
|
<span className="flex items-center text-xs text-muted-foreground">
|
|
89
|
-
|
|
99
|
+
{t("billing.ui.cards.view_details")}
|
|
90
100
|
<ChevronRight className="h-3 w-3 ms-1 rtl:rotate-180" />
|
|
91
101
|
</span>
|
|
92
102
|
</div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { ExternalLink, User } from "lucide-react";
|
|
4
5
|
import { useState } from "react";
|
|
5
6
|
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton } from "../../../../shadcnui";
|
|
7
|
+
import { useI18nLocale } from "../../../../i18n";
|
|
6
8
|
import { StripeCustomerInterface, StripeCustomerService } from "../../stripe-customer";
|
|
7
9
|
|
|
8
10
|
type CustomerInfoCardProps = {
|
|
@@ -11,18 +13,19 @@ type CustomerInfoCardProps = {
|
|
|
11
13
|
error?: string;
|
|
12
14
|
};
|
|
13
15
|
|
|
14
|
-
function formatBalance(balance: number | undefined, currency: string | undefined): string {
|
|
15
|
-
if (balance === undefined || balance === 0) return "$0.00";
|
|
16
|
+
function formatBalance(balance: number | undefined, currency: string | undefined, locale: string): string {
|
|
16
17
|
const currencyCode = currency?.toUpperCase() || "USD";
|
|
17
18
|
// Balance in Stripe is negative when customer has credit
|
|
18
|
-
const displayBalance = -balance;
|
|
19
|
-
return new Intl.NumberFormat(
|
|
19
|
+
const displayBalance = balance ? -balance : 0;
|
|
20
|
+
return new Intl.NumberFormat(locale, {
|
|
20
21
|
style: "currency",
|
|
21
22
|
currency: currencyCode,
|
|
22
23
|
}).format(displayBalance / 100);
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardProps) {
|
|
27
|
+
const t = useTranslations();
|
|
28
|
+
const locale = useI18nLocale();
|
|
26
29
|
const [portalLoading, setPortalLoading] = useState(false);
|
|
27
30
|
|
|
28
31
|
const handlePortalClick = async (e: React.MouseEvent) => {
|
|
@@ -42,7 +45,7 @@ export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardP
|
|
|
42
45
|
return (
|
|
43
46
|
<Card>
|
|
44
47
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
45
|
-
<CardTitle>
|
|
48
|
+
<CardTitle>{t("billing.ui.cards.customer.title")}</CardTitle>
|
|
46
49
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
47
50
|
</CardHeader>
|
|
48
51
|
<CardContent>
|
|
@@ -58,7 +61,7 @@ export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardP
|
|
|
58
61
|
return (
|
|
59
62
|
<Card>
|
|
60
63
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
61
|
-
<CardTitle>
|
|
64
|
+
<CardTitle>{t("billing.ui.cards.customer.title")}</CardTitle>
|
|
62
65
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
63
66
|
</CardHeader>
|
|
64
67
|
<CardContent>
|
|
@@ -72,12 +75,12 @@ export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardP
|
|
|
72
75
|
return (
|
|
73
76
|
<Card>
|
|
74
77
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
75
|
-
<CardTitle>
|
|
78
|
+
<CardTitle>{t("billing.ui.cards.customer.title")}</CardTitle>
|
|
76
79
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
77
80
|
</CardHeader>
|
|
78
81
|
<CardContent>
|
|
79
|
-
<p className="text-xl font-bold text-muted-foreground">
|
|
80
|
-
<p className="text-xs text-muted-foreground">
|
|
82
|
+
<p className="text-xl font-bold text-muted-foreground">{t("billing.ui.cards.customer.not_set_up")}</p>
|
|
83
|
+
<p className="text-xs text-muted-foreground">{t("billing.ui.cards.customer.not_set_up_description")}</p>
|
|
81
84
|
</CardContent>
|
|
82
85
|
</Card>
|
|
83
86
|
);
|
|
@@ -86,7 +89,7 @@ export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardP
|
|
|
86
89
|
return (
|
|
87
90
|
<Card>
|
|
88
91
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
89
|
-
<CardTitle>
|
|
92
|
+
<CardTitle>{t("billing.ui.cards.customer.title")}</CardTitle>
|
|
90
93
|
<User className="h-4 w-4 text-muted-foreground" />
|
|
91
94
|
</CardHeader>
|
|
92
95
|
<CardContent>
|
|
@@ -95,14 +98,14 @@ export function CustomerInfoCard({ customer, loading, error }: CustomerInfoCardP
|
|
|
95
98
|
{customer.email && <p className="text-sm text-muted-foreground">{customer.email}</p>}
|
|
96
99
|
{customer.balance !== undefined && customer.balance !== 0 && (
|
|
97
100
|
<p className="text-sm">
|
|
98
|
-
<span className="text-muted-foreground">
|
|
101
|
+
<span className="text-muted-foreground">{t("billing.ui.cards.customer.credit_balance")} </span>
|
|
99
102
|
<span className={customer.balance < 0 ? "text-success" : "text-destructive"}>
|
|
100
|
-
{formatBalance(customer.balance, customer.currency)}
|
|
103
|
+
{formatBalance(customer.balance, customer.currency, locale)}
|
|
101
104
|
</span>
|
|
102
105
|
</p>
|
|
103
106
|
)}
|
|
104
107
|
<Button variant="outline" size="sm" className="mt-2" onClick={handlePortalClick} disabled={portalLoading}>
|
|
105
|
-
{portalLoading ? "
|
|
108
|
+
{portalLoading ? t("common.loading") : t("billing.ui.cards.customer.manage_portal")}
|
|
106
109
|
<ExternalLink className="h-4 w-4 ms-1" />
|
|
107
110
|
</Button>
|
|
108
111
|
</div>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { ChevronRight, ReceiptIcon } from "lucide-react";
|
|
4
5
|
import { Badge, Card, CardContent, CardHeader, CardTitle, Skeleton } from "../../../../shadcnui";
|
|
5
6
|
import { InvoiceStatus, StripeInvoiceInterface } from "../../stripe-invoice";
|
|
7
|
+
import { formatDate, useBillingFormatOptions } from "../utils";
|
|
6
8
|
|
|
7
9
|
type InvoicesSummaryCardProps = {
|
|
8
10
|
invoices: StripeInvoiceInterface[];
|
|
@@ -11,6 +13,9 @@ type InvoicesSummaryCardProps = {
|
|
|
11
13
|
onViewAllClick: () => void;
|
|
12
14
|
};
|
|
13
15
|
|
|
16
|
+
/** Statuses with a `billing.ui.invoice_status.*` label; anything else Stripe adds is shown raw. */
|
|
17
|
+
const INVOICE_STATUSES: string[] = Object.values(InvoiceStatus);
|
|
18
|
+
|
|
14
19
|
function getStatusBadgeVariant(status: InvoiceStatus): "default" | "secondary" | "destructive" | "outline" {
|
|
15
20
|
switch (status) {
|
|
16
21
|
case InvoiceStatus.PAID:
|
|
@@ -25,28 +30,22 @@ function getStatusBadgeVariant(status: InvoiceStatus): "default" | "secondary" |
|
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
function
|
|
29
|
-
return new Date(date).toLocaleDateString(undefined, {
|
|
30
|
-
year: "numeric",
|
|
31
|
-
month: "short",
|
|
32
|
-
day: "numeric",
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function formatAmount(amount: number, currency: string): string {
|
|
33
|
+
function formatAmount(amount: number, currency: string, locale: string): string {
|
|
37
34
|
const currencyCode = currency?.toUpperCase() || "USD";
|
|
38
|
-
return new Intl.NumberFormat(
|
|
35
|
+
return new Intl.NumberFormat(locale, {
|
|
39
36
|
style: "currency",
|
|
40
37
|
currency: currencyCode,
|
|
41
38
|
}).format(amount / 100);
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
export function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }: InvoicesSummaryCardProps) {
|
|
42
|
+
const t = useTranslations();
|
|
43
|
+
const fmt = useBillingFormatOptions();
|
|
45
44
|
if (loading) {
|
|
46
45
|
return (
|
|
47
46
|
<Card>
|
|
48
47
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
49
|
-
<CardTitle>
|
|
48
|
+
<CardTitle>{t("billing.ui.cards.invoices.title")}</CardTitle>
|
|
50
49
|
<ReceiptIcon className="h-4 w-4 text-muted-foreground" />
|
|
51
50
|
</CardHeader>
|
|
52
51
|
<CardContent>
|
|
@@ -62,7 +61,7 @@ export function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }
|
|
|
62
61
|
return (
|
|
63
62
|
<Card>
|
|
64
63
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
65
|
-
<CardTitle>
|
|
64
|
+
<CardTitle>{t("billing.ui.cards.invoices.title")}</CardTitle>
|
|
66
65
|
<ReceiptIcon className="h-4 w-4 text-muted-foreground" />
|
|
67
66
|
</CardHeader>
|
|
68
67
|
<CardContent>
|
|
@@ -80,29 +79,42 @@ export function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }
|
|
|
80
79
|
return (
|
|
81
80
|
<Card className="cursor-pointer hover:bg-accent/50 transition-colors" onClick={onViewAllClick}>
|
|
82
81
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
83
|
-
<CardTitle>
|
|
82
|
+
<CardTitle>{t("billing.ui.cards.invoices.title")}</CardTitle>
|
|
84
83
|
<ReceiptIcon className="h-4 w-4 text-muted-foreground" />
|
|
85
84
|
</CardHeader>
|
|
86
85
|
<CardContent>
|
|
87
86
|
{invoices.length === 0 ? (
|
|
88
87
|
<div className="space-y-2">
|
|
89
|
-
<p className="text-xl font-bold text-muted-foreground">
|
|
90
|
-
<p className="text-xs text-muted-foreground">
|
|
88
|
+
<p className="text-xl font-bold text-muted-foreground">{t("billing.ui.invoices.empty_title")}</p>
|
|
89
|
+
<p className="text-xs text-muted-foreground">{t("billing.ui.cards.invoices.empty_description")}</p>
|
|
91
90
|
</div>
|
|
92
91
|
) : latestInvoice ? (
|
|
93
92
|
<div className="space-y-2">
|
|
94
93
|
<div className="flex items-center gap-2">
|
|
95
|
-
<p className="text-xl font-bold">
|
|
96
|
-
|
|
94
|
+
<p className="text-xl font-bold">
|
|
95
|
+
{formatAmount(latestInvoice.total, latestInvoice.currency, fmt.locale)}
|
|
96
|
+
</p>
|
|
97
|
+
<Badge variant={getStatusBadgeVariant(latestInvoice.status)}>
|
|
98
|
+
{INVOICE_STATUSES.includes(latestInvoice.status)
|
|
99
|
+
? t(`billing.ui.invoice_status.${latestInvoice.status}`)
|
|
100
|
+
: latestInvoice.status}
|
|
101
|
+
</Badge>
|
|
97
102
|
</div>
|
|
98
103
|
<p className="text-sm text-muted-foreground">
|
|
99
|
-
{latestInvoice.stripeInvoiceNumber ||
|
|
104
|
+
{latestInvoice.stripeInvoiceNumber ||
|
|
105
|
+
t("billing.ui.cards.invoices.invoice_from", { date: formatDate(latestInvoice.periodStart, fmt) })}
|
|
100
106
|
</p>
|
|
101
107
|
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
|
102
|
-
{paidInvoices.length > 0 &&
|
|
103
|
-
|
|
108
|
+
{paidInvoices.length > 0 && (
|
|
109
|
+
<span>{t("billing.ui.cards.invoices.paid_count", { count: paidInvoices.length })}</span>
|
|
110
|
+
)}
|
|
111
|
+
{openInvoices.length > 0 && (
|
|
112
|
+
<span className="text-warning">
|
|
113
|
+
{t("billing.ui.cards.invoices.open_count", { count: openInvoices.length })}
|
|
114
|
+
</span>
|
|
115
|
+
)}
|
|
104
116
|
<span className="flex items-center">
|
|
105
|
-
|
|
117
|
+
{t("billing.ui.cards.view_all")}
|
|
106
118
|
<ChevronRight className="h-3 w-3 ms-1 rtl:rotate-180" />
|
|
107
119
|
</span>
|
|
108
120
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { Wallet, ChevronRight } from "lucide-react";
|
|
4
5
|
import { Card, CardContent, CardHeader, CardTitle, Button, Skeleton } from "../../../../shadcnui";
|
|
5
6
|
import { PaymentMethodInterface } from "../../stripe-customer";
|
|
@@ -32,11 +33,12 @@ export function PaymentMethodSummaryCard({
|
|
|
32
33
|
error,
|
|
33
34
|
onManageClick,
|
|
34
35
|
}: PaymentMethodSummaryCardProps) {
|
|
36
|
+
const t = useTranslations();
|
|
35
37
|
if (loading) {
|
|
36
38
|
return (
|
|
37
39
|
<Card>
|
|
38
40
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
39
|
-
<CardTitle>
|
|
41
|
+
<CardTitle>{t("billing.ui.payment_method.title")}</CardTitle>
|
|
40
42
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
|
41
43
|
</CardHeader>
|
|
42
44
|
<CardContent>
|
|
@@ -51,7 +53,7 @@ export function PaymentMethodSummaryCard({
|
|
|
51
53
|
return (
|
|
52
54
|
<Card>
|
|
53
55
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
54
|
-
<CardTitle>
|
|
56
|
+
<CardTitle>{t("billing.ui.payment_method.title")}</CardTitle>
|
|
55
57
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
|
56
58
|
</CardHeader>
|
|
57
59
|
<CardContent>
|
|
@@ -67,14 +69,16 @@ export function PaymentMethodSummaryCard({
|
|
|
67
69
|
return (
|
|
68
70
|
<Card className="cursor-pointer hover:bg-accent/50 transition-colors" onClick={onManageClick}>
|
|
69
71
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
70
|
-
<CardTitle>
|
|
72
|
+
<CardTitle>{t("billing.ui.payment_method.title")}</CardTitle>
|
|
71
73
|
<Wallet className="h-4 w-4 text-muted-foreground" />
|
|
72
74
|
</CardHeader>
|
|
73
75
|
<CardContent>
|
|
74
76
|
{paymentMethods.length === 0 ? (
|
|
75
77
|
<div className="space-y-2">
|
|
76
|
-
<p className="text-xl font-bold text-muted-foreground">
|
|
77
|
-
|
|
78
|
+
<p className="text-xl font-bold text-muted-foreground">
|
|
79
|
+
{t("billing.ui.cards.payment_method.empty_title")}
|
|
80
|
+
</p>
|
|
81
|
+
<p className="text-xs text-muted-foreground">{t("billing.ui.cards.payment_method.empty_description")}</p>
|
|
78
82
|
<Button
|
|
79
83
|
variant="outline"
|
|
80
84
|
size="sm"
|
|
@@ -84,7 +88,7 @@ export function PaymentMethodSummaryCard({
|
|
|
84
88
|
onManageClick();
|
|
85
89
|
}}
|
|
86
90
|
>
|
|
87
|
-
|
|
91
|
+
{t("billing.ui.cards.payment_method.add_card")}
|
|
88
92
|
<ChevronRight className="h-4 w-4 ms-1 rtl:rotate-180" />
|
|
89
93
|
</Button>
|
|
90
94
|
</div>
|
|
@@ -94,17 +98,23 @@ export function PaymentMethodSummaryCard({
|
|
|
94
98
|
{getCardBrandIcon(defaultMethod.card.brand)} ****{defaultMethod.card.last4}
|
|
95
99
|
</p>
|
|
96
100
|
<p className="text-sm text-muted-foreground">
|
|
97
|
-
|
|
101
|
+
{t("billing.ui.payment_method.expires", {
|
|
102
|
+
date: `${String(defaultMethod.card.expMonth).padStart(2, "0")}/${defaultMethod.card.expYear}`,
|
|
103
|
+
})}
|
|
98
104
|
</p>
|
|
99
105
|
{paymentMethods.length > 1 && (
|
|
100
|
-
<p className="text-xs text-muted-foreground"
|
|
106
|
+
<p className="text-xs text-muted-foreground">
|
|
107
|
+
{t("billing.ui.cards.payment_method.more_cards", { count: paymentMethods.length - 1 })}
|
|
108
|
+
</p>
|
|
101
109
|
)}
|
|
102
110
|
</div>
|
|
103
111
|
) : (
|
|
104
112
|
<div className="space-y-2">
|
|
105
|
-
<p className="text-xl font-bold">{defaultMethod?.type || "
|
|
113
|
+
<p className="text-xl font-bold">{defaultMethod?.type || t("billing.ui.payment_method.title")}</p>
|
|
106
114
|
{paymentMethods.length > 1 && (
|
|
107
|
-
<p className="text-xs text-muted-foreground"
|
|
115
|
+
<p className="text-xs text-muted-foreground">
|
|
116
|
+
{t("billing.ui.cards.payment_method.more_methods", { count: paymentMethods.length - 1 })}
|
|
117
|
+
</p>
|
|
108
118
|
)}
|
|
109
119
|
</div>
|
|
110
120
|
)}
|