diff --git a/.changeset/green-elephants-melt.md b/.changeset/green-elephants-melt.md new file mode 100644 index 00000000000..f2053ca448a --- /dev/null +++ b/.changeset/green-elephants-melt.md @@ -0,0 +1,7 @@ +--- +"@clerk/clerk-js": patch +"@clerk/localizations": patch +"@clerk/types": patch +--- + +Replace expiration segmented list with dropdown and hide description field in `` component diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index c942a77329f..d044e48372b 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -1,10 +1,10 @@ { "files": [ - { "path": "./dist/clerk.js", "maxSize": "610kB" }, + { "path": "./dist/clerk.js", "maxSize": "610.32kB" }, { "path": "./dist/clerk.browser.js", "maxSize": "70.2KB" }, { "path": "./dist/clerk.legacy.browser.js", "maxSize": "113KB" }, { "path": "./dist/clerk.headless*.js", "maxSize": "53.06KB" }, - { "path": "./dist/ui-common*.js", "maxSize": "108.56KB" }, + { "path": "./dist/ui-common*.js", "maxSize": "108.75KB" }, { "path": "./dist/vendors*.js", "maxSize": "40.2KB" }, { "path": "./dist/coinbase*.js", "maxSize": "38KB" }, { "path": "./dist/createorganization*.js", "maxSize": "5KB" }, diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 4323d8204eb..fc6917bc9f7 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -161,22 +161,23 @@ export const ApiKeysTable = ({ - Created at{' '} - {apiKey.createdAt.toLocaleDateString(undefined, { - month: 'short', - day: '2-digit', - year: 'numeric', - })} - + localizationKey={ + apiKey.expiration + ? localizationKeys('apiKeys.createdAndExpirationStatus__expiresOn', { + createdDate: apiKey.createdAt, + expiresDate: apiKey.expiration, + }) + : localizationKeys('apiKeys.createdAndExpirationStatus__never', { + createdDate: apiKey.createdAt, + }) + } + /> diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index a5c89ca3790..b5453ef0aec 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -1,62 +1,127 @@ -import React, { useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; -import { Box, Button, Col, descriptors, Flex, FormLabel, localizationKeys, Text } from '@/ui/customizables'; +import { useApiKeysContext } from '@/ui/contexts'; +import { Box, Col, descriptors, FormLabel, localizationKeys, Text, useLocalizations } from '@/ui/customizables'; import { useActionContext } from '@/ui/elements/Action/ActionRoot'; import { Form } from '@/ui/elements/Form'; import { FormButtons } from '@/ui/elements/FormButtons'; import { FormContainer } from '@/ui/elements/FormContainer'; -import { SegmentedControl } from '@/ui/elements/SegmentedControl'; +import { Select, SelectButton, SelectOptionList } from '@/ui/elements/Select'; +import { ChevronUpDown } from '@/ui/icons'; import { mqu } from '@/ui/styledSystem'; import { useFormControl } from '@/ui/utils/useFormControl'; -export type OnCreateParams = { name: string; description?: string; expiration: number | undefined }; +const EXPIRATION_VALUES = ['never', '1d', '7d', '30d', '60d', '90d', '180d', '1y'] as const; + +type Expiration = (typeof EXPIRATION_VALUES)[number]; + +type ExpirationOption = { + value: Expiration; + label: string; +}; + +export type OnCreateParams = { + name: string; + description?: string; + secondsUntilExpiration: number | undefined; +}; interface CreateApiKeyFormProps { onCreate: (params: OnCreateParams, closeCardFn: () => void) => void; isSubmitting: boolean; } -export type Expiration = 'never' | '30d' | '90d' | 'custom'; +const EXPIRATION_DURATIONS: Record, (date: Date) => void> = { + '1d': date => date.setDate(date.getDate() + 1), + '7d': date => date.setDate(date.getDate() + 7), + '30d': date => date.setDate(date.getDate() + 30), + '60d': date => date.setDate(date.getDate() + 60), + '90d': date => date.setDate(date.getDate() + 90), + '180d': date => date.setDate(date.getDate() + 180), + '1y': date => date.setFullYear(date.getFullYear() + 1), +} as const; -const getTimeLeftInSeconds = (expirationOption: Expiration, customDate?: string) => { - if (expirationOption === 'never') { +const getExpirationLocalizationKey = (expiration: Expiration) => { + switch (expiration) { + case 'never': + return 'apiKeys.formFieldOption__expiration__never'; + case '1d': + return 'apiKeys.formFieldOption__expiration__1d'; + case '7d': + return 'apiKeys.formFieldOption__expiration__7d'; + case '30d': + return 'apiKeys.formFieldOption__expiration__30d'; + case '60d': + return 'apiKeys.formFieldOption__expiration__60d'; + case '90d': + return 'apiKeys.formFieldOption__expiration__90d'; + case '180d': + return 'apiKeys.formFieldOption__expiration__180d'; + case '1y': + return 'apiKeys.formFieldOption__expiration__1y'; + } +}; + +const getTimeLeftInSeconds = (expirationOption?: Expiration): number | undefined => { + if (expirationOption === 'never' || !expirationOption) { return; } const now = new Date(); - let future = new Date(now); + const future = new Date(now); - switch (expirationOption) { - case '30d': - future.setDate(future.getDate() + 30); - break; - case '90d': - future.setDate(future.getDate() + 90); - break; - case 'custom': - future = new Date(customDate as string); - break; - default: - throw new Error('Invalid expiration option'); - } - - const diffInMs = future.getTime() - now.getTime(); - const diffInSecs = Math.floor(diffInMs / 1000); - return diffInSecs; + EXPIRATION_DURATIONS[expirationOption](future); + return Math.floor((future.getTime() - now.getTime()) / 1000); }; -const getMinDate = () => { - const min = new Date(); - min.setDate(min.getDate() + 1); - return min.toISOString().split('T')[0]; +interface ExpirationSelectorProps { + selectedExpiration: ExpirationOption | null; + setSelectedExpiration: (value: ExpirationOption) => void; +} + +const ExpirationSelector: React.FC = ({ selectedExpiration, setSelectedExpiration }) => { + const buttonRef = useRef(null); + const { t } = useLocalizations(); + + const expirationOptions = EXPIRATION_VALUES.map(value => ({ + value, + label: t(localizationKeys(getExpirationLocalizationKey(value))), + })); + + return ( + + ); }; -export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormProps) => { - const [showAdvanced, setShowAdvanced] = useState(false); - const [expiration, setExpiration] = useState('never'); - const createApiKeyFormId = React.useId(); - const segmentedControlId = `${createApiKeyFormId}-segmented-control`; +export const CreateApiKeyForm: React.FC = ({ onCreate, isSubmitting }) => { + const [selectedExpiration, setSelectedExpiration] = useState(null); const { close: closeCardFn } = useActionContext(); + const { showDescription = false } = useApiKeysContext(); + const { t } = useLocalizations(); const nameField = useFormControl('name', '', { type: 'text', @@ -72,14 +137,30 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro isRequired: false, }); - const expirationDateField = useFormControl('apiKeyExpirationDate', '', { - type: 'date', - label: localizationKeys('formFieldLabel__apiKeyExpirationDate'), - placeholder: localizationKeys('formFieldInputPlaceholder__apiKeyExpirationDate'), - isRequired: false, - }); - const canSubmit = nameField.value.length > 2; + const expirationCaption = useMemo(() => { + const timeLeftInSeconds = getTimeLeftInSeconds(selectedExpiration?.value); + + if (!selectedExpiration?.value || !timeLeftInSeconds) { + return t(localizationKeys('apiKeys.formFieldCaption__expiration__never')); + } + + const expirationDate = new Date(Date.now() + timeLeftInSeconds * 1000); + return t( + localizationKeys('apiKeys.formFieldCaption__expiration__expiresOn', { + date: expirationDate.toLocaleString(undefined, { + year: 'numeric', + month: 'long', + day: '2-digit', + hour: 'numeric', + minute: '2-digit', + second: '2-digit', + hour12: true, + timeZoneName: 'short', + }), + }), + ); + }, [selectedExpiration?.value]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -87,7 +168,7 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro { name: nameField.value, description: descriptionField.value || undefined, - expiration: getTimeLeftInSeconds(expiration, expirationDateField.value), + secondsUntilExpiration: getTimeLeftInSeconds(selectedExpiration?.value), }, closeCardFn, ); @@ -100,107 +181,82 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro elementDescriptor={descriptors.apiKeysCreateForm} > - ({ + gap: t.space.$4, + display: 'flex', + flexDirection: 'row', + [mqu.sm]: { + flexDirection: 'column', + }, + })} > - - - {showAdvanced && ( - <> + + + + + + + + + + + + + + {showDescription && ( + ({ + borderTopWidth: t.borderWidths.$normal, + borderTopStyle: t.borderStyles.$solid, + borderTopColor: t.colors.$neutralAlpha100, + paddingTop: t.space.$4, + paddingBottom: t.space.$4, + })} + > - - - - - - setExpiration(value as Expiration)} - fullWidth - sx={t => ({ height: t.sizes.$8 })} - > - - - - - - - {expiration === 'custom' ? ( - - - - ) : ( - - )} - - + )} - - - - + + ); diff --git a/packages/clerk-js/src/ui/components/ApiKeys/RevokeAPIKeyConfirmationModal.tsx b/packages/clerk-js/src/ui/components/ApiKeys/RevokeAPIKeyConfirmationModal.tsx index 3428a6e912b..4653e10c080 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/RevokeAPIKeyConfirmationModal.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/RevokeAPIKeyConfirmationModal.tsx @@ -7,7 +7,7 @@ import { Form } from '@/ui/elements/Form'; import { FormButtons } from '@/ui/elements/FormButtons'; import { FormContainer } from '@/ui/elements/FormContainer'; import { Modal } from '@/ui/elements/Modal'; -import { localizationKeys } from '@/ui/localization'; +import { localizationKeys, useLocalizations } from '@/ui/localization'; import { useFormControl } from '@/ui/utils'; type RevokeAPIKeyConfirmationModalProps = { @@ -31,10 +31,20 @@ export const RevokeAPIKeyConfirmationModal = ({ }: RevokeAPIKeyConfirmationModalProps) => { const clerk = useClerk(); const { mutate } = useSWRConfig(); + const { t } = useLocalizations(); + + const revokeField = useFormControl('apiKeyRevokeConfirmation', '', { + type: 'text', + label: `Type "Revoke" to confirm`, + placeholder: 'Revoke', + isRequired: true, + }); + + const canSubmit = revokeField.value === t(localizationKeys('apiKeys.revokeConfirmation.confirmationText')); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!apiKeyId) return; + if (!apiKeyId || !canSubmit) return; await clerk.apiKeys.revoke({ apiKeyID: apiKeyId }); const cacheKey = { key: 'api-keys', subject }; @@ -43,16 +53,6 @@ export const RevokeAPIKeyConfirmationModal = ({ onClose(); }; - const revokeField = useFormControl('apiKeyRevokeConfirmation', '', { - type: 'text', - label: `Type "Revoke" to confirm`, - placeholder: 'Revoke', - isRequired: true, - }); - - // TODO: Make this locale-aware - const canSubmit = revokeField.value === 'Revoke'; - if (!isOpen) { return null; } @@ -77,6 +77,7 @@ export const RevokeAPIKeyConfirmationModal = ({ minHeight: '100%', height: '100%', width: '100%', + borderRadius: t.radii.$lg, }) : {}, ]} diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx index efdef3cb73f..539e6091823 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx @@ -1,6 +1,6 @@ import { useOrganization } from '@clerk/shared/react'; -import { ApiKeysContext } from '@/ui/contexts'; +import { ApiKeysContext, useOrganizationProfileContext } from '@/ui/contexts'; import { Col, localizationKeys } from '@/ui/customizables'; import { Header } from '@/ui/elements/Header'; import { useUnsafeNavbarContext } from '@/ui/elements/Navbar'; @@ -10,6 +10,7 @@ import { APIKeysPage } from '../ApiKeys/ApiKeys'; export const OrganizationAPIKeysPage = () => { const { organization } = useOrganization(); const { contentRef } = useUnsafeNavbarContext(); + const { apiKeysProps } = useOrganizationProfileContext(); if (!organization) { // We should never reach this point, but we'll return null to make TS happy @@ -24,7 +25,7 @@ export const OrganizationAPIKeysPage = () => { textVariant='h2' /> - + { const { user } = useUser(); const { contentRef } = useUnsafeNavbarContext(); + const { apiKeysProps } = useUserProfileContext(); if (!user) { // We should never reach this point, but we'll return null to make TS happy @@ -17,14 +18,17 @@ export const APIKeysPage = () => { } return ( - + - + { const isAccountPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.ACCOUNT; const isSecurityPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.SECURITY; const isBillingPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.BILLING; - const isApiKeysPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.API_KEYS; + const isAPIKeysPageRoot = pages.routes[0].id === USER_PROFILE_NAVBAR_ROUTE_ID.API_KEYS; const customPageRoutesWithContents = pages.contents?.map((customPage, index) => { const shouldFirstCustomItemBeOnRoot = !isAccountPageRoot && !isSecurityPageRoot && index === 0; @@ -95,7 +95,7 @@ export const UserProfileRoutes = () => { )} {apiKeysSettings.enabled && ( - + diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index 0b26ff5c4b3..93aeea8689a 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -476,6 +476,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'apiKeysCreateFormDescriptionInput', 'apiKeysCreateFormExpirationInput', 'apiKeysCreateFormSubmitButton', + 'apiKeysCreateFormExpirationCaption', 'apiKeysRevokeModal', 'apiKeysRevokeModalInput', 'apiKeysRevokeModalSubmitButton', diff --git a/packages/clerk-js/src/ui/utils/timeAgo.ts b/packages/clerk-js/src/ui/utils/timeAgo.ts index 83dd65e7908..5c7d690346a 100644 --- a/packages/clerk-js/src/ui/utils/timeAgo.ts +++ b/packages/clerk-js/src/ui/utils/timeAgo.ts @@ -7,20 +7,20 @@ export function timeAgo(date: Date | string | number) { if (isNaN(then.getTime())) return ''; const seconds = Math.floor((now.getTime() - then.getTime()) / 1000); - if (seconds < 60) return localizationKeys('apiKeys.dates.lastUsed__seconds', { seconds }); + if (seconds < 60) return localizationKeys('apiKeys.lastUsed__seconds', { seconds }); const minutes = Math.floor(seconds / 60); - if (minutes < 60) return localizationKeys('apiKeys.dates.lastUsed__minutes', { minutes }); + if (minutes < 60) return localizationKeys('apiKeys.lastUsed__minutes', { minutes }); const hours = Math.floor(minutes / 60); - if (hours < 24) return localizationKeys('apiKeys.dates.lastUsed__hours', { hours }); + if (hours < 24) return localizationKeys('apiKeys.lastUsed__hours', { hours }); const days = Math.floor(hours / 24); - if (days < 30) return localizationKeys('apiKeys.dates.lastUsed__days', { days }); + if (days < 30) return localizationKeys('apiKeys.lastUsed__days', { days }); const months = Math.floor(days / 30); - if (months < 12) return localizationKeys('apiKeys.dates.lastUsed__months', { months }); + if (months < 12) return localizationKeys('apiKeys.lastUsed__months', { months }); const years = Math.floor(months / 12); - return localizationKeys('apiKeys.dates.lastUsed__years', { years }); + return localizationKeys('apiKeys.lastUsed__years', { years }); } diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 7aa98251c7e..7d5702ac863 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -17,14 +17,6 @@ export const deDE: LocalizationResource = { apiKeys: { action__add: 'Neuen API-Key hinzufügen', action__search: 'Suche', - dates: { - lastUsed__days: undefined, - lastUsed__hours: undefined, - lastUsed__minutes: undefined, - lastUsed__months: undefined, - lastUsed__seconds: undefined, - lastUsed__years: undefined, - }, detailsTitle__emptyRow: 'Keine API-Keys gefunden', formButtonPrimary__add: 'API-Key erstellen', formHint: 'Geben Sie einen Namen an, um einen API-Key zu erstellen. Sie können ihn jederzeit widerrufen.', @@ -34,7 +26,22 @@ export const deDE: LocalizationResource = { formButtonPrimary__revoke: 'API-Key widerrufen', formHint: 'Sind Sie sicher, dass Sie diesen API-Key löschen wollen?', formTitle: 'API-Key "{{apiKeyName}}" widerrufen?', - }, + confirmationText: 'Widerrufen', + }, + formFieldOption__expiration__1d: undefined, + formFieldOption__expiration__7d: undefined, + formFieldOption__expiration__30d: undefined, + formFieldOption__expiration__60d: undefined, + formFieldOption__expiration__90d: undefined, + formFieldOption__expiration__180d: undefined, + formFieldOption__expiration__1y: undefined, + formFieldOption__expiration__never: undefined, + lastUsed__seconds: undefined, + lastUsed__minutes: undefined, + lastUsed__hours: undefined, + lastUsed__days: undefined, + lastUsed__months: undefined, + lastUsed__years: undefined, }, backButton: 'Zurück', badge__activePlan: 'Aktiv', @@ -180,7 +187,6 @@ export const deDE: LocalizationResource = { formFieldInputPlaceholder__username: 'Benutzername eingeben', formFieldLabel__apiKeyDescription: 'Beschreibung', formFieldLabel__apiKeyExpiration: 'Ablaufdatum', - formFieldLabel__apiKeyExpirationDate: 'Datum auswählen', formFieldLabel__apiKeyName: 'Name', formFieldLabel__automaticInvitations: 'Aktivieren Sie automatische Einladungen für diese Domain', formFieldLabel__backupCode: 'Sicherungscode', diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 979d3d07b74..0729f17a402 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -5,14 +5,20 @@ export const enUS: LocalizationResource = { apiKeys: { action__add: 'Add new key', action__search: 'Search keys', - dates: { - lastUsed__days: '{{days}}d ago', - lastUsed__hours: '{{hours}}h ago', - lastUsed__minutes: '{{minutes}}m ago', - lastUsed__months: '{{months}}mo ago', - lastUsed__seconds: '{{seconds}}s ago', - lastUsed__years: '{{years}}y ago', - }, + formFieldOption__expiration__1d: '1 Day', + formFieldOption__expiration__7d: '7 Days', + formFieldOption__expiration__30d: '30 Days', + formFieldOption__expiration__60d: '60 Days', + formFieldOption__expiration__90d: '90 Days', + formFieldOption__expiration__180d: '180 Days', + formFieldOption__expiration__1y: '1 Year', + formFieldOption__expiration__never: 'Never', + lastUsed__seconds: '{{seconds}}s ago', + lastUsed__minutes: '{{minutes}}m ago', + lastUsed__hours: '{{hours}}h ago', + lastUsed__days: '{{days}}d ago', + lastUsed__months: '{{months}}mo ago', + lastUsed__years: '{{years}}y ago', detailsTitle__emptyRow: 'No API keys found', formButtonPrimary__add: 'Create key', formHint: 'Provide a name to generate a new key. You’ll be able to revoke it anytime.', @@ -22,7 +28,13 @@ export const enUS: LocalizationResource = { formButtonPrimary__revoke: 'Revoke key', formHint: 'Are you sure you want to delete this Secret key?', formTitle: 'Revoke "{{apiKeyName}}" secret key?', + confirmationText: 'Revoke', }, + createdAndExpirationStatus__never: "Created {{ createdDate | shortDate('en-US') }} • Never expires", + createdAndExpirationStatus__expiresOn: + "Created {{ createdDate | shortDate('en-US') }} • Expires {{ expiresDate | longDate('en-US') }}", + formFieldCaption__expiration__never: 'This key will never expire', + formFieldCaption__expiration__expiresOn: 'Expiring {{ date }}', }, backButton: 'Back', badge__activePlan: 'Active', @@ -149,9 +161,9 @@ export const enUS: LocalizationResource = { formFieldError__verificationLinkExpired: 'The verification link expired. Please request a new link.', formFieldHintText__optional: 'Optional', formFieldHintText__slug: 'A slug is a human-readable ID that must be unique. It’s often used in URLs.', - formFieldInputPlaceholder__apiKeyDescription: 'Enter your secret key description', - formFieldInputPlaceholder__apiKeyExpirationDate: 'Enter expiration date', formFieldInputPlaceholder__apiKeyName: 'Enter your secret key name', + formFieldInputPlaceholder__apiKeyExpirationDate: 'Select date', + formFieldInputPlaceholder__apiKeyDescription: 'Explain why you’re generating this key', formFieldInputPlaceholder__backupCode: 'Enter backup code', formFieldInputPlaceholder__confirmDeletionUserAccount: 'Delete account', formFieldInputPlaceholder__emailAddress: 'Enter your email address', @@ -168,8 +180,7 @@ export const enUS: LocalizationResource = { formFieldInputPlaceholder__username: undefined, formFieldLabel__apiKeyDescription: 'Description', formFieldLabel__apiKeyExpiration: 'Expiration', - formFieldLabel__apiKeyExpirationDate: 'Select date', - formFieldLabel__apiKeyName: 'Name', + formFieldLabel__apiKeyName: 'Secret key name', formFieldLabel__automaticInvitations: 'Enable automatic invitations for this domain', formFieldLabel__backupCode: 'Backup code', formFieldLabel__confirmDeletion: 'Confirmation', @@ -216,7 +227,7 @@ export const enUS: LocalizationResource = { }, organizationProfile: { apiKeysPage: { - title: 'API Keys', + title: 'API keys', }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', @@ -338,7 +349,7 @@ export const enUS: LocalizationResource = { }, }, navbar: { - apiKeys: 'API Keys', + apiKeys: 'API keys', billing: 'Billing', description: 'Manage your organization.', general: 'General', @@ -857,7 +868,7 @@ export const enUS: LocalizationResource = { }, userProfile: { apiKeysPage: { - title: 'API Keys', + title: 'API keys', }, backupCodePage: { actionLabel__copied: 'Copied!', diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index f9101dc5bb4..dc647795bee 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -604,6 +604,7 @@ export type ElementsConfig = { apiKeysCreateFormDescriptionInput: WithOptions; apiKeysCreateFormExpirationInput: WithOptions; apiKeysCreateFormSubmitButton: WithOptions; + apiKeysCreateFormExpirationCaption: WithOptions; apiKeysRevokeModal: WithOptions; apiKeysRevokeModalInput: WithOptions; apiKeysRevokeModalSubmitButton: WithOptions; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index e25115150a9..bf0fd935b27 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -462,22 +462,21 @@ export interface Clerk { unmountPricingTable: (targetNode: HTMLDivElement) => void; /** - * @experimental * This API is in early access and may change in future releases. * * Mount a api keys component at the target element. + * @experimental * @param targetNode Target to mount the APIKeys component. * @param props Configuration parameters. */ mountApiKeys: (targetNode: HTMLDivElement, props?: APIKeysProps) => void; /** - * @experimental * This API is in early access and may change in future releases. * * Unmount a api keys component from the target element. * If there is no component mounted at the target node, results in a noop. - * + * @experimental * @param targetNode Target node to unmount the ApiKeys component from. */ unmountApiKeys: (targetNode: HTMLDivElement) => void; @@ -1334,6 +1333,12 @@ export type UserProfileProps = RoutingOptions & { * @experimental **/ __experimental_startPath?: string; + /** + * Specify options for the underlying component. + * e.g. + * @experimental + **/ + apiKeysProps?: APIKeysProps; }; export type UserProfileModalProps = WithoutRouting; @@ -1359,6 +1364,12 @@ export type OrganizationProfileProps = RoutingOptions & { * @experimental **/ __experimental_startPath?: string; + /** + * Specify options for the underlying component. + * e.g. + * @experimental + **/ + apiKeysProps?: APIKeysProps; }; export type OrganizationProfileModalProps = WithoutRouting; @@ -1683,6 +1694,11 @@ export type APIKeysProps = { * prop of ClerkProvider (if one is provided) */ appearance?: APIKeysTheme; + /** + * Whether to show the description field in the API key creation form. + * @default false + */ + showDescription?: boolean; }; export type GetAPIKeysParams = { diff --git a/packages/types/src/elementIds.ts b/packages/types/src/elementIds.ts index aa42c78eb5d..22284ef91de 100644 --- a/packages/types/src/elementIds.ts +++ b/packages/types/src/elementIds.ts @@ -56,4 +56,4 @@ export type OrganizationPreviewId = export type CardActionId = 'havingTrouble' | 'alternativeMethods' | 'signUp' | 'signIn' | 'usePasskey' | 'waitlist'; export type MenuId = 'invitation' | 'member' | ProfileSectionId; -export type SelectId = 'countryCode' | 'role' | 'paymentSource'; +export type SelectId = 'countryCode' | 'role' | 'paymentSource' | 'apiKeyExpiration'; diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 37bbcb81e34..16a1002f4b6 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -106,7 +106,6 @@ export type __internal_LocalizationResource = { formFieldLabel__apiKeyName: LocalizationValue; formFieldLabel__apiKeyDescription: LocalizationValue; formFieldLabel__apiKeyExpiration: LocalizationValue; - formFieldLabel__apiKeyExpirationDate: LocalizationValue; formFieldInputPlaceholder__emailAddress: LocalizationValue; formFieldInputPlaceholder__emailAddresses: LocalizationValue; formFieldInputPlaceholder__phoneNumber: LocalizationValue; @@ -1165,15 +1164,26 @@ export type __internal_LocalizationResource = { formTitle: LocalizationValue<'apiKeyName'>; formHint: LocalizationValue; formButtonPrimary__revoke: LocalizationValue; - }; - dates: { - lastUsed__seconds: LocalizationValue<'seconds'>; - lastUsed__minutes: LocalizationValue<'minutes'>; - lastUsed__hours: LocalizationValue<'hours'>; - lastUsed__days: LocalizationValue<'days'>; - lastUsed__months: LocalizationValue<'months'>; - lastUsed__years: LocalizationValue<'years'>; - }; + confirmationText: LocalizationValue; + }; + lastUsed__seconds: LocalizationValue<'seconds'>; + lastUsed__minutes: LocalizationValue<'minutes'>; + lastUsed__hours: LocalizationValue<'hours'>; + lastUsed__days: LocalizationValue<'days'>; + lastUsed__months: LocalizationValue<'months'>; + lastUsed__years: LocalizationValue<'years'>; + formFieldOption__expiration__1d: LocalizationValue; + formFieldOption__expiration__7d: LocalizationValue; + formFieldOption__expiration__30d: LocalizationValue; + formFieldOption__expiration__60d: LocalizationValue; + formFieldOption__expiration__90d: LocalizationValue; + formFieldOption__expiration__180d: LocalizationValue; + formFieldOption__expiration__1y: LocalizationValue; + formFieldOption__expiration__never: LocalizationValue; + createdAndExpirationStatus__never: LocalizationValue<'createdDate'>; + createdAndExpirationStatus__expiresOn: LocalizationValue<'createdDate' | 'expiresDate'>; + formFieldCaption__expiration__never: LocalizationValue; + formFieldCaption__expiration__expiresOn: LocalizationValue<'date'>; }; };