@carlonicora/nextjs-jsonapi 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/scripts/vitest-slow-tests.reporter.spec.ts +54 -0
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { useState, useCallback } from "react";
|
|
4
|
-
import { format } from "date-fns";
|
|
5
5
|
import { Copy, Check, RefreshCw, Pencil, Trash2, ExternalLink } from "lucide-react";
|
|
6
6
|
import {
|
|
7
7
|
Badge,
|
|
@@ -23,7 +23,9 @@ import {
|
|
|
23
23
|
AlertDialogHeader,
|
|
24
24
|
AlertDialogTitle,
|
|
25
25
|
} from "../../../shadcnui";
|
|
26
|
-
import {
|
|
26
|
+
import { useI18nLocale } from "../../../i18n";
|
|
27
|
+
import { useOAuthScopeText } from "../hooks/useOAuthScopeText";
|
|
28
|
+
import { OAuthClientInterface } from "../interfaces/oauth.interface";
|
|
27
29
|
|
|
28
30
|
export interface OAuthClientDetailProps {
|
|
29
31
|
/** The OAuth client to display */
|
|
@@ -48,6 +50,9 @@ export function OAuthClientDetail({
|
|
|
48
50
|
onDelete,
|
|
49
51
|
onRegenerateSecret,
|
|
50
52
|
}: OAuthClientDetailProps) {
|
|
53
|
+
const t = useTranslations();
|
|
54
|
+
const locale = useI18nLocale();
|
|
55
|
+
const scopeText = useOAuthScopeText();
|
|
51
56
|
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
52
57
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
53
58
|
const [showRegenerateConfirm, setShowRegenerateConfirm] = useState(false);
|
|
@@ -86,7 +91,9 @@ export function OAuthClientDetail({
|
|
|
86
91
|
}
|
|
87
92
|
}, [onRegenerateSecret]);
|
|
88
93
|
|
|
89
|
-
const createdDate = client.createdAt
|
|
94
|
+
const createdDate = client.createdAt
|
|
95
|
+
? new Date(client.createdAt).toLocaleDateString(locale, { dateStyle: "long" })
|
|
96
|
+
: t("oauth.ui.card.unknown");
|
|
90
97
|
|
|
91
98
|
return (
|
|
92
99
|
<>
|
|
@@ -99,23 +106,25 @@ export function OAuthClientDetail({
|
|
|
99
106
|
</div>
|
|
100
107
|
<div className="flex items-center gap-2">
|
|
101
108
|
<Badge variant={client.isActive ? "default" : "secondary"}>
|
|
102
|
-
{client.isActive ? "
|
|
109
|
+
{client.isActive ? t("oauth.ui.card.active") : t("oauth.ui.card.inactive")}
|
|
110
|
+
</Badge>
|
|
111
|
+
<Badge variant="outline">
|
|
112
|
+
{client.isConfidential ? t("oauth.ui.card.confidential") : t("oauth.ui.card.public")}
|
|
103
113
|
</Badge>
|
|
104
|
-
<Badge variant="outline">{client.isConfidential ? "Confidential" : "Public"}</Badge>
|
|
105
114
|
</div>
|
|
106
115
|
</div>
|
|
107
116
|
</CardHeader>
|
|
108
117
|
<CardContent className="space-y-6">
|
|
109
118
|
{/* Client ID */}
|
|
110
119
|
<div className="space-y-2">
|
|
111
|
-
<Label>
|
|
120
|
+
<Label>{t("oauth.ui.detail.client_id")}</Label>
|
|
112
121
|
<div className="flex gap-2">
|
|
113
122
|
<Input value={client.clientId} readOnly className="tabular-nums" />
|
|
114
123
|
<Button
|
|
115
124
|
variant="outline"
|
|
116
125
|
size="icon"
|
|
117
126
|
onClick={() => copyToClipboard(client.clientId, "clientId")}
|
|
118
|
-
title="
|
|
127
|
+
title={t("oauth.ui.detail.copy_client_id")}
|
|
119
128
|
>
|
|
120
129
|
{copiedField === "clientId" ? <Check className="text-success h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
121
130
|
</Button>
|
|
@@ -124,7 +133,7 @@ export function OAuthClientDetail({
|
|
|
124
133
|
|
|
125
134
|
{/* Client Secret */}
|
|
126
135
|
<div className="space-y-2">
|
|
127
|
-
<Label>
|
|
136
|
+
<Label>{t("oauth.ui.detail.client_secret")}</Label>
|
|
128
137
|
<div className="flex gap-2">
|
|
129
138
|
<Input value="••••••••••••••••••••••••••••••••" readOnly className="tabular-nums" />
|
|
130
139
|
{onRegenerateSecret && (
|
|
@@ -132,23 +141,21 @@ export function OAuthClientDetail({
|
|
|
132
141
|
variant="outline"
|
|
133
142
|
size="icon"
|
|
134
143
|
onClick={() => setShowRegenerateConfirm(true)}
|
|
135
|
-
title="
|
|
144
|
+
title={t("oauth.ui.detail.regenerate_secret")}
|
|
136
145
|
disabled={isLoading}
|
|
137
146
|
>
|
|
138
147
|
<RefreshCw className="h-4 w-4" />
|
|
139
148
|
</Button>
|
|
140
149
|
)}
|
|
141
150
|
</div>
|
|
142
|
-
<p className="text-xs text-muted-foreground">
|
|
143
|
-
Regenerating will invalidate the current secret and all existing tokens.
|
|
144
|
-
</p>
|
|
151
|
+
<p className="text-xs text-muted-foreground">{t("oauth.ui.detail.regenerate_help")}</p>
|
|
145
152
|
</div>
|
|
146
153
|
|
|
147
154
|
<Separator />
|
|
148
155
|
|
|
149
156
|
{/* Redirect URIs */}
|
|
150
157
|
<div className="space-y-2">
|
|
151
|
-
<Label>
|
|
158
|
+
<Label>{t("oauth.ui.redirect_uris.label")}</Label>
|
|
152
159
|
<ul className="space-y-1">
|
|
153
160
|
{client.redirectUris.map((uri, index) => (
|
|
154
161
|
<li key={index} className="flex items-center gap-2 text-sm tabular-nums">
|
|
@@ -161,11 +168,11 @@ export function OAuthClientDetail({
|
|
|
161
168
|
|
|
162
169
|
{/* Scopes */}
|
|
163
170
|
<div className="space-y-2">
|
|
164
|
-
<Label>
|
|
171
|
+
<Label>{t("oauth.ui.scope_selector.label")}</Label>
|
|
165
172
|
<div className="flex flex-wrap gap-2">
|
|
166
173
|
{client.allowedScopes.map((scope) => (
|
|
167
174
|
<Badge key={scope} variant="secondary">
|
|
168
|
-
{
|
|
175
|
+
{scopeText(scope).name}
|
|
169
176
|
</Badge>
|
|
170
177
|
))}
|
|
171
178
|
</div>
|
|
@@ -173,7 +180,7 @@ export function OAuthClientDetail({
|
|
|
173
180
|
|
|
174
181
|
{/* Grant Types */}
|
|
175
182
|
<div className="space-y-2">
|
|
176
|
-
<Label>
|
|
183
|
+
<Label>{t("oauth.ui.detail.grant_types")}</Label>
|
|
177
184
|
<div className="flex flex-wrap gap-2">
|
|
178
185
|
{client.allowedGrantTypes.map((grant) => (
|
|
179
186
|
<Badge key={grant} variant="outline">
|
|
@@ -187,7 +194,7 @@ export function OAuthClientDetail({
|
|
|
187
194
|
|
|
188
195
|
{/* Metadata */}
|
|
189
196
|
<div className="flex flex-wrap gap-x-6 gap-y-2 text-sm text-muted-foreground">
|
|
190
|
-
<span>
|
|
197
|
+
<span>{t("oauth.ui.detail.created", { date: createdDate })}</span>
|
|
191
198
|
</div>
|
|
192
199
|
|
|
193
200
|
{/* Actions */}
|
|
@@ -195,13 +202,13 @@ export function OAuthClientDetail({
|
|
|
195
202
|
{onEdit && (
|
|
196
203
|
<Button variant="outline" onClick={onEdit} disabled={isLoading}>
|
|
197
204
|
<Pencil className="h-4 w-4 me-2" />
|
|
198
|
-
|
|
205
|
+
{t("ui.buttons.edit")}
|
|
199
206
|
</Button>
|
|
200
207
|
)}
|
|
201
208
|
{onDelete && (
|
|
202
209
|
<Button variant="destructive" onClick={() => setShowDeleteConfirm(true)} disabled={isLoading}>
|
|
203
210
|
<Trash2 className="h-4 w-4 me-2" />
|
|
204
|
-
|
|
211
|
+
{t("ui.buttons.delete")}
|
|
205
212
|
</Button>
|
|
206
213
|
)}
|
|
207
214
|
</div>
|
|
@@ -212,19 +219,19 @@ export function OAuthClientDetail({
|
|
|
212
219
|
<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
|
|
213
220
|
<AlertDialogContent>
|
|
214
221
|
<AlertDialogHeader>
|
|
215
|
-
<AlertDialogTitle>
|
|
222
|
+
<AlertDialogTitle>{t("oauth.settings.delete_title")}</AlertDialogTitle>
|
|
216
223
|
<AlertDialogDescription>
|
|
217
|
-
|
|
224
|
+
{t("oauth.settings.delete_description", { name: client.name })}
|
|
218
225
|
</AlertDialogDescription>
|
|
219
226
|
</AlertDialogHeader>
|
|
220
227
|
<AlertDialogFooter>
|
|
221
|
-
<AlertDialogCancel disabled={isDeleting}>
|
|
228
|
+
<AlertDialogCancel disabled={isDeleting}>{t("ui.buttons.cancel")}</AlertDialogCancel>
|
|
222
229
|
<AlertDialogAction
|
|
223
230
|
onClick={handleDelete}
|
|
224
231
|
disabled={isDeleting}
|
|
225
232
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
226
233
|
>
|
|
227
|
-
{isDeleting ? "
|
|
234
|
+
{isDeleting ? t("oauth.ui.detail.deleting") : t("ui.buttons.delete")}
|
|
228
235
|
</AlertDialogAction>
|
|
229
236
|
</AlertDialogFooter>
|
|
230
237
|
</AlertDialogContent>
|
|
@@ -234,16 +241,13 @@ export function OAuthClientDetail({
|
|
|
234
241
|
<AlertDialog open={showRegenerateConfirm} onOpenChange={setShowRegenerateConfirm}>
|
|
235
242
|
<AlertDialogContent>
|
|
236
243
|
<AlertDialogHeader>
|
|
237
|
-
<AlertDialogTitle>
|
|
238
|
-
<AlertDialogDescription>
|
|
239
|
-
This will generate a new client secret and invalidate the old one. All existing tokens will be revoked.
|
|
240
|
-
You will need to update your application with the new secret.
|
|
241
|
-
</AlertDialogDescription>
|
|
244
|
+
<AlertDialogTitle>{t("oauth.ui.detail.regenerate_title")}</AlertDialogTitle>
|
|
245
|
+
<AlertDialogDescription>{t("oauth.ui.detail.regenerate_description")}</AlertDialogDescription>
|
|
242
246
|
</AlertDialogHeader>
|
|
243
247
|
<AlertDialogFooter>
|
|
244
|
-
<AlertDialogCancel disabled={isRegenerating}>
|
|
248
|
+
<AlertDialogCancel disabled={isRegenerating}>{t("ui.buttons.cancel")}</AlertDialogCancel>
|
|
245
249
|
<AlertDialogAction onClick={handleRegenerateSecret} disabled={isRegenerating}>
|
|
246
|
-
{isRegenerating ? "
|
|
250
|
+
{isRegenerating ? t("oauth.ui.detail.regenerating") : t("oauth.ui.detail.regenerate")}
|
|
247
251
|
</AlertDialogAction>
|
|
248
252
|
</AlertDialogFooter>
|
|
249
253
|
</AlertDialogContent>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { useState, useCallback } from "react";
|
|
4
5
|
import {
|
|
5
6
|
Button,
|
|
@@ -64,6 +65,7 @@ export function OAuthClientForm({
|
|
|
64
65
|
availableScopes,
|
|
65
66
|
defaultScopes = [],
|
|
66
67
|
}: OAuthClientFormProps) {
|
|
68
|
+
const t = useTranslations();
|
|
67
69
|
const isEditMode = !!client;
|
|
68
70
|
|
|
69
71
|
const [formState, setFormState] = useState<FormState>({
|
|
@@ -80,21 +82,21 @@ export function OAuthClientForm({
|
|
|
80
82
|
const newErrors: FormErrors = {};
|
|
81
83
|
|
|
82
84
|
if (!formState.name.trim()) {
|
|
83
|
-
newErrors.name = "
|
|
85
|
+
newErrors.name = t("oauth.ui.form.errors.name_required");
|
|
84
86
|
}
|
|
85
87
|
|
|
86
88
|
const validUris = formState.redirectUris.filter((uri) => uri.trim());
|
|
87
89
|
if (validUris.length === 0) {
|
|
88
|
-
newErrors.redirectUris = "
|
|
90
|
+
newErrors.redirectUris = t("oauth.ui.form.errors.redirect_uri_required");
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
if (formState.allowedScopes.length === 0) {
|
|
92
|
-
newErrors.allowedScopes = "
|
|
94
|
+
newErrors.allowedScopes = t("oauth.ui.form.errors.scope_required");
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
setErrors(newErrors);
|
|
96
98
|
return Object.keys(newErrors).length === 0;
|
|
97
|
-
}, [formState]);
|
|
99
|
+
}, [formState, t]);
|
|
98
100
|
|
|
99
101
|
const handleSubmit = useCallback(
|
|
100
102
|
async (e: React.FormEvent) => {
|
|
@@ -120,20 +122,20 @@ export function OAuthClientForm({
|
|
|
120
122
|
<Card>
|
|
121
123
|
<form onSubmit={handleSubmit}>
|
|
122
124
|
<CardHeader>
|
|
123
|
-
<CardTitle>{isEditMode ? "
|
|
125
|
+
<CardTitle>{isEditMode ? t("oauth.ui.form.edit_title") : t("oauth.settings.create_title")}</CardTitle>
|
|
124
126
|
<CardDescription>
|
|
125
|
-
{isEditMode ? "
|
|
127
|
+
{isEditMode ? t("oauth.ui.form.edit_description") : t("oauth.ui.form.create_description")}
|
|
126
128
|
</CardDescription>
|
|
127
129
|
</CardHeader>
|
|
128
130
|
<CardContent className="space-y-6">
|
|
129
131
|
{/* Name */}
|
|
130
132
|
<div className="space-y-2">
|
|
131
|
-
<Label htmlFor="name">
|
|
133
|
+
<Label htmlFor="name">{t("oauth.ui.form.name")} *</Label>
|
|
132
134
|
<Input
|
|
133
135
|
id="name"
|
|
134
136
|
value={formState.name}
|
|
135
137
|
onChange={(e) => setFormState((s) => ({ ...s, name: e.target.value }))}
|
|
136
|
-
placeholder="
|
|
138
|
+
placeholder={t("oauth.ui.form.name_placeholder")}
|
|
137
139
|
disabled={isLoading}
|
|
138
140
|
className={errors.name ? "border-destructive" : ""}
|
|
139
141
|
/>
|
|
@@ -142,12 +144,12 @@ export function OAuthClientForm({
|
|
|
142
144
|
|
|
143
145
|
{/* Description */}
|
|
144
146
|
<div className="space-y-2">
|
|
145
|
-
<Label htmlFor="description">
|
|
147
|
+
<Label htmlFor="description">{t("oauth.ui.form.description")}</Label>
|
|
146
148
|
<Textarea
|
|
147
149
|
id="description"
|
|
148
150
|
value={formState.description}
|
|
149
151
|
onChange={(e) => setFormState((s) => ({ ...s, description: e.target.value }))}
|
|
150
|
-
placeholder="
|
|
152
|
+
placeholder={t("oauth.ui.form.description_placeholder")}
|
|
151
153
|
disabled={isLoading}
|
|
152
154
|
rows={3}
|
|
153
155
|
/>
|
|
@@ -172,7 +174,7 @@ export function OAuthClientForm({
|
|
|
172
174
|
|
|
173
175
|
{/* Client Type */}
|
|
174
176
|
<div className="space-y-3">
|
|
175
|
-
<Label>
|
|
177
|
+
<Label>{t("oauth.ui.form.client_type")}</Label>
|
|
176
178
|
<RadioGroup
|
|
177
179
|
value={formState.isConfidential ? "confidential" : "public"}
|
|
178
180
|
onValueChange={(v) => setFormState((s) => ({ ...s, isConfidential: v === "confidential" }))}
|
|
@@ -182,36 +184,34 @@ export function OAuthClientForm({
|
|
|
182
184
|
<RadioGroupItem value="confidential" id="confidential" className="mt-1" />
|
|
183
185
|
<div>
|
|
184
186
|
<Label htmlFor="confidential" className="cursor-pointer">
|
|
185
|
-
|
|
187
|
+
{t("oauth.ui.card.confidential")}
|
|
186
188
|
</Label>
|
|
187
|
-
<p className="text-sm text-muted-foreground">
|
|
188
|
-
Server-side application that can securely store the client secret.
|
|
189
|
-
</p>
|
|
189
|
+
<p className="text-sm text-muted-foreground">{t("oauth.ui.form.confidential_help")}</p>
|
|
190
190
|
</div>
|
|
191
191
|
</div>
|
|
192
192
|
<div className="flex items-start space-x-3 p-3 rounded-md border">
|
|
193
193
|
<RadioGroupItem value="public" id="public" className="mt-1" />
|
|
194
194
|
<div>
|
|
195
195
|
<Label htmlFor="public" className="cursor-pointer">
|
|
196
|
-
|
|
196
|
+
{t("oauth.ui.card.public")}
|
|
197
197
|
</Label>
|
|
198
|
-
<p className="text-sm text-muted-foreground">
|
|
199
|
-
Mobile or desktop application. Requires PKCE for authorization.
|
|
200
|
-
</p>
|
|
198
|
+
<p className="text-sm text-muted-foreground">{t("oauth.ui.form.public_help")}</p>
|
|
201
199
|
</div>
|
|
202
200
|
</div>
|
|
203
201
|
</RadioGroup>
|
|
204
|
-
{isEditMode && (
|
|
205
|
-
<p className="text-sm text-muted-foreground">Client type cannot be changed after creation.</p>
|
|
206
|
-
)}
|
|
202
|
+
{isEditMode && <p className="text-sm text-muted-foreground">{t("oauth.ui.form.client_type_locked")}</p>}
|
|
207
203
|
</div>
|
|
208
204
|
</CardContent>
|
|
209
205
|
<CardFooter className="flex justify-end gap-3">
|
|
210
206
|
<Button type="button" variant="outline" onClick={onCancel} disabled={isLoading}>
|
|
211
|
-
|
|
207
|
+
{t("ui.buttons.cancel")}
|
|
212
208
|
</Button>
|
|
213
209
|
<Button type="submit" disabled={isLoading}>
|
|
214
|
-
{isLoading
|
|
210
|
+
{isLoading
|
|
211
|
+
? t("oauth.ui.form.saving")
|
|
212
|
+
: isEditMode
|
|
213
|
+
? t("oauth.ui.form.save_changes")
|
|
214
|
+
: t("oauth.ui.list.create")}
|
|
215
215
|
</Button>
|
|
216
216
|
</CardFooter>
|
|
217
217
|
</form>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { Plus, Key } from "lucide-react";
|
|
4
5
|
import { Button, Skeleton } from "../../../shadcnui";
|
|
5
6
|
import { SectionHeader } from "../../../components/typography";
|
|
@@ -38,9 +39,12 @@ export function OAuthClientList({
|
|
|
38
39
|
onCreateClick,
|
|
39
40
|
onEditClick,
|
|
40
41
|
onDeleteClick,
|
|
41
|
-
emptyStateMessage
|
|
42
|
-
title
|
|
42
|
+
emptyStateMessage: emptyStateMessageProp,
|
|
43
|
+
title: titleProp,
|
|
43
44
|
}: OAuthClientListProps) {
|
|
45
|
+
const t = useTranslations();
|
|
46
|
+
const emptyStateMessage = emptyStateMessageProp ?? t("oauth.ui.list.empty_message");
|
|
47
|
+
const title = titleProp ?? t("oauth.ui.list.title");
|
|
44
48
|
// Loading state
|
|
45
49
|
if (isLoading && clients.length === 0) {
|
|
46
50
|
return (
|
|
@@ -81,18 +85,18 @@ export function OAuthClientList({
|
|
|
81
85
|
{onCreateClick && (
|
|
82
86
|
<Button onClick={onCreateClick}>
|
|
83
87
|
<Plus className="h-4 w-4 me-2" />
|
|
84
|
-
|
|
88
|
+
{t("oauth.ui.list.new_app")}
|
|
85
89
|
</Button>
|
|
86
90
|
)}
|
|
87
91
|
</div>
|
|
88
92
|
<div className="rounded-lg border border-dashed p-12 text-center">
|
|
89
93
|
<Key className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
|
|
90
|
-
<h3 className="text-lg font-medium mb-2">
|
|
94
|
+
<h3 className="text-lg font-medium mb-2">{t("oauth.ui.list.empty_title")}</h3>
|
|
91
95
|
<p className="text-muted-foreground mb-4">{emptyStateMessage}</p>
|
|
92
96
|
{onCreateClick && (
|
|
93
97
|
<Button onClick={onCreateClick}>
|
|
94
98
|
<Plus className="h-4 w-4 me-2" />
|
|
95
|
-
|
|
99
|
+
{t("oauth.ui.list.create")}
|
|
96
100
|
</Button>
|
|
97
101
|
)}
|
|
98
102
|
</div>
|
|
@@ -108,7 +112,7 @@ export function OAuthClientList({
|
|
|
108
112
|
{onCreateClick && (
|
|
109
113
|
<Button onClick={onCreateClick}>
|
|
110
114
|
<Plus className="h-4 w-4 me-2" />
|
|
111
|
-
|
|
115
|
+
{t("oauth.ui.list.new_app")}
|
|
112
116
|
</Button>
|
|
113
117
|
)}
|
|
114
118
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { useState, useCallback } from "react";
|
|
4
5
|
import { Copy, Check, AlertTriangle } from "lucide-react";
|
|
5
6
|
import {
|
|
@@ -42,6 +43,7 @@ export interface OAuthClientSecretDisplayProps {
|
|
|
42
43
|
* ```
|
|
43
44
|
*/
|
|
44
45
|
export function OAuthClientSecretDisplay({ secret, onDismiss, open, clientName }: OAuthClientSecretDisplayProps) {
|
|
46
|
+
const t = useTranslations();
|
|
45
47
|
const [copied, setCopied] = useState(false);
|
|
46
48
|
|
|
47
49
|
const handleCopy = useCallback(async () => {
|
|
@@ -65,21 +67,21 @@ export function OAuthClientSecretDisplay({ secret, onDismiss, open, clientName }
|
|
|
65
67
|
<DialogHeader>
|
|
66
68
|
<DialogTitle className="flex items-center gap-2">
|
|
67
69
|
<AlertTriangle className="h-5 w-5 text-warning" />
|
|
68
|
-
|
|
70
|
+
{t("oauth.ui.secret_display.title")}
|
|
69
71
|
</DialogTitle>
|
|
70
72
|
<DialogDescription>
|
|
71
73
|
{clientName
|
|
72
|
-
?
|
|
73
|
-
: "
|
|
74
|
+
? t("oauth.ui.secret_display.description_named", { name: clientName })
|
|
75
|
+
: t("oauth.ui.secret_display.description")}
|
|
74
76
|
</DialogDescription>
|
|
75
77
|
</DialogHeader>
|
|
76
78
|
|
|
77
79
|
<Alert variant="destructive" className="my-4">
|
|
78
80
|
<AlertTriangle className="h-4 w-4" />
|
|
79
81
|
<AlertDescription>
|
|
80
|
-
<strong>
|
|
82
|
+
<strong>{t("oauth.ui.secret_display.warning_title")}</strong>
|
|
81
83
|
<br />
|
|
82
|
-
|
|
84
|
+
{t("oauth.ui.secret_display.warning_body")}
|
|
83
85
|
</AlertDescription>
|
|
84
86
|
</Alert>
|
|
85
87
|
|
|
@@ -92,17 +94,17 @@ export function OAuthClientSecretDisplay({ secret, onDismiss, open, clientName }
|
|
|
92
94
|
variant="outline"
|
|
93
95
|
size="icon"
|
|
94
96
|
onClick={handleCopy}
|
|
95
|
-
title={copied ? "
|
|
97
|
+
title={copied ? t("oauth.ui.secret_display.copied_short") : t("oauth.ui.secret_display.copy_to_clipboard")}
|
|
96
98
|
>
|
|
97
99
|
{copied ? <Check className="text-success h-4 w-4" /> : <Copy className="h-4 w-4" />}
|
|
98
100
|
</Button>
|
|
99
101
|
</div>
|
|
100
102
|
|
|
101
|
-
{copied && <p className="text-success text-center text-sm">
|
|
103
|
+
{copied && <p className="text-success text-center text-sm">{t("oauth.ui.secret_display.copied")}</p>}
|
|
102
104
|
|
|
103
105
|
<DialogFooter className="mt-4">
|
|
104
106
|
<Button onClick={handleDismiss} className="w-full">
|
|
105
|
-
|
|
107
|
+
{t("oauth.ui.secret_display.confirm")}
|
|
106
108
|
</Button>
|
|
107
109
|
</DialogFooter>
|
|
108
110
|
</DialogContent>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { useCallback } from "react";
|
|
4
5
|
import { Plus, Trash2 } from "lucide-react";
|
|
5
6
|
import { Button, Input, Label } from "../../../shadcnui";
|
|
@@ -67,8 +68,10 @@ export function OAuthRedirectUriInput({
|
|
|
67
68
|
onChange,
|
|
68
69
|
error,
|
|
69
70
|
disabled = false,
|
|
70
|
-
label
|
|
71
|
+
label: labelProp,
|
|
71
72
|
}: OAuthRedirectUriInputProps) {
|
|
73
|
+
const t = useTranslations();
|
|
74
|
+
const label = labelProp ?? t("oauth.ui.redirect_uris.label");
|
|
72
75
|
const handleAdd = useCallback(() => {
|
|
73
76
|
onChange([...value, ""]);
|
|
74
77
|
}, [value, onChange]);
|
|
@@ -94,10 +97,7 @@ export function OAuthRedirectUriInput({
|
|
|
94
97
|
return (
|
|
95
98
|
<div className="space-y-2">
|
|
96
99
|
<Label>{label} *</Label>
|
|
97
|
-
<p className="text-sm text-muted-foreground">
|
|
98
|
-
Enter the URIs where users will be redirected after authorization. Use https:// for production, or custom
|
|
99
|
-
schemes for mobile apps.
|
|
100
|
-
</p>
|
|
100
|
+
<p className="text-sm text-muted-foreground">{t("oauth.ui.redirect_uris.help")}</p>
|
|
101
101
|
|
|
102
102
|
<div className="space-y-2">
|
|
103
103
|
{value.map((uri, index) => {
|
|
@@ -109,15 +109,11 @@ export function OAuthRedirectUriInput({
|
|
|
109
109
|
<Input
|
|
110
110
|
value={uri}
|
|
111
111
|
onChange={(e) => handleChange(index, e.target.value)}
|
|
112
|
-
placeholder="
|
|
112
|
+
placeholder={t("oauth.ui.redirect_uris.placeholder")}
|
|
113
113
|
disabled={disabled}
|
|
114
114
|
className={!isValid ? "border-destructive" : ""}
|
|
115
115
|
/>
|
|
116
|
-
{!isValid && (
|
|
117
|
-
<p className="text-xs text-destructive mt-1">
|
|
118
|
-
Must be https://, http://localhost, or a custom scheme (app://)
|
|
119
|
-
</p>
|
|
120
|
-
)}
|
|
116
|
+
{!isValid && <p className="text-xs text-destructive mt-1">{t("oauth.ui.redirect_uris.invalid")}</p>}
|
|
121
117
|
</div>
|
|
122
118
|
<Button
|
|
123
119
|
type="button"
|
|
@@ -125,7 +121,7 @@ export function OAuthRedirectUriInput({
|
|
|
125
121
|
size="icon"
|
|
126
122
|
onClick={() => handleRemove(index)}
|
|
127
123
|
disabled={disabled || value.length === 1}
|
|
128
|
-
title="
|
|
124
|
+
title={t("oauth.ui.redirect_uris.remove")}
|
|
129
125
|
>
|
|
130
126
|
<Trash2 className="h-4 w-4" />
|
|
131
127
|
</Button>
|
|
@@ -136,7 +132,7 @@ export function OAuthRedirectUriInput({
|
|
|
136
132
|
|
|
137
133
|
<Button type="button" variant="outline" size="sm" onClick={handleAdd} disabled={disabled} className="mt-2">
|
|
138
134
|
<Plus className="h-4 w-4 me-2" />
|
|
139
|
-
|
|
135
|
+
{t("oauth.ui.redirect_uris.add")}
|
|
140
136
|
</Button>
|
|
141
137
|
|
|
142
138
|
{error && <p className="text-destructive text-xs/relaxed">{error}</p>}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import { useTranslations } from "next-intl";
|
|
3
4
|
import { useCallback } from "react";
|
|
4
5
|
import { Checkbox, Label } from "../../../shadcnui";
|
|
6
|
+
import { useOAuthScopeText } from "../hooks/useOAuthScopeText";
|
|
5
7
|
import { AVAILABLE_OAUTH_SCOPES, OAuthScopeInfo } from "../interfaces/oauth.interface";
|
|
6
8
|
|
|
7
9
|
export interface OAuthScopeSelectorProps {
|
|
@@ -39,8 +41,11 @@ export function OAuthScopeSelector({
|
|
|
39
41
|
availableScopes = AVAILABLE_OAUTH_SCOPES,
|
|
40
42
|
disabled = false,
|
|
41
43
|
error,
|
|
42
|
-
label
|
|
44
|
+
label: labelProp,
|
|
43
45
|
}: OAuthScopeSelectorProps) {
|
|
46
|
+
const t = useTranslations();
|
|
47
|
+
const scopeText = useOAuthScopeText();
|
|
48
|
+
const label = labelProp ?? t("oauth.ui.scope_selector.label");
|
|
44
49
|
const handleToggle = useCallback(
|
|
45
50
|
(scope: string, checked: boolean) => {
|
|
46
51
|
if (checked) {
|
|
@@ -56,7 +61,7 @@ export function OAuthScopeSelector({
|
|
|
56
61
|
const groupedScopes = availableScopes.reduce(
|
|
57
62
|
(acc, scope) => {
|
|
58
63
|
const [category] = scope.scope.split(":");
|
|
59
|
-
const groupName = category === scope.scope ? "
|
|
64
|
+
const groupName = category === scope.scope ? t("oauth.ui.scope_selector.general") : category;
|
|
60
65
|
|
|
61
66
|
if (!acc[groupName]) {
|
|
62
67
|
acc[groupName] = [];
|
|
@@ -71,7 +76,7 @@ export function OAuthScopeSelector({
|
|
|
71
76
|
<div className="space-y-4">
|
|
72
77
|
<div>
|
|
73
78
|
<Label>{label} *</Label>
|
|
74
|
-
<p className="text-sm text-muted-foreground">
|
|
79
|
+
<p className="text-sm text-muted-foreground">{t("oauth.ui.scope_selector.help")}</p>
|
|
75
80
|
</div>
|
|
76
81
|
|
|
77
82
|
<div className="space-y-4">
|
|
@@ -98,10 +103,14 @@ export function OAuthScopeSelector({
|
|
|
98
103
|
/>
|
|
99
104
|
<div className="flex-1">
|
|
100
105
|
<Label htmlFor={`scope-${scopeInfo.scope}`} className="cursor-pointer">
|
|
101
|
-
{scopeInfo.name}
|
|
102
|
-
{isAdmin &&
|
|
106
|
+
{scopeText(scopeInfo).name}
|
|
107
|
+
{isAdmin && (
|
|
108
|
+
<span className="ms-2 text-xs text-destructive">
|
|
109
|
+
{t("oauth.ui.scope_selector.dangerous")}
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
103
112
|
</Label>
|
|
104
|
-
<p className="text-xs text-muted-foreground">{scopeInfo.description}</p>
|
|
113
|
+
<p className="text-xs text-muted-foreground">{scopeText(scopeInfo).description}</p>
|
|
105
114
|
</div>
|
|
106
115
|
</div>
|
|
107
116
|
);
|
|
@@ -3,6 +3,8 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
import { OAuthClientForm } from "../OAuthClientForm";
|
|
4
4
|
import { OAUTH_SCOPE_DISPLAY, OAuthClientInterface } from "../../interfaces/oauth.interface";
|
|
5
5
|
|
|
6
|
+
vi.mock("next-intl", () => ({ useTranslations: () => (key: string) => key }));
|
|
7
|
+
|
|
6
8
|
const scopeCheckbox = (name: string) => screen.getByRole("checkbox", { name: new RegExp(name) });
|
|
7
9
|
|
|
8
10
|
const makeClient = (allowedScopes: string[]): OAuthClientInterface =>
|
|
@@ -18,9 +20,9 @@ describe("OAuthClientForm", () => {
|
|
|
18
20
|
it("renders only the scopes passed in availableScopes", () => {
|
|
19
21
|
render(<OAuthClientForm onSubmit={vi.fn()} onCancel={vi.fn()} availableScopes={[OAUTH_SCOPE_DISPLAY.mcp]} />);
|
|
20
22
|
|
|
21
|
-
expect(screen.getByText("
|
|
22
|
-
expect(screen.queryByText("
|
|
23
|
-
expect(screen.queryByText("
|
|
23
|
+
expect(screen.getByText("oauth.ui.scopes.mcp.name")).toBeTruthy();
|
|
24
|
+
expect(screen.queryByText("oauth.ui.scopes.read.name")).toBeNull();
|
|
25
|
+
expect(screen.queryByText("oauth.ui.scopes.write.name")).toBeNull();
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
it("preselects defaultScopes in create mode", () => {
|
|
@@ -33,8 +35,8 @@ describe("OAuthClientForm", () => {
|
|
|
33
35
|
/>,
|
|
34
36
|
);
|
|
35
37
|
|
|
36
|
-
expect(scopeCheckbox("
|
|
37
|
-
expect(scopeCheckbox("
|
|
38
|
+
expect(scopeCheckbox("oauth.ui.scopes.mcp.name").getAttribute("aria-checked")).toBe("true");
|
|
39
|
+
expect(scopeCheckbox("oauth.ui.scopes.read.name").getAttribute("aria-checked")).toBe("false");
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
it("uses client.allowedScopes instead of defaultScopes in edit mode", () => {
|
|
@@ -48,15 +50,15 @@ describe("OAuthClientForm", () => {
|
|
|
48
50
|
/>,
|
|
49
51
|
);
|
|
50
52
|
|
|
51
|
-
expect(scopeCheckbox("
|
|
52
|
-
expect(scopeCheckbox("
|
|
53
|
+
expect(scopeCheckbox("oauth.ui.scopes.read.name").getAttribute("aria-checked")).toBe("true");
|
|
54
|
+
expect(scopeCheckbox("oauth.ui.scopes.mcp.name").getAttribute("aria-checked")).toBe("false");
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
it("offers the generic scope list by default, without Only35 scopes", () => {
|
|
56
58
|
render(<OAuthClientForm onSubmit={vi.fn()} onCancel={vi.fn()} />);
|
|
57
59
|
|
|
58
|
-
expect(screen.getByText("
|
|
59
|
-
expect(screen.getByText("
|
|
60
|
+
expect(screen.getByText("oauth.ui.scopes.mcp.name")).toBeTruthy();
|
|
61
|
+
expect(screen.getByText("oauth.ui.scopes.read.name")).toBeTruthy();
|
|
60
62
|
expect(screen.queryByText("View Photographs")).toBeNull();
|
|
61
63
|
expect(screen.queryByText("View Rolls")).toBeNull();
|
|
62
64
|
});
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from "next-intl";
|
|
4
4
|
import { Eye, Pencil, Image, Upload, Film, FolderPlus, User, Shield, LucideIcon } from "lucide-react";
|
|
5
|
+
import { useOAuthScopeText } from "../../hooks/useOAuthScopeText";
|
|
5
6
|
import { OAuthScopeInfo } from "../../interfaces/oauth.interface";
|
|
6
7
|
import { MicroLabel } from "../../../../components/typography";
|
|
7
8
|
|
|
@@ -27,6 +28,7 @@ const SCOPE_ICONS: Record<string, LucideIcon> = {
|
|
|
27
28
|
*/
|
|
28
29
|
export function OAuthScopeList({ scopes }: OAuthScopeListProps) {
|
|
29
30
|
const t = useTranslations();
|
|
31
|
+
const scopeText = useOAuthScopeText();
|
|
30
32
|
|
|
31
33
|
if (scopes.length === 0) {
|
|
32
34
|
return null;
|
|
@@ -47,8 +49,8 @@ export function OAuthScopeList({ scopes }: OAuthScopeListProps) {
|
|
|
47
49
|
</div>
|
|
48
50
|
</div>
|
|
49
51
|
<div className="flex-1">
|
|
50
|
-
<p className="font-medium">{scope.name}</p>
|
|
51
|
-
<p className="text-sm text-muted-foreground">{scope.description}</p>
|
|
52
|
+
<p className="font-medium">{scopeText(scope).name}</p>
|
|
53
|
+
<p className="text-sm text-muted-foreground">{scopeText(scope).description}</p>
|
|
52
54
|
</div>
|
|
53
55
|
</li>
|
|
54
56
|
);
|