From 89ceb0e4e41bcc016bbb3f3bec036ba5f0c1ed6d Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 5 May 2025 14:43:55 -0700 Subject: [PATCH 01/38] chore: set initial files for new component --- packages/clerk-js/sandbox/app.ts | 5 ++++ packages/clerk-js/sandbox/template.html | 8 +++++++ packages/clerk-js/src/core/clerk.ts | 24 +++++++++++++++++++ .../ManageApiKeys/ManageApiKeys.tsx | 17 +++++++++++++ .../src/ui/components/ManageApiKeys/index.tsx | 1 + .../ui/contexts/ClerkUIComponentsContext.tsx | 9 ++++++- .../ui/contexts/components/ManageApiKeys.ts | 20 ++++++++++++++++ .../src/ui/contexts/components/index.ts | 1 + .../clerk-js/src/ui/lazyModules/components.ts | 6 +++++ packages/clerk-js/src/ui/types.ts | 13 ++++++++-- packages/types/src/appearance.ts | 6 ++++- packages/types/src/clerk.ts | 21 ++++++++++++++++ 12 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx create mode 100644 packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx create mode 100644 packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index e64bc222ee6..4fde3c997bf 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -33,6 +33,7 @@ const AVAILABLE_COMPONENTS = [ 'organizationSwitcher', 'waitlist', 'pricingTable', + 'manageApiKeys', ] as const; const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox'; @@ -91,6 +92,7 @@ const componentControls: Record<(typeof AVAILABLE_COMPONENTS)[number], Component organizationSwitcher: buildComponentControls('organizationSwitcher'), waitlist: buildComponentControls('waitlist'), pricingTable: buildComponentControls('pricingTable'), + manageApiKeys: buildComponentControls('manageApiKeys'), }; declare global { @@ -310,6 +312,9 @@ void (async () => { '/pricing-table': () => { Clerk.mountPricingTable(app, componentControls.pricingTable.getProps() ?? {}); }, + '/manage-api-keys': () => { + Clerk.mountManageApiKeys(app, componentControls.manageApiKeys.getProps() ?? {}); + }, '/open-sign-in': () => { mountOpenSignInButton(app, componentControls.signIn.getProps() ?? {}); }, diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index cd4207e24c4..4d13f32196a 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -260,6 +260,14 @@ PricingTable +
  • + + Manage API Keys + +
  • { + this.assertComponentsReady(this.#componentControls); + void this.#componentControls.ensureMounted({ preloadHint: 'ManageApiKeys' }).then(controls => + controls.mountComponent({ + name: 'ManageApiKeys', + appearanceKey: 'manageApiKeys', + node, + props, + }), + ); + + this.telemetry?.record(eventPrebuiltComponentMounted('ManageApiKeys', props)); + }; + + public unmountManageApiKeys = (node: HTMLDivElement): void => { + this.assertComponentsReady(this.#componentControls); + void this.#componentControls.ensureMounted().then(controls => + controls.unmountComponent({ + node, + }), + ); + }; + /** * `setActive` can be used to set the active session and/or organization. */ diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx new file mode 100644 index 00000000000..4cd2679481b --- /dev/null +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -0,0 +1,17 @@ +import { useEffect } from 'react'; + +import { useManageApiKeysContext } from '../../contexts'; + +export const ManageApiKeys = () => { + const ctx = useManageApiKeysContext(); + + useEffect(() => { + console.log(ctx); + }, [ctx]); + + return ( +
    +

    Manage API Keys

    +
    + ); +}; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx new file mode 100644 index 00000000000..1cd1c4ab7df --- /dev/null +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx @@ -0,0 +1 @@ +export * from './ManageApiKeys'; diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx index 7096376861e..909cc49860a 100644 --- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx +++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx @@ -1,10 +1,11 @@ -import type { PricingTableProps, UserButtonProps, WaitlistProps } from '@clerk/types'; +import type { ManageApiKeysProps, PricingTableProps, UserButtonProps, WaitlistProps } from '@clerk/types'; import type { ReactNode } from 'react'; import type { AvailableComponentName, AvailableComponentProps } from '../types'; import { CreateOrganizationContext, GoogleOneTapContext, + ManageApiKeysContext, OrganizationListContext, OrganizationProfileContext, OrganizationSwitcherContext, @@ -91,6 +92,12 @@ export function ComponentContextProvider({ ); + case 'ManageApiKeys': + return ( + + {children} + + ); default: throw new Error(`Unknown component context: ${componentName}`); diff --git a/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts b/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts new file mode 100644 index 00000000000..e3ee7fcc172 --- /dev/null +++ b/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +import type { ManageApiKeysCtx } from '../../types'; + +export const ManageApiKeysContext = createContext(null); + +export const useManageApiKeysContext = () => { + const context = useContext(ManageApiKeysContext); + + if (!context || context.componentName !== 'ManageApiKeys') { + throw new Error('Clerk: useManageApiKeysContext called outside ManageApiKeys.'); + } + + const { componentName, ...ctx } = context; + + return { + ...ctx, + componentName, + }; +}; diff --git a/packages/clerk-js/src/ui/contexts/components/index.ts b/packages/clerk-js/src/ui/contexts/components/index.ts index 5bb1e062d6f..6f9e5a2abf2 100644 --- a/packages/clerk-js/src/ui/contexts/components/index.ts +++ b/packages/clerk-js/src/ui/contexts/components/index.ts @@ -15,3 +15,4 @@ export * from './PricingTable'; export * from './Checkout'; export * from './Statements'; export * from './Plans'; +export * from './ManageApiKeys'; diff --git a/packages/clerk-js/src/ui/lazyModules/components.ts b/packages/clerk-js/src/ui/lazyModules/components.ts index eebba1e0a69..f3fbf423d07 100644 --- a/packages/clerk-js/src/ui/lazyModules/components.ts +++ b/packages/clerk-js/src/ui/lazyModules/components.ts @@ -21,6 +21,7 @@ const componentImportPaths = { Checkout: () => import(/* webpackChunkName: "checkout" */ '../components/Checkout'), SessionTasks: () => import(/* webpackChunkName: "sessionTasks" */ '../components/SessionTasks'), PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans'), + ManageApiKeys: () => import(/* webpackChunkName: "manageApiKeys" */ '../components/ManageApiKeys'), } as const; export const SignIn = lazy(() => componentImportPaths.SignIn().then(module => ({ default: module.SignIn }))); @@ -96,6 +97,10 @@ export const PricingTable = lazy(() => componentImportPaths.PricingTable().then(module => ({ default: module.PricingTable })), ); +export const ManageApiKeys = lazy(() => + componentImportPaths.ManageApiKeys().then(module => ({ default: module.ManageApiKeys })), +); + export const Checkout = lazy(() => componentImportPaths.Checkout().then(module => ({ default: module.Checkout }))); export const PlanDetails = lazy(() => @@ -133,6 +138,7 @@ export const ClerkComponents = { PricingTable, Checkout, PlanDetails, + ManageApiKeys, }; export type ClerkComponentName = keyof typeof ClerkComponents; diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts index 3cd7393c5fb..0701e360c22 100644 --- a/packages/clerk-js/src/ui/types.ts +++ b/packages/clerk-js/src/ui/types.ts @@ -7,6 +7,7 @@ import type { CommerceSubscriptionResource, CreateOrganizationProps, GoogleOneTapProps, + ManageApiKeysProps, NewSubscriptionRedirectUrl, OrganizationListProps, OrganizationProfileProps, @@ -50,7 +51,8 @@ export type AvailableComponentProps = | PricingTableProps | __internal_CheckoutProps | __internal_UserVerificationProps - | __internal_PlanDetailsProps; + | __internal_PlanDetailsProps + | ManageApiKeysProps; type ComponentMode = 'modal' | 'mounted'; @@ -117,6 +119,11 @@ export type PricingTableCtx = PricingTableProps & { mode?: ComponentMode; }; +export type ManageApiKeysCtx = ManageApiKeysProps & { + componentName: 'ManageApiKeys'; + mode?: ComponentMode; +}; + export type CheckoutCtx = __internal_CheckoutProps & { componentName: 'Checkout'; } & NewSubscriptionRedirectUrl; @@ -160,5 +167,7 @@ export type AvailableComponentCtx = | GoogleOneTapCtx | WaitlistCtx | PricingTableCtx - | CheckoutCtx; + | CheckoutCtx + | ManageApiKeysCtx; + export type AvailableComponentName = AvailableComponentCtx['componentName']; diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index 20c3edc9f1e..bbd6f4a6664 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -829,7 +829,7 @@ export type WaitlistTheme = Theme; export type PricingTableTheme = Theme; export type CheckoutTheme = Theme; export type PlanDetailTheme = Theme; - +export type ManageApiKeysTheme = Theme; export type Appearance = T & { /** * Theme overrides that only apply to the `` component @@ -883,4 +883,8 @@ export type Appearance = T & { * Theme overrides that only apply to the `` component */ checkout?: T; + /** + * Theme overrides that only apply to the `` component + */ + manageApiKeys?: T; }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 7c40f5c5d1e..a0b8a546d82 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -2,6 +2,7 @@ import type { Appearance, CheckoutTheme, CreateOrganizationTheme, + ManageApiKeysTheme, OrganizationListTheme, OrganizationProfileTheme, OrganizationSwitcherTheme, @@ -458,6 +459,21 @@ export interface Clerk { */ unmountPricingTable: (targetNode: HTMLDivElement) => void; + /** + * Mount a manage api keys component at the target element. + * @param targetNode Target to mount the ManageApiKeys component. + * @param props Configuration parameters. + */ + mountManageApiKeys: (targetNode: HTMLDivElement, props?: ManageApiKeysProps) => void; + + /** + * Unmount a manage api keys component from the target element. + * If there is no component mounted at the target node, results in a noop. + * + * @param targetNode Target node to unmount the ManageApiKeys component from. + */ + unmountManageApiKeys: (targetNode: HTMLDivElement) => void; + /** * Register a listener that triggers a callback each time important Clerk resources are changed. * Allows to hook up at different steps in the sign up, sign in processes. @@ -1608,6 +1624,11 @@ type PortalRoot = HTMLElement | null | undefined; export type PricingTableProps = PricingTableBaseProps & PricingTableDefaultProps; +export type ManageApiKeysProps = { + [key: string]: unknown; + appearance?: ManageApiKeysTheme; +}; + export type __internal_CheckoutProps = { appearance?: CheckoutTheme; planId?: string; From bdafb68802cfcab799444f8ddd2cd0f2f8385f64 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 5 May 2025 18:51:42 -0700 Subject: [PATCH 02/38] chore: add resources and test methods --- packages/clerk-js/src/core/clerk.ts | 6 ++ .../clerk-js/src/core/resources/ApiKey.ts | 61 +++++++++++++++++++ .../clerk-js/src/core/resources/internal.ts | 1 + .../ManageApiKeys/ManageApiKeys.tsx | 12 ++-- packages/types/src/apiKey.ts | 18 ++++++ packages/types/src/clerk.ts | 10 ++- packages/types/src/index.ts | 1 + packages/types/src/json.ts | 17 ++++++ 8 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 packages/clerk-js/src/core/resources/ApiKey.ts create mode 100644 packages/types/src/apiKey.ts diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 36dd9379888..6347d1a6abb 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -19,6 +19,7 @@ import type { __internal_ComponentNavigationContext, __internal_PlanDetailsProps, __internal_UserVerificationModalProps, + ApiKeyResource, AuthenticateWithCoinbaseWalletParams, AuthenticateWithGoogleOneTapParams, AuthenticateWithMetamaskParams, @@ -134,6 +135,7 @@ import { createFapiClient } from './fapiClient'; import { createClientFromJwt } from './jwt-client'; import { CommerceBilling } from './modules/commerce'; import { + ApiKey, BaseResource, Client, EmailLinkError, @@ -1061,6 +1063,10 @@ export class Clerk implements ClerkInterface { ); }; + public getApiKeys = async (): Promise => { + return ApiKey.getAll(); + }; + /** * `setActive` can be used to set the active session and/or organization. */ diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts new file mode 100644 index 00000000000..5dbab295239 --- /dev/null +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -0,0 +1,61 @@ +import type { ApiKeyJSON, ApiKeyResource } from '@clerk/types'; + +import { unixEpochToDate } from '../../utils/date'; +import { BaseResource } from './internal'; + +export class ApiKey extends BaseResource implements ApiKeyResource { + pathRoot = '/api_keys'; + + id = ''; + type = ''; + name = ''; + subject = ''; + scopes: string[] = []; + claims: Record | null = null; + revoked = false; + revocationReason: string | null = null; + expired = false; + expiration: Date | null = null; + createdBy: string | null = null; + creationReason: string | null = null; + createdAt: Date = new Date(); + updatedAt: Date = new Date(); + + constructor(data: ApiKeyJSON) { + super(); + this.fromJSON(data); + } + + protected fromJSON(data: ApiKeyJSON | null): this { + if (!data) { + return this; + } + + this.id = data.id; + this.expiration = data.expiration ? unixEpochToDate(data.expiration) : null; + this.updatedAt = unixEpochToDate(data.updated_at); + this.createdAt = unixEpochToDate(data.created_at); + return this; + } + + static async getAll(): Promise { + return this.clerk + .getFapiClient() + .request({ + method: 'GET', + path: '/api_keys', + pathPrefix: '', + search: { + subject: this.clerk.user?.id ?? '', + }, + headers: { + Authorization: `Bearer ${await this.clerk.session?.getToken()}`, + }, + }) + .then(res => { + const apiKeysJSON = res.payload as unknown as ApiKeyJSON[]; + return apiKeysJSON.map(json => new ApiKey(json)); + }) + .catch(() => []); + } +} diff --git a/packages/clerk-js/src/core/resources/internal.ts b/packages/clerk-js/src/core/resources/internal.ts index 6ae1cb0c007..bbf1132b415 100644 --- a/packages/clerk-js/src/core/resources/internal.ts +++ b/packages/clerk-js/src/core/resources/internal.ts @@ -42,3 +42,4 @@ export * from './UserOrganizationInvitation'; export * from './Verification'; export * from './Web3Wallet'; export * from './Waitlist'; +export * from './ApiKey'; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 4cd2679481b..3c0e0f75c01 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,13 +1,17 @@ +import { useClerk } from '@clerk/shared/react'; import { useEffect } from 'react'; -import { useManageApiKeysContext } from '../../contexts'; +// import { useManageApiKeysContext } from '../../contexts'; export const ManageApiKeys = () => { - const ctx = useManageApiKeysContext(); + const clerk = useClerk(); + // const ctx = useManageApiKeysContext(); useEffect(() => { - console.log(ctx); - }, [ctx]); + clerk.getApiKeys().then(apiKeys => { + console.log(apiKeys); + }); + }, [clerk]); return (
    diff --git a/packages/types/src/apiKey.ts b/packages/types/src/apiKey.ts new file mode 100644 index 00000000000..7d5a6e28429 --- /dev/null +++ b/packages/types/src/apiKey.ts @@ -0,0 +1,18 @@ +import type { ClerkResource } from './resource'; + +export interface ApiKeyResource extends ClerkResource { + id: string; + type: string; + name: string; + subject: string; + scopes: string[]; + claims: Record | null; + revoked: boolean; + revocationReason: string | null; + expired: boolean; + expiration: Date | null; + createdBy: string | null; + creationReason: string | null; + createdAt: Date; + updatedAt: Date; +} diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index a0b8a546d82..5739fb548ed 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -1,3 +1,4 @@ +import type { ApiKeyResource } from './apiKey'; import type { Appearance, CheckoutTheme, @@ -753,6 +754,11 @@ export interface Clerk { * initiated outside of the Clerk class. */ __internal_setActiveInProgress: boolean; + + /** + * Retrieves all API keys for the current user. + */ + getApiKeys: () => Promise; } export type HandleOAuthCallbackParams = TransferableOption & @@ -1625,7 +1631,9 @@ type PortalRoot = HTMLElement | null | undefined; export type PricingTableProps = PricingTableBaseProps & PricingTableDefaultProps; export type ManageApiKeysProps = { - [key: string]: unknown; + type?: string; + subject: string; + claims?: string; appearance?: ManageApiKeysTheme; }; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5f2bb42133b..b888a015b1c 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -66,5 +66,6 @@ export * from './passkey'; export * from './customMenuItems'; export * from './samlConnection'; export * from './waitlist'; +export * from './apiKey'; export * from './snapshots'; export * from './authObject'; diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index 1d1c9910ec5..02734e87584 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -717,3 +717,20 @@ export interface CommerceCheckoutJSON extends ClerkResourceJSON { totals: CommerceCheckoutTotalsJSON; is_immediate_plan_change: boolean; } + +export interface ApiKeyJSON extends ClerkResourceJSON { + id: string; + type: string; + name: string; + subject: string; + scopes: string[]; + claims: Record | null; + revoked: boolean; + revocation_reason: string | null; + expired: boolean; + expiration: number | null; + created_by: string | null; + creation_reason: string | null; + created_at: number; + updated_at: number; +} From cf450059b0e0ace7c5680ecbdb3729f87b705b2f Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Tue, 6 May 2025 00:23:37 -0500 Subject: [PATCH 03/38] chore(clerk-js): Remove Clerk.commerce (#5846) --- .changeset/proud-donuts-shop.md | 2 + .changeset/witty-doors-hear.md | 23 +++++++ .../src/ui/contexts/components/Invoices.tsx | 60 +++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .changeset/proud-donuts-shop.md create mode 100644 .changeset/witty-doors-hear.md create mode 100644 packages/clerk-js/src/ui/contexts/components/Invoices.tsx diff --git a/.changeset/proud-donuts-shop.md b/.changeset/proud-donuts-shop.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/proud-donuts-shop.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.changeset/witty-doors-hear.md b/.changeset/witty-doors-hear.md new file mode 100644 index 00000000000..28178bb81fb --- /dev/null +++ b/.changeset/witty-doors-hear.md @@ -0,0 +1,23 @@ +--- +'@clerk/clerk-js': minor +'@clerk/clerk-react': minor +'@clerk/types': minor +--- + +Expose Clerk Billing APIs. + +## Render the pricing table component +- `Clerk.mountPricingTable` +- `Clerk.unmountPricingTable` + +## Manage payment methods +- `Clerk.[user|organization].initializePaymentSource()` +- `Clerk.[user|organization].addPaymentSource()` +- `Clerk.[user|organization].getPaymentSources()` + +## Billing namespace +- `Clerk.billing` + - `Clerk.billing.getPlans()` + - `Clerk.billing.getSubscriptions()` + - `Clerk.billing.getInvoices()` + - `Clerk.billing.startCheckout()` diff --git a/packages/clerk-js/src/ui/contexts/components/Invoices.tsx b/packages/clerk-js/src/ui/contexts/components/Invoices.tsx new file mode 100644 index 00000000000..bc11ef8bf45 --- /dev/null +++ b/packages/clerk-js/src/ui/contexts/components/Invoices.tsx @@ -0,0 +1,60 @@ +import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; +import type { PropsWithChildren } from 'react'; +import { createContext, useContext } from 'react'; + +import { useFetch } from '../../hooks'; +import type { InvoicesCtx } from '../../types'; +import { useSubscriberTypeContext } from './SubscriberType'; + +const InvoicesContext = createContext(null); + +export const InvoicesContextProvider = ({ children }: PropsWithChildren) => { + const { billing } = useClerk(); + const { organization } = useOrganization(); + const subscriberType = useSubscriberTypeContext(); + const { user } = useUser(); + + const resource = subscriberType === 'org' ? organization : user; + + const { data, isLoading, revalidate } = useFetch( + billing.getInvoices, + { ...(subscriberType === 'org' ? { orgId: organization?.id } : {}) }, + undefined, + `commerce-invoices-${resource?.id}`, + ); + const { data: invoices, total_count: totalCount } = data || { data: [], totalCount: 0 }; + + const getInvoiceById = (invoiceId: string) => { + return invoices.find(invoice => invoice.id === invoiceId); + }; + + return ( + + {children} + + ); +}; + +export const useInvoicesContext = () => { + const context = useContext(InvoicesContext); + + if (!context || context.componentName !== 'Invoices') { + throw new Error('Clerk: useInvoicesContext called outside Invoices.'); + } + + const { componentName, ...ctx } = context; + + return { + ...ctx, + componentName, + }; +}; From 99df5e1d0d0d2cdf7ced73570f3eb8ba5d0e3277 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 12:05:01 -0700 Subject: [PATCH 04/38] chore: add simple table with calls to fapi --- packages/clerk-js/src/core/clerk.ts | 4 + packages/clerk-js/src/core/fapiClient.ts | 2 +- .../clerk-js/src/core/resources/ApiKey.ts | 63 ++++++--- .../ManageApiKeys/ManageApiKeys.tsx | 122 +++++++++++++++++- packages/types/src/clerk.ts | 7 + 5 files changed, 176 insertions(+), 22 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 6347d1a6abb..5322347d017 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1067,6 +1067,10 @@ export class Clerk implements ClerkInterface { return ApiKey.getAll(); }; + public getApiKeySecret = async (id: string): Promise => { + return ApiKey.getSecret(id); + }; + /** * `setActive` can be used to set the active session and/or organization. */ diff --git a/packages/clerk-js/src/core/fapiClient.ts b/packages/clerk-js/src/core/fapiClient.ts index 2ae7bd9a502..609ddff5862 100644 --- a/packages/clerk-js/src/core/fapiClient.ts +++ b/packages/clerk-js/src/core/fapiClient.ts @@ -224,8 +224,8 @@ export function createFapiClient(options: FapiClientOptions): FapiClient { let response: Response; const urlStr = requestInit.url.toString(); const fetchOpts: FapiRequestInit = { - ...requestInit, credentials: 'include', + ...requestInit, method: overwrittenRequestMethod, }; diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts index 5dbab295239..fdaaccdfa8f 100644 --- a/packages/clerk-js/src/core/resources/ApiKey.ts +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -6,20 +6,20 @@ import { BaseResource } from './internal'; export class ApiKey extends BaseResource implements ApiKeyResource { pathRoot = '/api_keys'; - id = ''; - type = ''; - name = ''; - subject = ''; - scopes: string[] = []; - claims: Record | null = null; - revoked = false; - revocationReason: string | null = null; - expired = false; - expiration: Date | null = null; - createdBy: string | null = null; - creationReason: string | null = null; - createdAt: Date = new Date(); - updatedAt: Date = new Date(); + id!: string; + type!: string; + name!: string; + subject!: string; + scopes!: string[]; + claims!: Record | null; + revoked!: boolean; + revocationReason!: string | null; + expired!: boolean; + expiration!: Date | null; + createdBy!: string | null; + creationReason!: string | null; + createdAt!: Date; + updatedAt!: Date; constructor(data: ApiKeyJSON) { super(); @@ -32,7 +32,17 @@ export class ApiKey extends BaseResource implements ApiKeyResource { } this.id = data.id; + this.type = data.type; + this.name = data.name; + this.subject = data.subject; + this.scopes = data.scopes; + this.claims = data.claims; + this.revoked = data.revoked; + this.revocationReason = data.revocation_reason; + this.expired = data.expired; this.expiration = data.expiration ? unixEpochToDate(data.expiration) : null; + this.createdBy = data.created_by; + this.creationReason = data.creation_reason; this.updatedAt = unixEpochToDate(data.updated_at); this.createdAt = unixEpochToDate(data.created_at); return this; @@ -41,7 +51,7 @@ export class ApiKey extends BaseResource implements ApiKeyResource { static async getAll(): Promise { return this.clerk .getFapiClient() - .request({ + .request<{ api_keys: ApiKeyJSON[] }>({ method: 'GET', path: '/api_keys', pathPrefix: '', @@ -51,11 +61,30 @@ export class ApiKey extends BaseResource implements ApiKeyResource { headers: { Authorization: `Bearer ${await this.clerk.session?.getToken()}`, }, + credentials: 'same-origin', }) .then(res => { - const apiKeysJSON = res.payload as unknown as ApiKeyJSON[]; - return apiKeysJSON.map(json => new ApiKey(json)); + const apiKeysJSON = res.payload as unknown as { api_keys: ApiKeyJSON[] }; + return apiKeysJSON.api_keys.map(json => new ApiKey(json)); }) .catch(() => []); } + + static async getSecret(id: string): Promise { + return this.clerk + .getFapiClient() + .request<{ secret: string }>({ + method: 'GET', + path: `/api_keys/${id}/secret`, + credentials: 'same-origin', + pathPrefix: '', + headers: { + Authorization: `Bearer ${await this.clerk.session?.getToken()}`, + }, + }) + .then(res => { + return (res.payload as any)?.secret ?? ''; + }) + .catch(() => ''); + } } diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 3c0e0f75c01..b005a93bca1 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,21 +1,135 @@ import { useClerk } from '@clerk/shared/react'; -import { useEffect } from 'react'; +import type { ApiKeyResource } from '@clerk/types'; +import { useEffect, useState } from 'react'; // import { useManageApiKeysContext } from '../../contexts'; export const ManageApiKeys = () => { const clerk = useClerk(); // const ctx = useManageApiKeysContext(); + const [apiKeys, setApiKeys] = useState([]); + const [revealedKeys, setRevealedKeys] = useState>({}); useEffect(() => { clerk.getApiKeys().then(apiKeys => { - console.log(apiKeys); + setApiKeys(apiKeys); }); }, [clerk]); + const toggleSecret = async (id: string) => { + setRevealedKeys(prev => { + if (prev[id]) { + return { ...prev, [id]: null }; + } + return prev; + }); + + if (!revealedKeys[id]) { + const secret = await clerk.getApiKeySecret(id); + setRevealedKeys(prev => ({ ...prev, [id]: secret })); + } + }; + return ( -
    -

    Manage API Keys

    +
    + + + + + + + + + + + {apiKeys.map(apiKey => ( + + + + + + + ))} + +
    NameLast usedKeyActions
    +
    {apiKey.name}
    +
    + Created at{' '} + {apiKey.createdAt.toLocaleDateString(undefined, { month: 'short', day: '2-digit', year: 'numeric' })} +
    +
    + {/* Placeholder for "Last used" */} + 3d ago + + + + + + +
    ); }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 5739fb548ed..b49accd6588 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -759,6 +759,13 @@ export interface Clerk { * Retrieves all API keys for the current user. */ getApiKeys: () => Promise; + + /** + * Retrieves the secret for a given API key ID. + * @param id - The ID of the API key to retrieve the secret for. + * @returns The secret for the given API key ID. + */ + getApiKeySecret: (id: string) => Promise; } export type HandleOAuthCallbackParams = TransferableOption & From 547f9e35eab8ad883d77768b31f6d6bda59d6a35 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 14:33:44 -0700 Subject: [PATCH 05/38] chore: use built in components --- .../ManageApiKeys/ManageApiKeys.tsx | 194 +++++++++--------- 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index b005a93bca1..b6ea38a2701 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,21 +1,15 @@ import { useClerk } from '@clerk/shared/react'; -import type { ApiKeyResource } from '@clerk/types'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; -// import { useManageApiKeysContext } from '../../contexts'; +import { Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; +import { useFetch } from '../../hooks'; +import { Clipboard, Eye, EyeSlash, Plus } from '../../icons'; export const ManageApiKeys = () => { const clerk = useClerk(); - // const ctx = useManageApiKeysContext(); - const [apiKeys, setApiKeys] = useState([]); + const { data: apiKeys } = useFetch(clerk.getApiKeys, {}); const [revealedKeys, setRevealedKeys] = useState>({}); - useEffect(() => { - clerk.getApiKeys().then(apiKeys => { - setApiKeys(apiKeys); - }); - }, [clerk]); - const toggleSecret = async (id: string) => { setRevealedKeys(prev => { if (prev[id]) { @@ -31,105 +25,111 @@ export const ManageApiKeys = () => { }; return ( -
    - + - - - - - - - - - - {apiKeys.map(apiKey => ( - - +
    NameLast usedKeyActions
    -
    {apiKey.name}
    -
    + + + + + + + + + + + + + + {apiKeys?.map(apiKey => ( + + - - + + - + - + + + + ))} - -
    NameLast usedKeyActions
    + {apiKey.name} + Created at{' '} - {apiKey.createdAt.toLocaleDateString(undefined, { month: 'short', day: '2-digit', year: 'numeric' })} - - - {/* Placeholder for "Last used" */} - 3d ago - - + + + {/* Placeholder for "Last used" */} + 3d ago + + + - - + - - + + -
    -
    +
    + ); }; From 430a292bb67cc75048477a653301b87aae5fe5b6 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 19:05:22 -0700 Subject: [PATCH 06/38] chore: add fake form --- .../ManageApiKeys/ManageApiKeys.tsx | 107 +++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index b6ea38a2701..3cb44b66bd2 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,14 +1,98 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; -import { Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; +import { Box, Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, Plus } from '../../icons'; +// AddApiKeyForm component +const AddApiKeyForm = ({ + onCreate, + onCancel, + loading, +}: { + onCreate: (name: string) => void; + onCancel: () => void; + loading?: boolean; +}) => { + const [name, setName] = useState(''); + + return ( + + Add new API key + Secret key name + setName(e.target.value)} + sx={{ + width: '100%', + fontSize: 16, + mb: 5, + borderRadius: 8, + border: '1px solid #ddd', + background: '#fafafa', + py: 3, + px: 3, + }} + /> + + + + + + ); +}; + export const ManageApiKeys = () => { const clerk = useClerk(); const { data: apiKeys } = useFetch(clerk.getApiKeys, {}); const [revealedKeys, setRevealedKeys] = useState>({}); + const [showAddForm, setShowAddForm] = useState(false); + const [creating, setCreating] = useState(false); const toggleSecret = async (id: string) => { setRevealedKeys(prev => { @@ -24,6 +108,17 @@ export const ManageApiKeys = () => { } }; + const handleCreate = async (_name: string) => { + setCreating(true); + try { + // await clerk.createApiKey({ name }); + setShowAddForm(false); + // refetch?.(); + } finally { + setCreating(false); + } + }; + return ( { /> + + {showAddForm && ( + void handleCreate('')} + onCancel={() => setShowAddForm(false)} + loading={creating} + /> + )} + From 6981d7e302155664fb5598b2c3a89b4f6740f5d8 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 19:09:46 -0700 Subject: [PATCH 07/38] chore: fix bad rebase --- .../src/ui/contexts/components/Invoices.tsx | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 packages/clerk-js/src/ui/contexts/components/Invoices.tsx diff --git a/packages/clerk-js/src/ui/contexts/components/Invoices.tsx b/packages/clerk-js/src/ui/contexts/components/Invoices.tsx deleted file mode 100644 index bc11ef8bf45..00000000000 --- a/packages/clerk-js/src/ui/contexts/components/Invoices.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; -import type { PropsWithChildren } from 'react'; -import { createContext, useContext } from 'react'; - -import { useFetch } from '../../hooks'; -import type { InvoicesCtx } from '../../types'; -import { useSubscriberTypeContext } from './SubscriberType'; - -const InvoicesContext = createContext(null); - -export const InvoicesContextProvider = ({ children }: PropsWithChildren) => { - const { billing } = useClerk(); - const { organization } = useOrganization(); - const subscriberType = useSubscriberTypeContext(); - const { user } = useUser(); - - const resource = subscriberType === 'org' ? organization : user; - - const { data, isLoading, revalidate } = useFetch( - billing.getInvoices, - { ...(subscriberType === 'org' ? { orgId: organization?.id } : {}) }, - undefined, - `commerce-invoices-${resource?.id}`, - ); - const { data: invoices, total_count: totalCount } = data || { data: [], totalCount: 0 }; - - const getInvoiceById = (invoiceId: string) => { - return invoices.find(invoice => invoice.id === invoiceId); - }; - - return ( - - {children} - - ); -}; - -export const useInvoicesContext = () => { - const context = useContext(InvoicesContext); - - if (!context || context.componentName !== 'Invoices') { - throw new Error('Clerk: useInvoicesContext called outside Invoices.'); - } - - const { componentName, ...ctx } = context; - - return { - ...ctx, - componentName, - }; -}; From c473fcdeccae59b8d7897495877652109fec5238 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 19:11:06 -0700 Subject: [PATCH 08/38] chore: fix bad rebase --- .changeset/proud-donuts-shop.md | 2 -- .changeset/witty-doors-hear.md | 23 ----------------------- 2 files changed, 25 deletions(-) delete mode 100644 .changeset/proud-donuts-shop.md delete mode 100644 .changeset/witty-doors-hear.md diff --git a/.changeset/proud-donuts-shop.md b/.changeset/proud-donuts-shop.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/proud-donuts-shop.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ---- diff --git a/.changeset/witty-doors-hear.md b/.changeset/witty-doors-hear.md deleted file mode 100644 index 28178bb81fb..00000000000 --- a/.changeset/witty-doors-hear.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@clerk/clerk-js': minor -'@clerk/clerk-react': minor -'@clerk/types': minor ---- - -Expose Clerk Billing APIs. - -## Render the pricing table component -- `Clerk.mountPricingTable` -- `Clerk.unmountPricingTable` - -## Manage payment methods -- `Clerk.[user|organization].initializePaymentSource()` -- `Clerk.[user|organization].addPaymentSource()` -- `Clerk.[user|organization].getPaymentSources()` - -## Billing namespace -- `Clerk.billing` - - `Clerk.billing.getPlans()` - - `Clerk.billing.getSubscriptions()` - - `Clerk.billing.getInvoices()` - - `Clerk.billing.startCheckout()` From 866b8e3e48ffdea15d6c6ff0da0a505d82513b57 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 6 May 2025 19:34:56 -0700 Subject: [PATCH 09/38] chore: add create api key func --- packages/clerk-js/src/core/clerk.ts | 4 + .../clerk-js/src/core/resources/ApiKey.ts | 31 ++++++ .../ManageApiKeys/CreateApiKeyForm.tsx | 54 +++++++++ .../ManageApiKeys/ManageApiKeys.tsx | 104 ++---------------- packages/types/src/clerk.ts | 7 ++ 5 files changed, 104 insertions(+), 96 deletions(-) create mode 100644 packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 5322347d017..1bf277f55fa 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1071,6 +1071,10 @@ export class Clerk implements ClerkInterface { return ApiKey.getSecret(id); }; + public createApiKey = async (name: string): Promise => { + return ApiKey.create(name); + }; + /** * `setActive` can be used to set the active session and/or organization. */ diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts index fdaaccdfa8f..b38f7aeed31 100644 --- a/packages/clerk-js/src/core/resources/ApiKey.ts +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -87,4 +87,35 @@ export class ApiKey extends BaseResource implements ApiKeyResource { }) .catch(() => ''); } + + static async create(name: string): Promise { + return this.clerk + .getFapiClient() + .request({ + method: 'POST', + path: '/api_keys', + pathPrefix: '', + search: { + subject: this.clerk.user?.id ?? '', + }, + headers: { + Authorization: `Bearer ${await this.clerk.session?.getToken()}`, + 'Content-Type': 'application/json', + }, + credentials: 'same-origin', + body: JSON.stringify({ + type: 'api_key', + name, + subject: this.clerk.user?.id ?? '', + claims: null, + scopes: [], + creation_reason: null, + seconds_until_expiration: null, + }), + }) + .then(res => { + const apiKeysJSON = res.payload as unknown as ApiKeyJSON; + return new ApiKey(apiKeysJSON); + }); + } } diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx new file mode 100644 index 00000000000..f2a318150d0 --- /dev/null +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx @@ -0,0 +1,54 @@ +import { useState } from 'react'; + +import { Box, Button, Flex, Input, Text } from '../../customizables'; + +export const CreateApiKeyForm = ({ + onCreate, + onCancel, + loading, +}: { + onCreate: (name: string) => void; + onCancel: () => void; + loading?: boolean; +}) => { + const [name, setName] = useState(''); + + return ( + + Add new API key + Secret key name + setName(e.target.value)} + sx={{ + width: '100%', + }} + /> + + + + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 3cb44b66bd2..4e541e1910e 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,95 +1,14 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; -import { Box, Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; +import { Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, Plus } from '../../icons'; - -// AddApiKeyForm component -const AddApiKeyForm = ({ - onCreate, - onCancel, - loading, -}: { - onCreate: (name: string) => void; - onCancel: () => void; - loading?: boolean; -}) => { - const [name, setName] = useState(''); - - return ( - - Add new API key - Secret key name - setName(e.target.value)} - sx={{ - width: '100%', - fontSize: 16, - mb: 5, - borderRadius: 8, - border: '1px solid #ddd', - background: '#fafafa', - py: 3, - px: 3, - }} - /> - - - - - - ); -}; +import { CreateApiKeyForm } from './CreateApiKeyForm'; export const ManageApiKeys = () => { const clerk = useClerk(); - const { data: apiKeys } = useFetch(clerk.getApiKeys, {}); + const { data: apiKeys, revalidate } = useFetch(clerk.getApiKeys, {}); const [revealedKeys, setRevealedKeys] = useState>({}); const [showAddForm, setShowAddForm] = useState(false); const [creating, setCreating] = useState(false); @@ -108,12 +27,12 @@ export const ManageApiKeys = () => { } }; - const handleCreate = async (_name: string) => { + const handleCreate = async (name: string) => { setCreating(true); try { - // await clerk.createApiKey({ name }); + await clerk.createApiKey(name); setShowAddForm(false); - // refetch?.(); + revalidate(); } finally { setCreating(false); } @@ -138,21 +57,14 @@ export const ManageApiKeys = () => { {showAddForm && ( - void handleCreate('')} + void handleCreate(name)} onCancel={() => setShowAddForm(false)} loading={creating} /> diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index b49accd6588..6a0ba59f5aa 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -766,6 +766,13 @@ export interface Clerk { * @returns The secret for the given API key ID. */ getApiKeySecret: (id: string) => Promise; + + /** + * Creates a new API key. + * @param name - The name of the API key. + * @returns The created API key. + */ + createApiKey: (name: string) => Promise; } export type HandleOAuthCallbackParams = TransferableOption & From 926c45524c194244af42f30a2c04ae6d764571fb Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 7 May 2025 11:20:57 -0700 Subject: [PATCH 10/38] chore: add prop types and improve fetching --- packages/clerk-js/src/core/clerk.ts | 9 +++-- .../clerk-js/src/core/resources/ApiKey.ts | 38 ++++++++----------- .../ManageApiKeys/ManageApiKeys.tsx | 16 ++++---- packages/types/src/clerk.ts | 26 +++++++------ 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 1bf277f55fa..ad12097dca7 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -31,6 +31,7 @@ import type { ClientJSONSnapshot, ClientResource, CommerceBillingNamespace, + CreateApiKeyParams, CreateOrganizationParams, CreateOrganizationProps, CredentialReturn, @@ -1067,12 +1068,12 @@ export class Clerk implements ClerkInterface { return ApiKey.getAll(); }; - public getApiKeySecret = async (id: string): Promise => { - return ApiKey.getSecret(id); + public getApiKeySecret = async (apiKeyID: string): Promise => { + return ApiKey.getSecret(apiKeyID); }; - public createApiKey = async (name: string): Promise => { - return ApiKey.create(name); + public createApiKey = async (params: CreateApiKeyParams): Promise => { + return ApiKey.create(params); }; /** diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts index b38f7aeed31..477104b1238 100644 --- a/packages/clerk-js/src/core/resources/ApiKey.ts +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -1,4 +1,4 @@ -import type { ApiKeyJSON, ApiKeyResource } from '@clerk/types'; +import type { ApiKeyJSON, ApiKeyResource, CreateApiKeyParams } from '@clerk/types'; import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './internal'; @@ -48,7 +48,7 @@ export class ApiKey extends BaseResource implements ApiKeyResource { return this; } - static async getAll(): Promise { + static async getAll(params?: { subject?: string }): Promise { return this.clerk .getFapiClient() .request<{ api_keys: ApiKeyJSON[] }>({ @@ -56,7 +56,7 @@ export class ApiKey extends BaseResource implements ApiKeyResource { path: '/api_keys', pathPrefix: '', search: { - subject: this.clerk.user?.id ?? '', + subject: params?.subject ?? this.clerk.organization?.id ?? this.clerk.user?.id ?? '', }, headers: { Authorization: `Bearer ${await this.clerk.session?.getToken()}`, @@ -83,39 +83,31 @@ export class ApiKey extends BaseResource implements ApiKeyResource { }, }) .then(res => { - return (res.payload as any)?.secret ?? ''; + const { secret } = res.payload as unknown as { secret: string }; + return secret; }) .catch(() => ''); } - static async create(name: string): Promise { - return this.clerk - .getFapiClient() - .request({ - method: 'POST', + static async create(params: CreateApiKeyParams): Promise { + const json = ( + await BaseResource._fetch({ path: '/api_keys', + method: 'POST', pathPrefix: '', - search: { - subject: this.clerk.user?.id ?? '', - }, headers: { Authorization: `Bearer ${await this.clerk.session?.getToken()}`, 'Content-Type': 'application/json', }, credentials: 'same-origin', body: JSON.stringify({ - type: 'api_key', - name, - subject: this.clerk.user?.id ?? '', - claims: null, - scopes: [], - creation_reason: null, - seconds_until_expiration: null, + ...params, + type: params.type ?? 'api_key', + subject: params.subject ?? this.clerk.organization?.id ?? this.clerk.user?.id ?? '', }), }) - .then(res => { - const apiKeysJSON = res.payload as unknown as ApiKeyJSON; - return new ApiKey(apiKeysJSON); - }); + )?.response as ApiKeyJSON; + + return new ApiKey(json); } } diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 4e541e1910e..fb8b7e7cc34 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -1,6 +1,7 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; +import { useManageApiKeysContext } from '../../contexts'; import { Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, Plus } from '../../icons'; @@ -8,9 +9,10 @@ import { CreateApiKeyForm } from './CreateApiKeyForm'; export const ManageApiKeys = () => { const clerk = useClerk(); - const { data: apiKeys, revalidate } = useFetch(clerk.getApiKeys, {}); + const ctx = useManageApiKeysContext(); + const { data: apiKeys, revalidate } = useFetch(() => clerk.getApiKeys({ subject: ctx.subject }), {}); const [revealedKeys, setRevealedKeys] = useState>({}); - const [showAddForm, setShowAddForm] = useState(false); + const [showCreateForm, setShowCreateForm] = useState(false); const [creating, setCreating] = useState(false); const toggleSecret = async (id: string) => { @@ -30,8 +32,8 @@ export const ManageApiKeys = () => { const handleCreate = async (name: string) => { setCreating(true); try { - await clerk.createApiKey(name); - setShowAddForm(false); + await clerk.createApiKey({ name }); + setShowCreateForm(false); revalidate(); } finally { setCreating(false); @@ -56,16 +58,16 @@ export const ManageApiKeys = () => { /> - {showAddForm && ( + {showCreateForm && ( void handleCreate(name)} - onCancel={() => setShowAddForm(false)} + onCancel={() => setShowCreateForm(false)} loading={creating} /> )} diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 6a0ba59f5aa..fa909f8b3e8 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -756,23 +756,19 @@ export interface Clerk { __internal_setActiveInProgress: boolean; /** - * Retrieves all API keys for the current user. + * Retrieves all API keys for the current user or organization. */ - getApiKeys: () => Promise; + getApiKeys: (params?: { subject?: string }) => Promise; /** * Retrieves the secret for a given API key ID. - * @param id - The ID of the API key to retrieve the secret for. - * @returns The secret for the given API key ID. */ - getApiKeySecret: (id: string) => Promise; + getApiKeySecret: (apiKeyID: string) => Promise; /** * Creates a new API key. - * @param name - The name of the API key. - * @returns The created API key. */ - createApiKey: (name: string) => Promise; + createApiKey: (params: CreateApiKeyParams) => Promise; } export type HandleOAuthCallbackParams = TransferableOption & @@ -1645,12 +1641,20 @@ type PortalRoot = HTMLElement | null | undefined; export type PricingTableProps = PricingTableBaseProps & PricingTableDefaultProps; export type ManageApiKeysProps = { - type?: string; - subject: string; - claims?: string; + type?: 'api_key'; + subject?: string; appearance?: ManageApiKeysTheme; }; +export type CreateApiKeyParams = { + type?: 'api_key'; + name: string; + subject?: string; + claims?: string | Record; + scopes?: string[]; + creationReason?: string; +}; + export type __internal_CheckoutProps = { appearance?: CheckoutTheme; planId?: string; From f50e3960bfffff2a1508f59ddd516090b9e9bdcb Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 7 May 2025 12:01:25 -0700 Subject: [PATCH 11/38] chore: add React component --- packages/react/src/components/index.ts | 1 + .../react/src/components/uiComponents.tsx | 29 +++++++++++ packages/react/src/isomorphicClerk.ts | 51 +++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index f932a18115d..4b988ca6f0a 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -10,6 +10,7 @@ export { GoogleOneTap, Waitlist, PricingTable, + ManageApiKeys, } from './uiComponents'; export { diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index efba0fe8130..4c65ecba6b8 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -2,6 +2,7 @@ import { logErrorInDevMode } from '@clerk/shared/utils'; import type { CreateOrganizationProps, GoogleOneTapProps, + ManageApiKeysProps, OrganizationListProps, OrganizationProfileProps, OrganizationSwitcherProps, @@ -600,3 +601,31 @@ export const PricingTable = withClerk( }, { component: 'PricingTable', renderWhileLoading: true }, ); + +export const ManageApiKeys = withClerk( + ({ clerk, component, fallback, ...props }: WithClerkProp) => { + const mountingStatus = useWaitForComponentMount(component); + const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded; + + const rendererRootProps = { + ...(shouldShowFallback && fallback && { style: { display: 'none' } }), + }; + + return ( + <> + {shouldShowFallback && fallback} + {clerk.loaded && ( + + )} + + ); + }, + { component: 'ManageApiKeys', renderWhileLoading: true }, +); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index d9b18bf73c3..cc89ba8baaa 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -7,6 +7,7 @@ import type { __internal_PlanDetailsProps, __internal_UserVerificationModalProps, __internal_UserVerificationProps, + ApiKeyResource, AuthenticateWithCoinbaseWalletParams, AuthenticateWithGoogleOneTapParams, AuthenticateWithMetamaskParams, @@ -17,6 +18,7 @@ import type { ClerkStatus, ClientResource, CommerceBillingNamespace, + CreateApiKeyParams, CreateOrganizationParams, CreateOrganizationProps, DomainOrProxyUrl, @@ -26,6 +28,7 @@ import type { JoinWaitlistParams, ListenerCallback, LoadedClerk, + ManageApiKeysProps, NextTaskParams, OrganizationListProps, OrganizationProfileProps, @@ -131,6 +134,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { private premountMethodCalls = new Map, MethodCallback>(); private premountWaitlistNodes = new Map(); private premountPricingTableNodes = new Map(); + private premountManageApiKeysNodes = new Map(); // A separate Map of `addListener` method calls to handle multiple listeners. private premountAddListenerCalls = new Map< ListenerCallback, @@ -609,6 +613,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { clerkjs.mountPricingTable(node, props); }); + this.premountManageApiKeysNodes.forEach((props, node) => { + clerkjs.mountManageApiKeys(node, props); + }); + /** * Only update status in case `clerk.status` is missing. In any other case, `clerk-js` should be the orchestrator. */ @@ -1050,6 +1058,22 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + mountManageApiKeys = (node: HTMLDivElement, props?: ManageApiKeysProps): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.mountManageApiKeys(node, props); + } else { + this.premountManageApiKeysNodes.set(node, props); + } + }; + + unmountManageApiKeys = (node: HTMLDivElement): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.unmountManageApiKeys(node); + } else { + this.premountManageApiKeysNodes.delete(node); + } + }; + addListener = (listener: ListenerCallback): UnsubscribeCallback => { if (this.clerkjs) { return this.clerkjs.addListener(listener); @@ -1286,6 +1310,33 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + getApiKeys = async (): Promise => { + const callback = () => this.clerkjs?.getApiKeys(); + if (this.clerkjs && this.loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('getApiKeys', callback); + } + }; + + getApiKeySecret = async (apiKeyId: string): Promise => { + const callback = () => this.clerkjs?.getApiKeySecret(apiKeyId); + if (this.clerkjs && this.loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('getApiKeySecret', callback); + } + }; + + createApiKey = async (params: CreateApiKeyParams): Promise => { + const callback = () => this.clerkjs?.createApiKey(params); + if (this.clerkjs && this.loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('createApiKey', callback); + } + }; + signOut = async (...args: Parameters) => { const callback = () => this.clerkjs?.signOut(...args); if (this.clerkjs && this.loaded) { From e152dc25e05e205ad9fd9f7b36d7648f9b4a28ed Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 7 May 2025 13:28:28 -0700 Subject: [PATCH 12/38] chore: accept props --- packages/clerk-js/src/core/clerk.ts | 5 +++-- packages/clerk-js/src/core/resources/ApiKey.ts | 4 ++-- packages/react/src/isomorphicClerk.ts | 5 +++-- packages/types/src/clerk.ts | 4 ++++ 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index ad12097dca7..4ee048d822a 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -39,6 +39,7 @@ import type { EnvironmentJSON, EnvironmentJSONSnapshot, EnvironmentResource, + GetApiKeysParams, GoogleOneTapProps, HandleEmailLinkVerificationParams, HandleOAuthCallbackParams, @@ -1064,8 +1065,8 @@ export class Clerk implements ClerkInterface { ); }; - public getApiKeys = async (): Promise => { - return ApiKey.getAll(); + public getApiKeys = async (params?: GetApiKeysParams): Promise => { + return ApiKey.getAll(params); }; public getApiKeySecret = async (apiKeyID: string): Promise => { diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts index 477104b1238..0940f325d46 100644 --- a/packages/clerk-js/src/core/resources/ApiKey.ts +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -1,4 +1,4 @@ -import type { ApiKeyJSON, ApiKeyResource, CreateApiKeyParams } from '@clerk/types'; +import type { ApiKeyJSON, ApiKeyResource, CreateApiKeyParams, GetApiKeysParams } from '@clerk/types'; import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './internal'; @@ -48,7 +48,7 @@ export class ApiKey extends BaseResource implements ApiKeyResource { return this; } - static async getAll(params?: { subject?: string }): Promise { + static async getAll(params?: GetApiKeysParams): Promise { return this.clerk .getFapiClient() .request<{ api_keys: ApiKeyJSON[] }>({ diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index cc89ba8baaa..0a217f95661 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -22,6 +22,7 @@ import type { CreateOrganizationParams, CreateOrganizationProps, DomainOrProxyUrl, + GetApiKeysParams, GoogleOneTapProps, HandleEmailLinkVerificationParams, HandleOAuthCallbackParams, @@ -1310,8 +1311,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - getApiKeys = async (): Promise => { - const callback = () => this.clerkjs?.getApiKeys(); + getApiKeys = async (params?: GetApiKeysParams): Promise => { + const callback = () => this.clerkjs?.getApiKeys(params); if (this.clerkjs && this.loaded) { return callback() as Promise; } else { diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index fa909f8b3e8..a5c4c720cdd 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -1646,6 +1646,10 @@ export type ManageApiKeysProps = { appearance?: ManageApiKeysTheme; }; +export type GetApiKeysParams = { + subject?: string; +}; + export type CreateApiKeyParams = { type?: 'api_key'; name: string; From 9257d6024f77bdc63dc30032203f908f085f5d6b Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Wed, 7 May 2025 13:35:30 -0700 Subject: [PATCH 13/38] chore: add copy button functionality --- .../ManageApiKeys/ManageApiKeys.tsx | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index fb8b7e7cc34..c6dc391e087 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -3,10 +3,26 @@ import { useState } from 'react'; import { useManageApiKeysContext } from '../../contexts'; import { Button, Flex, Flow, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; -import { useFetch } from '../../hooks'; +import { useClipboard, useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, Plus } from '../../icons'; import { CreateApiKeyForm } from './CreateApiKeyForm'; +const CopyButton = ({ text }: { text: string }) => { + const { onCopy, hasCopied } = useClipboard(text); + + return ( + + ); +}; + export const ManageApiKeys = () => { const clerk = useClerk(); const ctx = useManageApiKeysContext(); @@ -60,7 +76,7 @@ export const ManageApiKeys = () => { variant='solid' onClick={() => setShowCreateForm(true)} > - + Add new key + Add new key @@ -127,14 +143,7 @@ export const ManageApiKeys = () => { > - + diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 0a217f95661..7b8c3255b5e 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -37,6 +37,7 @@ import type { OrganizationSwitcherProps, PricingTableProps, RedirectOptions, + RevokeApiKeyParams, SetActiveParams, SignInProps, SignInRedirectOptions, @@ -1338,6 +1339,15 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + revokeApiKey = async (params: RevokeApiKeyParams): Promise => { + const callback = () => this.clerkjs?.revokeApiKey(params); + if (this.clerkjs && this.loaded) { + return callback() as Promise; + } else { + this.premountMethodCalls.set('revokeApiKey', callback); + } + }; + signOut = async (...args: Parameters) => { const callback = () => this.clerkjs?.signOut(...args); if (this.clerkjs && this.loaded) { diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index a5c4c720cdd..1fcb20b9d32 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -769,6 +769,11 @@ export interface Clerk { * Creates a new API key. */ createApiKey: (params: CreateApiKeyParams) => Promise; + + /** + * Revokes a given API key by ID. + */ + revokeApiKey: (params: RevokeApiKeyParams) => Promise; } export type HandleOAuthCallbackParams = TransferableOption & @@ -1659,6 +1664,11 @@ export type CreateApiKeyParams = { creationReason?: string; }; +export type RevokeApiKeyParams = { + apiKeyID: string; + revocationReason?: string; +}; + export type __internal_CheckoutProps = { appearance?: CheckoutTheme; planId?: string; From 5d5b67e49af733d6bdfef59049edc81518beef3c Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 8 May 2025 10:14:52 -0700 Subject: [PATCH 16/38] chore: set minimum fields --- packages/clerk-js/src/core/clerk.ts | 24 ++-- .../clerk-js/src/core/resources/ApiKey.ts | 3 +- .../ManageApiKeys/CreateApiKeyForm.tsx | 136 +++++++++++++----- .../ManageApiKeys/ManageApiKeys.tsx | 7 +- packages/types/src/clerk.ts | 7 +- 5 files changed, 119 insertions(+), 58 deletions(-) diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 513cc409af4..15f556686ee 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -1066,22 +1066,6 @@ export class Clerk implements ClerkInterface { ); }; - public getApiKeys = async (params?: GetApiKeysParams): Promise => { - return ApiKey.getAll(params); - }; - - public getApiKeySecret = async (apiKeyID: string): Promise => { - return ApiKey.getSecret(apiKeyID); - }; - - public createApiKey = async (params: CreateApiKeyParams): Promise => { - return ApiKey.create(params); - }; - - public revokeApiKey = async (params: RevokeApiKeyParams): Promise => { - return ApiKey.revoke(params); - }; - /** * `setActive` can be used to set the active session and/or organization. */ @@ -2072,6 +2056,14 @@ export class Clerk implements ClerkInterface { this.environment = environment; } + public getApiKeys = (params?: GetApiKeysParams): Promise => ApiKey.getAll(params); + + public getApiKeySecret = (apiKeyID: string): Promise => ApiKey.getSecret(apiKeyID); + + public createApiKey = (params: CreateApiKeyParams): Promise => ApiKey.create(params); + + public revokeApiKey = (params: RevokeApiKeyParams): Promise => ApiKey.revoke(params); + __internal_setCountry = (country: string | null) => { if (!this.__internal_country) { this.__internal_country = country; diff --git a/packages/clerk-js/src/core/resources/ApiKey.ts b/packages/clerk-js/src/core/resources/ApiKey.ts index e23c218cab5..46646d862b7 100644 --- a/packages/clerk-js/src/core/resources/ApiKey.ts +++ b/packages/clerk-js/src/core/resources/ApiKey.ts @@ -107,9 +107,10 @@ export class ApiKey extends BaseResource implements ApiKeyResource { }, credentials: 'same-origin', body: JSON.stringify({ - ...params, type: params.type ?? 'api_key', subject: params.subject ?? this.clerk.organization?.id ?? this.clerk.user?.id ?? '', + creation_reason: params.creationReason, + seconds_until_expiration: params.secondsUntilExpiration, }), }) )?.response as ApiKeyJSON; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx index f2a318150d0..6a40c20f861 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx @@ -1,53 +1,119 @@ -import { useState } from 'react'; +import React, { useState } from 'react'; import { Box, Button, Flex, Input, Text } from '../../customizables'; +import { Select } from '../../elements'; -export const CreateApiKeyForm = ({ - onCreate, - onCancel, - loading, -}: { - onCreate: (name: string) => void; +interface CreateApiKeyFormProps { + onCreate: (params: { name: string; description?: string; expiration?: string }) => void; onCancel: () => void; loading?: boolean; -}) => { +} + +const DURATIONS = [ + { label: 'Never', value: '' }, + { label: '30 days', value: '30d' }, + { label: '90 days', value: '90d' }, + // { label: 'Custom', value: 'custom' }, +]; + +export const CreateApiKeyForm: React.FC = ({ onCreate, onCancel, loading }) => { const [name, setName] = useState(''); + const [showAdvanced, setShowAdvanced] = useState(false); + const [expiration, setExpiration] = useState(''); + const [description, setDescription] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onCreate({ + name, + description: showAdvanced ? description : undefined, + expiration: showAdvanced ? expiration : undefined, + }); + }; return ( - - Add new API key - Secret key name - setName(e.target.value)} - sx={{ - width: '100%', - }} - /> + + - + {showAdvanced && ( + <> + + + Description + + setDescription(e.target.value)} + required + /> + + + + Expiration + + setName(e.target.value)} - required - /> - - + + + + {showAdvanced && ( - <> - - - Description - - setDescription(e.target.value)} - required - /> - - - - Expiration - - + + } + /> + - {apiKeys?.map(apiKey => ( + {paginatedApiKeys.map(apiKey => (
    - +
    {apiKey.name} @@ -173,6 +175,18 @@ export const ManageApiKeys = () => { ))}
    -
    + + + ); }; diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index ec4a3ab87ea..32b008da046 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const arSA: LocalizationResource = { locale: 'ar-SA', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'الرجوع', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index 77f7a47a4ef..5b3ccf0f378 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const beBY: LocalizationResource = { locale: 'be-BY', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Назад', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 3b364021edd..004f5556c5f 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const bgBG: LocalizationResource = { locale: 'bg-BG', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Назад', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index d5ab3d0f1a6..a99ec3e8448 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const caES: LocalizationResource = { locale: 'ca-ES', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Enrere', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 85211310c0e..94be5766948 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const csCZ: LocalizationResource = { locale: 'cs-CZ', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Zpět', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 191358c02d8..98233b189b7 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const daDK: LocalizationResource = { locale: 'da-DK', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Tilbage', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 88570af2f47..aceedd3d049 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const deDE: LocalizationResource = { locale: 'de-DE', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Zurück', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 765bee74b7b..78e2febf92d 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const elGR: LocalizationResource = { locale: 'el-GR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Πίσω', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index a871c052b21..d40bf59d1c9 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const enGB: LocalizationResource = { locale: 'en-GB', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Back', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index a99d356efd5..41eb802d37e 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -2,6 +2,12 @@ import type { LocalizationResource } from '@clerk/types'; export const enUS: LocalizationResource = { locale: 'en-US', + apiKey: { + apiKeyPage: { + formHint: 'Provide a name to generate a new key. You’ll be able to revoke it anytime.', + title: 'Add new API key', + }, + }, backButton: 'Back', badge__activePlan: 'Active', badge__canceledEndsAt: "Canceled • Ends {{ date | shortDate('en-US') }}", diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index db0536376e8..370349d7a3b 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const esES: LocalizationResource = { locale: 'es-ES', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Atrás', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index a464c79eaff..98a7f9cdbd4 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const esMX: LocalizationResource = { locale: 'es-MX', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Atrás', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 0b6ae8995da..b8577c0f243 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const fiFI: LocalizationResource = { locale: 'fi-FI', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Takaisin', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 980529080af..171eb1753f6 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const frFR: LocalizationResource = { locale: 'fr-FR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Retour', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 87d9395780e..8cf119ea355 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const heIL: LocalizationResource = { locale: 'he-IL', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'חזור', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index c3fc8251382..3e1660f1e2b 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const hrHR: LocalizationResource = { locale: 'hr-HR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Natrag', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index ad396aa98f5..f860bf4d5a7 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const huHU: LocalizationResource = { locale: 'hu-HU', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Vissza', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 4baacc854af..c9655a328b4 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const idID: LocalizationResource = { locale: 'id-ID', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Kembali', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 24ad0c9ee51..43199ca276c 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const isIS: LocalizationResource = { locale: 'is-IS', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Til baka', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index c520bebdd9f..ec21411def6 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const itIT: LocalizationResource = { locale: 'it-IT', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Indietro', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 976d4b87911..4a532aa4df4 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const jaJP: LocalizationResource = { locale: 'ja-JP', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: '戻る', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index fd7e331d28a..0583d3d5c02 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const koKR: LocalizationResource = { locale: 'ko-KR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: '돌아가기', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 09a292c7f11..019a6320600 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const mnMN: LocalizationResource = { locale: 'mn-MN', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Буцах', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 0963a4feb13..b9a69078905 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const nbNO: LocalizationResource = { locale: 'nb-NO', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Tilbake', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index ee56befe088..8fb0e1639ac 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const nlBE: LocalizationResource = { locale: 'nl-NL', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Terug', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index d7eba2b08da..05e62fdd08a 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const nlNL: LocalizationResource = { locale: 'nl-NL', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Terug', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index a5e4febaf41..677f38525e6 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const plPL: LocalizationResource = { locale: 'pl-PL', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Powrót', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 8e9f19fb8cf..393218effab 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const ptBR: LocalizationResource = { locale: 'pt-BR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Voltar', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 51557786769..6312452cc75 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const ptPT: LocalizationResource = { locale: 'pt-PT', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Voltar', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index 9fff055e57b..02fb91d1074 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const roRO: LocalizationResource = { locale: 'ro-RO', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Înapoi', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index ebb511bd6bd..9d7dc6fe484 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const ruRU: LocalizationResource = { locale: 'ru-RU', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Назад', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index e6520494835..a2e29f0f203 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const skSK: LocalizationResource = { locale: 'sk-SK', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Späť', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 98f340c97c6..83db5362547 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const srRS: LocalizationResource = { locale: 'sr-RS', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Nazad', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 288e3955b20..b17ec026761 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const svSE: LocalizationResource = { locale: 'sv-SE', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Tillbaka', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index df236622272..11715d2c342 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const thTH: LocalizationResource = { locale: 'th-TH', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'กลับ', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index eec564f88dc..86d96442b73 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const trTR: LocalizationResource = { locale: 'tr-TR', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Geri', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 39fbac50daf..61cc841af8f 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const ukUA: LocalizationResource = { locale: 'uk-UA', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Назад', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index fa38207d623..4e0123bed24 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const viVN: LocalizationResource = { locale: 'vi-VN', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: 'Quay lại', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index e0120563596..589976aa0c5 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const zhCN: LocalizationResource = { locale: 'zh-CN', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: '返回', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 772aca6672e..2b543005b6f 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -14,6 +14,12 @@ import type { LocalizationResource } from '@clerk/types'; export const zhTW: LocalizationResource = { locale: 'zh-TW', + apiKey: { + apiKeyPage: { + formHint: undefined, + title: undefined, + }, + }, backButton: '返回', badge__activePlan: undefined, badge__canceledEndsAt: undefined, diff --git a/packages/types/src/elementIds.ts b/packages/types/src/elementIds.ts index c5cf21acb76..353c4d735dd 100644 --- a/packages/types/src/elementIds.ts +++ b/packages/types/src/elementIds.ts @@ -21,7 +21,8 @@ export type FieldId = | 'enrollmentMode' | 'affiliationEmailAddress' | 'deleteExistingInvitationsSuggestions' - | 'legalAccepted'; + | 'legalAccepted' + | 'description'; export type ProfileSectionId = | 'profile' | 'username' diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 445397a19cd..50ec259b2be 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -979,6 +979,13 @@ type _LocalizationResource = { message: LocalizationValue; }; }; + apiKey: { + apiKeyPage: { + title: LocalizationValue; + verifyTitle: LocalizationValue; + formHint: LocalizationValue; + }; + }; }; type WithParamName = T & From 97eb6918fb90a9f502b0e56f5941fdc3b9371af1 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 8 May 2025 15:03:29 -0700 Subject: [PATCH 18/38] chore: try refetch --- .../ManageApiKeys/CreateApiKeyForm.tsx | 7 +- .../ManageApiKeys/ManageApiKeys.tsx | 299 +++++++++++------- .../elements/contexts/FlowMetadataContext.tsx | 3 +- 3 files changed, 187 insertions(+), 122 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx index c8b1c9a3911..ad2974038a5 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Button, Flex, localizationKeys } from '../../customizables'; -import { Form, FormButtons, FormContainer, withCardStateProvider } from '../../elements'; +import { Form, FormButtons, FormContainer } from '../../elements'; import { useFormControl } from '../../utils'; interface CreateApiKeyFormProps { @@ -10,7 +10,7 @@ interface CreateApiKeyFormProps { loading?: boolean; } -export const CreateApiKeyForm = withCardStateProvider(({ onCreate, onCancel }: CreateApiKeyFormProps) => { +export const CreateApiKeyForm = ({ onCreate, onCancel }: CreateApiKeyFormProps) => { const [showAdvanced, setShowAdvanced] = useState(false); const nameField = useFormControl('name', '', { @@ -52,6 +52,7 @@ export const CreateApiKeyForm = withCardStateProvider(({ onCreate, onCancel }: C )} + {/* TODO: Add Expiration column */} ); -}); +}; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 3f4e0ce1cf2..0439d00e74f 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -2,11 +2,26 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; import { useManageApiKeysContext } from '../../contexts'; -import { Box, Button, Flex, Icon, Input, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; -import { InputWithIcon, Pagination } from '../../elements'; +import { + Box, + Button, + Col, + Flex, + Flow, + Icon, + Input, + Spinner, + Table, + Tbody, + Td, + Text, + Th, + Thead, + Tr, +} from '../../customizables'; +import { Card, InputWithIcon, Pagination, ThreeDotsMenu, withCardStateProvider } from '../../elements'; import { useClipboard, useFetch } from '../../hooks'; -import { Clipboard, Eye, EyeSlash, MagnifyingGlass, Trash } from '../../icons'; -import { InternalThemeProvider } from '../../styledSystem'; +import { Clipboard, Eye, EyeSlash, MagnifyingGlass } from '../../icons'; import { CreateApiKeyForm } from './CreateApiKeyForm'; const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { @@ -33,19 +48,32 @@ const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { ); }; -export const ManageApiKeys = () => { +export const ManageApiKeys = withCardStateProvider(() => { const clerk = useClerk(); const ctx = useManageApiKeysContext(); - const { data: apiKeys, revalidate } = useFetch(() => clerk.getApiKeys({ subject: ctx.subject }), {}); + const { + data: apiKeys, + isLoading, + revalidate, + } = useFetch( + () => clerk.getApiKeys({ subject: ctx.subject }), + { subject: ctx.subject }, + {}, + `api-key-subject-${ctx.subject}`, + ); const [revealedKeys, setRevealedKeys] = useState>({}); const [showCreateForm, setShowCreateForm] = useState(false); const [page, setPage] = useState(1); + const [search, setSearch] = useState(''); const itemsPerPage = ctx.perPage ?? 5; - const itemCount = apiKeys?.length ?? 0; + + const filteredApiKeys = (apiKeys ?? []).filter(key => key.name.toLowerCase().includes(search.toLowerCase())); + + const itemCount = filteredApiKeys.length; const pageCount = Math.max(1, Math.ceil(itemCount / itemsPerPage)); const startingRow = itemCount > 0 ? (page - 1) * itemsPerPage + 1 : 0; const endingRow = Math.min(page * itemsPerPage, itemCount); - const paginatedApiKeys = apiKeys?.slice(startingRow - 1, endingRow) ?? []; + const paginatedApiKeys = filteredApiKeys.slice(startingRow - 1, endingRow); const toggleSecret = async (id: string) => { setRevealedKeys(prev => { @@ -77,116 +105,151 @@ export const ManageApiKeys = () => { }; return ( - - - - } - /> - - - - - {showCreateForm && ( - void handleCreate(params)} - onCancel={() => setShowCreateForm(false)} - /> - )} - - - - - - - - - - - - {paginatedApiKeys.map(apiKey => ( - - - - + + + } + value={search} + onChange={e => { + setSearch(e.target.value); + setPage(1); }} - aria-label='API key (hidden)' - tabIndex={-1} /> - - - - - - ))} - -
    NameLast usedKeyActions
    - {apiKey.name} - - Created at{' '} - {apiKey.createdAt.toLocaleDateString(undefined, { - month: 'short', - day: '2-digit', - year: 'numeric', - })} - - - - {/* Placeholder for "Last used" */} - 3d ago - - - + + +
    - -
    - - -
    + + +
    + + {showCreateForm && ( + void handleCreate(params)} + onCancel={() => setShowCreateForm(false)} + /> + )} + + + + + + + + + + + + {isLoading ? ( + + + + ) : ( + paginatedApiKeys.map(apiKey => ( + + + + + + + )) + )} + +
    NameLast usedKeyActions
    + +
    + {apiKey.name} + + Created at{' '} + {apiKey.createdAt.toLocaleDateString(undefined, { + month: 'short', + day: '2-digit', + year: 'numeric', + })} + + + + 3d ago + + + + + + + + + + + + void revokeApiKey(apiKey.id), + isDisabled: false, + }, + ]} + elementId={'member'} + /> +
    + + + + + + ); -}; +}); diff --git a/packages/clerk-js/src/ui/elements/contexts/FlowMetadataContext.tsx b/packages/clerk-js/src/ui/elements/contexts/FlowMetadataContext.tsx index d4d139f94e4..e7c0046b080 100644 --- a/packages/clerk-js/src/ui/elements/contexts/FlowMetadataContext.tsx +++ b/packages/clerk-js/src/ui/elements/contexts/FlowMetadataContext.tsx @@ -17,7 +17,8 @@ type FlowMetadata = { | 'waitlist' | 'checkout' | 'planDetails' - | 'pricingTable'; + | 'pricingTable' + | 'apiKey'; part?: | 'start' | 'emailCode' From 55a9c10decaeeb8810a74b87d8162d70fe3d0e9b Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 8 May 2025 17:53:21 -0700 Subject: [PATCH 19/38] chore: fix revalidation and more styling --- .../ManageApiKeys/CreateApiKeyForm.tsx | 11 +- .../ManageApiKeys/ManageApiKeys.tsx | 108 ++++++++++-------- .../ui/components/UserProfile/ApiKeysPage.tsx | 10 ++ .../UserProfile/UserProfileRoutes.tsx | 14 +++ packages/clerk-js/src/ui/constants.ts | 1 + .../src/ui/utils/createCustomPages.tsx | 8 +- 6 files changed, 98 insertions(+), 54 deletions(-) create mode 100644 packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx index ad2974038a5..464e2ae7e2d 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx @@ -2,16 +2,16 @@ import React, { useState } from 'react'; import { Button, Flex, localizationKeys } from '../../customizables'; import { Form, FormButtons, FormContainer } from '../../elements'; +import { useActionContext } from '../../elements/Action/ActionRoot'; import { useFormControl } from '../../utils'; interface CreateApiKeyFormProps { - onCreate: (params: { name: string; description?: string; expiration?: number }) => void; - onCancel: () => void; - loading?: boolean; + onCreate: (params: { name: string; description?: string; expiration?: number; closeFn: () => void }) => void; } -export const CreateApiKeyForm = ({ onCreate, onCancel }: CreateApiKeyFormProps) => { +export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { const [showAdvanced, setShowAdvanced] = useState(false); + const { close } = useActionContext(); const nameField = useFormControl('name', '', { type: 'text', @@ -35,6 +35,7 @@ export const CreateApiKeyForm = ({ onCreate, onCancel }: CreateApiKeyFormProps) name: nameField.value, description: descriptionField.value || undefined, expiration: undefined, + closeFn: close, }); }; @@ -65,7 +66,7 @@ export const CreateApiKeyForm = ({ onCreate, onCancel }: CreateApiKeyFormProps) diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx index 0439d00e74f..776155f8db4 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx @@ -20,6 +20,7 @@ import { Tr, } from '../../customizables'; import { Card, InputWithIcon, Pagination, ThreeDotsMenu, withCardStateProvider } from '../../elements'; +import { Action } from '../../elements/Action'; import { useClipboard, useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, MagnifyingGlass } from '../../icons'; import { CreateApiKeyForm } from './CreateApiKeyForm'; @@ -55,14 +56,8 @@ export const ManageApiKeys = withCardStateProvider(() => { data: apiKeys, isLoading, revalidate, - } = useFetch( - () => clerk.getApiKeys({ subject: ctx.subject }), - { subject: ctx.subject }, - {}, - `api-key-subject-${ctx.subject}`, - ); + } = useFetch(clerk.getApiKeys, { subject: ctx.subject }, undefined, `api-key-source-${ctx.subject}`); const [revealedKeys, setRevealedKeys] = useState>({}); - const [showCreateForm, setShowCreateForm] = useState(false); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const itemsPerPage = ctx.perPage ?? 5; @@ -89,18 +84,24 @@ export const ManageApiKeys = withCardStateProvider(() => { } }; - const handleCreate = async (params: { name: string; description?: string; expiration?: number }) => { + const handleCreate = async (params: { + name: string; + description?: string; + expiration?: number; + closeFn: () => void; + }) => { await clerk.createApiKey({ name: params.name, creationReason: params.description, secondsUntilExpiration: params.expiration, }); - setShowCreateForm(false); + params.closeFn(); revalidate(); }; const revokeApiKey = async (apiKeyID: string) => { await clerk.revokeApiKey({ apiKeyID, revocationReason: 'Revoked by user' }); + setPage(1); revalidate(); }; @@ -109,35 +110,44 @@ export const ManageApiKeys = withCardStateProvider(() => { - - - } - value={search} - onChange={e => { - setSearch(e.target.value); - setPage(1); - }} - /> - - - - - {showCreateForm && ( - void handleCreate(params)} - onCancel={() => setShowCreateForm(false)} - /> - )} + + } + value={search} + onChange={e => { + setSearch(e.target.value); + setPage(1); + }} + /> + + + + + + + + ({ + paddingTop: t.space.$6, + paddingBottom: t.space.$6, + })} + > + + void handleCreate(params)} /> + + + + @@ -236,17 +246,19 @@ export const ManageApiKeys = withCardStateProvider(() => {
    - + {itemCount > 5 && ( + + )}
    diff --git a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx new file mode 100644 index 00000000000..92ea2318a7a --- /dev/null +++ b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx @@ -0,0 +1,10 @@ +import { ManageApiKeysContext } from '../../contexts'; +import { ManageApiKeys } from '../ManageApiKeys'; + +export const ApiKeysPage = () => { + return ( + + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx index 6252af0e006..6017c520938 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx @@ -15,6 +15,12 @@ const BillingPage = lazy(() => })), ); +const ApiKeysPage = lazy(() => + import(/* webpackChunkName: "up-api-key-page"*/ './ApiKeysPage').then(module => ({ + default: module.ApiKeysPage, + })), +); + export const UserProfileRoutes = () => { const { pages } = useUserProfileContext(); const { commerceSettings } = useEnvironment(); @@ -22,6 +28,7 @@ export const UserProfileRoutes = () => { 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 customPageRoutesWithContents = pages.contents?.map((customPage, index) => { const shouldFirstCustomItemBeOnRoot = !isAccountPageRoot && !isSecurityPageRoot && index === 0; @@ -80,6 +87,13 @@ export const UserProfileRoutes = () => { )} + + + + + + + ); diff --git a/packages/clerk-js/src/ui/constants.ts b/packages/clerk-js/src/ui/constants.ts index 0dc8bc2a1cc..5be19ae5358 100644 --- a/packages/clerk-js/src/ui/constants.ts +++ b/packages/clerk-js/src/ui/constants.ts @@ -2,6 +2,7 @@ export const USER_PROFILE_NAVBAR_ROUTE_ID = { ACCOUNT: 'account', SECURITY: 'security', BILLING: 'billing', + API_KEYS: 'apiKeys', }; export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = { diff --git a/packages/clerk-js/src/ui/utils/createCustomPages.tsx b/packages/clerk-js/src/ui/utils/createCustomPages.tsx index ec1e1c6ece5..e2c977b1c6e 100644 --- a/packages/clerk-js/src/ui/utils/createCustomPages.tsx +++ b/packages/clerk-js/src/ui/utils/createCustomPages.tsx @@ -3,7 +3,7 @@ import type { CustomPage, EnvironmentResource, LoadedClerk } from '@clerk/types' import { disabledBillingFeature, hasPaidOrgPlans, hasPaidUserPlans, isValidUrl } from '../../utils'; import { ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID, USER_PROFILE_NAVBAR_ROUTE_ID } from '../constants'; import type { NavbarRoute } from '../elements'; -import { CreditCard, Organization, TickShield, User, Users } from '../icons'; +import { CreditCard, Organization, TickShield, User, Users, InformationCircle } from '../icons'; import { localizationKeys } from '../localization'; import { ExternalElementMounter } from './ExternalElementMounter'; import { isDevelopmentSDK } from './runtimeEnvironment'; @@ -259,6 +259,12 @@ const getUserProfileDefaultRoutes = ({ commerce }: { commerce: boolean }): GetDe icon: TickShield, path: 'security', }, + { + name: 'API keys', + id: USER_PROFILE_NAVBAR_ROUTE_ID.API_KEYS, + icon: InformationCircle, + path: 'api-keys', + }, ]; if (commerce) { INITIAL_ROUTES.push({ From 80f7e91815b5b32395028ece4d905651ffaa2fec Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 8 May 2025 18:32:45 -0700 Subject: [PATCH 20/38] chore: rename component to --- packages/clerk-js/sandbox/app.ts | 8 ++++---- packages/clerk-js/src/core/clerk.ts | 14 ++++++------- .../ManageApiKeys.tsx => ApiKeys/ApiKeys.tsx} | 6 +++--- .../CreateApiKeyForm.tsx | 0 .../src/ui/components/ApiKeys/index.tsx | 1 + .../src/ui/components/ManageApiKeys/index.tsx | 1 - .../ui/components/UserProfile/ApiKeysPage.tsx | 10 +++++----- .../ui/contexts/ClerkUIComponentsContext.tsx | 10 +++++----- .../src/ui/contexts/components/ApiKeys.ts | 20 +++++++++++++++++++ .../ui/contexts/components/ManageApiKeys.ts | 20 ------------------- .../src/ui/contexts/components/index.ts | 2 +- .../clerk-js/src/ui/lazyModules/components.ts | 8 +++----- packages/clerk-js/src/ui/types.ts | 10 +++++----- packages/react/src/components/index.ts | 2 +- .../react/src/components/uiComponents.tsx | 12 +++++------ packages/react/src/isomorphicClerk.ts | 20 +++++++++---------- packages/types/src/appearance.ts | 6 +++--- packages/types/src/clerk.ts | 18 ++++++++--------- 18 files changed, 83 insertions(+), 85 deletions(-) rename packages/clerk-js/src/ui/components/{ManageApiKeys/ManageApiKeys.tsx => ApiKeys/ApiKeys.tsx} (98%) rename packages/clerk-js/src/ui/components/{ManageApiKeys => ApiKeys}/CreateApiKeyForm.tsx (100%) create mode 100644 packages/clerk-js/src/ui/components/ApiKeys/index.tsx delete mode 100644 packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx create mode 100644 packages/clerk-js/src/ui/contexts/components/ApiKeys.ts delete mode 100644 packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 4fde3c997bf..8aa960f224e 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -33,7 +33,7 @@ const AVAILABLE_COMPONENTS = [ 'organizationSwitcher', 'waitlist', 'pricingTable', - 'manageApiKeys', + 'apiKeys', ] as const; const COMPONENT_PROPS_NAMESPACE = 'clerk-js-sandbox'; @@ -92,7 +92,7 @@ const componentControls: Record<(typeof AVAILABLE_COMPONENTS)[number], Component organizationSwitcher: buildComponentControls('organizationSwitcher'), waitlist: buildComponentControls('waitlist'), pricingTable: buildComponentControls('pricingTable'), - manageApiKeys: buildComponentControls('manageApiKeys'), + apiKeys: buildComponentControls('apiKeys'), }; declare global { @@ -312,8 +312,8 @@ void (async () => { '/pricing-table': () => { Clerk.mountPricingTable(app, componentControls.pricingTable.getProps() ?? {}); }, - '/manage-api-keys': () => { - Clerk.mountManageApiKeys(app, componentControls.manageApiKeys.getProps() ?? {}); + '/api-keys': () => { + Clerk.mountApiKeys(app, componentControls.apiKeys.getProps() ?? {}); }, '/open-sign-in': () => { mountOpenSignInButton(app, componentControls.signIn.getProps() ?? {}); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 15f556686ee..8368dd6db8a 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -20,6 +20,7 @@ import type { __internal_PlanDetailsProps, __internal_UserVerificationModalProps, ApiKeyResource, + ApiKeysProps, AuthenticateWithCoinbaseWalletParams, AuthenticateWithGoogleOneTapParams, AuthenticateWithMetamaskParams, @@ -46,7 +47,6 @@ import type { InstanceType, JoinWaitlistParams, ListenerCallback, - ManageApiKeysProps, NavigateOptions, NextTaskParams, OrganizationListProps, @@ -1043,21 +1043,21 @@ export class Clerk implements ClerkInterface { ); }; - public mountManageApiKeys = (node: HTMLDivElement, props?: ManageApiKeysProps): void => { + public mountApiKeys = (node: HTMLDivElement, props?: ApiKeysProps): void => { this.assertComponentsReady(this.#componentControls); - void this.#componentControls.ensureMounted({ preloadHint: 'ManageApiKeys' }).then(controls => + void this.#componentControls.ensureMounted({ preloadHint: 'ApiKeys' }).then(controls => controls.mountComponent({ - name: 'ManageApiKeys', - appearanceKey: 'manageApiKeys', + name: 'ApiKeys', + appearanceKey: 'apiKeys', node, props, }), ); - this.telemetry?.record(eventPrebuiltComponentMounted('ManageApiKeys', props)); + this.telemetry?.record(eventPrebuiltComponentMounted('ApiKeys', props)); }; - public unmountManageApiKeys = (node: HTMLDivElement): void => { + public unmountApiKeys = (node: HTMLDivElement): void => { this.assertComponentsReady(this.#componentControls); void this.#componentControls.ensureMounted().then(controls => controls.unmountComponent({ diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx similarity index 98% rename from packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx rename to packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index 776155f8db4..fdf69ba6090 100644 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/ManageApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -1,7 +1,7 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; -import { useManageApiKeysContext } from '../../contexts'; +import { useApiKeysContext } from '../../contexts'; import { Box, Button, @@ -49,9 +49,9 @@ const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { ); }; -export const ManageApiKeys = withCardStateProvider(() => { +export const ApiKeys = withCardStateProvider(() => { const clerk = useClerk(); - const ctx = useManageApiKeysContext(); + const ctx = useApiKeysContext(); const { data: apiKeys, isLoading, diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx similarity index 100% rename from packages/clerk-js/src/ui/components/ManageApiKeys/CreateApiKeyForm.tsx rename to packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx diff --git a/packages/clerk-js/src/ui/components/ApiKeys/index.tsx b/packages/clerk-js/src/ui/components/ApiKeys/index.tsx new file mode 100644 index 00000000000..a83ede2a3d7 --- /dev/null +++ b/packages/clerk-js/src/ui/components/ApiKeys/index.tsx @@ -0,0 +1 @@ +export * from './ApiKeys'; diff --git a/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx b/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx deleted file mode 100644 index 1cd1c4ab7df..00000000000 --- a/packages/clerk-js/src/ui/components/ManageApiKeys/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export * from './ManageApiKeys'; diff --git a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx index 92ea2318a7a..351f0316505 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx @@ -1,10 +1,10 @@ -import { ManageApiKeysContext } from '../../contexts'; -import { ManageApiKeys } from '../ManageApiKeys'; +import { ApiKeysContext } from '../../contexts'; +import { ApiKeys } from '../ApiKeys'; export const ApiKeysPage = () => { return ( - - - + + + ); }; diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx index 909cc49860a..ea6d16af069 100644 --- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx +++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx @@ -1,11 +1,11 @@ -import type { ManageApiKeysProps, PricingTableProps, UserButtonProps, WaitlistProps } from '@clerk/types'; +import type { ApiKeysProps, PricingTableProps, UserButtonProps, WaitlistProps } from '@clerk/types'; import type { ReactNode } from 'react'; import type { AvailableComponentName, AvailableComponentProps } from '../types'; import { + ApiKeysContext, CreateOrganizationContext, GoogleOneTapContext, - ManageApiKeysContext, OrganizationListContext, OrganizationProfileContext, OrganizationSwitcherContext, @@ -92,11 +92,11 @@ export function ComponentContextProvider({ ); - case 'ManageApiKeys': + case 'ApiKeys': return ( - + {children} - + ); default: diff --git a/packages/clerk-js/src/ui/contexts/components/ApiKeys.ts b/packages/clerk-js/src/ui/contexts/components/ApiKeys.ts new file mode 100644 index 00000000000..e1823898d83 --- /dev/null +++ b/packages/clerk-js/src/ui/contexts/components/ApiKeys.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +import type { ApiKeysCtx } from '../../types'; + +export const ApiKeysContext = createContext(null); + +export const useApiKeysContext = () => { + const context = useContext(ApiKeysContext); + + if (!context || context.componentName !== 'ApiKeys') { + throw new Error('Clerk: useApiKeysContext called outside ApiKeys.'); + } + + const { componentName, ...ctx } = context; + + return { + ...ctx, + componentName, + }; +}; diff --git a/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts b/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts deleted file mode 100644 index e3ee7fcc172..00000000000 --- a/packages/clerk-js/src/ui/contexts/components/ManageApiKeys.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createContext, useContext } from 'react'; - -import type { ManageApiKeysCtx } from '../../types'; - -export const ManageApiKeysContext = createContext(null); - -export const useManageApiKeysContext = () => { - const context = useContext(ManageApiKeysContext); - - if (!context || context.componentName !== 'ManageApiKeys') { - throw new Error('Clerk: useManageApiKeysContext called outside ManageApiKeys.'); - } - - const { componentName, ...ctx } = context; - - return { - ...ctx, - componentName, - }; -}; diff --git a/packages/clerk-js/src/ui/contexts/components/index.ts b/packages/clerk-js/src/ui/contexts/components/index.ts index 6f9e5a2abf2..f8674b6a932 100644 --- a/packages/clerk-js/src/ui/contexts/components/index.ts +++ b/packages/clerk-js/src/ui/contexts/components/index.ts @@ -15,4 +15,4 @@ export * from './PricingTable'; export * from './Checkout'; export * from './Statements'; export * from './Plans'; -export * from './ManageApiKeys'; +export * from './ApiKeys'; diff --git a/packages/clerk-js/src/ui/lazyModules/components.ts b/packages/clerk-js/src/ui/lazyModules/components.ts index f3fbf423d07..edb02b887ca 100644 --- a/packages/clerk-js/src/ui/lazyModules/components.ts +++ b/packages/clerk-js/src/ui/lazyModules/components.ts @@ -21,7 +21,7 @@ const componentImportPaths = { Checkout: () => import(/* webpackChunkName: "checkout" */ '../components/Checkout'), SessionTasks: () => import(/* webpackChunkName: "sessionTasks" */ '../components/SessionTasks'), PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans'), - ManageApiKeys: () => import(/* webpackChunkName: "manageApiKeys" */ '../components/ManageApiKeys'), + ApiKeys: () => import(/* webpackChunkName: "apiKeys" */ '../components/ApiKeys'), } as const; export const SignIn = lazy(() => componentImportPaths.SignIn().then(module => ({ default: module.SignIn }))); @@ -97,9 +97,7 @@ export const PricingTable = lazy(() => componentImportPaths.PricingTable().then(module => ({ default: module.PricingTable })), ); -export const ManageApiKeys = lazy(() => - componentImportPaths.ManageApiKeys().then(module => ({ default: module.ManageApiKeys })), -); +export const ApiKeys = lazy(() => componentImportPaths.ApiKeys().then(module => ({ default: module.ApiKeys }))); export const Checkout = lazy(() => componentImportPaths.Checkout().then(module => ({ default: module.Checkout }))); @@ -138,7 +136,7 @@ export const ClerkComponents = { PricingTable, Checkout, PlanDetails, - ManageApiKeys, + ApiKeys, }; export type ClerkComponentName = keyof typeof ClerkComponents; diff --git a/packages/clerk-js/src/ui/types.ts b/packages/clerk-js/src/ui/types.ts index 0701e360c22..c0f9b009124 100644 --- a/packages/clerk-js/src/ui/types.ts +++ b/packages/clerk-js/src/ui/types.ts @@ -2,12 +2,12 @@ import type { __internal_CheckoutProps, __internal_PlanDetailsProps, __internal_UserVerificationProps, + ApiKeysProps, CommercePlanResource, CommerceStatementResource, CommerceSubscriptionResource, CreateOrganizationProps, GoogleOneTapProps, - ManageApiKeysProps, NewSubscriptionRedirectUrl, OrganizationListProps, OrganizationProfileProps, @@ -52,7 +52,7 @@ export type AvailableComponentProps = | __internal_CheckoutProps | __internal_UserVerificationProps | __internal_PlanDetailsProps - | ManageApiKeysProps; + | ApiKeysProps; type ComponentMode = 'modal' | 'mounted'; @@ -119,8 +119,8 @@ export type PricingTableCtx = PricingTableProps & { mode?: ComponentMode; }; -export type ManageApiKeysCtx = ManageApiKeysProps & { - componentName: 'ManageApiKeys'; +export type ApiKeysCtx = ApiKeysProps & { + componentName: 'ApiKeys'; mode?: ComponentMode; }; @@ -168,6 +168,6 @@ export type AvailableComponentCtx = | WaitlistCtx | PricingTableCtx | CheckoutCtx - | ManageApiKeysCtx; + | ApiKeysCtx; export type AvailableComponentName = AvailableComponentCtx['componentName']; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 4b988ca6f0a..263227ee084 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -10,7 +10,7 @@ export { GoogleOneTap, Waitlist, PricingTable, - ManageApiKeys, + ApiKeys, } from './uiComponents'; export { diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index 4c65ecba6b8..7ec3dc7f09d 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -1,8 +1,8 @@ import { logErrorInDevMode } from '@clerk/shared/utils'; import type { + ApiKeysProps, CreateOrganizationProps, GoogleOneTapProps, - ManageApiKeysProps, OrganizationListProps, OrganizationProfileProps, OrganizationSwitcherProps, @@ -602,8 +602,8 @@ export const PricingTable = withClerk( { component: 'PricingTable', renderWhileLoading: true }, ); -export const ManageApiKeys = withClerk( - ({ clerk, component, fallback, ...props }: WithClerkProp) => { +export const ApiKeys = withClerk( + ({ clerk, component, fallback, ...props }: WithClerkProp) => { const mountingStatus = useWaitForComponentMount(component); const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded; @@ -617,8 +617,8 @@ export const ManageApiKeys = withClerk( {clerk.loaded && ( ); }, - { component: 'ManageApiKeys', renderWhileLoading: true }, + { component: 'ApiKeys', renderWhileLoading: true }, ); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index 7b8c3255b5e..b3a7b2db84f 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -8,6 +8,7 @@ import type { __internal_UserVerificationModalProps, __internal_UserVerificationProps, ApiKeyResource, + ApiKeysProps, AuthenticateWithCoinbaseWalletParams, AuthenticateWithGoogleOneTapParams, AuthenticateWithMetamaskParams, @@ -29,7 +30,6 @@ import type { JoinWaitlistParams, ListenerCallback, LoadedClerk, - ManageApiKeysProps, NextTaskParams, OrganizationListProps, OrganizationProfileProps, @@ -136,7 +136,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { private premountMethodCalls = new Map, MethodCallback>(); private premountWaitlistNodes = new Map(); private premountPricingTableNodes = new Map(); - private premountManageApiKeysNodes = new Map(); + private premountApiKeysNodes = new Map(); // A separate Map of `addListener` method calls to handle multiple listeners. private premountAddListenerCalls = new Map< ListenerCallback, @@ -615,8 +615,8 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { clerkjs.mountPricingTable(node, props); }); - this.premountManageApiKeysNodes.forEach((props, node) => { - clerkjs.mountManageApiKeys(node, props); + this.premountApiKeysNodes.forEach((props, node) => { + clerkjs.mountApiKeys(node, props); }); /** @@ -1060,19 +1060,19 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - mountManageApiKeys = (node: HTMLDivElement, props?: ManageApiKeysProps): void => { + mountApiKeys = (node: HTMLDivElement, props?: ApiKeysProps): void => { if (this.clerkjs && this.loaded) { - this.clerkjs.mountManageApiKeys(node, props); + this.clerkjs.mountApiKeys(node, props); } else { - this.premountManageApiKeysNodes.set(node, props); + this.premountApiKeysNodes.set(node, props); } }; - unmountManageApiKeys = (node: HTMLDivElement): void => { + unmountApiKeys = (node: HTMLDivElement): void => { if (this.clerkjs && this.loaded) { - this.clerkjs.unmountManageApiKeys(node); + this.clerkjs.unmountApiKeys(node); } else { - this.premountManageApiKeysNodes.delete(node); + this.premountApiKeysNodes.delete(node); } }; diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index bbd6f4a6664..5da6e6296c4 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -829,7 +829,7 @@ export type WaitlistTheme = Theme; export type PricingTableTheme = Theme; export type CheckoutTheme = Theme; export type PlanDetailTheme = Theme; -export type ManageApiKeysTheme = Theme; +export type ApiKeysTheme = Theme; export type Appearance = T & { /** * Theme overrides that only apply to the `` component @@ -884,7 +884,7 @@ export type Appearance = T & { */ checkout?: T; /** - * Theme overrides that only apply to the `` component + * Theme overrides that only apply to the `` component */ - manageApiKeys?: T; + apiKeys?: T; }; diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 1fa0afe5ad5..82bd52b1c66 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -1,9 +1,9 @@ import type { ApiKeyResource } from './apiKey'; import type { + ApiKeysTheme, Appearance, CheckoutTheme, CreateOrganizationTheme, - ManageApiKeysTheme, OrganizationListTheme, OrganizationProfileTheme, OrganizationSwitcherTheme, @@ -461,19 +461,19 @@ export interface Clerk { unmountPricingTable: (targetNode: HTMLDivElement) => void; /** - * Mount a manage api keys component at the target element. - * @param targetNode Target to mount the ManageApiKeys component. + * Mount a api keys component at the target element. + * @param targetNode Target to mount the ApiKeys component. * @param props Configuration parameters. */ - mountManageApiKeys: (targetNode: HTMLDivElement, props?: ManageApiKeysProps) => void; + mountApiKeys: (targetNode: HTMLDivElement, props?: ApiKeysProps) => void; /** - * Unmount a manage api keys component from the target element. + * Unmount a api keys component from the target element. * If there is no component mounted at the target node, results in a noop. * - * @param targetNode Target node to unmount the ManageApiKeys component from. + * @param targetNode Target node to unmount the ApiKeys component from. */ - unmountManageApiKeys: (targetNode: HTMLDivElement) => void; + unmountApiKeys: (targetNode: HTMLDivElement) => void; /** * Register a listener that triggers a callback each time important Clerk resources are changed. @@ -1645,12 +1645,12 @@ type PortalRoot = HTMLElement | null | undefined; export type PricingTableProps = PricingTableBaseProps & PricingTableDefaultProps; -export type ManageApiKeysProps = { +export type ApiKeysProps = { type?: 'api_key'; subject?: string; perPage?: number; showSecretByDefault?: boolean; - appearance?: ManageApiKeysTheme; + appearance?: ApiKeysTheme; }; export type GetApiKeysParams = { From c85d38773e7b2c032ab8f05c6e853e81bcb19779 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 8 May 2025 19:47:58 -0700 Subject: [PATCH 21/38] chore: add expiration field --- .../src/ui/components/ApiKeys/ApiKeys.tsx | 32 +++++++++-- .../components/ApiKeys/CreateApiKeyForm.tsx | 54 ++++++++++++++++--- 2 files changed, 73 insertions(+), 13 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index fdf69ba6090..7b890c1f4e0 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -23,7 +23,7 @@ import { Card, InputWithIcon, Pagination, ThreeDotsMenu, withCardStateProvider } import { Action } from '../../elements/Action'; import { useClipboard, useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash, MagnifyingGlass } from '../../icons'; -import { CreateApiKeyForm } from './CreateApiKeyForm'; +import { CreateApiKeyForm, Expiration } from './CreateApiKeyForm'; const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { const clerk = useClerk(); @@ -49,6 +49,28 @@ const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { ); }; +function getTimeLeftInSeconds(expirationOption: Expiration) { + if (expirationOption === 'never') { + return; + } + + const now = new Date(); + const future = new Date(now); + + if (expirationOption === '30d') { + future.setDate(future.getDate() + 30); + } else if (expirationOption === '90d') { + future.setDate(future.getDate() + 90); + } else { + throw new Error('TODO: Improve time handling'); + } + + const diffInMs = future.getTime() - now.getTime(); + const diffInSecs = Math.floor(diffInMs / 1000); + + return diffInSecs; +} + export const ApiKeys = withCardStateProvider(() => { const clerk = useClerk(); const ctx = useApiKeysContext(); @@ -87,13 +109,13 @@ export const ApiKeys = withCardStateProvider(() => { const handleCreate = async (params: { name: string; description?: string; - expiration?: number; + expiration: Expiration; closeFn: () => void; }) => { await clerk.createApiKey({ name: params.name, creationReason: params.description, - secondsUntilExpiration: params.expiration, + secondsUntilExpiration: getTimeLeftInSeconds(params.expiration), }); params.closeFn(); revalidate(); @@ -230,8 +252,8 @@ export const ApiKeys = withCardStateProvider(() => { void revokeApiKey(apiKey.id), isDisabled: false, diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index 464e2ae7e2d..bef9c08c188 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -1,17 +1,22 @@ import React, { useState } from 'react'; -import { Button, Flex, localizationKeys } from '../../customizables'; -import { Form, FormButtons, FormContainer } from '../../elements'; +import { Button, Col, Flex, localizationKeys, Text } from '../../customizables'; +import { Form, FormButtons, FormContainer, SegmentedControl } from '../../elements'; import { useActionContext } from '../../elements/Action/ActionRoot'; import { useFormControl } from '../../utils'; interface CreateApiKeyFormProps { - onCreate: (params: { name: string; description?: string; expiration?: number; closeFn: () => void }) => void; + onCreate: (params: { name: string; description?: string; expiration: Expiration; closeFn: () => void }) => void; } +export type Expiration = 'never' | '30d' | '90d' | 'custom'; + export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { const [showAdvanced, setShowAdvanced] = useState(false); const { close } = useActionContext(); + const [expiration, setExpiration] = useState('never'); + const createApiKeyFormId = React.useId(); + const segmentedControlId = `${createApiKeyFormId}-segmented-control`; const nameField = useFormControl('name', '', { type: 'text', @@ -34,7 +39,7 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { onCreate({ name: nameField.value, description: descriptionField.value || undefined, - expiration: undefined, + expiration, closeFn: close, }); }; @@ -49,11 +54,44 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { {showAdvanced && ( - - - + <> + + + + + + Expiration + + setExpiration(value as Expiration)} + fullWidth + > + + + + + + + )} - {/* TODO: Add Expiration column */} Date: Fri, 9 May 2025 11:01:04 -0700 Subject: [PATCH 22/38] chore: add api keys component or user and org profile --- packages/clerk-js/sandbox/template.html | 2 +- .../src/ui/components/ApiKeys/ApiKeys.tsx | 351 +++++------------- .../ui/components/ApiKeys/ApiKeysTable.tsx | 119 ++++++ .../src/ui/components/ApiKeys/shared.ts | 94 +++++ .../OrganizationApiKeysPage.tsx | 38 ++ .../OrganizationProfileRoutes.tsx | 16 +- .../ui/components/UserProfile/ApiKeysPage.tsx | 36 +- .../UserProfile/UserProfileRoutes.tsx | 2 +- packages/clerk-js/src/ui/constants.ts | 1 + .../components/OrganizationProfile.ts | 3 + 10 files changed, 405 insertions(+), 257 deletions(-) create mode 100644 packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx create mode 100644 packages/clerk-js/src/ui/components/ApiKeys/shared.ts create mode 100644 packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 4d13f32196a..b1f51b01ff5 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -263,7 +263,7 @@
  • Manage API Keys diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index 7b890c1f4e0..ab0b894acb6 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -1,31 +1,18 @@ -import { useClerk } from '@clerk/shared/react'; +import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; import { useState } from 'react'; import { useApiKeysContext } from '../../contexts'; -import { - Box, - Button, - Col, - Flex, - Flow, - Icon, - Input, - Spinner, - Table, - Tbody, - Td, - Text, - Th, - Thead, - Tr, -} from '../../customizables'; -import { Card, InputWithIcon, Pagination, ThreeDotsMenu, withCardStateProvider } from '../../elements'; +import { Box, Button, Col, Flex, Flow, Icon } from '../../customizables'; +import { Card, InputWithIcon, Pagination, withCardStateProvider } from '../../elements'; import { Action } from '../../elements/Action'; -import { useClipboard, useFetch } from '../../hooks'; -import { Clipboard, Eye, EyeSlash, MagnifyingGlass } from '../../icons'; -import { CreateApiKeyForm, Expiration } from './CreateApiKeyForm'; - -const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { +import { useClipboard } from '../../hooks'; +import { Clipboard, MagnifyingGlass } from '../../icons'; +import { ApiKeysTable } from './ApiKeysTable'; +import type { Expiration } from './CreateApiKeyForm'; +import { CreateApiKeyForm } from './CreateApiKeyForm'; +import { useApiKeys } from './shared'; + +export const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { const clerk = useClerk(); const [text, setText] = useState(''); const { onCopy, hasCopied } = useClipboard(text); @@ -49,239 +36,103 @@ const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { ); }; -function getTimeLeftInSeconds(expirationOption: Expiration) { - if (expirationOption === 'never') { - return; - } - - const now = new Date(); - const future = new Date(now); - - if (expirationOption === '30d') { - future.setDate(future.getDate() + 30); - } else if (expirationOption === '90d') { - future.setDate(future.getDate() + 90); - } else { - throw new Error('TODO: Improve time handling'); - } - - const diffInMs = future.getTime() - now.getTime(); - const diffInSecs = Math.floor(diffInMs / 1000); - - return diffInSecs; -} +export const ApiKeysInternal = ({ + apiKeys, + isLoading, + revealedKeys, + toggleSecret, + revokeApiKey, + CopyButton, + search, + setSearch, + page, + setPage, + pageCount, + itemCount, + startingRow, + endingRow, + handleCreate, +}: { + apiKeys: any[]; + isLoading: boolean; + revealedKeys: Record; + toggleSecret: (id: string) => void; + revokeApiKey: (id: string) => void; + CopyButton: React.ComponentType<{ apiKeyID: string }>; + search: string; + setSearch: (s: string) => void; + page: number; + setPage: (n: number) => void; + pageCount: number; + itemCount: number; + startingRow: number; + endingRow: number; + handleCreate: (params: { name: string; description?: string; expiration: Expiration; closeFn: () => void }) => void; +}) => { + return ( + + + + + } + value={search} + onChange={e => { + setSearch(e.target.value); + setPage(1); + }} + /> + + + + + + + ({ paddingTop: t.space.$6, paddingBottom: t.space.$6 })}> + + void handleCreate(params)} /> + + + + + + {itemCount > 5 && ( + + )} + + ); +}; export const ApiKeys = withCardStateProvider(() => { - const clerk = useClerk(); const ctx = useApiKeysContext(); - const { - data: apiKeys, - isLoading, - revalidate, - } = useFetch(clerk.getApiKeys, { subject: ctx.subject }, undefined, `api-key-source-${ctx.subject}`); - const [revealedKeys, setRevealedKeys] = useState>({}); - const [page, setPage] = useState(1); - const [search, setSearch] = useState(''); - const itemsPerPage = ctx.perPage ?? 5; - - const filteredApiKeys = (apiKeys ?? []).filter(key => key.name.toLowerCase().includes(search.toLowerCase())); - - const itemCount = filteredApiKeys.length; - const pageCount = Math.max(1, Math.ceil(itemCount / itemsPerPage)); - const startingRow = itemCount > 0 ? (page - 1) * itemsPerPage + 1 : 0; - const endingRow = Math.min(page * itemsPerPage, itemCount); - const paginatedApiKeys = filteredApiKeys.slice(startingRow - 1, endingRow); - - const toggleSecret = async (id: string) => { - setRevealedKeys(prev => { - if (prev[id]) { - return { ...prev, [id]: null }; - } - return prev; - }); - - if (!revealedKeys[id]) { - const secret = await clerk.getApiKeySecret(id); - setRevealedKeys(prev => ({ ...prev, [id]: secret })); - } - }; - - const handleCreate = async (params: { - name: string; - description?: string; - expiration: Expiration; - closeFn: () => void; - }) => { - await clerk.createApiKey({ - name: params.name, - creationReason: params.description, - secondsUntilExpiration: getTimeLeftInSeconds(params.expiration), - }); - params.closeFn(); - revalidate(); - }; - - const revokeApiKey = async (apiKeyID: string) => { - await clerk.revokeApiKey({ apiKeyID, revocationReason: 'Revoked by user' }); - setPage(1); - revalidate(); - }; - + const { user } = useUser(); + const { organization } = useOrganization(); + const apiKeysManager = useApiKeys(ctx.subject ?? organization?.id ?? user?.id ?? '', ctx.perPage); return ( - - - - - } - value={search} - onChange={e => { - setSearch(e.target.value); - setPage(1); - }} - /> - - - - - - - - ({ - paddingTop: t.space.$6, - paddingBottom: t.space.$6, - })} - > - - void handleCreate(params)} /> - - - - - - - - - - - - - - - - {isLoading ? ( - - - - ) : ( - paginatedApiKeys.map(apiKey => ( - - - - - - - )) - )} - -
    NameLast usedKeyActions
    - -
    - {apiKey.name} - - Created at{' '} - {apiKey.createdAt.toLocaleDateString(undefined, { - month: 'short', - day: '2-digit', - year: 'numeric', - })} - - - - 3d ago - - - - - - - - - - - - void revokeApiKey(apiKey.id), - isDisabled: false, - }, - ]} - elementId={'member'} - /> -
    - - {itemCount > 5 && ( - - )} - +
    diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx new file mode 100644 index 00000000000..52f814239da --- /dev/null +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -0,0 +1,119 @@ +import React from 'react'; + +import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; +import { ThreeDotsMenu } from '../../elements'; +import { Eye, EyeSlash } from '../../icons'; + +export const ApiKeysTable = ({ + apiKeys, + isLoading, + revealedKeys, + toggleSecret, + revokeApiKey, + CopyButton, +}: { + apiKeys: any[]; + isLoading: boolean; + revealedKeys: Record; + toggleSecret: (id: string) => void; + revokeApiKey: (id: string) => void; + CopyButton: React.ComponentType<{ apiKeyID: string }>; +}) => { + return ( + + + + + + + + + + + {isLoading ? ( + + + + ) : ( + apiKeys.map(apiKey => ( + + + + + + + )) + )} + +
    NameLast usedKeyActions
    + +
    + {apiKey.name} + + Created at{' '} + {apiKey.createdAt.toLocaleDateString(undefined, { + month: 'short', + day: '2-digit', + year: 'numeric', + })} + + + + 3d ago + + + + + + + + + + + void revokeApiKey(apiKey.id), + isDisabled: false, + }, + ]} + elementId={'member'} + /> +
    + ); +}; diff --git a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts new file mode 100644 index 00000000000..653d740de66 --- /dev/null +++ b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts @@ -0,0 +1,94 @@ +import { useClerk } from '@clerk/shared/react'; +import { useState } from 'react'; + +import { useFetch } from '../../hooks'; +import type { Expiration } from './CreateApiKeyForm'; + +function getTimeLeftInSeconds(expirationOption: Expiration) { + if (expirationOption === 'never') { + return; + } + const now = new Date(); + const future = new Date(now); + if (expirationOption === '30d') { + future.setDate(future.getDate() + 30); + } else if (expirationOption === '90d') { + future.setDate(future.getDate() + 90); + } else { + throw new Error('TODO: Improve time handling'); + } + const diffInMs = future.getTime() - now.getTime(); + const diffInSecs = Math.floor(diffInMs / 1000); + return diffInSecs; +} + +export function useApiKeys(subject: string, perPage: number = 5) { + const clerk = useClerk(); + const { + data: apiKeys, + isLoading, + revalidate, + } = useFetch(clerk.getApiKeys, { subject }, undefined, `api-key-source-${subject}`); + const [revealedKeys, setRevealedKeys] = useState>({}); + const [page, setPage] = useState(1); + const [search, setSearch] = useState(''); + const itemsPerPage = perPage; + + const filteredApiKeys = (apiKeys ?? []).filter(key => key.name.toLowerCase().includes(search.toLowerCase())); + const itemCount = filteredApiKeys.length; + const pageCount = Math.max(1, Math.ceil(itemCount / itemsPerPage)); + const startingRow = itemCount > 0 ? (page - 1) * itemsPerPage + 1 : 0; + const endingRow = Math.min(page * itemsPerPage, itemCount); + const paginatedApiKeys = filteredApiKeys.slice(startingRow - 1, endingRow); + + const toggleSecret = async (id: string) => { + setRevealedKeys(prev => { + if (prev[id]) { + return { ...prev, [id]: null }; + } + return prev; + }); + if (!revealedKeys[id]) { + const secret = await clerk.getApiKeySecret(id); + setRevealedKeys(prev => ({ ...prev, [id]: secret })); + } + }; + + const handleCreate = async (params: { + name: string; + description?: string; + expiration: Expiration; + closeFn: () => void; + }) => { + await clerk.createApiKey({ + name: params.name, + creationReason: params.description, + secondsUntilExpiration: getTimeLeftInSeconds(params.expiration), + }); + params.closeFn(); + revalidate(); + }; + + const revokeApiKey = async (apiKeyID: string) => { + await clerk.revokeApiKey({ apiKeyID, revocationReason: 'Revoked by user' }); + setPage(1); + revalidate(); + }; + + return { + apiKeys: paginatedApiKeys, + isLoading: isLoading ?? false, + revealedKeys, + toggleSecret, + revokeApiKey, + search, + setSearch, + page, + setPage, + pageCount, + itemCount, + startingRow, + endingRow, + handleCreate, + }; +} diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx new file mode 100644 index 00000000000..27bb37f0eda --- /dev/null +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx @@ -0,0 +1,38 @@ +import { useOrganization } from '@clerk/shared/react'; + +import { ApiKeysContext, useApiKeysContext } from '../../contexts'; +import { Col } from '../../customizables'; +import { Header } from '../../elements'; +import { ApiKeysInternal, CopyButton } from '../ApiKeys'; +import { useApiKeys } from '../ApiKeys/shared'; + +function APIKeysPageInternal() { + const ctx = useApiKeysContext(); + const { organization } = useOrganization(); + const apiKeys = useApiKeys(organization?.id ?? '', ctx.perPage); + return ( + + + + ); +} + +export const OrganizationApiKeysPage = () => { + return ( + + + {/* TODO: Add localization key */} + + + + + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx index 1b51625a650..59c0eca0536 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -15,8 +15,15 @@ const OrganizationBillingPage = lazy(() => })), ); +const OrganizationApiKeysPage = lazy(() => + import(/* webpackChunkName: "op-api-keys-page"*/ './OrganizationApiKeysPage').then(module => ({ + default: module.OrganizationApiKeysPage, + })), +); + export const OrganizationProfileRoutes = () => { - const { pages, isMembersPageRoot, isGeneralPageRoot, isBillingPageRoot } = useOrganizationProfileContext(); + const { pages, isMembersPageRoot, isGeneralPageRoot, isBillingPageRoot, isApiKeysPageRoot } = + useOrganizationProfileContext(); const { commerceSettings } = useEnvironment(); const customPageRoutesWithContents = pages.contents?.map((customPage, index) => { @@ -60,6 +67,13 @@ export const OrganizationProfileRoutes = () => { + + + + + + + {commerceSettings.billing.enabled && commerceSettings.billing.hasPaidOrgPlans && ( diff --git a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx index 351f0316505..9e5ead04912 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx @@ -1,10 +1,38 @@ -import { ApiKeysContext } from '../../contexts'; -import { ApiKeys } from '../ApiKeys'; +import { useUser } from '@clerk/shared/react'; -export const ApiKeysPage = () => { +import { ApiKeysContext, useApiKeysContext } from '../../contexts'; +import { Col } from '../../customizables'; +import { Header } from '../../elements'; +import { ApiKeysInternal, CopyButton } from '../ApiKeys'; +import { useApiKeys } from '../ApiKeys/shared'; + +function APIKeysPageInternal() { + const ctx = useApiKeysContext(); + const { user } = useUser(); + const apiKeys = useApiKeys(user?.id ?? '', ctx.perPage); return ( - + ); +} + +export const ApiKeysPage = () => { + return ( + + + + + + + + + ); }; diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx index 6017c520938..d921685f52e 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx @@ -16,7 +16,7 @@ const BillingPage = lazy(() => ); const ApiKeysPage = lazy(() => - import(/* webpackChunkName: "up-api-key-page"*/ './ApiKeysPage').then(module => ({ + import(/* webpackChunkName: "up-api-keys-page"*/ './ApiKeysPage').then(module => ({ default: module.ApiKeysPage, })), ); diff --git a/packages/clerk-js/src/ui/constants.ts b/packages/clerk-js/src/ui/constants.ts index 5be19ae5358..b32e4bc8b7f 100644 --- a/packages/clerk-js/src/ui/constants.ts +++ b/packages/clerk-js/src/ui/constants.ts @@ -9,6 +9,7 @@ export const ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID = { GENERAL: 'general', MEMBERS: 'members', BILLING: 'billing', + API_KEYS: 'apiKeys', }; export const USER_BUTTON_ITEM_ID = { diff --git a/packages/clerk-js/src/ui/contexts/components/OrganizationProfile.ts b/packages/clerk-js/src/ui/contexts/components/OrganizationProfile.ts index 4b65617ef9d..6970b31a181 100644 --- a/packages/clerk-js/src/ui/contexts/components/OrganizationProfile.ts +++ b/packages/clerk-js/src/ui/contexts/components/OrganizationProfile.ts @@ -22,6 +22,7 @@ export type OrganizationProfileContextType = OrganizationProfileCtx & { isMembersPageRoot: boolean; isGeneralPageRoot: boolean; isBillingPageRoot: boolean; + isApiKeysPageRoot: boolean; }; export const OrganizationProfileContext = createContext(null); @@ -49,6 +50,7 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType const isMembersPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.MEMBERS; const isGeneralPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.GENERAL; const isBillingPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.BILLING; + const isApiKeysPageRoot = pages.routes[0].id === ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.API_KEYS; const navigateToGeneralPageRoot = () => navigate(isGeneralPageRoot ? '../' : isMembersPageRoot ? './organization-general' : '../organization-general'); @@ -61,5 +63,6 @@ export const useOrganizationProfileContext = (): OrganizationProfileContextType isMembersPageRoot, isGeneralPageRoot, isBillingPageRoot, + isApiKeysPageRoot, }; }; From 709a340f8bfb13f02a60872e3b3d4edc3a0740c8 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 9 May 2025 11:42:11 -0700 Subject: [PATCH 23/38] chore: add missing org profile sidebar nav --- .../OrganizationProfileRoutes.tsx | 16 +++++++++------- .../components/UserProfile/UserProfileRoutes.tsx | 4 +++- .../clerk-js/src/ui/utils/createCustomPages.tsx | 6 ++++++ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx index 59c0eca0536..0aac970db95 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfileRoutes.tsx @@ -67,13 +67,6 @@ export const OrganizationProfileRoutes = () => { - - - - - - - {commerceSettings.billing.enabled && commerceSettings.billing.hasPaidOrgPlans && ( @@ -103,6 +96,15 @@ export const OrganizationProfileRoutes = () => { )} + + + + + + + + + ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx index d921685f52e..b16211be26d 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfileRoutes.tsx @@ -90,7 +90,9 @@ export const UserProfileRoutes = () => { - + + + diff --git a/packages/clerk-js/src/ui/utils/createCustomPages.tsx b/packages/clerk-js/src/ui/utils/createCustomPages.tsx index e2c977b1c6e..a31753b6ea2 100644 --- a/packages/clerk-js/src/ui/utils/createCustomPages.tsx +++ b/packages/clerk-js/src/ui/utils/createCustomPages.tsx @@ -305,6 +305,12 @@ const getOrganizationProfileDefaultRoutes = ({ commerce }: { commerce: boolean } icon: Users, path: 'organization-members', }, + { + name: 'API keys', + id: ORGANIZATION_PROFILE_NAVBAR_ROUTE_ID.API_KEYS, + icon: InformationCircle, + path: 'organization-api-keys', + }, ]; if (commerce) { INITIAL_ROUTES.push({ From 5be01e5b9877f51708b72fa5da2213099a671ba2 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 9 May 2025 13:00:49 -0700 Subject: [PATCH 24/38] chore: clean up props --- .../src/ui/components/ApiKeys/ApiKeys.tsx | 92 +++++-------------- .../ui/components/ApiKeys/ApiKeysTable.tsx | 42 +++++---- .../src/ui/components/ApiKeys/shared.ts | 18 +--- .../OrganizationApiKeysPage.tsx | 23 +---- .../ui/components/UserProfile/ApiKeysPage.tsx | 21 +---- 5 files changed, 53 insertions(+), 143 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index ab0b894acb6..a28ced232fd 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -1,74 +1,30 @@ -import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; -import { useState } from 'react'; +import { useOrganization, useUser } from '@clerk/shared/react'; import { useApiKeysContext } from '../../contexts'; import { Box, Button, Col, Flex, Flow, Icon } from '../../customizables'; import { Card, InputWithIcon, Pagination, withCardStateProvider } from '../../elements'; import { Action } from '../../elements/Action'; -import { useClipboard } from '../../hooks'; -import { Clipboard, MagnifyingGlass } from '../../icons'; +import { MagnifyingGlass } from '../../icons'; import { ApiKeysTable } from './ApiKeysTable'; -import type { Expiration } from './CreateApiKeyForm'; import { CreateApiKeyForm } from './CreateApiKeyForm'; import { useApiKeys } from './shared'; -export const CopyButton = ({ apiKeyID }: { apiKeyID: string }) => { - const clerk = useClerk(); - const [text, setText] = useState(''); - const { onCopy, hasCopied } = useClipboard(text); +export const ApiKeysInternal = ({ subject }: { subject: string }) => { + const { + apiKeys, + isLoading, + revokeApiKey, + search, + setSearch, + page, + setPage, + pageCount, + itemCount, + startingRow, + endingRow, + handleCreate, + } = useApiKeys({ subject }); - const fetchSecret = async () => { - const secret = await clerk.getApiKeySecret(apiKeyID); - setText(secret); - onCopy(); - }; - - return ( - - ); -}; - -export const ApiKeysInternal = ({ - apiKeys, - isLoading, - revealedKeys, - toggleSecret, - revokeApiKey, - CopyButton, - search, - setSearch, - page, - setPage, - pageCount, - itemCount, - startingRow, - endingRow, - handleCreate, -}: { - apiKeys: any[]; - isLoading: boolean; - revealedKeys: Record; - toggleSecret: (id: string) => void; - revokeApiKey: (id: string) => void; - CopyButton: React.ComponentType<{ apiKeyID: string }>; - search: string; - setSearch: (s: string) => void; - page: number; - setPage: (n: number) => void; - pageCount: number; - itemCount: number; - startingRow: number; - endingRow: number; - handleCreate: (params: { name: string; description?: string; expiration: Expiration; closeFn: () => void }) => void; -}) => { return ( @@ -100,12 +56,9 @@ export const ApiKeysInternal = ({ {itemCount > 5 && ( { const ctx = useApiKeysContext(); const { user } = useUser(); const { organization } = useOrganization(); - const apiKeysManager = useApiKeys(ctx.subject ?? organization?.id ?? user?.id ?? '', ctx.perPage); + return ( - + diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 52f814239da..6fbcdfc8f7d 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -1,23 +1,17 @@ -import React from 'react'; +import type { ApiKeyResource } from '@clerk/types'; import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; -import { Eye, EyeSlash } from '../../icons'; +import { Clipboard, Eye } from '../../icons'; export const ApiKeysTable = ({ - apiKeys, + rows, isLoading, - revealedKeys, - toggleSecret, - revokeApiKey, - CopyButton, + onRevoke, }: { - apiKeys: any[]; + rows: ApiKeyResource[]; isLoading: boolean; - revealedKeys: Record; - toggleSecret: (id: string) => void; - revokeApiKey: (id: string) => void; - CopyButton: React.ComponentType<{ apiKeyID: string }>; + onRevoke: (id: string) => void; }) => { return ( @@ -40,7 +34,7 @@ export const ApiKeysTable = ({ ) : ( - apiKeys.map(apiKey => ( + rows.map(apiKey => ( - {/* TODO: Add localization key */} - + ); diff --git a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx index 9e5ead04912..9c546430613 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx @@ -1,26 +1,13 @@ import { useUser } from '@clerk/shared/react'; -import { ApiKeysContext, useApiKeysContext } from '../../contexts'; +import { ApiKeysContext } from '../../contexts'; import { Col } from '../../customizables'; import { Header } from '../../elements'; -import { ApiKeysInternal, CopyButton } from '../ApiKeys'; -import { useApiKeys } from '../ApiKeys/shared'; +import { ApiKeysInternal } from '../ApiKeys'; -function APIKeysPageInternal() { - const ctx = useApiKeysContext(); +export const ApiKeysPage = () => { const { user } = useUser(); - const apiKeys = useApiKeys(user?.id ?? '', ctx.perPage); - return ( - - - - ); -} -export const ApiKeysPage = () => { return ( @@ -31,7 +18,7 @@ export const ApiKeysPage = () => { /> - + ); From 3c67fb9a2a7a275423d056952ce172e96b3e131d Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Fri, 9 May 2025 14:42:09 -0700 Subject: [PATCH 25/38] chore: clean up props --- .../ui/components/ApiKeys/ApiKeysTable.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 6fbcdfc8f7d..e47cdc81e9f 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -2,8 +2,24 @@ import type { ApiKeyResource } from '@clerk/types'; import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; +import { useClipboard } from '../../hooks'; import { Clipboard, Eye } from '../../icons'; +const CopyButton = ({ text }: { text: string }) => { + const { onCopy } = useClipboard(text); + return ( + + ); +}; + export const ApiKeysTable = ({ rows, isLoading, @@ -87,15 +103,7 @@ export const ApiKeysTable = ({ - {/* */} - + - + - + @@ -93,10 +93,7 @@ export const ApiKeysTable = ({ + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index 91d9cf1f508..c8f172b26d5 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -35,7 +35,7 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro isRequired: false, }); - const canSubmit = nameField.value.length > 1; + const canSubmit = nameField.value.length > 2; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); @@ -51,8 +51,8 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro return ( diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index a2f246039e8..03df02c2f5b 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const arSA: LocalizationResource = { locale: 'ar-SA', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const arSA: LocalizationResource = { titleWithoutPersonal: 'أختر منظمة', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'دعوات تلقائية', badge__automaticSuggestion: 'أقتراحات تلقائية', badge__manualInvitation: 'ليس هناك تسجيل تلقائي', @@ -812,6 +816,9 @@ export const arSA: LocalizationResource = { action__signOutAll: 'تسجيل الخروج من جميع الحسابات', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'تم النسخ', actionLabel__copy: 'نسخ الكل', diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index 0c656345403..70bb01bac98 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const beBY: LocalizationResource = { locale: 'be-BY', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const beBY: LocalizationResource = { titleWithoutPersonal: 'Выберыце арганізацыю', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -820,6 +824,9 @@ export const beBY: LocalizationResource = { action__signOutAll: 'Выйсці з усіх уліковых запісаў', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Скапіравана!', actionLabel__copy: 'Скапіраваць усё', diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index b79fb1d51d9..986c2771e55 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const bgBG: LocalizationResource = { locale: 'bg-BG', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const bgBG: LocalizationResource = { titleWithoutPersonal: 'Изберете организация', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Автоматични покани', badge__automaticSuggestion: 'Автоматични предложения', badge__manualInvitation: 'Няма автоматично включване', @@ -811,6 +815,9 @@ export const bgBG: LocalizationResource = { action__signOutAll: 'Изход от всички акаунти', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Копирано!', actionLabel__copy: 'Копиране на всички', diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index 15d841a7fbb..0ae306d25c7 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const caES: LocalizationResource = { locale: 'ca-ES', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const caES: LocalizationResource = { titleWithoutPersonal: 'Trieu una organització', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Invitacions automàtiques', badge__automaticSuggestion: 'Suggeriments automàtics', badge__manualInvitation: 'Sense inscripció automàtica', @@ -815,6 +819,9 @@ export const caES: LocalizationResource = { action__signOutAll: 'Tanca sessió de tots els comptes', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiat!', actionLabel__copy: 'Copia tot', diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index cff00a7cf33..90e0370b026 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const csCZ: LocalizationResource = { locale: 'cs-CZ', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const csCZ: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -811,6 +815,9 @@ export const csCZ: LocalizationResource = { action__signOutAll: 'Odhlásit se ze všech účtů', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Zkopírováno!', actionLabel__copy: 'Zkopírovat vše', diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 040be30d5c5..91d1e3157db 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const daDK: LocalizationResource = { locale: 'da-DK', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const daDK: LocalizationResource = { titleWithoutPersonal: 'Vælg en organisation', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatiske invitationer', badge__automaticSuggestion: 'Automatiske forslag', badge__manualInvitation: 'Ingen automatisk tilmelding', @@ -812,6 +816,9 @@ export const daDK: LocalizationResource = { action__signOutAll: 'Log ud af alle konti', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopieret!', actionLabel__copy: 'Kopier alle', diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 22b0fedb2ec..a70ce0a1448 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const deDE: LocalizationResource = { locale: 'de-DE', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -205,6 +206,9 @@ export const deDE: LocalizationResource = { titleWithoutPersonal: 'Organisation auswählen', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatische Einladungen', badge__automaticSuggestion: 'Automatische Vorschläge', badge__manualInvitation: 'Keine automatische Aufnahme', @@ -833,6 +837,9 @@ export const deDE: LocalizationResource = { action__signOutAll: 'Melden Sie sich von allen Konten ab', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopiert!', actionLabel__copy: 'Kopiere alle', diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 7ac68eaf368..68269d1d408 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const elGR: LocalizationResource = { locale: 'el-GR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const elGR: LocalizationResource = { titleWithoutPersonal: 'Επιλέξτε οργανισμό', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Αυτόματες προσκλήσεις', badge__automaticSuggestion: 'Αυτόματες προτάσεις', badge__manualInvitation: 'Χωρίς αυτόματη εγγραφή', @@ -820,6 +824,9 @@ export const elGR: LocalizationResource = { action__signOutAll: 'Αποσύνδεση από όλους τους λογαριασμούς', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Αντιγράφηκαν!', actionLabel__copy: 'Αντιγραφή όλων', diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 775c0cebffe..684b566a7d7 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const enGB: LocalizationResource = { locale: 'en-GB', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const enGB: LocalizationResource = { titleWithoutPersonal: 'Choose an organisation', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -820,6 +824,9 @@ export const enGB: LocalizationResource = { action__signOutAll: 'Sign out of all accounts', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copied!', actionLabel__copy: 'Copy all', diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 81a1c2e522c..7cc340faa48 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -2,9 +2,10 @@ import type { LocalizationResource } from '@clerk/types'; export const enUS: LocalizationResource = { locale: 'en-US', - apiKey: { + apiKeys: { action__add: 'Add new key', action__search: 'Search keys', + detailsTitle__emptyRow: 'No API keys found', formHint: 'Provide a name to generate a new key. You’ll be able to revoke it anytime.', formTitle: 'Add new API key', menuAction__revoke: 'Revoke key', @@ -193,6 +194,9 @@ export const enUS: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: 'API Keys', + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -290,9 +294,6 @@ export const enUS: LocalizationResource = { headerTitle__requests: 'Requests', }, }, - apiKeysPage: { - title: 'API Keys', - }, navbar: { billing: 'Billing', description: 'Manage your organization.', @@ -811,6 +812,9 @@ export const enUS: LocalizationResource = { action__signOutAll: 'Sign out of all accounts', }, userProfile: { + apiKeysPage: { + title: 'API Keys', + }, backupCodePage: { actionLabel__copied: 'Copied!', actionLabel__copy: 'Copy all', @@ -914,9 +918,6 @@ export const enUS: LocalizationResource = { title: 'Add email address', verifyTitle: 'Verify email address', }, - apiKeysPage: { - title: 'API Keys', - }, formButtonPrimary__add: 'Add', formButtonPrimary__continue: 'Continue', formButtonPrimary__finish: 'Finish', diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index b29a41adab6..b76d83fd495 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const esES: LocalizationResource = { locale: 'es-ES', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const esES: LocalizationResource = { titleWithoutPersonal: 'Escoja una organización', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Invitaciones automáticas', badge__automaticSuggestion: 'Sugerencias automáticas', badge__manualInvitation: 'Sin inscripción automática', @@ -817,6 +821,9 @@ export const esES: LocalizationResource = { action__signOutAll: 'Salir de todas las cuentas', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: '¡Copiado!', actionLabel__copy: 'Copiar todo', diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index a86c513054a..2b5c5483615 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const esMX: LocalizationResource = { locale: 'es-MX', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const esMX: LocalizationResource = { titleWithoutPersonal: 'Elige una organización', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Invitaciones automaticas', badge__automaticSuggestion: 'Sugerencias automaticas', badge__manualInvitation: 'Sin inscripciónes automaticas', @@ -818,6 +822,9 @@ export const esMX: LocalizationResource = { action__signOutAll: 'Salir de todas las cuentas', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiado!', actionLabel__copy: 'Copiar todo', diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index 46906c66f55..2a372fdabcd 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const fiFI: LocalizationResource = { locale: 'fi-FI', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const fiFI: LocalizationResource = { titleWithoutPersonal: 'Valitse organisaatio', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automaattiset kutsut', badge__automaticSuggestion: 'Automaattiset ehdotukset', badge__manualInvitation: 'Ei automaattista liittymistä', @@ -815,6 +819,9 @@ export const fiFI: LocalizationResource = { action__signOutAll: 'Kirjaudu ulos kaikista tileistä', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopioitu', actionLabel__copy: 'Kopioi', diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index a1e900298e3..42e8119016b 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const frFR: LocalizationResource = { locale: 'fr-FR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const frFR: LocalizationResource = { titleWithoutPersonal: 'Choisissez une organisation', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Invitations automatiques', badge__automaticSuggestion: 'Suggestions automatiques', badge__manualInvitation: "Pas d'inscription automatique", @@ -820,6 +824,9 @@ export const frFR: LocalizationResource = { action__signOutAll: 'Se déconnecter de tous les comptes', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copié !', actionLabel__copy: 'Copier tous les codes', diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index ef88b031d06..33a7dafb3ca 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const heIL: LocalizationResource = { locale: 'he-IL', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const heIL: LocalizationResource = { titleWithoutPersonal: 'בחר ארגון', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'הזמנות אוטומטיות', badge__automaticSuggestion: 'הצעות אוטומטיות', badge__manualInvitation: 'ללא הרשמה אוטומטית', @@ -805,6 +809,9 @@ export const heIL: LocalizationResource = { action__signOutAll: 'התנתק מכל החשבונות', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'הועתק!', actionLabel__copy: 'העתק הכל', diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index f4132a19ba2..06b9de29c05 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const hrHR: LocalizationResource = { locale: 'hr-HR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const hrHR: LocalizationResource = { titleWithoutPersonal: 'Odaberite organizaciju', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatske pozivnice', badge__automaticSuggestion: 'Automatski prijedlozi', badge__manualInvitation: 'Bez automatskog učlanjenja', @@ -820,6 +824,9 @@ export const hrHR: LocalizationResource = { action__signOutAll: 'Odjavi se sa svih računa', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopirano!', actionLabel__copy: 'Kopiraj sve', diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index fccd28983bc..4084b83bd75 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const huHU: LocalizationResource = { locale: 'hu-HU', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const huHU: LocalizationResource = { titleWithoutPersonal: 'Válassz egy szervezetet', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatikus meghívások', badge__automaticSuggestion: 'Automatikus javaslatok', badge__manualInvitation: 'Nincs automatikus felvétel', @@ -816,6 +820,9 @@ export const huHU: LocalizationResource = { action__signOutAll: 'Kijelentkezés minden fiókból', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kimásolva!', actionLabel__copy: 'Az összes kimásolása', diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 1209b8c6b64..f06cdc523f5 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const idID: LocalizationResource = { locale: 'id-ID', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const idID: LocalizationResource = { titleWithoutPersonal: 'Pilih organisasi', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Undangan otomatis', badge__automaticSuggestion: 'Saran otomatis', badge__manualInvitation: 'Tanpa pendaftaran otomatis', @@ -824,6 +828,9 @@ export const idID: LocalizationResource = { action__signOutAll: 'Keluar dari semua akun', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Disalin!', actionLabel__copy: 'Salin semua', diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 393512a33da..940338312d8 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const isIS: LocalizationResource = { locale: 'is-IS', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const isIS: LocalizationResource = { titleWithoutPersonal: 'Veldu samtök', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Sjálfvirk boð', badge__automaticSuggestion: 'Sjálfvirkar tillögur', badge__manualInvitation: 'Engin sjálfvirk skráning', @@ -818,6 +822,9 @@ export const isIS: LocalizationResource = { action__signOutAll: 'Skrá út af öllum reikningum', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Afritað!', actionLabel__copy: 'Afrita allt', diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index aa0e93be1b3..b193c7539ef 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const itIT: LocalizationResource = { locale: 'it-IT', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const itIT: LocalizationResource = { titleWithoutPersonal: 'Scegli un’organizzazione', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Inviti automatici', badge__automaticSuggestion: 'Suggerimenti automatici', badge__manualInvitation: 'Nessuna iscrizione automatica', @@ -815,6 +819,9 @@ export const itIT: LocalizationResource = { action__signOutAll: 'Disconnetti da tutti gli accounts', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiati!', actionLabel__copy: 'Copia tutti', diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 17aa8af369d..597b17fe73b 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const jaJP: LocalizationResource = { locale: 'ja-JP', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const jaJP: LocalizationResource = { titleWithoutPersonal: '組織を選択', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: '自動招待', badge__automaticSuggestion: '自動サジェスト', badge__manualInvitation: '自動登録なし', @@ -814,6 +818,9 @@ export const jaJP: LocalizationResource = { action__signOutAll: '全てのアカウントからサインアウト', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'コピー済み!', actionLabel__copy: 'すべてコピー', diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 3fe723b2445..029ab9ee2bb 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const koKR: LocalizationResource = { locale: 'ko-KR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const koKR: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -808,6 +812,9 @@ export const koKR: LocalizationResource = { action__signOutAll: '모든 계정에서 로그아웃', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: '복사 완료!', actionLabel__copy: '전체 복사', diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index b671a2265de..20881c52fce 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const mnMN: LocalizationResource = { locale: 'mn-MN', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const mnMN: LocalizationResource = { titleWithoutPersonal: 'Байгууллага сонгоно уу', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Автомат урилга', badge__automaticSuggestion: 'Автомат саналууд', badge__manualInvitation: 'Автомат бүртгэл байхгүй', @@ -815,6 +819,9 @@ export const mnMN: LocalizationResource = { action__signOutAll: 'Бүх бүртгэлээс гарна уу', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Хуулсан!', actionLabel__copy: 'Бүгдийг хуулах', diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index 3f82c43a9e0..090d5bdc78e 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const nbNO: LocalizationResource = { locale: 'nb-NO', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const nbNO: LocalizationResource = { titleWithoutPersonal: 'Velg en organiasjon', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatisk invitasjon', badge__automaticSuggestion: 'Automatisk forslag', badge__manualInvitation: 'Ingen automatisk registrering', @@ -814,6 +818,9 @@ export const nbNO: LocalizationResource = { action__signOutAll: 'Logg ut av alle kontoer', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopiert!', actionLabel__copy: 'Kopier alle', diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index a581ad572f0..58221092221 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const nlBE: LocalizationResource = { locale: 'nl-NL', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const nlBE: LocalizationResource = { titleWithoutPersonal: 'Kies een organisatie', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatische Uitnodiging', badge__automaticSuggestion: 'Automatische Suggesties', badge__manualInvitation: 'Geen automatische inschrijving', @@ -813,6 +817,9 @@ export const nlBE: LocalizationResource = { action__signOutAll: 'Uitloggen uit alle accounts', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Gekopieerd!', actionLabel__copy: 'Kopieer', diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 58a78eaffe7..d5b9be6f61c 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const nlNL: LocalizationResource = { locale: 'nl-NL', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const nlNL: LocalizationResource = { titleWithoutPersonal: 'Kies een organisatie', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatische Uitnodiging', badge__automaticSuggestion: 'Automatische Suggesties', badge__manualInvitation: 'Geen automatische inschrijving', @@ -813,6 +817,9 @@ export const nlNL: LocalizationResource = { action__signOutAll: 'Uitloggen uit alle accounts', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Gekopieerd!', actionLabel__copy: 'Kopieer', diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index eb955ce7367..828c2272002 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const plPL: LocalizationResource = { locale: 'pl-PL', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const plPL: LocalizationResource = { titleWithoutPersonal: 'Wybierz organizację', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatyczne zaproszenia', badge__automaticSuggestion: 'Automatyczne sugestie', badge__manualInvitation: 'Brak automatycznej rejestracji', @@ -823,6 +827,9 @@ export const plPL: LocalizationResource = { action__signOutAll: 'Wyloguj ze wszystkich kont', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Skopiowane!', actionLabel__copy: 'Skopiuj wszystkie', diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 8678f3e8203..149cab720e2 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const ptBR: LocalizationResource = { locale: 'pt-BR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const ptBR: LocalizationResource = { titleWithoutPersonal: 'Selecione uma organização', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Convites automáticos', badge__automaticSuggestion: 'Sugestões automáticas', badge__manualInvitation: 'Sem inscrição automática', @@ -819,6 +823,9 @@ export const ptBR: LocalizationResource = { action__signOutAll: 'Sair de todas as contas', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiado!', actionLabel__copy: 'Copiar tudo', diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 133af3788ec..0b90429390b 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const ptPT: LocalizationResource = { locale: 'pt-PT', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const ptPT: LocalizationResource = { titleWithoutPersonal: 'Selecione uma organização', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Convites automáticos', badge__automaticSuggestion: 'Sugestões automáticas', badge__manualInvitation: 'Sem inscrição automática', @@ -813,6 +817,9 @@ export const ptPT: LocalizationResource = { action__signOutAll: 'Terminar sessão de todas as contas', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiado!', actionLabel__copy: 'Copiar tudo', diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index 54ab3746b01..b2f228c421b 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const roRO: LocalizationResource = { locale: 'ro-RO', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -203,6 +204,9 @@ export const roRO: LocalizationResource = { titleWithoutPersonal: 'Selectați o organizație', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Invitații automate', badge__automaticSuggestion: 'Sugestii automate', badge__manualInvitation: 'Fără înscriere automată', @@ -818,6 +822,9 @@ export const roRO: LocalizationResource = { action__signOutAll: 'Deconectați-vă din toate conturile', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Copiat!', actionLabel__copy: 'Copiați toate', diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 98848bdd68b..f096624c6de 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const ruRU: LocalizationResource = { locale: 'ru-RU', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -205,6 +206,9 @@ export const ruRU: LocalizationResource = { titleWithoutPersonal: 'Выбрать организацию', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Автоматические приглашения', badge__automaticSuggestion: 'Автоматические предложения', badge__manualInvitation: 'Нет автоматической регистрации', @@ -828,6 +832,9 @@ export const ruRU: LocalizationResource = { action__signOutAll: 'Выйти из всех учетных записей', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Скопировано!', actionLabel__copy: 'Копировать все', diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index e50a8263c19..33962894878 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const skSK: LocalizationResource = { locale: 'sk-SK', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const skSK: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -811,6 +815,9 @@ export const skSK: LocalizationResource = { action__signOutAll: 'Odhlásiť sa zo všetkých účtov', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Skopírované!', actionLabel__copy: 'Kopírovať všetko', diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index 55ef2b25c57..35a44fcfc5e 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const srRS: LocalizationResource = { locale: 'sr-RS', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const srRS: LocalizationResource = { titleWithoutPersonal: 'Izaberi organizaciju', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatske pozivnice', badge__automaticSuggestion: 'Automatski predlozi', badge__manualInvitation: 'Bez automatskog uključivanja', @@ -814,6 +818,9 @@ export const srRS: LocalizationResource = { action__signOutAll: 'Odjavi se sa svih naloga', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopirano!', actionLabel__copy: 'Kopiraj sve', diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 3b82f7f8cb1..e194cd99a0b 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const svSE: LocalizationResource = { locale: 'sv-SE', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const svSE: LocalizationResource = { titleWithoutPersonal: 'Välj en organisation', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatiska inbjudningar', badge__automaticSuggestion: 'Automatiska förslag', badge__manualInvitation: 'Ingen automatisk registrering', @@ -817,6 +821,9 @@ export const svSE: LocalizationResource = { action__signOutAll: 'Logga ut från alla konton', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopierat!', actionLabel__copy: 'Kopiera alla', diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index f4f38bbf3bd..2baa2b79654 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const thTH: LocalizationResource = { locale: 'th-TH', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const thTH: LocalizationResource = { titleWithoutPersonal: 'เลือกองค์กร', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'คำเชิญอัตโนมัติ', badge__automaticSuggestion: 'ข้อเสนอแนะอัตโนมัติ', badge__manualInvitation: 'ไม่มีการลงทะเบียนอัตโนมัติ', @@ -813,6 +817,9 @@ export const thTH: LocalizationResource = { action__signOutAll: 'ออกจากระบบทุกบัญชี', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'คัดลอกแล้ว!', actionLabel__copy: 'คัดลอกทั้งหมด', diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index 4e5cbe39f2a..363d5952fe2 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const trTR: LocalizationResource = { locale: 'tr-TR', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -201,6 +202,9 @@ export const trTR: LocalizationResource = { titleWithoutPersonal: 'Bir organizasyon seçin', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Otomatik davetler', badge__automaticSuggestion: 'Otomatik öneriler', badge__manualInvitation: 'Davetler', @@ -817,6 +821,9 @@ export const trTR: LocalizationResource = { action__signOutAll: 'Tüm hesaplardan çıkış yap', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Kopyalandı!', actionLabel__copy: 'Hepsini kopyala', diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 66aad9be631..7dbe04b8e94 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const ukUA: LocalizationResource = { locale: 'uk-UA', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const ukUA: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -811,6 +815,9 @@ export const ukUA: LocalizationResource = { action__signOutAll: 'Вийти з усіх акаунтів', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Скопійовано!', actionLabel__copy: 'Копіювати все', diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 77b9a105513..50aa127375e 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const viVN: LocalizationResource = { locale: 'vi-VN', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -202,6 +203,9 @@ export const viVN: LocalizationResource = { titleWithoutPersonal: 'Choose an organization', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: 'Automatic invitations', badge__automaticSuggestion: 'Automatic suggestions', badge__manualInvitation: 'No automatic enrollment', @@ -812,6 +816,9 @@ export const viVN: LocalizationResource = { action__signOutAll: 'Đăng xuất khỏi tất cả các tài khoản', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: 'Đã sao chép!', actionLabel__copy: 'Sao chép tất cả', diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 5d557eaad53..dccefd3747e 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const zhCN: LocalizationResource = { locale: 'zh-CN', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -200,6 +201,9 @@ export const zhCN: LocalizationResource = { titleWithoutPersonal: '选择一个组织', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: '自动邀请', badge__automaticSuggestion: '自动建议', badge__manualInvitation: '无自动注册', @@ -799,6 +803,9 @@ export const zhCN: LocalizationResource = { action__signOutAll: '退出所有账户', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: '已复制!', actionLabel__copy: '复制全部', diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index ca8bd8c6bd0..07f261652df 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -14,9 +14,10 @@ import type { LocalizationResource } from '@clerk/types'; export const zhTW: LocalizationResource = { locale: 'zh-TW', - apiKey: { + apiKeys: { action__add: undefined, action__search: undefined, + detailsTitle__emptyRow: undefined, formHint: undefined, formTitle: undefined, menuAction__revoke: undefined, @@ -200,6 +201,9 @@ export const zhTW: LocalizationResource = { titleWithoutPersonal: '選擇一個組織', }, organizationProfile: { + apiKeysPage: { + title: undefined, + }, badge__automaticInvitation: '自動邀請', badge__automaticSuggestion: '自動建議', badge__manualInvitation: '不自動註冊', @@ -800,6 +804,9 @@ export const zhTW: LocalizationResource = { action__signOutAll: '退出所有帳戶', }, userProfile: { + apiKeysPage: { + title: undefined, + }, backupCodePage: { actionLabel__copied: '已複製!', actionLabel__copy: '複製全部', diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 34ed4a62eac..79cfae620f1 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -641,6 +641,7 @@ type _LocalizationResource = { }; apiKeysPage: { title: LocalizationValue; + detailsTitle__emptyRow: LocalizationValue; }; passkeyScreen: { title__rename: LocalizationValue; @@ -1006,6 +1007,7 @@ type _LocalizationResource = { }; apiKeysPage: { title: LocalizationValue; + detailsTitle__emptyRow: LocalizationValue; }; }; createOrganization: { @@ -1049,12 +1051,13 @@ type _LocalizationResource = { message: LocalizationValue; }; }; - apiKey: { + apiKeys: { formTitle: LocalizationValue; formHint: LocalizationValue; menuAction__revoke: LocalizationValue; action__search: LocalizationValue; action__add: LocalizationValue; + detailsTitle__emptyRow: LocalizationValue; }; }; From 9584125106ddfabde35f29b796b266eea9cf8866 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 15 May 2025 18:44:51 -0700 Subject: [PATCH 37/38] chore: add save button locale --- packages/clerk-js/sandbox/template.html | 2 +- .../clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx | 1 + packages/localizations/src/ar-SA.ts | 1 + packages/localizations/src/be-BY.ts | 1 + packages/localizations/src/bg-BG.ts | 1 + packages/localizations/src/ca-ES.ts | 1 + packages/localizations/src/cs-CZ.ts | 1 + packages/localizations/src/da-DK.ts | 1 + packages/localizations/src/de-DE.ts | 1 + packages/localizations/src/el-GR.ts | 1 + packages/localizations/src/en-GB.ts | 1 + packages/localizations/src/en-US.ts | 1 + packages/localizations/src/es-ES.ts | 1 + packages/localizations/src/es-MX.ts | 1 + packages/localizations/src/fi-FI.ts | 1 + packages/localizations/src/fr-FR.ts | 1 + packages/localizations/src/he-IL.ts | 1 + packages/localizations/src/hr-HR.ts | 1 + packages/localizations/src/hu-HU.ts | 1 + packages/localizations/src/id-ID.ts | 1 + packages/localizations/src/is-IS.ts | 1 + packages/localizations/src/it-IT.ts | 1 + packages/localizations/src/ja-JP.ts | 1 + packages/localizations/src/ko-KR.ts | 1 + packages/localizations/src/mn-MN.ts | 1 + packages/localizations/src/nb-NO.ts | 1 + packages/localizations/src/nl-BE.ts | 1 + packages/localizations/src/nl-NL.ts | 1 + packages/localizations/src/pl-PL.ts | 1 + packages/localizations/src/pt-BR.ts | 1 + packages/localizations/src/pt-PT.ts | 1 + packages/localizations/src/ro-RO.ts | 1 + packages/localizations/src/ru-RU.ts | 1 + packages/localizations/src/sk-SK.ts | 1 + packages/localizations/src/sr-RS.ts | 1 + packages/localizations/src/sv-SE.ts | 1 + packages/localizations/src/th-TH.ts | 1 + packages/localizations/src/tr-TR.ts | 1 + packages/localizations/src/uk-UA.ts | 1 + packages/localizations/src/vi-VN.ts | 1 + packages/localizations/src/zh-CN.ts | 1 + packages/localizations/src/zh-TW.ts | 1 + packages/types/src/localization.ts | 1 + 43 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index b1f51b01ff5..21794bdb864 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -265,7 +265,7 @@ class="relative isolate flex w-full rounded-md border border-white px-2 py-[0.4375rem] text-sm hover:bg-gray-50 aria-[current]:bg-gray-50" href="/api-keys" > - Manage API Keys + API Keys
  • diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index c8f172b26d5..6869fce5b4a 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -109,6 +109,7 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro {showAdvanced ? 'Hide' : 'Show'} advanced settings Date: Thu, 15 May 2025 19:03:37 -0700 Subject: [PATCH 38/38] chore: fix close form btn --- .../clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index 6869fce5b4a..f30f5d0ab80 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -111,7 +111,7 @@ export const CreateApiKeyForm = ({ onCreate, isSubmitting }: CreateApiKeyFormPro
  • {apiKey.name} @@ -77,8 +71,8 @@ export const ApiKeysTable = ({ }} > void toggleSecret(apiKey.id)} - aria-label={revealedKeys[apiKey.id] ? 'Hide key' : 'Show key'} + aria-label={'Show key'} > - + {/* */} + - + {/* */} + @@ -103,7 +105,7 @@ export const ApiKeysTable = ({ // @ts-expect-error: TODO: Add locales label: 'Revoke key', isDestructive: true, - onClick: () => void revokeApiKey(apiKey.id), + onClick: () => onRevoke(apiKey.id), isDisabled: false, }, ]} diff --git a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts index 653d740de66..129345cccf8 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts +++ b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts @@ -22,14 +22,13 @@ function getTimeLeftInSeconds(expirationOption: Expiration) { return diffInSecs; } -export function useApiKeys(subject: string, perPage: number = 5) { +export function useApiKeys({ subject, perPage = 5 }: { subject: string; perPage?: number }) { const clerk = useClerk(); const { data: apiKeys, isLoading, revalidate, } = useFetch(clerk.getApiKeys, { subject }, undefined, `api-key-source-${subject}`); - const [revealedKeys, setRevealedKeys] = useState>({}); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const itemsPerPage = perPage; @@ -41,19 +40,6 @@ export function useApiKeys(subject: string, perPage: number = 5) { const endingRow = Math.min(page * itemsPerPage, itemCount); const paginatedApiKeys = filteredApiKeys.slice(startingRow - 1, endingRow); - const toggleSecret = async (id: string) => { - setRevealedKeys(prev => { - if (prev[id]) { - return { ...prev, [id]: null }; - } - return prev; - }); - if (!revealedKeys[id]) { - const secret = await clerk.getApiKeySecret(id); - setRevealedKeys(prev => ({ ...prev, [id]: secret })); - } - }; - const handleCreate = async (params: { name: string; description?: string; @@ -78,8 +64,6 @@ export function useApiKeys(subject: string, perPage: number = 5) { return { apiKeys: paginatedApiKeys, isLoading: isLoading ?? false, - revealedKeys, - toggleSecret, revokeApiKey, search, setSearch, diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx index 27bb37f0eda..790e065cb5d 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx @@ -1,37 +1,24 @@ import { useOrganization } from '@clerk/shared/react'; -import { ApiKeysContext, useApiKeysContext } from '../../contexts'; +import { ApiKeysContext } from '../../contexts'; import { Col } from '../../customizables'; import { Header } from '../../elements'; -import { ApiKeysInternal, CopyButton } from '../ApiKeys'; -import { useApiKeys } from '../ApiKeys/shared'; +import { ApiKeysInternal } from '../ApiKeys'; -function APIKeysPageInternal() { - const ctx = useApiKeysContext(); +export const OrganizationApiKeysPage = () => { const { organization } = useOrganization(); - const apiKeys = useApiKeys(organization?.id ?? '', ctx.perPage); - return ( - - - - ); -} -export const OrganizationApiKeysPage = () => { return (
    From 692fb02404df0f707857f09c4a689d7fa824c070 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 12 May 2025 21:54:56 -0700 Subject: [PATCH 26/38] chore: add api key secret fetcher and clean up components --- .../ui/components/ApiKeys/ApiKeysTable.tsx | 74 +++++++++++-------- .../src/ui/components/ApiKeys/shared.ts | 14 ++++ 2 files changed, 59 insertions(+), 29 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index e47cdc81e9f..06d05abfeca 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -1,12 +1,16 @@ import type { ApiKeyResource } from '@clerk/types'; +import { useState } from 'react'; +import { useApiKeySecret } from '../../components/ApiKeys/shared'; import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; import { useClipboard } from '../../hooks'; -import { Clipboard, Eye } from '../../icons'; +import { Clipboard, Eye, EyeSlash } from '../../icons'; + +const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { + const { data: apiKeySecret } = useApiKeySecret(apiKeyID); + const { onCopy } = useClipboard(apiKeySecret ?? ''); -const CopyButton = ({ text }: { text: string }) => { - const { onCopy } = useClipboard(text); return ( + + ); +}; + export const ApiKeysTable = ({ rows, isLoading, @@ -77,33 +116,10 @@ export const ApiKeysTable = ({ - - - - - + + diff --git a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts index 129345cccf8..8013d6cad0e 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts +++ b/packages/clerk-js/src/ui/components/ApiKeys/shared.ts @@ -76,3 +76,17 @@ export function useApiKeys({ subject, perPage = 5 }: { subject: string; perPage? handleCreate, }; } + +export function useApiKeySecret(apiKeyID: string) { + const clerk = useClerk(); + + async function getApiKeySecret(apiKeyID?: string) { + if (apiKeyID) { + const secret = await clerk.getApiKeySecret(apiKeyID); + return secret; + } + return ''; + } + + return useFetch(getApiKeySecret, apiKeyID, undefined, `api-key-secret-source-${apiKeyID}`); +} From 74224cbd2eeaaece82c16e53486cd610b7f47d0b Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 12 May 2025 22:14:09 -0700 Subject: [PATCH 27/38] chore: adjust table heading widths --- .../clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 06d05abfeca..bb43ce121fb 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -73,9 +73,9 @@ export const ApiKeysTable = ({
    NameLast usedLast used KeyActionsActions
    {apiKey.name} - + Created at{' '} {apiKey.createdAt.toLocaleDateString(undefined, { month: 'short', From c4d8e6fe776173c9d740504348183f3cbdb16724 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 13 May 2025 11:07:15 -0700 Subject: [PATCH 28/38] chore: improve secret fetching --- .../ui/components/ApiKeys/ApiKeysTable.tsx | 37 +++++++++++++------ .../src/ui/components/ApiKeys/shared.ts | 14 ------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index bb43ce121fb..42b8ce909b4 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -1,23 +1,43 @@ +import { useClerk } from '@clerk/shared/react'; import type { ApiKeyResource } from '@clerk/types'; import { useState } from 'react'; -import { useApiKeySecret } from '../../components/ApiKeys/shared'; import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; -import { useClipboard } from '../../hooks'; +import { useClipboard, useFetch } from '../../hooks'; import { Clipboard, Eye, EyeSlash } from '../../icons'; +const useApiKeySecret = ({ apiKeyID, enabled }: { apiKeyID: string; enabled: boolean }) => { + const clerk = useClerk(); + + const getSecret = async (apiKeyID?: string) => { + if (!apiKeyID) { + return ''; + } + const secret = await clerk.getApiKeySecret(apiKeyID); + return secret; + }; + + return useFetch(getSecret, enabled ? apiKeyID : undefined); +}; + const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { - const { data: apiKeySecret } = useApiKeySecret(apiKeyID); + const [enabled, setEnabled] = useState(false); + const { data: apiKeySecret } = useApiKeySecret({ apiKeyID, enabled }); const { onCopy } = useClipboard(apiKeySecret ?? ''); + const handleCopy = () => { + setEnabled(true); + onCopy(); + }; + return ( @@ -26,7 +46,7 @@ const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { const SecretInputWithToggle = ({ apiKeyID }: { apiKeyID: string }) => { const [revealed, setRevealed] = useState(false); - const { data: apiKeySecret } = useApiKeySecret(apiKeyID); + const { data: apiKeySecret } = useApiKeySecret({ apiKeyID, enabled: revealed }); return ( - - 3d ago - + 3d ago Date: Tue, 13 May 2025 11:13:12 -0700 Subject: [PATCH 29/38] chore: improve secret fetching --- .../src/ui/components/ApiKeys/ApiKeysTable.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 42b8ce909b4..ec59ca174e0 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -1,6 +1,6 @@ import { useClerk } from '@clerk/shared/react'; import type { ApiKeyResource } from '@clerk/types'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; @@ -26,10 +26,12 @@ const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { const { data: apiKeySecret } = useApiKeySecret({ apiKeyID, enabled }); const { onCopy } = useClipboard(apiKeySecret ?? ''); - const handleCopy = () => { - setEnabled(true); - onCopy(); - }; + useEffect(() => { + if (enabled && apiKeySecret) { + onCopy(); + setEnabled(false); + } + }, [enabled, apiKeySecret, onCopy]); return ( From bc951608090bcc83beb3c1296383d2d2b2442a12 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Tue, 13 May 2025 15:02:49 -0700 Subject: [PATCH 30/38] chore: add locales --- .../ui/components/ApiKeys/ApiKeysTable.tsx | 45 ++++++++---- .../components/ApiKeys/CreateApiKeyForm.tsx | 19 +++-- .../src/ui/components/ApiKeys/shared.ts | 72 +++++++++++++++++++ packages/localizations/src/ar-SA.ts | 12 ++-- packages/localizations/src/be-BY.ts | 12 ++-- packages/localizations/src/bg-BG.ts | 12 ++-- packages/localizations/src/ca-ES.ts | 12 ++-- packages/localizations/src/cs-CZ.ts | 12 ++-- packages/localizations/src/da-DK.ts | 12 ++-- packages/localizations/src/de-DE.ts | 12 ++-- packages/localizations/src/el-GR.ts | 12 ++-- packages/localizations/src/en-GB.ts | 12 ++-- packages/localizations/src/en-US.ts | 12 ++-- packages/localizations/src/es-ES.ts | 12 ++-- packages/localizations/src/es-MX.ts | 12 ++-- packages/localizations/src/fi-FI.ts | 12 ++-- packages/localizations/src/fr-FR.ts | 12 ++-- packages/localizations/src/he-IL.ts | 12 ++-- packages/localizations/src/hr-HR.ts | 12 ++-- packages/localizations/src/hu-HU.ts | 12 ++-- packages/localizations/src/id-ID.ts | 12 ++-- packages/localizations/src/is-IS.ts | 12 ++-- packages/localizations/src/it-IT.ts | 12 ++-- packages/localizations/src/ja-JP.ts | 12 ++-- packages/localizations/src/ko-KR.ts | 12 ++-- packages/localizations/src/mn-MN.ts | 12 ++-- packages/localizations/src/nb-NO.ts | 12 ++-- packages/localizations/src/nl-BE.ts | 12 ++-- packages/localizations/src/nl-NL.ts | 12 ++-- packages/localizations/src/pl-PL.ts | 12 ++-- packages/localizations/src/pt-BR.ts | 12 ++-- packages/localizations/src/pt-PT.ts | 12 ++-- packages/localizations/src/ro-RO.ts | 12 ++-- packages/localizations/src/ru-RU.ts | 12 ++-- packages/localizations/src/sk-SK.ts | 12 ++-- packages/localizations/src/sr-RS.ts | 12 ++-- packages/localizations/src/sv-SE.ts | 12 ++-- packages/localizations/src/th-TH.ts | 12 ++-- packages/localizations/src/tr-TR.ts | 12 ++-- packages/localizations/src/uk-UA.ts | 12 ++-- packages/localizations/src/vi-VN.ts | 12 ++-- packages/localizations/src/zh-CN.ts | 12 ++-- packages/localizations/src/zh-TW.ts | 12 ++-- packages/types/src/localization.ts | 13 ++-- 44 files changed, 440 insertions(+), 189 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index ec59ca174e0..d1bbe92e797 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -2,10 +2,25 @@ import { useClerk } from '@clerk/shared/react'; import type { ApiKeyResource } from '@clerk/types'; import { useEffect, useState } from 'react'; -import { Button, Flex, Icon, Input, Spinner, Table, Tbody, Td, Text, Th, Thead, Tr } from '../../customizables'; +import { + Button, + descriptors, + Flex, + Icon, + Input, + localizationKeys, + Spinner, + Table, + Tbody, + Td, + Text, + Th, + Thead, + Tr, +} from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; import { useClipboard, useFetch } from '../../hooks'; -import { Clipboard, Eye, EyeSlash } from '../../icons'; +import { Check, Copy, Eye, EyeSlash } from '../../icons'; const useApiKeySecret = ({ apiKeyID, enabled }: { apiKeyID: string; enabled: boolean }) => { const clerk = useClerk(); @@ -24,7 +39,7 @@ const useApiKeySecret = ({ apiKeyID, enabled }: { apiKeyID: string; enabled: boo const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { const [enabled, setEnabled] = useState(false); const { data: apiKeySecret } = useApiKeySecret({ apiKeyID, enabled }); - const { onCopy } = useClipboard(apiKeySecret ?? ''); + const { onCopy, hasCopied } = useClipboard(apiKeySecret ?? ''); useEffect(() => { if (enabled && apiKeySecret) { @@ -36,12 +51,12 @@ const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { return ( ); }; @@ -74,6 +89,7 @@ const SecretInputWithToggle = ({ apiKeyID }: { apiKeyID: string }) => { sx={{ position: 'absolute', right: 0 }} aria-label={'Show key'} onClick={() => setRevealed(!revealed)} + focusRing={false} > @@ -91,13 +107,13 @@ export const ApiKeysTable = ({ onRevoke: (id: string) => void; }) => { return ( - +
    - + - + @@ -107,6 +123,7 @@ export const ApiKeysTable = ({ @@ -114,8 +131,11 @@ export const ApiKeysTable = ({ rows.map(apiKey => ( diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index bef9c08c188..e9d7c4f958d 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -20,15 +20,15 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { const nameField = useFormControl('name', '', { type: 'text', - label: 'Name', - placeholder: 'Enter your secret key name', + label: localizationKeys('formFieldLabel__apiKeyName'), + placeholder: localizationKeys('formFieldInputPlaceholder__apiKeyName'), isRequired: true, }); const descriptionField = useFormControl('description', '', { type: 'text', - label: 'Description', - placeholder: 'Enter a description for your API key', + label: localizationKeys('formFieldLabel__apiKeyDescription'), + placeholder: localizationKeys('formFieldInputPlaceholder__apiKeyDescription'), isRequired: false, }); @@ -46,8 +46,8 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { return ( @@ -61,10 +61,9 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { - Expiration - + variant='subtitle' + localizationKey={localizationKeys('formFieldLabel__apiKeyExpiration')} + /> Date: Tue, 13 May 2025 16:46:52 -0700 Subject: [PATCH 31/38] chore: action locales --- .../clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx | 10 +++++++--- .../src/ui/components/ApiKeys/ApiKeysTable.tsx | 6 +++--- .../src/ui/components/ApiKeys/CreateApiKeyForm.tsx | 14 ++++++++------ packages/localizations/src/ar-SA.ts | 4 ++++ packages/localizations/src/be-BY.ts | 4 ++++ packages/localizations/src/bg-BG.ts | 4 ++++ packages/localizations/src/ca-ES.ts | 4 ++++ packages/localizations/src/cs-CZ.ts | 4 ++++ packages/localizations/src/da-DK.ts | 4 ++++ packages/localizations/src/de-DE.ts | 4 ++++ packages/localizations/src/el-GR.ts | 4 ++++ packages/localizations/src/en-GB.ts | 4 ++++ packages/localizations/src/en-US.ts | 2 ++ packages/localizations/src/es-ES.ts | 4 ++++ packages/localizations/src/es-MX.ts | 4 ++++ packages/localizations/src/fi-FI.ts | 4 ++++ packages/localizations/src/fr-FR.ts | 4 ++++ packages/localizations/src/he-IL.ts | 4 ++++ packages/localizations/src/hr-HR.ts | 4 ++++ packages/localizations/src/hu-HU.ts | 4 ++++ packages/localizations/src/id-ID.ts | 4 ++++ packages/localizations/src/is-IS.ts | 4 ++++ packages/localizations/src/it-IT.ts | 4 ++++ packages/localizations/src/ja-JP.ts | 4 ++++ packages/localizations/src/ko-KR.ts | 4 ++++ packages/localizations/src/mn-MN.ts | 4 ++++ packages/localizations/src/nb-NO.ts | 4 ++++ packages/localizations/src/nl-BE.ts | 4 ++++ packages/localizations/src/nl-NL.ts | 4 ++++ packages/localizations/src/pl-PL.ts | 4 ++++ packages/localizations/src/pt-BR.ts | 4 ++++ packages/localizations/src/pt-PT.ts | 4 ++++ packages/localizations/src/ro-RO.ts | 4 ++++ packages/localizations/src/ru-RU.ts | 4 ++++ packages/localizations/src/sk-SK.ts | 4 ++++ packages/localizations/src/sr-RS.ts | 4 ++++ packages/localizations/src/sv-SE.ts | 4 ++++ packages/localizations/src/th-TH.ts | 4 ++++ packages/localizations/src/tr-TR.ts | 4 ++++ packages/localizations/src/uk-UA.ts | 4 ++++ packages/localizations/src/vi-VN.ts | 4 ++++ packages/localizations/src/zh-CN.ts | 4 ++++ packages/localizations/src/zh-TW.ts | 4 ++++ packages/types/src/localization.ts | 2 ++ 44 files changed, 178 insertions(+), 12 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index a28ced232fd..2fd3208d782 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -1,7 +1,7 @@ import { useOrganization, useUser } from '@clerk/shared/react'; import { useApiKeysContext } from '../../contexts'; -import { Box, Button, Col, Flex, Flow, Icon } from '../../customizables'; +import { Box, Button, Col, Flex, Flow, Icon, localizationKeys, useLocalizations } from '../../customizables'; import { Card, InputWithIcon, Pagination, withCardStateProvider } from '../../elements'; import { Action } from '../../elements/Action'; import { MagnifyingGlass } from '../../icons'; @@ -24,6 +24,7 @@ export const ApiKeysInternal = ({ subject }: { subject: string }) => { endingRow, handleCreate, } = useApiKeys({ subject }); + const { t } = useLocalizations(); return ( @@ -34,7 +35,7 @@ export const ApiKeysInternal = ({ subject }: { subject: string }) => { > } value={search} onChange={e => { @@ -44,7 +45,10 @@ export const ApiKeysInternal = ({ subject }: { subject: string }) => { /> - +
    NameLast usedLast used KeyActionsActions
    - {apiKey.name} - + {apiKey.name} + Created at{' '} {apiKey.createdAt.toLocaleDateString(undefined, { month: 'short', @@ -140,14 +160,11 @@ export const ApiKeysTable = ({ onRevoke(apiKey.id), - isDisabled: false, }, ]} - elementId={'member'} />
    +
    - + - + diff --git a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx index e9d7c4f958d..3ff4ffae39b 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/CreateApiKeyForm.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; -import { Button, Col, Flex, localizationKeys, Text } from '../../customizables'; +import { Button, Col, Flex, FormLabel, localizationKeys, Text } from '../../customizables'; import { Form, FormButtons, FormContainer, SegmentedControl } from '../../elements'; import { useActionContext } from '../../elements/Action/ActionRoot'; import { useFormControl } from '../../utils'; @@ -59,11 +59,13 @@ export const CreateApiKeyForm = ({ onCreate }: CreateApiKeyFormProps) => { - + + + Date: Tue, 13 May 2025 17:11:47 -0700 Subject: [PATCH 32/38] chore: add locales to api keys page in user and org profile --- .../clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx | 11 +++++++---- .../components/ApiKeys/{shared.ts => useApiKeys.ts} | 4 ++-- .../OrganizationProfile/OrganizationApiKeysPage.tsx | 5 ++--- .../src/ui/components/UserProfile/ApiKeysPage.tsx | 5 ++--- packages/localizations/src/en-US.ts | 6 ++++++ packages/types/src/localization.ts | 6 ++++++ 6 files changed, 25 insertions(+), 12 deletions(-) rename packages/clerk-js/src/ui/components/ApiKeys/{shared.ts => useApiKeys.ts} (97%) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index 2fd3208d782..ca2e70124cf 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -7,9 +7,9 @@ import { Action } from '../../elements/Action'; import { MagnifyingGlass } from '../../icons'; import { ApiKeysTable } from './ApiKeysTable'; import { CreateApiKeyForm } from './CreateApiKeyForm'; -import { useApiKeys } from './shared'; +import { useApiKeys } from './useApiKeys'; -export const ApiKeysInternal = ({ subject }: { subject: string }) => { +export const ApiKeysInternal = ({ subject, perPage }: { subject: string; perPage?: number }) => { const { apiKeys, isLoading, @@ -23,7 +23,7 @@ export const ApiKeysInternal = ({ subject }: { subject: string }) => { startingRow, endingRow, handleCreate, - } = useApiKeys({ subject }); + } = useApiKeys({ subject, perPage }); const { t } = useLocalizations(); return ( @@ -86,7 +86,10 @@ export const ApiKeys = withCardStateProvider(() => { - + diff --git a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts b/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts similarity index 97% rename from packages/clerk-js/src/ui/components/ApiKeys/shared.ts rename to packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts index ab36330af46..6160bfdf70d 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/shared.ts +++ b/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts @@ -23,7 +23,7 @@ function getTimeLeftInSeconds(expirationOption: Expiration) { return diffInSecs; } -export function useApiKeys({ subject, perPage = 5 }: { subject: string; perPage?: number }) { +export const useApiKeys = ({ subject, perPage = 5 }: { subject: string; perPage?: number }) => { const clerk = useClerk(); const { data: apiKeys, @@ -76,7 +76,7 @@ export function useApiKeys({ subject, perPage = 5 }: { subject: string; perPage? endingRow, handleCreate, }; -} +}; export const testFakeData = [ { diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx index 790e065cb5d..0b95db159cf 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationApiKeysPage.tsx @@ -1,7 +1,7 @@ import { useOrganization } from '@clerk/shared/react'; import { ApiKeysContext } from '../../contexts'; -import { Col } from '../../customizables'; +import { Col, localizationKeys } from '../../customizables'; import { Header } from '../../elements'; import { ApiKeysInternal } from '../ApiKeys'; @@ -12,8 +12,7 @@ export const OrganizationApiKeysPage = () => { diff --git a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx index 9c546430613..e4b421afef5 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/ApiKeysPage.tsx @@ -1,7 +1,7 @@ import { useUser } from '@clerk/shared/react'; import { ApiKeysContext } from '../../contexts'; -import { Col } from '../../customizables'; +import { Col, localizationKeys } from '../../customizables'; import { Header } from '../../elements'; import { ApiKeysInternal } from '../ApiKeys'; @@ -12,8 +12,7 @@ export const ApiKeysPage = () => { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 4346a76b840..e169fad350a 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -256,6 +256,9 @@ export const enUS: LocalizationResource = { headerTitle__requests: 'Requests', }, }, + apiKeysPage: { + title: 'API Keys', + }, navbar: { billing: 'Billing', description: 'Manage your organization.', @@ -854,6 +857,9 @@ export const enUS: LocalizationResource = { title: 'Add email address', verifyTitle: 'Verify email address', }, + apiKeysPage: { + title: 'API Keys', + }, formButtonPrimary__add: 'Add', formButtonPrimary__continue: 'Continue', formButtonPrimary__finish: 'Finish', diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 5ae79e6b12b..7e14b8b115d 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -582,6 +582,9 @@ type _LocalizationResource = { successMessage: LocalizationValue; }; }; + apiKeysPage: { + title: LocalizationValue; + }; passkeyScreen: { title__rename: LocalizationValue; subtitle__rename: LocalizationValue; @@ -944,6 +947,9 @@ type _LocalizationResource = { noPermissionsToManageBilling: LocalizationValue; }; }; + apiKeysPage: { + title: LocalizationValue; + }; }; createOrganization: { title: LocalizationValue; From 12a317f00496c64ff600dd032eff8b8f7b147c81 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 15 May 2025 12:43:33 -0700 Subject: [PATCH 33/38] chore: switch to swr for fetching --- .../src/ui/components/ApiKeys/ApiKeysTable.tsx | 13 +++---------- .../src/ui/components/ApiKeys/useApiKeys.ts | 10 +++++----- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx index 91ab0947629..5c99419f6b6 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeysTable.tsx @@ -1,6 +1,7 @@ import { useClerk } from '@clerk/shared/react'; import type { ApiKeyResource } from '@clerk/types'; import { useEffect, useState } from 'react'; +import useSWR from 'swr'; import { Button, @@ -19,21 +20,13 @@ import { Tr, } from '../../customizables'; import { ThreeDotsMenu } from '../../elements'; -import { useClipboard, useFetch } from '../../hooks'; +import { useClipboard } from '../../hooks'; import { Check, Copy, Eye, EyeSlash } from '../../icons'; const useApiKeySecret = ({ apiKeyID, enabled }: { apiKeyID: string; enabled: boolean }) => { const clerk = useClerk(); - const getSecret = async (apiKeyID?: string) => { - if (!apiKeyID) { - return ''; - } - const secret = await clerk.getApiKeySecret(apiKeyID); - return secret; - }; - - return useFetch(getSecret, enabled ? apiKeyID : undefined); + return useSWR(enabled ? ['api-key-secret', apiKeyID] : null, ([_, id]) => clerk.getApiKeySecret(id)); }; const CopySecretButton = ({ apiKeyID }: { apiKeyID: string }) => { diff --git a/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts b/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts index 6160bfdf70d..99618e37f91 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts +++ b/packages/clerk-js/src/ui/components/ApiKeys/useApiKeys.ts @@ -1,8 +1,8 @@ import { useClerk } from '@clerk/shared/react'; import { useState } from 'react'; +import useSWR from 'swr'; import { unixEpochToDate } from '../../../utils/date'; -import { useFetch } from '../../hooks'; import type { Expiration } from './CreateApiKeyForm'; function getTimeLeftInSeconds(expirationOption: Expiration) { @@ -28,8 +28,8 @@ export const useApiKeys = ({ subject, perPage = 5 }: { subject: string; perPage? const { data: apiKeys, isLoading, - revalidate, - } = useFetch(clerk.getApiKeys, { subject }, undefined, `api-key-source-${subject}`); + mutate, + } = useSWR(['api-keys', subject], ([_, userIdOrOrgId]) => clerk.getApiKeys({ subject: userIdOrOrgId })); const [page, setPage] = useState(1); const [search, setSearch] = useState(''); const itemsPerPage = perPage; @@ -53,13 +53,13 @@ export const useApiKeys = ({ subject, perPage = 5 }: { subject: string; perPage? secondsUntilExpiration: getTimeLeftInSeconds(params.expiration), }); params.closeFn(); - revalidate(); + void mutate(); }; const revokeApiKey = async (apiKeyID: string) => { await clerk.revokeApiKey({ apiKeyID, revocationReason: 'Revoked by user' }); setPage(1); - revalidate(); + void mutate(); }; return { From 84ba52db5035c9cea279523f041e6aa003b71dcf Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Thu, 15 May 2025 14:52:42 -0700 Subject: [PATCH 34/38] chore: clean up fetchers and mutations --- .../src/ui/components/ApiKeys/ApiKeys.tsx | 54 ++++++- .../components/ApiKeys/CreateApiKeyForm.tsx | 24 +-- .../src/ui/components/ApiKeys/useApiKeys.ts | 145 +++++------------- packages/localizations/src/en-US.ts | 2 +- 4 files changed, 100 insertions(+), 125 deletions(-) diff --git a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx index ca2e70124cf..4ae25c23966 100644 --- a/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx +++ b/packages/clerk-js/src/ui/components/ApiKeys/ApiKeys.tsx @@ -1,4 +1,7 @@ -import { useOrganization, useUser } from '@clerk/shared/react'; +import { isClerkAPIResponseError } from '@clerk/shared/error'; +import { useClerk, useOrganization, useUser } from '@clerk/shared/react'; +import type { CreateApiKeyParams } from '@clerk/types'; +import useSWRMutation from 'swr/mutation'; import { useApiKeysContext } from '../../contexts'; import { Box, Button, Col, Flex, Flow, Icon, localizationKeys, useLocalizations } from '../../customizables'; @@ -6,14 +9,14 @@ import { Card, InputWithIcon, Pagination, withCardStateProvider } from '../../el import { Action } from '../../elements/Action'; import { MagnifyingGlass } from '../../icons'; import { ApiKeysTable } from './ApiKeysTable'; +import type { OnCreateParams } from './CreateApiKeyForm'; import { CreateApiKeyForm } from './CreateApiKeyForm'; -import { useApiKeys } from './useApiKeys'; +import { getTimeLeftInSeconds, useApiKeys } from './useApiKeys'; export const ApiKeysInternal = ({ subject, perPage }: { subject: string; perPage?: number }) => { const { apiKeys, isLoading, - revokeApiKey, search, setSearch, page, @@ -22,9 +25,40 @@ export const ApiKeysInternal = ({ subject, perPage }: { subject: string; perPage itemCount, startingRow, endingRow, - handleCreate, + mutate: mutateApiKeys, + cacheKey, } = useApiKeys({ subject, perPage }); + const { trigger: createApiKey, isMutating } = useSWRMutation( + cacheKey, + (_, { arg }: { arg: CreateApiKeyParams }) => clerk.createApiKey(arg), + { + throwOnError: false, + onError(err) { + if (isClerkAPIResponseError(err)) { + console.log(err.errors); + console.log(err.message); + console.log(err.name); + } + }, + }, + ); const { t } = useLocalizations(); + const clerk = useClerk(); + + const handleRevokeApiKey = async (id: string) => { + await clerk.revokeApiKey({ apiKeyID: id }); + void mutateApiKeys(); + setPage(1); + }; + + const handleCreateApiKey = async (params: OnCreateParams, closeCardFn: () => void) => { + await createApiKey({ + name: params.name, + creationReason: params.description, + secondsUntilExpiration: getTimeLeftInSeconds(params.expiration), + }); + closeCardFn(); + }; return ( @@ -44,7 +78,10 @@ export const ApiKeysInternal = ({ subject, perPage }: { subject: string; perPage }} /> - + @@ -153,7 +155,7 @@ export const ApiKeysTable = ({ onRevoke(apiKey.id), }, @@ -167,3 +169,20 @@ export const ApiKeysTable = ({
    NameLast usedLast used KeyActionsActions
    ); }; + +const EmptyRow = () => { + return ( +
    + +