From 7d5f0c952ac3015a438d60e474463e5b97b29b73 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 15:45:48 -0700 Subject: [PATCH 01/13] Add methods to manage enterprise connection --- packages/clerk-js/src/core/resources/User.ts | 50 +---- .../src/core/resources/__tests__/User.test.ts | 195 ------------------ .../src/utils/meEnterpriseConnectionBody.ts | 72 +++++++ packages/shared/src/types/user.ts | 16 +- 4 files changed, 83 insertions(+), 250 deletions(-) create mode 100644 packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index af80f6704bb..5b53f23f6c4 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -21,8 +21,9 @@ import type { EnterpriseConnectionTestRunsPaginatedJSON, ExternalAccountJSON, ExternalAccountResource, + CreateMeEnterpriseConnectionParams, GetEnterpriseConnectionsParams, - GetEnterpriseConnectionTestRunsParams, + UpdateMeEnterpriseConnectionParams, GetOrganizationMemberships, GetUserOrganizationInvitationsParams, GetUserOrganizationSuggestionsParams, @@ -47,6 +48,10 @@ import { deepCamelToSnake } from '@clerk/shared/underscore'; import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams'; import { unixEpochToDate } from '../../utils/date'; +import { + buildCreateMeEnterpriseConnectionBody, + buildUpdateMeEnterpriseConnectionBody, +} from '../../utils/meEnterpriseConnectionBody'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing'; @@ -335,7 +340,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections`, method: 'POST', - body: toMeEnterpriseConnectionBody(params) as any, + body: buildCreateMeEnterpriseConnectionBody(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -350,7 +355,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, method: 'PATCH', - body: toMeEnterpriseConnectionBody(params) as any, + body: buildUpdateMeEnterpriseConnectionBody(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -368,45 +373,6 @@ export class User extends BaseResource implements UserResource { return new DeletedObject(json); }; - createEnterpriseConnectionTestRun = async ( - enterpriseConnectionId: string, - ): Promise => { - const json = ( - await BaseResource._fetch({ - path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, - method: 'POST', - }) - )?.response as unknown as EnterpriseConnectionTestRunInitJSON; - - return { url: json.url }; - }; - - getEnterpriseConnectionTestRuns = async ( - enterpriseConnectionId: string, - params?: GetEnterpriseConnectionTestRunsParams, - ): Promise> => { - const { status, ...rest } = params || {}; - const search = convertPageToOffsetSearchParams(rest); - if (status?.length) { - for (const s of status) { - search.append('status', s); - } - } - - const res = await BaseResource._fetch({ - path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, - method: 'GET', - search, - }); - - const payload = res?.response as unknown as EnterpriseConnectionTestRunsPaginatedJSON | undefined; - - return { - total_count: payload?.total_count ?? 0, - data: (payload?.data ?? []).map((row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row)), - }; - }; - initializePaymentMethod: typeof initializePaymentMethod = params => { return initializePaymentMethod(params); }; diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 0dad85bc27e..23f50e38276 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -240,135 +240,6 @@ describe('User', () => { }); }); - it('preserves `saml.attributeMapping` and `saml.customAttributes` keys when creating an enterprise connection', async () => { - BaseResource._fetch = vi.fn().mockReturnValue( - Promise.resolve({ - response: { - id: 'ec_new', - object: 'enterprise_connection' as const, - name: 'New SSO', - active: true, - provider: 'saml_okta', - logo_public_url: null, - domains: [], - organization_id: null, - sync_user_attributes: true, - disable_additional_identifications: false, - allow_organization_account_linking: false, - custom_attributes: [], - oauth_config: null, - saml_connection: null, - created_at: 1, - updated_at: 1, - }, - }), - ); - - const user = new User({ - email_addresses: [], - phone_numbers: [], - web3_wallets: [], - external_accounts: [], - } as unknown as UserJSON); - - await user.createEnterpriseConnection({ - provider: 'saml_okta', - name: 'New SSO', - saml: { - idpEntityId: 'https://idp.example.com', - attributeMapping: { - emailAddress: 'mail', - firstName: 'givenName', - 'custom:role': 'role', - }, - }, - }); - - // @ts-ignore - expect(BaseResource._fetch).toHaveBeenCalledWith({ - method: 'POST', - path: '/me/enterprise_connections', - body: { - provider: 'saml_okta', - name: 'New SSO', - saml: { - idp_entity_id: 'https://idp.example.com', - attribute_mapping: { - emailAddress: 'mail', - firstName: 'givenName', - 'custom:role': 'role', - }, - }, - }, - }); - }); - - it('preserves `customAttributes` and `saml.attributeMapping` keys when updating an enterprise connection', async () => { - // @ts-ignore - BaseResource._fetch = vi.fn().mockReturnValue( - Promise.resolve({ - response: { - id: 'ec_123', - object: 'enterprise_connection' as const, - name: 'Updated', - active: true, - provider: 'saml_okta', - logo_public_url: null, - domains: [], - organization_id: null, - sync_user_attributes: true, - disable_additional_identifications: false, - allow_organization_account_linking: false, - custom_attributes: [], - oauth_config: null, - saml_connection: null, - created_at: 1, - updated_at: 2, - }, - }), - ); - - const user = new User({ - email_addresses: [], - phone_numbers: [], - web3_wallets: [], - external_accounts: [], - } as unknown as UserJSON); - - await user.updateEnterpriseConnection('ec_123', { - customAttributes: { - MyClaim: 'x', - CustomValue: 'y', - nestedCamelKey: { innerCamelKey: 'z' }, - }, - saml: { - attributeMapping: { - emailAddress: 'mail', - firstName: 'givenName', - }, - }, - }); - - // @ts-ignore - expect(BaseResource._fetch).toHaveBeenCalledWith({ - method: 'PATCH', - path: '/me/enterprise_connections/ec_123', - body: { - custom_attributes: { - MyClaim: 'x', - CustomValue: 'y', - nestedCamelKey: { innerCamelKey: 'z' }, - }, - saml: { - attribute_mapping: { - emailAddress: 'mail', - firstName: 'givenName', - }, - }, - }, - }); - }); - it('deletes an enterprise connection', async () => { const deletedJSON = { object: 'enterprise_connection', @@ -398,72 +269,6 @@ describe('User', () => { expect(result.deleted).toBe(true); }); - it('creates an enterprise connection test run', async () => { - // @ts-ignore - BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: { url: 'https://example.com/test' } })); - - const user = new User({ - email_addresses: [], - phone_numbers: [], - web3_wallets: [], - external_accounts: [], - } as unknown as UserJSON); - - const init = await user.createEnterpriseConnectionTestRun('ec_123'); - - // @ts-ignore - expect(BaseResource._fetch).toHaveBeenCalledWith({ - method: 'POST', - path: '/me/enterprise_connections/ec_123/test_runs', - }); - - expect(init.url).toBe('https://example.com/test'); - }); - - it('lists enterprise connection test runs', async () => { - const paginated = { - data: [ - { - object: 'enterprise_connection_test_run' as const, - id: 'run_1', - status: 'success', - connection_type: 'saml' as const, - created_at: 1700000000000, - }, - ], - total_count: 1, - }; - - // @ts-ignore - BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: paginated })); - - const user = new User({ - email_addresses: [], - phone_numbers: [], - web3_wallets: [], - external_accounts: [], - } as unknown as UserJSON); - - const result = await user.getEnterpriseConnectionTestRuns('ec_123', { - initialPage: 1, - pageSize: 10, - status: ['pending', 'success'], - }); - - // @ts-ignore - const call = BaseResource._fetch.mock.calls[0][0]; - expect(call.method).toBe('GET'); - expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs'); - expect(call.search.get('limit')).toBe('10'); - expect(call.search.get('offset')).toBe('0'); - expect(call.search.getAll('status')).toEqual(['pending', 'success']); - - expect(result.total_count).toBe(1); - expect(result.data).toHaveLength(1); - expect(result.data[0].id).toBe('run_1'); - expect(result.data[0].connectionType).toBe('saml'); - }); - it('creates a web3 wallet', async () => { const targetWeb3Wallet = '0x0000000000000000000000000000000000000000'; const web3WalletJSON = { diff --git a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts new file mode 100644 index 00000000000..7b5c13fc417 --- /dev/null +++ b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts @@ -0,0 +1,72 @@ +import type { + CreateMeEnterpriseConnectionParams, + MeEnterpriseConnectionOidcInput, + MeEnterpriseConnectionSamlInput, + UpdateMeEnterpriseConnectionParams, +} from '@clerk/shared/types'; + +function samlToJson(saml: MeEnterpriseConnectionSamlInput): Record { + const body: Record = {}; + if (saml.idpEntityId !== undefined) body.idp_entity_id = saml.idpEntityId; + if (saml.idpSsoUrl !== undefined) body.idp_sso_url = saml.idpSsoUrl; + if (saml.idpCertificate !== undefined) body.idp_certificate = saml.idpCertificate; + if (saml.idpMetadataUrl !== undefined) body.idp_metadata_url = saml.idpMetadataUrl; + if (saml.idpMetadata !== undefined) body.idp_metadata = saml.idpMetadata; + if (saml.attributeMapping !== undefined) body.attribute_mapping = saml.attributeMapping; + if (saml.allowSubdomains !== undefined) body.allow_subdomains = saml.allowSubdomains; + if (saml.allowIdpInitiated !== undefined) body.allow_idp_initiated = saml.allowIdpInitiated; + if (saml.forceAuthn !== undefined) body.force_authn = saml.forceAuthn; + return body; +} + +function oidcToJson(oidc: MeEnterpriseConnectionOidcInput): Record { + const body: Record = {}; + if (oidc.clientId !== undefined) body.client_id = oidc.clientId; + if (oidc.clientSecret !== undefined) body.client_secret = oidc.clientSecret; + if (oidc.discoveryUrl !== undefined) body.discovery_url = oidc.discoveryUrl; + if (oidc.authUrl !== undefined) body.auth_url = oidc.authUrl; + if (oidc.tokenUrl !== undefined) body.token_url = oidc.tokenUrl; + if (oidc.userInfoUrl !== undefined) body.user_info_url = oidc.userInfoUrl; + if (oidc.requiresPkce !== undefined) body.requires_pkce = oidc.requiresPkce; + return body; +} + +export function buildCreateMeEnterpriseConnectionBody( + params: CreateMeEnterpriseConnectionParams, +): Record { + const body: Record = { + provider: params.provider, + name: params.name, + }; + if (params.organizationId !== undefined) { + body.organization_id = params.organizationId; + } + if (params.saml !== undefined) { + body.saml = params.saml === null ? null : samlToJson(params.saml); + } + if (params.oidc !== undefined) { + body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); + } + return body; +} + +export function buildUpdateMeEnterpriseConnectionBody( + params: UpdateMeEnterpriseConnectionParams, +): Record { + const body: Record = {}; + if (params.name !== undefined) body.name = params.name; + if (params.active !== undefined) body.active = params.active; + if (params.syncUserAttributes !== undefined) body.sync_user_attributes = params.syncUserAttributes; + if (params.disableAdditionalIdentifications !== undefined) { + body.disable_additional_identifications = params.disableAdditionalIdentifications; + } + if (params.organizationId !== undefined) body.organization_id = params.organizationId; + if (params.customAttributes !== undefined) body.custom_attributes = params.customAttributes; + if (params.saml !== undefined) { + body.saml = params.saml === null ? null : samlToJson(params.saml); + } + if (params.oidc !== undefined) { + body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); + } + return body; +} diff --git a/packages/shared/src/types/user.ts b/packages/shared/src/types/user.ts index 43e5aa0a492..4ea4c35be39 100644 --- a/packages/shared/src/types/user.ts +++ b/packages/shared/src/types/user.ts @@ -8,11 +8,6 @@ import type { EnterpriseConnectionResource, UpdateMeEnterpriseConnectionParams, } from './enterpriseConnection'; -import type { - EnterpriseConnectionTestRunInitResource, - EnterpriseConnectionTestRunResource, - GetEnterpriseConnectionTestRunsParams, -} from './enterpriseConnectionTestRun'; import type { ExternalAccountResource } from './externalAccount'; import type { ImageResource } from './image'; import type { UserJSON } from './json'; @@ -129,19 +124,14 @@ export interface UserResource extends ClerkResource, BillingPayerMethods { getOrganizationCreationDefaults: () => Promise; leaveOrganization: (organizationId: string) => Promise; getEnterpriseConnections: (params?: GetEnterpriseConnectionsParams) => Promise; - createEnterpriseConnection: (params: CreateMeEnterpriseConnectionParams) => Promise; + createEnterpriseConnection: ( + params: CreateMeEnterpriseConnectionParams, + ) => Promise; updateEnterpriseConnection: ( enterpriseConnectionId: string, params: UpdateMeEnterpriseConnectionParams, ) => Promise; deleteEnterpriseConnection: (enterpriseConnectionId: string) => Promise; - createEnterpriseConnectionTestRun: ( - enterpriseConnectionId: string, - ) => Promise; - getEnterpriseConnectionTestRuns: ( - enterpriseConnectionId: string, - params?: GetEnterpriseConnectionTestRunsParams, - ) => Promise>; createTOTP: () => Promise; verifyTOTP: (params: VerifyTOTPParams) => Promise; disableTOTP: () => Promise; From 9c6d43fc07fc910a30d2bfd2e6fa311411d0859e Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 15:55:17 -0700 Subject: [PATCH 02/13] Add enterprise connection test run resource --- .../resources/EnterpriseConnectionTestRun.ts | 14 +++- packages/clerk-js/src/core/resources/User.ts | 58 +++++++++++++-- .../src/core/resources/__tests__/User.test.ts | 67 +++++++++++++++++ .../src/utils/meEnterpriseConnectionBody.ts | 72 ------------------- packages/shared/src/types/user.ts | 16 ++++- 5 files changed, 143 insertions(+), 84 deletions(-) delete mode 100644 packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts diff --git a/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts index 94713414487..b930dc3dae5 100644 --- a/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts +++ b/packages/clerk-js/src/core/resources/EnterpriseConnectionTestRun.ts @@ -14,9 +14,11 @@ import type { import { unixEpochToDate } from '../../utils/date'; import { clerkUnsupportedReloadMethod } from '../errors'; +import { BaseResource } from './Base'; -export class EnterpriseConnectionTestRun implements EnterpriseConnectionTestRunResource { +export class EnterpriseConnectionTestRun extends BaseResource implements EnterpriseConnectionTestRunResource { pathRoot = '/me'; + private enterpriseConnectionId = ''; id!: string; status!: string; @@ -27,15 +29,21 @@ export class EnterpriseConnectionTestRun implements EnterpriseConnectionTestRunR oauth: EnterpriseConnectionTestRunOauthPayloadResource | null = null; createdAt: Date | null = null; - constructor(data: EnterpriseConnectionTestRunJSON) { + constructor(data: EnterpriseConnectionTestRunJSON, enterpriseConnectionId: string) { + super(); + this.enterpriseConnectionId = enterpriseConnectionId; this.fromJSON(data); } + protected path(): string { + return `${this.pathRoot}/enterprise_connections/${this.enterpriseConnectionId}/test_runs/${this.id}`; + } + reload(_?: ClerkResourceReloadParams): Promise { clerkUnsupportedReloadMethod('EnterpriseConnectionTestRun'); } - private fromJSON(data: EnterpriseConnectionTestRunJSON | null): this { + protected fromJSON(data: EnterpriseConnectionTestRunJSON | null): this { if (!data) { return this; } diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 5b53f23f6c4..6054f58db90 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -1,4 +1,5 @@ import { getFullName } from '@clerk/shared/internal/clerk-js/user'; +import { deepCamelToSnake } from '@clerk/shared/underscore'; import type { BackupCodeJSON, BackupCodeResource, @@ -21,7 +22,14 @@ import type { EnterpriseConnectionTestRunsPaginatedJSON, ExternalAccountJSON, ExternalAccountResource, + ClerkPaginatedResponse, CreateMeEnterpriseConnectionParams, + EnterpriseConnectionTestRunInitJSON, + EnterpriseConnectionTestRunJSON, + EnterpriseConnectionTestRunsPaginatedJSON, + EnterpriseConnectionTestRunInitResource, + EnterpriseConnectionTestRunResource, + GetEnterpriseConnectionTestRunsParams, GetEnterpriseConnectionsParams, UpdateMeEnterpriseConnectionParams, GetOrganizationMemberships, @@ -48,10 +56,7 @@ import { deepCamelToSnake } from '@clerk/shared/underscore'; import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams'; import { unixEpochToDate } from '../../utils/date'; -import { - buildCreateMeEnterpriseConnectionBody, - buildUpdateMeEnterpriseConnectionBody, -} from '../../utils/meEnterpriseConnectionBody'; +import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffsetSearchParams'; import { normalizeUnsafeMetadata } from '../../utils/resourceParams'; import { eventBus, events } from '../events'; import { addPaymentMethod, getPaymentMethods, initializePaymentMethod } from '../modules/billing'; @@ -340,7 +345,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections`, method: 'POST', - body: buildCreateMeEnterpriseConnectionBody(params) as any, + body: deepCamelToSnake(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -355,7 +360,7 @@ export class User extends BaseResource implements UserResource { await BaseResource._fetch({ path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}`, method: 'PATCH', - body: buildUpdateMeEnterpriseConnectionBody(params) as any, + body: deepCamelToSnake(params) as any, }) )?.response as unknown as EnterpriseConnectionJSON; @@ -373,6 +378,47 @@ export class User extends BaseResource implements UserResource { return new DeletedObject(json); }; + createEnterpriseConnectionTestRun = async ( + enterpriseConnectionId: string, + ): Promise => { + const json = ( + await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, + method: 'POST', + }) + )?.response as unknown as EnterpriseConnectionTestRunInitJSON; + + return { url: json.url }; + }; + + getEnterpriseConnectionTestRuns = async ( + enterpriseConnectionId: string, + params?: GetEnterpriseConnectionTestRunsParams, + ): Promise> => { + const { status, ...rest } = params || {}; + const search = convertPageToOffsetSearchParams({ ...rest, paginated: true }); + if (status?.length) { + for (const s of status) { + search.append('status', s); + } + } + + const res = await BaseResource._fetch({ + path: `${this.path()}/enterprise_connections/${enterpriseConnectionId}/test_runs`, + method: 'GET', + search, + }); + + const payload = res?.response as unknown as EnterpriseConnectionTestRunsPaginatedJSON | undefined; + + return { + total_count: payload?.total_count ?? 0, + data: (payload?.data ?? []).map( + (row: EnterpriseConnectionTestRunJSON) => new EnterpriseConnectionTestRun(row, enterpriseConnectionId), + ), + }; + }; + initializePaymentMethod: typeof initializePaymentMethod = params => { return initializePaymentMethod(params); }; diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 23f50e38276..7665e5165e3 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -269,6 +269,73 @@ describe('User', () => { expect(result.deleted).toBe(true); }); + it('creates an enterprise connection test run', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: { url: 'https://example.com/test' } })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const init = await user.createEnterpriseConnectionTestRun('ec_123'); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'POST', + path: '/me/enterprise_connections/ec_123/test_runs', + }); + + expect(init.url).toBe('https://example.com/test'); + }); + + it('lists enterprise connection test runs', async () => { + const paginated = { + data: [ + { + object: 'enterprise_connection_test_run' as const, + id: 'run_1', + status: 'success', + connection_type: 'saml' as const, + created_at: 1700000000000, + }, + ], + total_count: 1, + }; + + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: paginated })); + + const user = new User({ + email_addresses: [], + phone_numbers: [], + web3_wallets: [], + external_accounts: [], + } as unknown as UserJSON); + + const result = await user.getEnterpriseConnectionTestRuns('ec_123', { + initialPage: 1, + pageSize: 10, + status: ['pending', 'success'], + }); + + // @ts-ignore + const call = BaseResource._fetch.mock.calls[0][0]; + expect(call.method).toBe('GET'); + expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs'); + expect(call.search.get('limit')).toBe('10'); + expect(call.search.get('offset')).toBe('0'); + expect(call.search.get('paginated')).toBe('true'); + expect(call.search.getAll('status')).toEqual(['pending', 'success']); + + expect(result.total_count).toBe(1); + expect(result.data).toHaveLength(1); + expect(result.data[0].id).toBe('run_1'); + expect(result.data[0].connectionType).toBe('saml'); + }); + it('creates a web3 wallet', async () => { const targetWeb3Wallet = '0x0000000000000000000000000000000000000000'; const web3WalletJSON = { diff --git a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts b/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts deleted file mode 100644 index 7b5c13fc417..00000000000 --- a/packages/clerk-js/src/utils/meEnterpriseConnectionBody.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { - CreateMeEnterpriseConnectionParams, - MeEnterpriseConnectionOidcInput, - MeEnterpriseConnectionSamlInput, - UpdateMeEnterpriseConnectionParams, -} from '@clerk/shared/types'; - -function samlToJson(saml: MeEnterpriseConnectionSamlInput): Record { - const body: Record = {}; - if (saml.idpEntityId !== undefined) body.idp_entity_id = saml.idpEntityId; - if (saml.idpSsoUrl !== undefined) body.idp_sso_url = saml.idpSsoUrl; - if (saml.idpCertificate !== undefined) body.idp_certificate = saml.idpCertificate; - if (saml.idpMetadataUrl !== undefined) body.idp_metadata_url = saml.idpMetadataUrl; - if (saml.idpMetadata !== undefined) body.idp_metadata = saml.idpMetadata; - if (saml.attributeMapping !== undefined) body.attribute_mapping = saml.attributeMapping; - if (saml.allowSubdomains !== undefined) body.allow_subdomains = saml.allowSubdomains; - if (saml.allowIdpInitiated !== undefined) body.allow_idp_initiated = saml.allowIdpInitiated; - if (saml.forceAuthn !== undefined) body.force_authn = saml.forceAuthn; - return body; -} - -function oidcToJson(oidc: MeEnterpriseConnectionOidcInput): Record { - const body: Record = {}; - if (oidc.clientId !== undefined) body.client_id = oidc.clientId; - if (oidc.clientSecret !== undefined) body.client_secret = oidc.clientSecret; - if (oidc.discoveryUrl !== undefined) body.discovery_url = oidc.discoveryUrl; - if (oidc.authUrl !== undefined) body.auth_url = oidc.authUrl; - if (oidc.tokenUrl !== undefined) body.token_url = oidc.tokenUrl; - if (oidc.userInfoUrl !== undefined) body.user_info_url = oidc.userInfoUrl; - if (oidc.requiresPkce !== undefined) body.requires_pkce = oidc.requiresPkce; - return body; -} - -export function buildCreateMeEnterpriseConnectionBody( - params: CreateMeEnterpriseConnectionParams, -): Record { - const body: Record = { - provider: params.provider, - name: params.name, - }; - if (params.organizationId !== undefined) { - body.organization_id = params.organizationId; - } - if (params.saml !== undefined) { - body.saml = params.saml === null ? null : samlToJson(params.saml); - } - if (params.oidc !== undefined) { - body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); - } - return body; -} - -export function buildUpdateMeEnterpriseConnectionBody( - params: UpdateMeEnterpriseConnectionParams, -): Record { - const body: Record = {}; - if (params.name !== undefined) body.name = params.name; - if (params.active !== undefined) body.active = params.active; - if (params.syncUserAttributes !== undefined) body.sync_user_attributes = params.syncUserAttributes; - if (params.disableAdditionalIdentifications !== undefined) { - body.disable_additional_identifications = params.disableAdditionalIdentifications; - } - if (params.organizationId !== undefined) body.organization_id = params.organizationId; - if (params.customAttributes !== undefined) body.custom_attributes = params.customAttributes; - if (params.saml !== undefined) { - body.saml = params.saml === null ? null : samlToJson(params.saml); - } - if (params.oidc !== undefined) { - body.oidc = params.oidc === null ? null : oidcToJson(params.oidc); - } - return body; -} diff --git a/packages/shared/src/types/user.ts b/packages/shared/src/types/user.ts index 4ea4c35be39..43e5aa0a492 100644 --- a/packages/shared/src/types/user.ts +++ b/packages/shared/src/types/user.ts @@ -8,6 +8,11 @@ import type { EnterpriseConnectionResource, UpdateMeEnterpriseConnectionParams, } from './enterpriseConnection'; +import type { + EnterpriseConnectionTestRunInitResource, + EnterpriseConnectionTestRunResource, + GetEnterpriseConnectionTestRunsParams, +} from './enterpriseConnectionTestRun'; import type { ExternalAccountResource } from './externalAccount'; import type { ImageResource } from './image'; import type { UserJSON } from './json'; @@ -124,14 +129,19 @@ export interface UserResource extends ClerkResource, BillingPayerMethods { getOrganizationCreationDefaults: () => Promise; leaveOrganization: (organizationId: string) => Promise; getEnterpriseConnections: (params?: GetEnterpriseConnectionsParams) => Promise; - createEnterpriseConnection: ( - params: CreateMeEnterpriseConnectionParams, - ) => Promise; + createEnterpriseConnection: (params: CreateMeEnterpriseConnectionParams) => Promise; updateEnterpriseConnection: ( enterpriseConnectionId: string, params: UpdateMeEnterpriseConnectionParams, ) => Promise; deleteEnterpriseConnection: (enterpriseConnectionId: string) => Promise; + createEnterpriseConnectionTestRun: ( + enterpriseConnectionId: string, + ) => Promise; + getEnterpriseConnectionTestRuns: ( + enterpriseConnectionId: string, + params?: GetEnterpriseConnectionTestRunsParams, + ) => Promise>; createTOTP: () => Promise; verifyTOTP: (params: VerifyTOTPParams) => Promise; disableTOTP: () => Promise; From 1a213876300f13bf53301dc1b7133194ffdfebea Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 16:47:16 -0700 Subject: [PATCH 03/13] Update `useUserEnterpriseConnections` to have mutation methods --- packages/clerk-js/src/core/resources/__tests__/User.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/__tests__/User.test.ts b/packages/clerk-js/src/core/resources/__tests__/User.test.ts index 7665e5165e3..ba2c814cc68 100644 --- a/packages/clerk-js/src/core/resources/__tests__/User.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/User.test.ts @@ -327,7 +327,6 @@ describe('User', () => { expect(call.path).toBe('/me/enterprise_connections/ec_123/test_runs'); expect(call.search.get('limit')).toBe('10'); expect(call.search.get('offset')).toBe('0'); - expect(call.search.get('paginated')).toBe('true'); expect(call.search.getAll('status')).toEqual(['pending', 'success']); expect(result.total_count).toBe(1); From 82b701903eb0a6c6e70452245393a45fd4bd2d79 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 22:07:07 -0700 Subject: [PATCH 04/13] Add basic infra for component across packages --- packages/astro/src/astro-components/index.ts | 1 + .../interactive/ConfigureSSO.astro | 11 +++++++ .../mount-clerk-astro-js-components.ts | 2 +- packages/astro/src/types.ts | 2 +- .../__snapshots__/exports.test.ts.snap | 1 + .../chrome-extension/src/react/re-exports.ts | 1 + packages/clerk-js/sandbox/app.ts | 5 +++ packages/clerk-js/sandbox/template.html | 8 +++++ packages/clerk-js/src/core/clerk.ts | 33 +++++++++++++++++++ .../src/client-boundary/uiComponents.tsx | 1 + packages/nextjs/src/index.ts | 1 + packages/nuxt/src/module.ts | 2 ++ .../__snapshots__/exports.test.ts.snap | 1 + packages/react/src/components/index.ts | 1 + .../react/src/components/uiComponents.tsx | 32 ++++++++++++++++++ packages/react/src/isomorphicClerk.ts | 22 +++++++++++++ packages/shared/src/types/clerk.ts | 25 ++++++++++++++ .../__snapshots__/exports.test.ts.snap | 1 + .../components/ConfigureSSO/ConfigureSSO.tsx | 3 ++ .../src/contexts/ClerkUIComponentsContext.tsx | 8 +++++ .../src/contexts/components/ConfigureSSO.ts | 20 +++++++++++ packages/ui/src/contexts/components/index.ts | 1 + packages/ui/src/internal/appearance.ts | 5 +++ packages/ui/src/lazyModules/components.ts | 6 ++++ packages/ui/src/types.ts | 10 +++++- packages/vue/src/components/index.ts | 2 +- .../components/ui-components/ConfigureSSO.vue | 17 ++++++++++ 27 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 packages/astro/src/astro-components/interactive/ConfigureSSO.astro create mode 100644 packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx create mode 100644 packages/ui/src/contexts/components/ConfigureSSO.ts create mode 100644 packages/vue/src/components/ui-components/ConfigureSSO.vue diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 0f02bca09ff..0d50e3a1e92 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -31,3 +31,4 @@ export { default as Waitlist } from './interactive/Waitlist.astro'; export { default as OAuthConsent } from './interactive/OAuthConsent.astro'; export { default as PricingTable } from './interactive/PricingTable.astro'; export { default as APIKeys } from './interactive/APIKeys.astro'; +export { default as ConfigureSSO } from './interactive/ConfigureSSO.astro'; diff --git a/packages/astro/src/astro-components/interactive/ConfigureSSO.astro b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro new file mode 100644 index 00000000000..c1f00cb798e --- /dev/null +++ b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro @@ -0,0 +1,11 @@ +--- +import type { ConfigureSSOProps } from '@clerk/shared/types'; +type Props = ConfigureSSOProps; + +import InternalUIComponentRenderer from './InternalUIComponentRenderer.astro'; +--- + + diff --git a/packages/astro/src/internal/mount-clerk-astro-js-components.ts b/packages/astro/src/internal/mount-clerk-astro-js-components.ts index 97720d3de67..26fcb937350 100644 --- a/packages/astro/src/internal/mount-clerk-astro-js-components.ts +++ b/packages/astro/src/internal/mount-clerk-astro-js-components.ts @@ -21,7 +21,7 @@ const mountAllClerkAstroJSComponents = () => { waitlist: 'mountWaitlist', 'pricing-table': 'mountPricingTable', 'api-keys': 'mountAPIKeys', - 'oauth-consent': 'mountOAuthConsent', + 'configure-sso': 'mountConfigureSSO', } as const satisfies Record; Object.entries(mountFns).forEach(([category, mountFn]) => { diff --git a/packages/astro/src/types.ts b/packages/astro/src/types.ts index 5807f6c3b3e..d2a2a89a22b 100644 --- a/packages/astro/src/types.ts +++ b/packages/astro/src/types.ts @@ -120,4 +120,4 @@ export type InternalUIComponentId = | 'waitlist' | 'pricing-table' | 'api-keys' - | 'oauth-consent'; + | 'configure-sso'; diff --git a/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap index d661f790661..8c72048290a 100644 --- a/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap @@ -9,6 +9,7 @@ exports[`public exports > should not include a breaking change 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", + "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/chrome-extension/src/react/re-exports.ts b/packages/chrome-extension/src/react/re-exports.ts index 62dafa1d664..681bea42b14 100644 --- a/packages/chrome-extension/src/react/re-exports.ts +++ b/packages/chrome-extension/src/react/re-exports.ts @@ -5,6 +5,7 @@ export { ClerkFailed, ClerkLoaded, ClerkLoading, + ConfigureSSO, CreateOrganization, HandleSSOCallback, OrganizationList, diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 37b6433e226..924bb0ed231 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -32,6 +32,7 @@ const AVAILABLE_COMPONENTS = [ 'waitlist', 'pricingTable', 'apiKeys', + 'configureSSO', 'oauthConsent', 'taskChooseOrganization', 'taskResetPassword', @@ -136,6 +137,7 @@ const componentControls: Record = { waitlist: buildComponentControls('waitlist'), pricingTable: buildComponentControls('pricingTable'), apiKeys: buildComponentControls('apiKeys'), + configureSSO: buildComponentControls('configureSSO'), oauthConsent: buildComponentControls('oauthConsent'), taskChooseOrganization: buildComponentControls('taskChooseOrganization'), taskResetPassword: buildComponentControls('taskResetPassword'), @@ -468,6 +470,9 @@ void (async () => { '/api-keys': () => { Clerk.mountAPIKeys(app, componentControls.apiKeys.getProps() ?? {}); }, + '/configure-sso': () => { + Clerk.mountConfigureSSO(app, componentControls.configureSSO.getProps() ?? {}); + }, '/oauth-consent': () => { const searchParams = new URLSearchParams(window.location.search); const scopes = (searchParams.get('scope')?.split(',') ?? []).map(scope => ({ diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html index 422e7496cb8..9591fe7e852 100644 --- a/packages/clerk-js/sandbox/template.html +++ b/packages/clerk-js/sandbox/template.html @@ -177,6 +177,14 @@ API Keys +
  • + + ConfigureSSO + +
  • ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; + /** + * Mount a configure SSO component at the target element. + * @param targetNode Target to mount the ConfigureSSO component. + * @param props Configuration parameters. + */ + public mountConfigureSSO = (node: HTMLDivElement, props?: ConfigureSSOProps) => { + this.assertComponentsReady(this.#clerkUI); + const component = 'ConfigureSSO'; + void this.#clerkUI + .then(ui => ui.ensureMounted({ preloadHint: component })) + .then(controls => + controls.mountComponent({ + name: component, + appearanceKey: 'configureSSO', + node, + props, + }), + ); + + this.telemetry?.record(eventPrebuiltComponentMounted(component, props)); + }; + + /** + * Unmount a configure SSO 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 ConfigureSSO component from. + */ + public unmountConfigureSSO = (node: HTMLDivElement) => { + void this.#clerkUI?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); + }; + public mountTaskChooseOrganization = (node: HTMLDivElement, props?: TaskChooseOrganizationProps) => { const { isEnabled: isOrganizationsEnabled } = this.__internal_attemptToEnableEnvironmentSetting({ for: 'organizations', diff --git a/packages/nextjs/src/client-boundary/uiComponents.tsx b/packages/nextjs/src/client-boundary/uiComponents.tsx index f6f65fad650..18c8a8e9ccb 100644 --- a/packages/nextjs/src/client-boundary/uiComponents.tsx +++ b/packages/nextjs/src/client-boundary/uiComponents.tsx @@ -13,6 +13,7 @@ import { useEnforceCorrectRoutingProps } from './hooks/useEnforceRoutingProps'; export { APIKeys, + ConfigureSSO, CreateOrganization, GoogleOneTap, HandleSSOCallback, diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index 283a7935cfc..35b52734264 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -23,6 +23,7 @@ export { */ export { APIKeys, + ConfigureSSO, CreateOrganization, GoogleOneTap, OAuthConsent, diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 897b2ff9f03..f582fb880f1 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -190,6 +190,8 @@ export default defineNuxtModule({ 'Waitlist', // API Keys 'APIKeys', + // SSO + 'ConfigureSSO', ]; otherComponents.forEach(component => { void addComponent({ diff --git a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap index 27525d4ce63..7e834b2a7f8 100644 --- a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap @@ -23,6 +23,7 @@ exports[`root public exports > should not change unexpectedly 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", + "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 0cec6374f29..a3f7472fa28 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -1,5 +1,6 @@ export { APIKeys, + ConfigureSSO, CreateOrganization, GoogleOneTap, OAuthConsent, diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index a87b83af675..f797fac09de 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -1,6 +1,7 @@ import type { __internal_OAuthConsentProps, APIKeysProps, + ConfigureSSOProps, CreateOrganizationProps, GoogleOneTapProps, OrganizationListProps, @@ -644,6 +645,37 @@ export const APIKeys = withClerk( { component: 'ApiKeys', renderWhileLoading: true }, ); +/** + * @experimental This component is in early access and may change in future releases. + */ +export const ConfigureSSO = 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: 'ConfigureSSO', renderWhileLoading: true }, +); + export const OAuthConsent = withClerk( ({ clerk, component, fallback, ...props }: WithClerkProp<__internal_OAuthConsentProps & FallbackProp>) => { const mountingStatus = useWaitForComponentMount(component); diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index e8fbe7530ee..cb697599d1f 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -25,6 +25,7 @@ import type { ClerkOptions, ClerkStatus, ClientResource, + ConfigureSSOProps, CreateOrganizationParams, CreateOrganizationProps, DomainOrProxyUrl, @@ -159,6 +160,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { private premountWaitlistNodes = new Map(); private premountPricingTableNodes = new Map(); private premountAPIKeysNodes = new Map(); + private premountConfigureSSONodes = new Map(); private premountOAuthConsentNodes = new Map(); private premountTaskChooseOrganizationNodes = new Map(); private premountTaskResetPasswordNodes = new Map(); @@ -735,6 +737,10 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { clerkjs.mountAPIKeys(node, props); }); + this.premountConfigureSSONodes.forEach((props, node) => { + clerkjs.mountConfigureSSO(node, props); + }); + this.premountOAuthConsentNodes.forEach((props, node) => { clerkjs.__internal_mountOAuthConsent(node, props); }); @@ -1271,6 +1277,22 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + mountConfigureSSO = (node: HTMLDivElement, props?: ConfigureSSOProps): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.mountConfigureSSO(node, props); + } else { + this.premountConfigureSSONodes.set(node, props); + } + }; + + unmountConfigureSSO = (node: HTMLDivElement): void => { + if (this.clerkjs && this.loaded) { + this.clerkjs.unmountConfigureSSO(node); + } else { + this.premountConfigureSSONodes.delete(node); + } + }; + __internal_mountOAuthConsent = (node: HTMLDivElement, props?: __internal_OAuthConsentProps) => { if (this.clerkjs && this.loaded) { this.clerkjs.__internal_mountOAuthConsent(node, props); diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index be87e83f76f..21f5a9441dd 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -661,6 +661,22 @@ export interface Clerk { */ unmountAPIKeys: (targetNode: HTMLDivElement) => void; + /** + * Mount a configure SSO component at the target element. + * + * @param targetNode - Target to mount the ConfigureSSO component. + * @param props - Configuration parameters. + */ + mountConfigureSSO: (targetNode: HTMLDivElement, props?: ConfigureSSOProps) => void; + + /** + * Unmount a configure SSO 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 ConfigureSSO component from. + */ + unmountConfigureSSO: (targetNode: HTMLDivElement) => void; + /** * Mounts a OAuth consent component at the target element. * @@ -2149,6 +2165,15 @@ export type APIKeysProps = { showDescription?: boolean; }; +export type ConfigureSSOProps = { + /** + * Customisation options to fully match the Clerk components to your own brand. + * These options serve as overrides and will be merged with the global `appearance` + * prop of ClerkProvider (if one is provided) + */ + appearance?: ClerkAppearanceTheme; +}; + export type GetAPIKeysParams = ClerkPaginationParams<{ subject?: string; query?: string; diff --git a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap index 4a5318392ee..49e8fd0b9a3 100644 --- a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap @@ -29,6 +29,7 @@ exports[`root public exports > should not change unexpectedly 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", + "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx new file mode 100644 index 00000000000..0844ba8e131 --- /dev/null +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -0,0 +1,3 @@ +export const ConfigureSSO = () => { + return
    ; +}; diff --git a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx index 282160cf2af..a8a837a2081 100644 --- a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx +++ b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx @@ -1,6 +1,7 @@ import type { OAuthConsentProps, APIKeysProps, + ConfigureSSOProps, PricingTableProps, TaskChooseOrganizationProps, TaskResetPasswordProps, @@ -13,6 +14,7 @@ import type { ReactNode } from 'react'; import type { AvailableComponentName, AvailableComponentProps } from '../types'; import { APIKeysContext, + ConfigureSSOContext, CreateOrganizationContext, GoogleOneTapContext, OAuthConsentContext, @@ -114,6 +116,12 @@ export function ComponentContextProvider({ {children} ); + case 'ConfigureSSO': + return ( + + {children} + + ); case 'OAuthConsent': { // Translate capital-A `oAuth*` props from the accounts portal into // the lowercase `oauth*` context shape the component reads. diff --git a/packages/ui/src/contexts/components/ConfigureSSO.ts b/packages/ui/src/contexts/components/ConfigureSSO.ts new file mode 100644 index 00000000000..7ce177a6d85 --- /dev/null +++ b/packages/ui/src/contexts/components/ConfigureSSO.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +import type { ConfigureSSOCtx } from '../../types'; + +export const ConfigureSSOContext = createContext(null); + +export const useConfigureSSOContext = () => { + const context = useContext(ConfigureSSOContext); + + if (!context || context.componentName !== 'ConfigureSSO') { + throw new Error('Clerk: useConfigureSSOContext called outside ConfigureSSO.'); + } + + const { componentName, ...ctx } = context; + + return { + ...ctx, + componentName, + }; +}; diff --git a/packages/ui/src/contexts/components/index.ts b/packages/ui/src/contexts/components/index.ts index 4242c494ad6..25b15a20fab 100644 --- a/packages/ui/src/contexts/components/index.ts +++ b/packages/ui/src/contexts/components/index.ts @@ -1,5 +1,6 @@ export * from './APIKeys'; export * from './Checkout'; +export * from './ConfigureSSO'; export * from './CreateOrganization'; export * from './GoogleOneTap'; export * from './OAuthConsent'; diff --git a/packages/ui/src/internal/appearance.ts b/packages/ui/src/internal/appearance.ts index 71bc4815a94..f43dc73a9ab 100644 --- a/packages/ui/src/internal/appearance.ts +++ b/packages/ui/src/internal/appearance.ts @@ -1014,6 +1014,7 @@ export type CheckoutTheme = Theme; export type PlanDetailTheme = Theme; export type SubscriptionDetailsTheme = Theme; export type APIKeysTheme = Theme; +export type ConfigureSSOTheme = Theme; export type OAuthConsentTheme = Theme; export type TaskChooseOrganizationTheme = Theme; export type TaskResetPasswordTheme = Theme; @@ -1090,6 +1091,10 @@ export type Appearance = T & * Theme overrides that only apply to the `` component */ apiKeys?: T; + /** + * Theme overrides that only apply to the `` component + */ + configureSSO?: T; /** * Theme overrides that only apply to the `` component */ diff --git a/packages/ui/src/lazyModules/components.ts b/packages/ui/src/lazyModules/components.ts index 0cc57f424ca..3c8e62f7e39 100644 --- a/packages/ui/src/lazyModules/components.ts +++ b/packages/ui/src/lazyModules/components.ts @@ -29,6 +29,7 @@ const componentImportPaths = { PlanDetails: () => import(/* webpackChunkName: "planDetails" */ '../components/Plans/PlanDetails'), SubscriptionDetails: () => import(/* webpackChunkName: "subscriptionDetails" */ '../components/SubscriptionDetails'), APIKeys: () => import(/* webpackChunkName: "apiKeys" */ '../components/APIKeys/APIKeys'), + ConfigureSSO: () => import(/* webpackChunkName: "configureSSO" */ '../components/ConfigureSSO/ConfigureSSO'), OAuthConsent: () => import(/* webpackChunkName: "oauthConsent" */ '../components/OAuthConsent/OAuthConsent'), EnableOrganizationsPrompt: () => import(/* webpackChunkName: "enableOrganizationsPrompt" */ '../components/devPrompts/EnableOrganizationsPrompt'), @@ -120,6 +121,10 @@ export const PricingTable = lazy(() => export const APIKeys = lazy(() => componentImportPaths.APIKeys().then(module => ({ default: module.APIKeys }))); +export const ConfigureSSO = lazy(() => + componentImportPaths.ConfigureSSO().then(module => ({ default: module.ConfigureSSO })), +); + export const Checkout = lazy(() => componentImportPaths.Checkout().then(module => ({ default: module.Checkout }))); export const TaskChooseOrganization = lazy(() => @@ -180,6 +185,7 @@ export const ClerkComponents = { Checkout, PlanDetails, APIKeys, + ConfigureSSO, OAuthConsent, SubscriptionDetails, TaskChooseOrganization, diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index 8bf8cbddd30..e684b1c205d 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -6,6 +6,7 @@ import type { __internal_UserVerificationProps, APIKeysProps, ClerkAppearanceTheme, + ConfigureSSOProps, CreateOrganizationProps, GoogleOneTapProps, NewSubscriptionRedirectUrl, @@ -64,7 +65,8 @@ export type AvailableComponentProps = | __internal_SubscriptionDetailsProps | __internal_PlanDetailsProps | APIKeysProps - | OAuthConsentProps + | ConfigureSSOProps + | __internal_OAuthConsentProps | TaskChooseOrganizationProps | TaskResetPasswordProps | TaskSetupMFAProps; @@ -145,6 +147,11 @@ export type APIKeysCtx = APIKeysProps & { mode?: ComponentMode; }; +export type ConfigureSSOCtx = ConfigureSSOProps & { + componentName: 'ConfigureSSO'; + mode?: ComponentMode; +}; + export type CheckoutCtx = __internal_CheckoutProps & { componentName: 'Checkout'; } & NewSubscriptionRedirectUrl; @@ -249,6 +256,7 @@ export type AvailableComponentCtx = | PricingTableCtx | CheckoutCtx | APIKeysCtx + | ConfigureSSOCtx | OAuthConsentCtx | SubscriptionDetailsCtx | PlanDetailsCtx diff --git a/packages/vue/src/components/index.ts b/packages/vue/src/components/index.ts index 10f7d6aa756..fabac0bd21c 100644 --- a/packages/vue/src/components/index.ts +++ b/packages/vue/src/components/index.ts @@ -6,7 +6,7 @@ export { default as CreateOrganization } from './ui-components/CreateOrganizatio export { default as OrganizationList } from './ui-components/OrganizationList.vue'; export { default as PricingTable } from './ui-components/PricingTable.vue'; export { default as APIKeys } from './ui-components/APIKeys.vue'; -export { default as OAuthConsent } from './ui-components/OAuthConsent.vue'; +export { default as ConfigureSSO } from './ui-components/ConfigureSSO.vue'; export { UserProfile } from './ui-components/UserProfile'; export { OrganizationProfile } from './ui-components/OrganizationProfile'; export { OrganizationSwitcher } from './ui-components/OrganizationSwitcher'; diff --git a/packages/vue/src/components/ui-components/ConfigureSSO.vue b/packages/vue/src/components/ui-components/ConfigureSSO.vue new file mode 100644 index 00000000000..4baa233da07 --- /dev/null +++ b/packages/vue/src/components/ui-components/ConfigureSSO.vue @@ -0,0 +1,17 @@ + + + From 724470d43c013553e6732199440ad804d95ddd0f Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Wed, 29 Apr 2026 22:32:01 -0700 Subject: [PATCH 05/13] Render basic sidebar --- packages/localizations/src/ar-SA.ts | 5 +++ packages/localizations/src/be-BY.ts | 5 +++ packages/localizations/src/bg-BG.ts | 5 +++ packages/localizations/src/bn-IN.ts | 5 +++ packages/localizations/src/ca-ES.ts | 5 +++ packages/localizations/src/cs-CZ.ts | 5 +++ packages/localizations/src/da-DK.ts | 5 +++ packages/localizations/src/de-DE.ts | 5 +++ packages/localizations/src/el-GR.ts | 5 +++ packages/localizations/src/en-GB.ts | 5 +++ packages/localizations/src/en-US.ts | 5 +++ packages/localizations/src/es-CR.ts | 5 +++ packages/localizations/src/es-ES.ts | 5 +++ packages/localizations/src/es-MX.ts | 5 +++ packages/localizations/src/es-UY.ts | 5 +++ packages/localizations/src/fa-IR.ts | 5 +++ packages/localizations/src/fi-FI.ts | 5 +++ packages/localizations/src/fr-FR.ts | 5 +++ packages/localizations/src/he-IL.ts | 5 +++ packages/localizations/src/hi-IN.ts | 5 +++ packages/localizations/src/hr-HR.ts | 5 +++ packages/localizations/src/hu-HU.ts | 5 +++ packages/localizations/src/id-ID.ts | 5 +++ packages/localizations/src/is-IS.ts | 5 +++ packages/localizations/src/it-IT.ts | 5 +++ packages/localizations/src/ja-JP.ts | 5 +++ packages/localizations/src/kk-KZ.ts | 5 +++ packages/localizations/src/ko-KR.ts | 5 +++ packages/localizations/src/mn-MN.ts | 5 +++ packages/localizations/src/ms-MY.ts | 5 +++ packages/localizations/src/nb-NO.ts | 5 +++ packages/localizations/src/nl-BE.ts | 5 +++ packages/localizations/src/nl-NL.ts | 5 +++ packages/localizations/src/pl-PL.ts | 5 +++ packages/localizations/src/pt-BR.ts | 5 +++ packages/localizations/src/pt-PT.ts | 5 +++ packages/localizations/src/ro-RO.ts | 5 +++ packages/localizations/src/ru-RU.ts | 5 +++ packages/localizations/src/sk-SK.ts | 5 +++ packages/localizations/src/sr-RS.ts | 5 +++ packages/localizations/src/sv-SE.ts | 5 +++ packages/localizations/src/ta-IN.ts | 5 +++ packages/localizations/src/te-IN.ts | 5 +++ packages/localizations/src/th-TH.ts | 5 +++ packages/localizations/src/tr-TR.ts | 5 +++ packages/localizations/src/uk-UA.ts | 5 +++ packages/localizations/src/vi-VN.ts | 5 +++ packages/localizations/src/zh-CN.ts | 5 +++ packages/localizations/src/zh-TW.ts | 5 +++ packages/shared/src/types/localization.ts | 5 +++ .../components/ConfigureSSO/ConfigureSSO.tsx | 44 ++++++++++++++++++- packages/ui/src/elements/Navbar.tsx | 14 +++--- packages/ui/src/elements/contexts/index.tsx | 1 + 53 files changed, 301 insertions(+), 8 deletions(-) diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 79c8c63c469..e32c9bfc2b7 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -178,6 +178,11 @@ export const arSA: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'تكوين تسجيل الدخول الموحد (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'أنشاء منظمة', invitePage: { diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index c5b109d1dca..2b16a7ae936 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -178,6 +178,11 @@ export const beBY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Налада адзінага ўваходу (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Стварыць арганізацыю', invitePage: { diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 49004e24135..96704ca286f 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -179,6 +179,11 @@ export const bgBG: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Конфигуриране на единен вход (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Създаване на организация', invitePage: { diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index b9a2463165b..765c3d7815a 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -178,6 +178,11 @@ export const bnIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'একক সাইন-অন (SSO) কনফিগার করুন', + }, + }, createOrganization: { formButtonSubmit: 'সংগঠন তৈরি করুন', invitePage: { diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index ab92189175a..fd021b11257 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -185,6 +185,11 @@ export const caES: LocalizationResource = { viewPayment: 'Veure pagament', year: 'Any', }, + configureSSO: { + navbar: { + title: "Configura l'inici de sessió únic (SSO)", + }, + }, createOrganization: { formButtonSubmit: 'Crea organització', invitePage: { diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 34da5326211..e23888e312f 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -182,6 +182,11 @@ export const csCZ: LocalizationResource = { viewPayment: undefined, year: 'Rok', }, + configureSSO: { + navbar: { + title: 'Nastavit jednotné přihlášení (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Vytvořit organizaci', invitePage: { diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 472f49f2ae6..5df63b312ed 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -178,6 +178,11 @@ export const daDK: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurer single sign-on (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Opret organisation', invitePage: { diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index f4fb1d2ba13..db24f051d2e 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -184,6 +184,11 @@ export const deDE: LocalizationResource = { viewPayment: 'Zahlung anzeigen', year: 'Jahr', }, + configureSSO: { + navbar: { + title: 'Single Sign-On (SSO) konfigurieren', + }, + }, createOrganization: { formButtonSubmit: 'Organisation erstellen', invitePage: { diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index 4ba17bb6493..9147179a512 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -178,6 +178,11 @@ export const elGR: LocalizationResource = { viewPayment: 'Προβολή πληρωμής', year: 'έτος', }, + configureSSO: { + navbar: { + title: 'Διαμόρφωση Ενιαίας Σύνδεσης (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Δημιουργία οργανισμού', invitePage: { diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index b0aa4889f53..d529df0a938 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -178,6 +178,11 @@ export const enGB: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configure Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Create organisation', invitePage: { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 3b5b3e1bc61..21f706f2883 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -197,6 +197,11 @@ export const enUS: LocalizationResource = { yearAbbreviation: 'yr', yearPerUnit: 'Year per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Configure Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Create organization', invitePage: { diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index e305c0d0e6c..8b1028c51f1 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -178,6 +178,11 @@ export const esCR: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 45982e3c4be..6e0736e7078 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -184,6 +184,11 @@ export const esES: LocalizationResource = { viewPayment: 'Ver pago', year: 'Año', }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index cb340de4858..12a91d526e5 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -179,6 +179,11 @@ export const esMX: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index a4a387ef619..dc9a5f1fac2 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -178,6 +178,11 @@ export const esUY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Configurar inicio de sesión único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crear organización', invitePage: { diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 04c9b1ef62b..1389120f971 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -183,6 +183,11 @@ export const faIR: LocalizationResource = { viewPayment: 'مشاهده پرداخت', year: 'سال', }, + configureSSO: { + navbar: { + title: 'پیکربندی ورود یکپارچه (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'ایجاد سازمان', invitePage: { diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index e4fa4e1a821..d835761ecbc 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -206,6 +206,11 @@ export const fiFI: LocalizationResource = { yearAbbreviation: 'v', yearPerUnit: 'Vuosi per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Määritä kertakirjautuminen (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Luo organisaatio', invitePage: { diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index 5e4b3c05764..9f7fdfaee50 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -186,6 +186,11 @@ export const frFR: LocalizationResource = { viewPayment: 'Voir le paiement', year: 'An', }, + configureSSO: { + navbar: { + title: "Configurer l'authentification unique (SSO)", + }, + }, createOrganization: { formButtonSubmit: 'Créer l’organisation', invitePage: { diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 29913555481..6771d4daf90 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -178,6 +178,11 @@ export const heIL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'הגדרת כניסה אחידה (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'צור ארגון', invitePage: { diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 94039ba7ee2..29651d6cbd3 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -178,6 +178,11 @@ export const hiIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'सिंगल साइन-ऑन (SSO) कॉन्फ़िगर करें', + }, + }, createOrganization: { formButtonSubmit: 'संगठन बनाएँ', invitePage: { diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index da8a1f39024..1af45a26c6e 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -207,6 +207,11 @@ export const hrHR: LocalizationResource = { yearAbbreviation: 'god', yearPerUnit: 'Godina po {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Konfiguriraj jedinstvenu prijavu (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'kreiraj organizaciju', invitePage: { diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 9b634dc17de..8ba7b812c75 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -207,6 +207,11 @@ export const huHU: LocalizationResource = { yearAbbreviation: 'év', yearPerUnit: 'Év / {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Egyszeri bejelentkezés (SSO) beállítása', + }, + }, createOrganization: { formButtonSubmit: 'Szervezet létrehozása', invitePage: { diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 1297208c560..0b9880267ad 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -178,6 +178,11 @@ export const idID: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurasi Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Buat organisasi', invitePage: { diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 686dc893e31..e32720033ca 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -206,6 +206,11 @@ export const isIS: LocalizationResource = { yearAbbreviation: 'ár', yearPerUnit: 'Ár á {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Stilla einnar innskráningar (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Stofna samtök', invitePage: { diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index 7fd8e514f1c..93ee04f749c 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -184,6 +184,11 @@ export const itIT: LocalizationResource = { viewPayment: undefined, year: 'Anno', }, + configureSSO: { + navbar: { + title: 'Configura Single Sign-On (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Crea organizzazione', invitePage: { diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 053cc788f0d..a9f62ce514f 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -189,6 +189,11 @@ export const jaJP: LocalizationResource = { viewPayment: '支払いを表示', year: '年', }, + configureSSO: { + navbar: { + title: 'シングルサインオン(SSO)を設定', + }, + }, createOrganization: { formButtonSubmit: '組織を作成する', invitePage: { diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index d75f6034f2c..1c7c752c36a 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -178,6 +178,11 @@ export const kkKZ: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Бірыңғай кіруді конфигурациялау (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Ұйым құру', invitePage: { diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 28d9d3d309d..ddfec4d8212 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -182,6 +182,11 @@ export const koKR: LocalizationResource = { viewPayment: '결제 보기', year: '년', }, + configureSSO: { + navbar: { + title: '싱글 사인온(SSO) 구성', + }, + }, createOrganization: { formButtonSubmit: '조직 만들기', invitePage: { diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 66ac4813eb4..4ccdebd039d 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -178,6 +178,11 @@ export const mnMN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Нэгдсэн нэвтрэлт (SSO) тохируулах', + }, + }, createOrganization: { formButtonSubmit: 'Байгуулга үүсгэх', invitePage: { diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index ddd520f34f9..0e8da7e7832 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -178,6 +178,11 @@ export const msMY: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurasi Log Masuk Tunggal (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Cipta organisasi', invitePage: { diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index da2737185f0..b063903b209 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -207,6 +207,11 @@ export const nbNO: LocalizationResource = { yearAbbreviation: 'år', yearPerUnit: 'År per {{unitName}}', }, + configureSSO: { + navbar: { + title: 'Konfigurer enkeltpålogging (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Opprett organisasjon', invitePage: { diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index cf172ff19cb..10df71a968a 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -178,6 +178,11 @@ export const nlBE: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Single sign-on (SSO) configureren', + }, + }, createOrganization: { formButtonSubmit: 'Creëer organisatie', invitePage: { diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 860e62983d6..128189d82db 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -178,6 +178,11 @@ export const nlNL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Single sign-on (SSO) configureren', + }, + }, createOrganization: { formButtonSubmit: 'Creëer organisatie', invitePage: { diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 7a4e2d3d116..d87215cc89a 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -178,6 +178,11 @@ export const plPL: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Skonfiguruj logowanie jednokrotne (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Utwórz organizację', invitePage: { diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index c4c17027071..604fde40ec2 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -184,6 +184,11 @@ export const ptBR: LocalizationResource = { viewPayment: 'Ver pagamento', year: 'Ano', }, + configureSSO: { + navbar: { + title: 'Configurar logon único (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Criar organização', invitePage: { diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 0dc1f68eaae..76519707741 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -186,6 +186,11 @@ export const ptPT: LocalizationResource = { viewPayment: 'Ver pagamento', year: 'Ano', }, + configureSSO: { + navbar: { + title: 'Configurar autenticação única (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Criar organização', invitePage: { diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index eadcabedded..c54f0159634 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -184,6 +184,11 @@ export const roRO: LocalizationResource = { viewPayment: 'Vezi plata', year: 'An', }, + configureSSO: { + navbar: { + title: 'Configurați autentificarea unică (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Creează organizație', invitePage: { diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 3dcfeb98df0..439dbabad3f 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -178,6 +178,11 @@ export const ruRU: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Настроить единый вход (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Создать организацию', invitePage: { diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index 6fcd15b73e9..6d141338a80 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -178,6 +178,11 @@ export const skSK: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Nastaviť jednotné prihlasovanie (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Vytvoriť organizáciu', invitePage: { diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index fbcca7e1d6c..5edc8c961d6 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -178,6 +178,11 @@ export const srRS: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfiguriši jedinstvenu prijavu (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Kreiraj organizaciju', invitePage: { diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index a5e80ff65ed..fb3d7b2d18a 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -178,6 +178,11 @@ export const svSE: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Konfigurera enkel inloggning (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Skapa organisation', invitePage: { diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 5baa4de9e6c..77fa6116a45 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -178,6 +178,11 @@ export const taIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'ஒற்றை உள்நுழைவை (SSO) உள்ளமை', + }, + }, createOrganization: { formButtonSubmit: 'நிறுவனத்தை உருவாக்கு', invitePage: { diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index 4d63ab5b552..a019b92757a 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -178,6 +178,11 @@ export const teIN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'సింగిల్ సైన్-ఆన్ (SSO) కాన్ఫిగర్ చేయండి', + }, + }, createOrganization: { formButtonSubmit: 'సంస్థను సృష్టించండి', invitePage: { diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index b074c63a6a9..d8c8264b878 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -182,6 +182,11 @@ export const thTH: LocalizationResource = { viewPayment: 'ดูการชำระเงิน', year: 'ปี', }, + configureSSO: { + navbar: { + title: 'กำหนดค่าการลงชื่อเข้าใช้แบบครั้งเดียว (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'สร้างองค์กร', invitePage: { diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index e5af54c494c..cfddf9e7a04 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -178,6 +178,11 @@ export const trTR: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Tek Oturum Açmayı (SSO) Yapılandır', + }, + }, createOrganization: { formButtonSubmit: 'Oluştur', invitePage: { diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index 01495d07413..d1f62de46c8 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -178,6 +178,11 @@ export const ukUA: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: 'Налаштувати єдиний вхід (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Створити організацію', invitePage: { diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index 512e3b2672d..411ca53019b 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -182,6 +182,11 @@ export const viVN: LocalizationResource = { viewPayment: undefined, year: 'Năm', }, + configureSSO: { + navbar: { + title: 'Cấu hình đăng nhập một lần (SSO)', + }, + }, createOrganization: { formButtonSubmit: 'Tạo tổ chức', invitePage: { diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index f11b0913eda..8835fdfc716 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -178,6 +178,11 @@ export const zhCN: LocalizationResource = { viewPayment: undefined, year: undefined, }, + configureSSO: { + navbar: { + title: '配置单点登录 (SSO)', + }, + }, createOrganization: { formButtonSubmit: '创建组织', invitePage: { diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index c37d6efd9ea..5bd804bfe98 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -184,6 +184,11 @@ export const zhTW: LocalizationResource = { viewPayment: '查看付款', year: '年', }, + configureSSO: { + navbar: { + title: '設定單一登入 (SSO)', + }, + }, createOrganization: { formButtonSubmit: '創建組織', invitePage: { diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 222509565bb..33f3d1d6e21 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1292,6 +1292,11 @@ export type __internal_LocalizationResource = { message: LocalizationValue; }; }; + configureSSO: { + navbar: { + title: LocalizationValue; + }; + }; apiKeys: { formTitle: LocalizationValue; formHint: LocalizationValue; diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index 0844ba8e131..d2408e5a3f1 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -1,3 +1,43 @@ -export const ConfigureSSO = () => { - return
    ; +import type { ConfigureSSOProps } from '@clerk/shared/types'; +import React from 'react'; + +import { withCoreUserGuard } from '@/contexts'; +import { Flow, localizationKeys } from '@/customizables'; +import { withCardStateProvider } from '@/elements/contexts'; +import { NavBar, NavbarContextProvider } from '@/elements/Navbar'; +import { ProfileCard } from '@/elements/ProfileCard'; +import { Route, Switch } from '@/router'; + +const ConfigureSSOInternal = () => { + return ( + + + + + + + + + + ); }; + +const AuthenticatedContent = withCoreUserGuard(() => { + const contentRef = React.useRef(null); + return ( + ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })} + > + + + + + + ); +}); + +export const ConfigureSSO: React.ComponentType = withCardStateProvider(ConfigureSSOInternal); diff --git a/packages/ui/src/elements/Navbar.tsx b/packages/ui/src/elements/Navbar.tsx index 8b2a577c792..fd43bf9fb65 100644 --- a/packages/ui/src/elements/Navbar.tsx +++ b/packages/ui/src/elements/Navbar.tsx @@ -44,7 +44,7 @@ export type NavbarRoute = { }; type NavBarProps = { title: LocalizationKey; - description: LocalizationKey; + description?: LocalizationKey; contentRef: React.RefObject; routes: NavbarRoute[]; header?: React.ReactNode; @@ -140,7 +140,7 @@ export const NavBar = (props: NavBarProps) => { }; const NavbarContainer = ( - props: React.PropsWithChildren<{ title: LocalizationKey | string; description: LocalizationKey | string }>, + props: React.PropsWithChildren<{ title: LocalizationKey | string; description?: LocalizationKey | string }>, ) => { const { title, description } = props; return ( @@ -175,10 +175,12 @@ const NavbarContainer = ( localizationKey={title} /> - + {description ? ( + + ) : null} {props.children} diff --git a/packages/ui/src/elements/contexts/index.tsx b/packages/ui/src/elements/contexts/index.tsx index cecccfe3d88..44eef53bf88 100644 --- a/packages/ui/src/elements/contexts/index.tsx +++ b/packages/ui/src/elements/contexts/index.tsx @@ -98,6 +98,7 @@ export type FlowMetadata = { | 'planDetails' | 'pricingTable' | 'apiKeys' + | 'configureSSO' | 'oauthConsent' | 'subscriptionDetails' | 'tasks' From b0b6a2b7a346e964275e66ff634626c2e644a621 Mon Sep 17 00:00:00 2001 From: Laura Beatris Date: Thu, 30 Apr 2026 08:23:14 -0700 Subject: [PATCH 06/13] Update to `__experimental` prefix --- packages/astro/src/astro-components/index.ts | 2 +- .../interactive/ConfigureSSO.astro | 4 ++-- .../mount-clerk-astro-js-components.ts | 2 +- .../__snapshots__/exports.test.ts.snap | 1 - .../chrome-extension/src/react/re-exports.ts | 1 - packages/clerk-js/sandbox/app.ts | 2 +- packages/clerk-js/src/core/clerk.ts | 19 +++++++++++-------- .../src/client-boundary/uiComponents.tsx | 1 - packages/nextjs/src/index.ts | 1 - packages/nuxt/src/module.ts | 19 +++++++++++++++++-- .../__snapshots__/exports.test.ts.snap | 1 - packages/react/src/components/index.ts | 1 - .../react/src/components/uiComponents.tsx | 8 ++++---- packages/react/src/experimental.ts | 11 +++++++++++ packages/react/src/isomorphicClerk.ts | 14 +++++++------- packages/shared/src/types/clerk.ts | 13 ++++++++++--- .../__snapshots__/exports.test.ts.snap | 1 - .../components/ConfigureSSO/ConfigureSSO.tsx | 5 +++-- .../src/contexts/ClerkUIComponentsContext.tsx | 6 +++--- packages/ui/src/internal/appearance.ts | 6 ++++-- packages/ui/src/types.ts | 6 +++--- packages/vue/src/components/index.ts | 1 - .../components/ui-components/ConfigureSSO.vue | 8 ++++---- packages/vue/src/experimental.ts | 15 ++++++++++++++- 24 files changed, 96 insertions(+), 52 deletions(-) diff --git a/packages/astro/src/astro-components/index.ts b/packages/astro/src/astro-components/index.ts index 0d50e3a1e92..facc4145374 100644 --- a/packages/astro/src/astro-components/index.ts +++ b/packages/astro/src/astro-components/index.ts @@ -31,4 +31,4 @@ export { default as Waitlist } from './interactive/Waitlist.astro'; export { default as OAuthConsent } from './interactive/OAuthConsent.astro'; export { default as PricingTable } from './interactive/PricingTable.astro'; export { default as APIKeys } from './interactive/APIKeys.astro'; -export { default as ConfigureSSO } from './interactive/ConfigureSSO.astro'; +export { default as __experimental_ConfigureSSO } from './interactive/ConfigureSSO.astro'; diff --git a/packages/astro/src/astro-components/interactive/ConfigureSSO.astro b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro index c1f00cb798e..9fdb7bf37f0 100644 --- a/packages/astro/src/astro-components/interactive/ConfigureSSO.astro +++ b/packages/astro/src/astro-components/interactive/ConfigureSSO.astro @@ -1,6 +1,6 @@ --- -import type { ConfigureSSOProps } from '@clerk/shared/types'; -type Props = ConfigureSSOProps; +import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types'; +type Props = __experimental_ConfigureSSOProps; import InternalUIComponentRenderer from './InternalUIComponentRenderer.astro'; --- diff --git a/packages/astro/src/internal/mount-clerk-astro-js-components.ts b/packages/astro/src/internal/mount-clerk-astro-js-components.ts index 26fcb937350..c4a6ac81ed8 100644 --- a/packages/astro/src/internal/mount-clerk-astro-js-components.ts +++ b/packages/astro/src/internal/mount-clerk-astro-js-components.ts @@ -21,7 +21,7 @@ const mountAllClerkAstroJSComponents = () => { waitlist: 'mountWaitlist', 'pricing-table': 'mountPricingTable', 'api-keys': 'mountAPIKeys', - 'configure-sso': 'mountConfigureSSO', + 'configure-sso': '__experimental_mountConfigureSSO', } as const satisfies Record; Object.entries(mountFns).forEach(([category, mountFn]) => { diff --git a/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap index 8c72048290a..d661f790661 100644 --- a/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/chrome-extension/src/__tests__/__snapshots__/exports.test.ts.snap @@ -9,7 +9,6 @@ exports[`public exports > should not include a breaking change 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", - "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/chrome-extension/src/react/re-exports.ts b/packages/chrome-extension/src/react/re-exports.ts index 681bea42b14..62dafa1d664 100644 --- a/packages/chrome-extension/src/react/re-exports.ts +++ b/packages/chrome-extension/src/react/re-exports.ts @@ -5,7 +5,6 @@ export { ClerkFailed, ClerkLoaded, ClerkLoading, - ConfigureSSO, CreateOrganization, HandleSSOCallback, OrganizationList, diff --git a/packages/clerk-js/sandbox/app.ts b/packages/clerk-js/sandbox/app.ts index 924bb0ed231..8277c29a117 100644 --- a/packages/clerk-js/sandbox/app.ts +++ b/packages/clerk-js/sandbox/app.ts @@ -471,7 +471,7 @@ void (async () => { Clerk.mountAPIKeys(app, componentControls.apiKeys.getProps() ?? {}); }, '/configure-sso': () => { - Clerk.mountConfigureSSO(app, componentControls.configureSSO.getProps() ?? {}); + Clerk.__experimental_mountConfigureSSO(app, componentControls.configureSSO.getProps() ?? {}); }, '/oauth-consent': () => { const searchParams = new URLSearchParams(window.location.search); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index 9a0d44ab6b3..02ebf9617dc 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -53,6 +53,7 @@ import { } from '@clerk/shared/telemetry'; import type { __experimental_CheckoutOptions, + __experimental_ConfigureSSOProps, __internal_AttemptToEnableEnvironmentSettingParams, __internal_AttemptToEnableEnvironmentSettingResult, __internal_CheckoutProps, @@ -71,13 +72,12 @@ import type { AuthenticateWithSolanaParams, BillingNamespace, CheckoutSignalValue, - Clerk as ClerkInterface, ClerkAPIError, ClerkAuthenticateWithWeb3Params, + Clerk as ClerkInterface, ClerkOptions, ClientJSONSnapshot, ClientResource, - ConfigureSSOProps, CreateOrganizationParams, CreateOrganizationProps, CredentialReturn, @@ -523,7 +523,7 @@ export class Clerk implements ClerkInterface { ) { // Typing this.#options as ClerkOptions to ensure proper type checking. TypeScript will infer the type as `never` // since missing both `routerPush` and `routerReplace` is not a valid ClerkOptions. - const options = this.#options as ClerkOptions; + const options = this.#options; const missingRouter = !options.routerPush ? 'routerPush' : 'routerReplace'; logger.warnOnce( `Clerk: Both \`routerPush\` and \`routerReplace\` need to be defined, but \`${missingRouter}\` is not defined. This may cause issues with navigation in your application.`, @@ -1429,10 +1429,12 @@ export class Clerk implements ClerkInterface { /** * Mount a configure SSO component at the target element. + * + * @experimental * @param targetNode Target to mount the ConfigureSSO component. * @param props Configuration parameters. */ - public mountConfigureSSO = (node: HTMLDivElement, props?: ConfigureSSOProps) => { + public __experimental_mountConfigureSSO = (node: HTMLDivElement, props?: __experimental_ConfigureSSOProps) => { this.assertComponentsReady(this.#clerkUI); const component = 'ConfigureSSO'; void this.#clerkUI @@ -1440,7 +1442,7 @@ export class Clerk implements ClerkInterface { .then(controls => controls.mountComponent({ name: component, - appearanceKey: 'configureSSO', + appearanceKey: '__experimental_configureSSO', node, props, }), @@ -1453,9 +1455,10 @@ export class Clerk implements ClerkInterface { * Unmount a configure SSO component from the target element. * If there is no component mounted at the target node, results in a noop. * + * @experimental * @param targetNode Target node to unmount the ConfigureSSO component from. */ - public unmountConfigureSSO = (node: HTMLDivElement) => { + public __experimental_unmountConfigureSSO = (node: HTMLDivElement) => { void this.#clerkUI?.then(ui => ui.ensureMounted()).then(controls => controls.unmountComponent({ node })); }; @@ -2981,8 +2984,8 @@ export class Clerk implements ClerkInterface { this.#authService = await AuthCookieService.create( this, this.#fapiClient, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this.#instanceType!, + + this.#instanceType, this.#publicEventBus, ); diff --git a/packages/nextjs/src/client-boundary/uiComponents.tsx b/packages/nextjs/src/client-boundary/uiComponents.tsx index 18c8a8e9ccb..f6f65fad650 100644 --- a/packages/nextjs/src/client-boundary/uiComponents.tsx +++ b/packages/nextjs/src/client-boundary/uiComponents.tsx @@ -13,7 +13,6 @@ import { useEnforceCorrectRoutingProps } from './hooks/useEnforceRoutingProps'; export { APIKeys, - ConfigureSSO, CreateOrganization, GoogleOneTap, HandleSSOCallback, diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index 35b52734264..283a7935cfc 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -23,7 +23,6 @@ export { */ export { APIKeys, - ConfigureSSO, CreateOrganization, GoogleOneTap, OAuthConsent, diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index f582fb880f1..ccabf817440 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -190,8 +190,6 @@ export default defineNuxtModule({ 'Waitlist', // API Keys 'APIKeys', - // SSO - 'ConfigureSSO', ]; otherComponents.forEach(component => { void addComponent({ @@ -200,5 +198,22 @@ export default defineNuxtModule({ filePath: '@clerk/vue', }); }); + + /** + * Experimental components from `@clerk/vue/experimental`. + * @experimental These components and their prop types are unstable and may change in future releases. + */ + // eslint-disable-next-line @typescript-eslint/consistent-type-imports + const experimentalComponents: Array = [ + // SSO + 'ConfigureSSO', + ]; + experimentalComponents.forEach(component => { + void addComponent({ + name: component, + export: component, + filePath: '@clerk/vue/experimental', + }); + }); }, }); diff --git a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap index 7e834b2a7f8..27525d4ce63 100644 --- a/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap @@ -23,7 +23,6 @@ exports[`root public exports > should not change unexpectedly 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", - "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index a3f7472fa28..0cec6374f29 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -1,6 +1,5 @@ export { APIKeys, - ConfigureSSO, CreateOrganization, GoogleOneTap, OAuthConsent, diff --git a/packages/react/src/components/uiComponents.tsx b/packages/react/src/components/uiComponents.tsx index f797fac09de..ba7c941618b 100644 --- a/packages/react/src/components/uiComponents.tsx +++ b/packages/react/src/components/uiComponents.tsx @@ -1,7 +1,7 @@ import type { + __experimental_ConfigureSSOProps, __internal_OAuthConsentProps, APIKeysProps, - ConfigureSSOProps, CreateOrganizationProps, GoogleOneTapProps, OrganizationListProps, @@ -649,7 +649,7 @@ export const APIKeys = withClerk( * @experimental This component is in early access and may change in future releases. */ export const ConfigureSSO = withClerk( - ({ clerk, component, fallback, ...props }: WithClerkProp) => { + ({ clerk, component, fallback, ...props }: WithClerkProp<__experimental_ConfigureSSOProps & FallbackProp>) => { const mountingStatus = useWaitForComponentMount(component); const shouldShowFallback = mountingStatus === 'rendering' || !clerk.loaded; @@ -663,8 +663,8 @@ export const ConfigureSSO = withClerk( {clerk.loaded && ( (); private premountPricingTableNodes = new Map(); private premountAPIKeysNodes = new Map(); - private premountConfigureSSONodes = new Map(); + private premountConfigureSSONodes = new Map(); private premountOAuthConsentNodes = new Map(); private premountTaskChooseOrganizationNodes = new Map(); private premountTaskResetPasswordNodes = new Map(); @@ -738,7 +738,7 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { }); this.premountConfigureSSONodes.forEach((props, node) => { - clerkjs.mountConfigureSSO(node, props); + clerkjs.__experimental_mountConfigureSSO(node, props); }); this.premountOAuthConsentNodes.forEach((props, node) => { @@ -1277,17 +1277,17 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - mountConfigureSSO = (node: HTMLDivElement, props?: ConfigureSSOProps): void => { + __experimental_mountConfigureSSO = (node: HTMLDivElement, props?: __experimental_ConfigureSSOProps): void => { if (this.clerkjs && this.loaded) { - this.clerkjs.mountConfigureSSO(node, props); + this.clerkjs.__experimental_mountConfigureSSO(node, props); } else { this.premountConfigureSSONodes.set(node, props); } }; - unmountConfigureSSO = (node: HTMLDivElement): void => { + __experimental_unmountConfigureSSO = (node: HTMLDivElement): void => { if (this.clerkjs && this.loaded) { - this.clerkjs.unmountConfigureSSO(node); + this.clerkjs.__experimental_unmountConfigureSSO(node); } else { this.premountConfigureSSONodes.delete(node); } diff --git a/packages/shared/src/types/clerk.ts b/packages/shared/src/types/clerk.ts index 21f5a9441dd..88613446bcb 100644 --- a/packages/shared/src/types/clerk.ts +++ b/packages/shared/src/types/clerk.ts @@ -664,18 +664,22 @@ export interface Clerk { /** * Mount a configure SSO component at the target element. * + * @experimental This method is in early access and may change in future releases. + * * @param targetNode - Target to mount the ConfigureSSO component. * @param props - Configuration parameters. */ - mountConfigureSSO: (targetNode: HTMLDivElement, props?: ConfigureSSOProps) => void; + __experimental_mountConfigureSSO: (targetNode: HTMLDivElement, props?: __experimental_ConfigureSSOProps) => void; /** * Unmount a configure SSO component from the target element. * If there is no component mounted at the target node, results in a noop. * + * @experimental This method is in early access and may change in future releases. + * * @param targetNode - Target node to unmount the ConfigureSSO component from. */ - unmountConfigureSSO: (targetNode: HTMLDivElement) => void; + __experimental_unmountConfigureSSO: (targetNode: HTMLDivElement) => void; /** * Mounts a OAuth consent component at the target element. @@ -2165,7 +2169,10 @@ export type APIKeysProps = { showDescription?: boolean; }; -export type ConfigureSSOProps = { +/** + * @experimental This type is in early access and may change in future releases. + */ +export type __experimental_ConfigureSSOProps = { /** * Customisation options to fully match the Clerk components to your own brand. * These options serve as overrides and will be merged with the global `appearance` diff --git a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap index 49e8fd0b9a3..4a5318392ee 100644 --- a/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap +++ b/packages/tanstack-react-start/src/__tests__/__snapshots__/exports.test.ts.snap @@ -29,7 +29,6 @@ exports[`root public exports > should not change unexpectedly 1`] = ` "ClerkLoaded", "ClerkLoading", "ClerkProvider", - "ConfigureSSO", "CreateOrganization", "GoogleOneTap", "HandleSSOCallback", diff --git a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx index d2408e5a3f1..9534a13ac61 100644 --- a/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx +++ b/packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx @@ -1,4 +1,4 @@ -import type { ConfigureSSOProps } from '@clerk/shared/types'; +import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types'; import React from 'react'; import { withCoreUserGuard } from '@/contexts'; @@ -40,4 +40,5 @@ const AuthenticatedContent = withCoreUserGuard(() => { ); }); -export const ConfigureSSO: React.ComponentType = withCardStateProvider(ConfigureSSOInternal); +export const ConfigureSSO: React.ComponentType<__experimental_ConfigureSSOProps> = + withCardStateProvider(ConfigureSSOInternal); diff --git a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx index a8a837a2081..8a78aa8d5cf 100644 --- a/packages/ui/src/contexts/ClerkUIComponentsContext.tsx +++ b/packages/ui/src/contexts/ClerkUIComponentsContext.tsx @@ -1,7 +1,7 @@ import type { - OAuthConsentProps, + __experimental_ConfigureSSOProps, + __internal_OAuthConsentProps, APIKeysProps, - ConfigureSSOProps, PricingTableProps, TaskChooseOrganizationProps, TaskResetPasswordProps, @@ -118,7 +118,7 @@ export function ComponentContextProvider({ ); case 'ConfigureSSO': return ( - + {children} ); diff --git a/packages/ui/src/internal/appearance.ts b/packages/ui/src/internal/appearance.ts index f43dc73a9ab..a619493d925 100644 --- a/packages/ui/src/internal/appearance.ts +++ b/packages/ui/src/internal/appearance.ts @@ -1014,7 +1014,7 @@ export type CheckoutTheme = Theme; export type PlanDetailTheme = Theme; export type SubscriptionDetailsTheme = Theme; export type APIKeysTheme = Theme; -export type ConfigureSSOTheme = Theme; +export type __experimental_ConfigureSSOTheme = Theme; export type OAuthConsentTheme = Theme; export type TaskChooseOrganizationTheme = Theme; export type TaskResetPasswordTheme = Theme; @@ -1093,8 +1093,10 @@ export type Appearance = T & apiKeys?: T; /** * Theme overrides that only apply to the `` component + * + * @experimental This appearance key is in early access and may change in future releases. */ - configureSSO?: T; + __experimental_configureSSO?: T; /** * Theme overrides that only apply to the `` component */ diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index e684b1c205d..b37ec787136 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -1,4 +1,5 @@ import type { + __experimental_ConfigureSSOProps, __internal_CheckoutProps, OAuthConsentProps, __internal_PlanDetailsProps, @@ -6,7 +7,6 @@ import type { __internal_UserVerificationProps, APIKeysProps, ClerkAppearanceTheme, - ConfigureSSOProps, CreateOrganizationProps, GoogleOneTapProps, NewSubscriptionRedirectUrl, @@ -65,7 +65,7 @@ export type AvailableComponentProps = | __internal_SubscriptionDetailsProps | __internal_PlanDetailsProps | APIKeysProps - | ConfigureSSOProps + | __experimental_ConfigureSSOProps | __internal_OAuthConsentProps | TaskChooseOrganizationProps | TaskResetPasswordProps @@ -147,7 +147,7 @@ export type APIKeysCtx = APIKeysProps & { mode?: ComponentMode; }; -export type ConfigureSSOCtx = ConfigureSSOProps & { +export type ConfigureSSOCtx = __experimental_ConfigureSSOProps & { componentName: 'ConfigureSSO'; mode?: ComponentMode; }; diff --git a/packages/vue/src/components/index.ts b/packages/vue/src/components/index.ts index fabac0bd21c..ff0eda5f804 100644 --- a/packages/vue/src/components/index.ts +++ b/packages/vue/src/components/index.ts @@ -6,7 +6,6 @@ export { default as CreateOrganization } from './ui-components/CreateOrganizatio export { default as OrganizationList } from './ui-components/OrganizationList.vue'; export { default as PricingTable } from './ui-components/PricingTable.vue'; export { default as APIKeys } from './ui-components/APIKeys.vue'; -export { default as ConfigureSSO } from './ui-components/ConfigureSSO.vue'; export { UserProfile } from './ui-components/UserProfile'; export { OrganizationProfile } from './ui-components/OrganizationProfile'; export { OrganizationSwitcher } from './ui-components/OrganizationSwitcher'; diff --git a/packages/vue/src/components/ui-components/ConfigureSSO.vue b/packages/vue/src/components/ui-components/ConfigureSSO.vue index 4baa233da07..bcdd2eea036 100644 --- a/packages/vue/src/components/ui-components/ConfigureSSO.vue +++ b/packages/vue/src/components/ui-components/ConfigureSSO.vue @@ -1,16 +1,16 @@