From 71e940f86936319f74d92eafca1eb262a019ad08 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:52:30 -0300 Subject: [PATCH 01/10] Update factor type to check for enterprise connection --- .../src/ui/components/SignIn/SignInStart.tsx | 24 +++++------ .../src/ui/components/SignIn/shared.ts | 43 ++++++++++++++++++- packages/types/src/factors.ts | 4 ++ 3 files changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index a46ec20057e..f508cc6c0bf 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -39,7 +39,7 @@ import { useLoadingStatus } from '../../hooks'; import { useSupportEmail } from '../../hooks/useSupportEmail'; import { useRouter } from '../../router'; import { handleCombinedFlowTransfer } from './handleCombinedFlowTransfer'; -import { useHandleAuthenticateWithPasskey } from './shared'; +import { getEnterpriseSSOFlowType, useHandleAuthenticateWithPasskey } from './shared'; import { SignInAlternativePhoneCodePhoneNumberCard } from './SignInAlternativePhoneCodePhoneNumberCard'; import { SignInSocialButtons } from './SignInSocialButtons'; import { @@ -224,12 +224,14 @@ function SignInStartInternal(): JSX.Element { }) .then(res => { switch (res.status) { - case 'needs_first_factor': - if (hasOnlyEnterpriseSSOFirstFactors(res)) { + case 'needs_first_factor': { + const enterpriseSSOFlowType = getEnterpriseSSOFlowType(res); + if (enterpriseSSOFlowType?.type === 'redirect') { return authenticateWithEnterpriseSSO(); } return navigate('factor-one'); + } case 'needs_second_factor': return navigate('factor-two'); case 'complete': @@ -253,7 +255,7 @@ function SignInStartInternal(): JSX.Element { // Keep the card in loading state during SSO redirect to prevent UI flicker // This is necessary because there's a brief delay between initiating the SSO flow // and the actual redirect to the external Identity Provider - const isRedirectingToSSOProvider = hasOnlyEnterpriseSSOFirstFactors(signIn); + const isRedirectingToSSOProvider = !!getEnterpriseSSOFlowType(signIn); if (isRedirectingToSSOProvider) { return; } @@ -381,12 +383,14 @@ function SignInStartInternal(): JSX.Element { await authenticateWithEnterpriseSSO(); } break; - case 'needs_first_factor': - if (hasOnlyEnterpriseSSOFirstFactors(res)) { + case 'needs_first_factor': { + const enterpriseSSOFlowType = getEnterpriseSSOFlowType(res); + if (enterpriseSSOFlowType?.type === 'redirect') { await authenticateWithEnterpriseSSO(); break; } return navigate('factor-one'); + } case 'needs_second_factor': return navigate('factor-two'); case 'complete': @@ -638,14 +642,6 @@ function SignInStartInternal(): JSX.Element { ); } -const hasOnlyEnterpriseSSOFirstFactors = (signIn: SignInResource): boolean => { - if (!signIn.supportedFirstFactors?.length) { - return false; - } - - return signIn.supportedFirstFactors.every(ff => ff.strategy === 'enterprise_sso'); -}; - const InstantPasswordRow = ({ field }: { field?: FormControlState<'password'> }) => { const [autofilled, setAutofilled] = useState(false); const ref = useRef(null); diff --git a/packages/clerk-js/src/ui/components/SignIn/shared.ts b/packages/clerk-js/src/ui/components/SignIn/shared.ts index f14c04c91b4..03696776ff7 100644 --- a/packages/clerk-js/src/ui/components/SignIn/shared.ts +++ b/packages/clerk-js/src/ui/components/SignIn/shared.ts @@ -1,5 +1,6 @@ import { isClerkRuntimeError, isUserLockedError } from '@clerk/shared/error'; import { useClerk } from '@clerk/shared/react'; +import type { SignInResource } from '@clerk/types'; import { useCallback, useEffect } from 'react'; import { useCardState } from '@/ui/elements/contexts'; @@ -62,4 +63,44 @@ function useHandleAuthenticateWithPasskey(onSecondFactor: () => Promise }, []); } -export { useHandleAuthenticateWithPasskey }; +/** + * Analyzes the sign-in's supported first factors to determine whether the user + * should be redirected directly to a single enterprise SSO provider or presented + * with a choice between multiple enterprise connections. + * + * @example + * ```typescript + * const flowType = getEnterpriseSSOFlowType(signIn); + * if (flowType?.type === 'redirect') { + * // Redirect user directly to SSO provider + * } else if (flowType?.type === 'choose') { + * // Show enterprise connection selection UI + * } + * ``` + */ +function getEnterpriseSSOFlowType(signIn: SignInResource): { type: 'redirect' | 'choose' } | undefined { + if (!signIn.supportedFirstFactors?.length) { + return; + } + + let hasEnterpriseConnectionsToChoose = false; + const hasEnterpriseSSOFactors = signIn.supportedFirstFactors.every(ff => { + if ('enterpriseConnection' in ff) { + hasEnterpriseConnectionsToChoose = true; + } + + return ff.strategy === 'enterprise_sso'; + }); + + if (!hasEnterpriseSSOFactors) { + return; + } + + if (hasEnterpriseConnectionsToChoose) { + return { type: 'choose' }; + } + + return { type: 'redirect' }; +} + +export { getEnterpriseSSOFlowType, useHandleAuthenticateWithPasskey }; diff --git a/packages/types/src/factors.ts b/packages/types/src/factors.ts index 38c1a4e0d12..feb9fe074e0 100644 --- a/packages/types/src/factors.ts +++ b/packages/types/src/factors.ts @@ -63,6 +63,10 @@ export type SamlFactor = { export type EnterpriseSSOFactor = { strategy: EnterpriseSSOStrategy; + enterpriseConnection?: { + id: string; + name: string; + }; }; export type TOTPFactor = { From eb91b6e6f60d8e4f5960fa13fac49d98b3fb5205 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Wed, 8 Oct 2025 19:50:46 -0300 Subject: [PATCH 02/10] Add component to list enterprise connections --- .../SignInChooseEnterpriseConnection.tsx | 122 ++++++++++++++++++ .../ui/components/SignIn/SignInFactorOne.tsx | 7 + .../ui/customizables/elementDescriptors.ts | 3 + .../src/ui/elements/contexts/index.tsx | 3 +- packages/localizations/src/en-US.ts | 4 + packages/types/src/appearance.ts | 3 + packages/types/src/localization.ts | 4 + 7 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx new file mode 100644 index 00000000000..4037cf5d6d8 --- /dev/null +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -0,0 +1,122 @@ +import type { Button } from '@/ui/customizables'; +import { descriptors, Flex, Flow, Grid, localizationKeys, SimpleButton, Spinner, Text } from '@/ui/customizables'; +import { Card } from '@/ui/elements/Card'; +import { useCardState } from '@/ui/elements/contexts'; +import { Header } from '@/ui/elements/Header'; +import type { PropsOfComponent } from '@/ui/styledSystem'; + +export const SignInChooseEnterpriseConnection = () => { + const card = useCardState(); + + const handleEnterpriseSSO = () => { + // TODO - Post sign-in with enterprise connection ID + }; + + return ( + + + + + + + + {card.error} + + + {mockEnterpriseConnections.map(({ id, name }) => ( + + ))} + + + + + + + ); +}; + +type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { + icon: React.ReactElement; + id: string; + label?: string; + isLoading: boolean; +}; + +const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButtonProps): JSX.Element => { + const { icon, isLoading, label } = props; + + return ( + [ + { + gap: theme.space.$4, + position: 'relative', + justifyContent: 'flex-start', + }, + props.sx, + ]} + > + + {(isLoading || icon) && ( + ({ flex: `0 0 ${theme.space.$4}` })} + > + {isLoading && ( + + )} + + )} + + {label} + + + + ); +}; + +// TODO - Replace with API response +const mockEnterpriseConnections = [ + { + id: 1, + name: 'Foo Corp 1', + }, + { + id: 2, + name: 'Foo Corp 2', + }, +]; diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx index 3a0634b7477..e5427ff567e 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx @@ -12,6 +12,8 @@ import { useAlternativeStrategies } from '../../hooks/useAlternativeStrategies'; import { localizationKeys } from '../../localization'; import { useRouter } from '../../router'; import { AlternativeMethods } from './AlternativeMethods'; +import { getEnterpriseSSOFlowType } from './shared'; +import { SignInChooseEnterpriseConnection } from './SignInChooseEnterpriseConnection'; import { SignInFactorOneAlternativePhoneCodeCard } from './SignInFactorOneAlternativePhoneCodeCard'; import { SignInFactorOneEmailCodeCard } from './SignInFactorOneEmailCodeCard'; import { SignInFactorOneEmailLinkCard } from './SignInFactorOneEmailLinkCard'; @@ -122,6 +124,11 @@ function SignInFactorOneInternal(): JSX.Element { prevCurrentFactor: prev.currentFactor, })); }; + + if (getEnterpriseSSOFlowType(signIn)?.type === 'choose') { + return ; + } + if (showAllStrategies || showForgotPasswordStrategies) { const canGoBack = factorHasLocalStrategy(currentFactor); diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index a48d622df94..14121369845 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -505,6 +505,9 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'subscriptionDetailsDetailRow', 'subscriptionDetailsDetailRowLabel', 'subscriptionDetailsDetailRowValue', + + 'chooseEnterpriseConnectionButton', + 'chooseEnterpriseConnectionButtonText', ] as const).map(camelize) as (keyof ElementsConfig)[]; type TargettableClassname = `${typeof CLASS_PREFIX}${K}`; diff --git a/packages/clerk-js/src/ui/elements/contexts/index.tsx b/packages/clerk-js/src/ui/elements/contexts/index.tsx index 40e4e82262e..23e0f17d97c 100644 --- a/packages/clerk-js/src/ui/elements/contexts/index.tsx +++ b/packages/clerk-js/src/ui/elements/contexts/index.tsx @@ -123,7 +123,8 @@ export type FlowMetadata = { | 'popover' | 'complete' | 'accountSwitcher' - | 'chooseOrganization'; + | 'chooseOrganization' + | 'chooseEnterpriseConnection'; }; const [FlowMetadataCtx, useFlowMetadata] = createContextAndHook('FlowMetadata'); diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index c44053ebee9..410176085bb 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -714,6 +714,10 @@ export const enUS: LocalizationResource = { subtitle: 'To continue, please enter the verification code generated by your authenticator app', title: 'Two-step verification', }, + chooseEnterpriseConnection: { + subtitle: 'Select the enterprise account with which you wish to continue.', + title: 'Choose your enterprise account', + }, }, signInEnterPasswordTitle: 'Enter your password', signUp: { diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index 4330d90b008..f16deff5848 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -640,6 +640,9 @@ export type ElementsConfig = { subscriptionDetailsDetailRow: WithOptions; subscriptionDetailsDetailRowLabel: WithOptions; subscriptionDetailsDetailRowValue: WithOptions; + + chooseEnterpriseConnectionButton: WithOptions; + chooseEnterpriseConnectionButtonText: WithOptions; }; export type Elements = { diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index a9050305ec5..58d3c556955 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -515,6 +515,10 @@ export type __internal_LocalizationResource = { action__addAccount: LocalizationValue; action__signOutAll: LocalizationValue; }; + chooseEnterpriseConnection: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; }; reverification: { password: { From 0685ba6b48adc08fde7299e73255b4fba2767ef9 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 11:59:51 -0300 Subject: [PATCH 03/10] Refactor sign-in supported first factors logic --- .../SignInChooseEnterpriseConnection.tsx | 101 +++++++++----- .../ui/components/SignIn/SignInFactorOne.tsx | 8 +- .../src/ui/components/SignIn/SignInStart.tsx | 28 ++-- .../src/ui/components/SignIn/shared.ts | 130 +++++++++--------- packages/types/src/factors.ts | 12 +- 5 files changed, 160 insertions(+), 119 deletions(-) diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx index 4037cf5d6d8..b413a2865d5 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -1,15 +1,37 @@ -import type { Button } from '@/ui/customizables'; +import type { ComponentType } from 'react'; + +import { withRedirect } from '@/ui/common'; +import { useCoreSignIn, useSignInContext } from '@/ui/contexts'; import { descriptors, Flex, Flow, Grid, localizationKeys, SimpleButton, Spinner, Text } from '@/ui/customizables'; import { Card } from '@/ui/elements/Card'; import { useCardState } from '@/ui/elements/contexts'; import { Header } from '@/ui/elements/Header'; -import type { PropsOfComponent } from '@/ui/styledSystem'; +import type { InternalTheme, PropsOfComponent } from '@/ui/styledSystem'; +import type { AvailableComponentProps } from '@/ui/types'; + +import { hasMultipleEnterpriseConnections } from './shared'; + +/** + * @experimental + */ +const SignInChooseEnterpriseConnectionInternal = () => { + const signIn = useCoreSignIn(); -export const SignInChooseEnterpriseConnection = () => { const card = useCardState(); - const handleEnterpriseSSO = () => { + if (!hasMultipleEnterpriseConnections(signIn.supportedFirstFactors)) { + // This should not happen due to the HOC guard, but provides type safety + return null; + } + + const enterpriseConnections = signIn.supportedFirstFactors.map(ff => ({ + id: ff.enterpriseConnectionId, + name: ff.enterpriseConnectionName, + })); + + const handleEnterpriseSSO = (connectionId: string) => { // TODO - Post sign-in with enterprise connection ID + console.log('Signing in with enterprise connection:', connectionId); }; return ( @@ -23,16 +45,16 @@ export const SignInChooseEnterpriseConnection = () => { {card.error} - {mockEnterpriseConnections.map(({ id, name }) => ( + {enterpriseConnections.map(({ id, name }) => ( handleEnterpriseSSO(id)} + isLoading={card.isLoading} /> ))} @@ -44,31 +66,32 @@ export const SignInChooseEnterpriseConnection = () => { ); }; -type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { - icon: React.ReactElement; +type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { id: string; label?: string; isLoading: boolean; + onClick: () => void; }; const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButtonProps): JSX.Element => { - const { icon, isLoading, label } = props; + const { isLoading, label, onClick, ...rest } = props; return ( [ + onClick={onClick} + {...rest} + sx={(theme: InternalTheme) => [ { gap: theme.space.$4, position: 'relative', justifyContent: 'flex-start', }, - props.sx, + (rest as any).sx, ]} > - {(isLoading || icon) && ( + {isLoading && ( ({ flex: `0 0 ${theme.space.$4}` })} + sx={(theme: InternalTheme) => ({ flex: `0 0 ${theme.space.$4}` })} > - {isLoading && ( - - )} + )} (Component: ComponentType

) => { + const displayName = Component.displayName || Component.name || 'Component'; + Component.displayName = displayName; + + const HOC = (props: P) => { + const signIn = useCoreSignIn(); + const signInCtx = useSignInContext(); + + return withRedirect( + Component, + () => !hasMultipleEnterpriseConnections(signIn.supportedFirstFactors), + ({ clerk }) => signInCtx.signInUrl || clerk.buildSignInUrl(), + 'There are no enterprise connections available to sign-in. Clerk is redirecting to the `signInUrl` URL instead.', + )(props); + }; + + HOC.displayName = `withEnterpriseConnectionsGuard(${displayName})`; + + return HOC; +}; + +export const SignInChooseEnterpriseConnection = withEnterpriseConnectionsGuard( + SignInChooseEnterpriseConnectionInternal, +); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx index e5427ff567e..7f6a9a77467 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx @@ -12,7 +12,7 @@ import { useAlternativeStrategies } from '../../hooks/useAlternativeStrategies'; import { localizationKeys } from '../../localization'; import { useRouter } from '../../router'; import { AlternativeMethods } from './AlternativeMethods'; -import { getEnterpriseSSOFlowType } from './shared'; +import { hasMultipleEnterpriseConnections } from './shared'; import { SignInChooseEnterpriseConnection } from './SignInChooseEnterpriseConnection'; import { SignInFactorOneAlternativePhoneCodeCard } from './SignInFactorOneAlternativePhoneCodeCard'; import { SignInFactorOneEmailCodeCard } from './SignInFactorOneEmailCodeCard'; @@ -125,7 +125,11 @@ function SignInFactorOneInternal(): JSX.Element { })); }; - if (getEnterpriseSSOFlowType(signIn)?.type === 'choose') { + /** + * Prompt to choose between a list of enterprise connections as supported first factors + * @experimental + */ + if (hasMultipleEnterpriseConnections(signIn.supportedFirstFactors)) { return ; } diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index f508cc6c0bf..6b0dd0cc8a9 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -39,7 +39,7 @@ import { useLoadingStatus } from '../../hooks'; import { useSupportEmail } from '../../hooks/useSupportEmail'; import { useRouter } from '../../router'; import { handleCombinedFlowTransfer } from './handleCombinedFlowTransfer'; -import { getEnterpriseSSOFlowType, useHandleAuthenticateWithPasskey } from './shared'; +import { hasMultipleEnterpriseConnections, useHandleAuthenticateWithPasskey } from './shared'; import { SignInAlternativePhoneCodePhoneNumberCard } from './SignInAlternativePhoneCodePhoneNumberCard'; import { SignInSocialButtons } from './SignInSocialButtons'; import { @@ -225,12 +225,11 @@ function SignInStartInternal(): JSX.Element { .then(res => { switch (res.status) { case 'needs_first_factor': { - const enterpriseSSOFlowType = getEnterpriseSSOFlowType(res); - if (enterpriseSSOFlowType?.type === 'redirect') { - return authenticateWithEnterpriseSSO(); + if (!hasOnlyEnterpriseSSOFirstFactors(res) || hasMultipleEnterpriseConnections(res.supportedFirstFactors)) { + return navigate('factor-one'); } - return navigate('factor-one'); + return authenticateWithEnterpriseSSO(); } case 'needs_second_factor': return navigate('factor-two'); @@ -255,7 +254,7 @@ function SignInStartInternal(): JSX.Element { // Keep the card in loading state during SSO redirect to prevent UI flicker // This is necessary because there's a brief delay between initiating the SSO flow // and the actual redirect to the external Identity Provider - const isRedirectingToSSOProvider = !!getEnterpriseSSOFlowType(signIn); + const isRedirectingToSSOProvider = !!hasOnlyEnterpriseSSOFirstFactors(signIn); if (isRedirectingToSSOProvider) { return; } @@ -384,12 +383,11 @@ function SignInStartInternal(): JSX.Element { } break; case 'needs_first_factor': { - const enterpriseSSOFlowType = getEnterpriseSSOFlowType(res); - if (enterpriseSSOFlowType?.type === 'redirect') { - await authenticateWithEnterpriseSSO(); - break; + if (!hasOnlyEnterpriseSSOFirstFactors(res) || hasMultipleEnterpriseConnections(res.supportedFirstFactors)) { + return navigate('factor-one'); } - return navigate('factor-one'); + + return authenticateWithEnterpriseSSO(); } case 'needs_second_factor': return navigate('factor-two'); @@ -642,6 +640,14 @@ function SignInStartInternal(): JSX.Element { ); } +const hasOnlyEnterpriseSSOFirstFactors = (signIn: SignInResource): boolean => { + if (!signIn.supportedFirstFactors?.length) { + return false; + } + + return signIn.supportedFirstFactors.every(ff => ff.strategy === 'enterprise_sso'); +}; + const InstantPasswordRow = ({ field }: { field?: FormControlState<'password'> }) => { const [autofilled, setAutofilled] = useState(false); const ref = useRef(null); diff --git a/packages/clerk-js/src/ui/components/SignIn/shared.ts b/packages/clerk-js/src/ui/components/SignIn/shared.ts index 03696776ff7..d4bd1a02706 100644 --- a/packages/clerk-js/src/ui/components/SignIn/shared.ts +++ b/packages/clerk-js/src/ui/components/SignIn/shared.ts @@ -1,6 +1,6 @@ import { isClerkRuntimeError, isUserLockedError } from '@clerk/shared/error'; import { useClerk } from '@clerk/shared/react'; -import type { SignInResource } from '@clerk/types'; +import type { EnterpriseSSOFactor, SignInFirstFactor } from '@clerk/types'; import { useCallback, useEffect } from 'react'; import { useCardState } from '@/ui/elements/contexts'; @@ -25,82 +25,76 @@ function useHandleAuthenticateWithPasskey(onSecondFactor: () => Promise }; }, []); - return useCallback(async (...args: Parameters) => { - try { - const res = await authenticateWithPasskey(...args); - switch (res.status) { - case 'complete': - return setActive({ - session: res.createdSessionId, - navigate: async ({ session }) => { - await navigateOnSetActive({ session, redirectUrl: afterSignInUrl }); - }, - }); - case 'needs_second_factor': - return onSecondFactor(); - default: - return console.error(clerkInvalidFAPIResponse(res.status, supportEmail)); - } - } catch (err) { - const { flow } = args[0] || {}; - - if (isClerkRuntimeError(err)) { - // In any case if the call gets aborted we should skip showing an error. This prevents updating the state of unmounted components. - if (err.code === 'passkey_operation_aborted') { - return; + return useCallback( + async (...args: Parameters) => { + try { + const res = await authenticateWithPasskey(...args); + switch (res.status) { + case 'complete': + return setActive({ + session: res.createdSessionId, + navigate: async ({ session }) => { + await navigateOnSetActive({ session, redirectUrl: afterSignInUrl }); + }, + }); + case 'needs_second_factor': + return onSecondFactor(); + default: + return console.error(clerkInvalidFAPIResponse(res.status, supportEmail)); } - // In case of autofill, if retrieval of credentials is cancelled by the user avoid showing errors as it results to pour UX. - if (flow === 'autofill' && err.code === 'passkey_retrieval_cancelled') { - return; + } catch (err) { + const { flow } = args[0] || {}; + + if (isClerkRuntimeError(err)) { + // In any case if the call gets aborted we should skip showing an error. This prevents updating the state of unmounted components. + if (err.code === 'passkey_operation_aborted') { + return; + } + // In case of autofill, if retrieval of credentials is cancelled by the user avoid showing errors as it results to pour UX. + if (flow === 'autofill' && err.code === 'passkey_retrieval_cancelled') { + return; + } } - } - if (isUserLockedError(err)) { - return __internal_navigateWithError('..', err.errors[0]); + if (isUserLockedError(err)) { + return __internal_navigateWithError('..', err.errors[0]); + } + handleError(err, [], card.setError); } - handleError(err, [], card.setError); - } - }, []); + }, + [ + authenticateWithPasskey, + setActive, + navigateOnSetActive, + afterSignInUrl, + onSecondFactor, + supportEmail, + __internal_navigateWithError, + card.setError, + ], + ); } /** - * Analyzes the sign-in's supported first factors to determine whether the user - * should be redirected directly to a single enterprise SSO provider or presented - * with a choice between multiple enterprise connections. - * - * @example - * ```typescript - * const flowType = getEnterpriseSSOFlowType(signIn); - * if (flowType?.type === 'redirect') { - * // Redirect user directly to SSO provider - * } else if (flowType?.type === 'choose') { - * // Show enterprise connection selection UI - * } - * ``` + * Type guard that checks if all factors in the array are enterprise SSO factors + * with both `enterpriseConnectionId` and `enterpriseConnectionName` properties. + * This is used to determine if the user should be presented with a choice + * between multiple enterprise connections. + * @experimental */ -function getEnterpriseSSOFlowType(signIn: SignInResource): { type: 'redirect' | 'choose' } | undefined { - if (!signIn.supportedFirstFactors?.length) { - return; - } - - let hasEnterpriseConnectionsToChoose = false; - const hasEnterpriseSSOFactors = signIn.supportedFirstFactors.every(ff => { - if ('enterpriseConnection' in ff) { - hasEnterpriseConnectionsToChoose = true; - } - - return ff.strategy === 'enterprise_sso'; - }); - - if (!hasEnterpriseSSOFactors) { - return; - } - - if (hasEnterpriseConnectionsToChoose) { - return { type: 'choose' }; +function hasMultipleEnterpriseConnections( + factors: SignInFirstFactor[] | null, +): factors is Array { + if (!factors?.length) { + return false; } - return { type: 'redirect' }; + return factors.every( + factor => + factor.strategy === 'enterprise_sso' && + 'enterpriseConnectionId' in factor && + 'enterpriseConnectionName' in factor, + ); } -export { getEnterpriseSSOFlowType, useHandleAuthenticateWithPasskey }; +export { hasMultipleEnterpriseConnections, useHandleAuthenticateWithPasskey }; diff --git a/packages/types/src/factors.ts b/packages/types/src/factors.ts index feb9fe074e0..72632aadec1 100644 --- a/packages/types/src/factors.ts +++ b/packages/types/src/factors.ts @@ -63,10 +63,14 @@ export type SamlFactor = { export type EnterpriseSSOFactor = { strategy: EnterpriseSSOStrategy; - enterpriseConnection?: { - id: string; - name: string; - }; + /** + * @experimental + */ + enterpriseConnectionId?: string; + /** + * @experimental + */ + enterpriseConnectionName?: string; }; export type TOTPFactor = { From 030015b23903391d5c6b46a494b83212a4ed52a9 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:47:41 -0300 Subject: [PATCH 04/10] Add resource for enterprise connection --- .../clerk-js/src/core/resources/SignUp.ts | 34 +++++++++++++++++++ .../src/ui/components/SignUp/SignUpStart.tsx | 16 ++++++++- packages/types/src/json.ts | 9 +++++ packages/types/src/signUp.ts | 9 +++++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 02e23aae5f4..85fe9d1f550 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -16,6 +16,8 @@ import type { PrepareWeb3WalletVerificationParams, SignUpAuthenticateWithWeb3Params, SignUpCreateParams, + SignUpEnterpriseConnectionJSON, + SignUpEnterpriseConnectionResource, SignUpField, SignUpFutureCreateParams, SignUpFutureEmailCodeVerifyParams, @@ -551,6 +553,17 @@ export class SignUp extends BaseResource implements SignUpResource { return false; } + + public __experimental_getEnterpriseConnections(): Promise { + return BaseResource._fetch({ + path: `/client/sign_ups/${this.id}/enterprise_connections`, + method: 'GET', + }).then(res => { + const enterpriseConnections = res?.response as unknown as SignUpEnterpriseConnectionJSON[]; + + return enterpriseConnections.map(enterpriseConnection => new SignUpEnterpriseConnection(enterpriseConnection)); + }); + } } class SignUpFuture implements SignUpFutureResource { @@ -889,3 +902,24 @@ class SignUpFuture implements SignUpFutureResource { }); } } + +class SignUpEnterpriseConnection extends BaseResource implements SignUpEnterpriseConnectionResource { + id!: string; + name!: string; + logoPublicUrl!: string | null; + + constructor(data: SignUpEnterpriseConnectionJSON) { + super(); + this.fromJSON(data); + } + + protected fromJSON(data: SignUpEnterpriseConnectionJSON | null): this { + if (data) { + this.id = data.id; + this.name = data.name; + this.logoPublicUrl = data.logo_public_url; + } + + return this; + } +} diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index ffc1e0c2dd9..457aefac599 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -3,6 +3,7 @@ import { useClerk } from '@clerk/shared/react'; import type { PhoneCodeChannel, PhoneCodeChannelData, SignUpResource } from '@clerk/types'; import React from 'react'; +import { isClerkAPIResponseError } from '@/index.headless'; import { Card } from '@/ui/elements/Card'; import { useCardState, withCardStateProvider } from '@/ui/elements/contexts'; import { Header } from '@/ui/elements/Header'; @@ -355,7 +356,20 @@ function SignUpStartInternal(): JSX.Element { oidcPrompt, }), ) - .catch(err => handleError(err, fieldsToSubmit, card.setError)) + .catch(err => { + /** + * @experimental + */ + if ( + isClerkAPIResponseError(err) && + err.errors?.[0]?.code === 'enterprise_connection_id_is_required_when_multiple_connections' + ) { + // TODO - navigate to enterprise connections screen + return; + } + + return handleError(err, fieldsToSubmit, card.setError); + }) .finally(() => card.setIdle()); }; diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index 3714242dc53..39a6428febb 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -140,6 +140,15 @@ export interface SignUpJSON extends ClerkResourceJSON { verifications: SignUpVerificationsJSON | null; } +/** + * @experimental + */ +export interface SignUpEnterpriseConnectionJSON extends ClerkResourceJSON { + id: string; + name: string; + logo_public_url: string | null; +} + export interface SessionJSON extends ClerkResourceJSON { object: 'session'; id: string; diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index f6c9e9bd48b..b4769e229b4 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -114,3 +114,12 @@ export interface SignUpResource extends ClerkResource { */ __internal_future: SignUpFutureResource; } + +/** + * @experimental + */ +export interface SignUpEnterpriseConnectionResource extends ClerkResource { + id: string; + name: string; + logoPublicUrl: string | null; +} From bd48187c3078a665d3d4833d7932109d632cb653 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:27:38 -0300 Subject: [PATCH 05/10] Display enterprise connections for sign-up --- .../clerk-js/src/core/resources/SignUp.ts | 2 - .../common/ChooseEnterpriseConnectionCard.tsx | 117 ++++++++++++++++++ .../SignInChooseEnterpriseConnection.tsx | 103 ++------------- .../src/ui/components/SignIn/shared.ts | 76 +++++------- .../SignUpChooseEnterpriseConnection.tsx | 36 ++++++ .../src/ui/components/SignUp/SignUpStart.tsx | 5 +- .../src/ui/components/SignUp/index.tsx | 4 + packages/types/src/json.ts | 1 - packages/types/src/localization.ts | 4 + packages/types/src/signUp.ts | 6 +- 10 files changed, 207 insertions(+), 147 deletions(-) create mode 100644 packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx create mode 100644 packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index 85fe9d1f550..b63a12fdd38 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -906,7 +906,6 @@ class SignUpFuture implements SignUpFutureResource { class SignUpEnterpriseConnection extends BaseResource implements SignUpEnterpriseConnectionResource { id!: string; name!: string; - logoPublicUrl!: string | null; constructor(data: SignUpEnterpriseConnectionJSON) { super(); @@ -917,7 +916,6 @@ class SignUpEnterpriseConnection extends BaseResource implements SignUpEnterpris if (data) { this.id = data.id; this.name = data.name; - this.logoPublicUrl = data.logo_public_url; } return this; diff --git a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx new file mode 100644 index 00000000000..309d1a9fda4 --- /dev/null +++ b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx @@ -0,0 +1,117 @@ +import type { LocalizationKey } from '@/ui/customizables'; +import { descriptors, Flex, Grid, SimpleButton, Spinner, Text } from '@/ui/customizables'; +import { Card } from '@/ui/elements/Card'; +import { useCardState } from '@/ui/elements/contexts'; +import { Header } from '@/ui/elements/Header'; +import type { InternalTheme, PropsOfComponent } from '@/ui/styledSystem'; + +type ChooseEnterpriseConnectionCardProps = { + title: LocalizationKey; + subtitle: LocalizationKey; + onClick: (id: string) => Promise; + enterpriseConnections: Array<{ id: string; name: string }>; +}; + +/** + * @experimental + */ +export const ChooseEnterpriseConnectionCard = ({ + title, + subtitle, + onClick, + enterpriseConnections, +}: ChooseEnterpriseConnectionCardProps) => { + const card = useCardState(); + + return ( + + + + + + + {card.error} + + + {enterpriseConnections?.map(({ id, name }) => ( + + ))} + + + + + + ); +}; + +type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { + id: string; + label?: string; + isLoading: boolean; + onClick: () => void; +}; + +const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButtonProps): JSX.Element => { + const { isLoading, label, onClick, ...rest } = props; + + return ( + [ + { + gap: theme.space.$4, + position: 'relative', + justifyContent: 'flex-start', + }, + (rest as any).sx, + ]} + > + + {isLoading && ( + ({ flex: `0 0 ${theme.space.$4}` })} + > + + + )} + + {label} + + + + ); +}; diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx index b413a2865d5..371e67bfa6d 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -2,11 +2,7 @@ import type { ComponentType } from 'react'; import { withRedirect } from '@/ui/common'; import { useCoreSignIn, useSignInContext } from '@/ui/contexts'; -import { descriptors, Flex, Flow, Grid, localizationKeys, SimpleButton, Spinner, Text } from '@/ui/customizables'; -import { Card } from '@/ui/elements/Card'; -import { useCardState } from '@/ui/elements/contexts'; -import { Header } from '@/ui/elements/Header'; -import type { InternalTheme, PropsOfComponent } from '@/ui/styledSystem'; +import { Flow, localizationKeys } from '@/ui/customizables'; import type { AvailableComponentProps } from '@/ui/types'; import { hasMultipleEnterpriseConnections } from './shared'; @@ -17,8 +13,6 @@ import { hasMultipleEnterpriseConnections } from './shared'; const SignInChooseEnterpriseConnectionInternal = () => { const signIn = useCoreSignIn(); - const card = useCardState(); - if (!hasMultipleEnterpriseConnections(signIn.supportedFirstFactors)) { // This should not happen due to the HOC guard, but provides type safety return null; @@ -36,99 +30,16 @@ const SignInChooseEnterpriseConnectionInternal = () => { return ( - - - - - - - {card.error} - - - {enterpriseConnections.map(({ id, name }) => ( - handleEnterpriseSSO(id)} - isLoading={card.isLoading} - /> - ))} - - - - - + ); }; -type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { - id: string; - label?: string; - isLoading: boolean; - onClick: () => void; -}; - -const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButtonProps): JSX.Element => { - const { isLoading, label, onClick, ...rest } = props; - - return ( - [ - { - gap: theme.space.$4, - position: 'relative', - justifyContent: 'flex-start', - }, - (rest as any).sx, - ]} - > - - {isLoading && ( - ({ flex: `0 0 ${theme.space.$4}` })} - > - - - )} - - {label} - - - - ); -}; - const withEnterpriseConnectionsGuard =

(Component: ComponentType

) => { const displayName = Component.displayName || Component.name || 'Component'; Component.displayName = displayName; diff --git a/packages/clerk-js/src/ui/components/SignIn/shared.ts b/packages/clerk-js/src/ui/components/SignIn/shared.ts index d4bd1a02706..41a5828d9b5 100644 --- a/packages/clerk-js/src/ui/components/SignIn/shared.ts +++ b/packages/clerk-js/src/ui/components/SignIn/shared.ts @@ -25,54 +25,42 @@ function useHandleAuthenticateWithPasskey(onSecondFactor: () => Promise }; }, []); - return useCallback( - async (...args: Parameters) => { - try { - const res = await authenticateWithPasskey(...args); - switch (res.status) { - case 'complete': - return setActive({ - session: res.createdSessionId, - navigate: async ({ session }) => { - await navigateOnSetActive({ session, redirectUrl: afterSignInUrl }); - }, - }); - case 'needs_second_factor': - return onSecondFactor(); - default: - return console.error(clerkInvalidFAPIResponse(res.status, supportEmail)); - } - } catch (err) { - const { flow } = args[0] || {}; + return useCallback(async (...args: Parameters) => { + try { + const res = await authenticateWithPasskey(...args); + switch (res.status) { + case 'complete': + return setActive({ + session: res.createdSessionId, + navigate: async ({ session }) => { + await navigateOnSetActive({ session, redirectUrl: afterSignInUrl }); + }, + }); + case 'needs_second_factor': + return onSecondFactor(); + default: + return console.error(clerkInvalidFAPIResponse(res.status, supportEmail)); + } + } catch (err) { + const { flow } = args[0] || {}; - if (isClerkRuntimeError(err)) { - // In any case if the call gets aborted we should skip showing an error. This prevents updating the state of unmounted components. - if (err.code === 'passkey_operation_aborted') { - return; - } - // In case of autofill, if retrieval of credentials is cancelled by the user avoid showing errors as it results to pour UX. - if (flow === 'autofill' && err.code === 'passkey_retrieval_cancelled') { - return; - } + if (isClerkRuntimeError(err)) { + // In any case if the call gets aborted we should skip showing an error. This prevents updating the state of unmounted components. + if (err.code === 'passkey_operation_aborted') { + return; } - - if (isUserLockedError(err)) { - return __internal_navigateWithError('..', err.errors[0]); + // In case of autofill, if retrieval of credentials is cancelled by the user avoid showing errors as it results to pour UX. + if (flow === 'autofill' && err.code === 'passkey_retrieval_cancelled') { + return; } - handleError(err, [], card.setError); } - }, - [ - authenticateWithPasskey, - setActive, - navigateOnSetActive, - afterSignInUrl, - onSecondFactor, - supportEmail, - __internal_navigateWithError, - card.setError, - ], - ); + + if (isUserLockedError(err)) { + return __internal_navigateWithError('..', err.errors[0]); + } + handleError(err, [], card.setError); + } + }, []); } /** diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx new file mode 100644 index 00000000000..b9ad4469bf0 --- /dev/null +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx @@ -0,0 +1,36 @@ +import { ChooseEnterpriseConnectionCard } from '@/ui/common/ChooseEnterpriseConnectionCard'; +import { useCoreSignUp } from '@/ui/contexts'; +import { Flow, localizationKeys } from '@/ui/customizables'; +import { LoadingCard } from '@/ui/elements/LoadingCard'; +import { useFetch } from '@/ui/hooks'; + +/** + * @experimental + */ +export const SignUpChooseEnterpriseConnection = () => { + const signUp = useCoreSignUp(); + const { data: enterpriseConnections, isLoading } = useFetch(signUp?.__experimental_getEnterpriseConnections, { + signUpId: signUp.id, + }); + + const handleEnterpriseSSO = (connectionId: string) => { + // TODO - Post sign-up with enterprise connection ID + console.log('Signing up with enterprise connection:', connectionId); + }; + + return ( + /* TODO - Add as a shared component to reuse between sign-in/sign-up */ + + {isLoading ? ( + + ) : ( + + )} + + ); +}; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 457aefac599..5aae24a1c86 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -362,10 +362,9 @@ function SignUpStartInternal(): JSX.Element { */ if ( isClerkAPIResponseError(err) && - err.errors?.[0]?.code === 'enterprise_connection_id_is_required_when_multiple_connections' + err.errors?.[0]?.code === 'enterprise_connection_id_is_required_with_multiple_connections' ) { - // TODO - navigate to enterprise connections screen - return; + return navigate('../choose-enterprise-connection'); } return handleError(err, fieldsToSubmit, card.setError); diff --git a/packages/clerk-js/src/ui/components/SignUp/index.tsx b/packages/clerk-js/src/ui/components/SignUp/index.tsx index a46993739fd..dd740178449 100644 --- a/packages/clerk-js/src/ui/components/SignUp/index.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/index.tsx @@ -9,6 +9,7 @@ import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowC import { SignUpContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts'; import { Flow } from '../../customizables'; import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router'; +import { SignUpChooseEnterpriseConnection } from './SignUpChooseEnterpriseConnection'; import { SignUpContinue } from './SignUpContinue'; import { SignUpSSOCallback } from './SignUpSSOCallback'; import { SignUpStart } from './SignUpStart'; @@ -82,6 +83,9 @@ function SignUpRoutes(): JSX.Element { + + + diff --git a/packages/types/src/json.ts b/packages/types/src/json.ts index 39a6428febb..dc1fdee75e5 100644 --- a/packages/types/src/json.ts +++ b/packages/types/src/json.ts @@ -146,7 +146,6 @@ export interface SignUpJSON extends ClerkResourceJSON { export interface SignUpEnterpriseConnectionJSON extends ClerkResourceJSON { id: string; name: string; - logo_public_url: string | null; } export interface SessionJSON extends ClerkResourceJSON { diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 58d3c556955..c22ba05556a 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -361,6 +361,10 @@ export type __internal_LocalizationResource = { label__onlyTermsOfService: LocalizationValue<'termsOfServiceLink'>; }; }; + chooseEnterpriseConnection: { + title: LocalizationValue; + subtitle: LocalizationValue; + }; }; signIn: { start: { diff --git a/packages/types/src/signUp.ts b/packages/types/src/signUp.ts index b4769e229b4..8cfcc7debce 100644 --- a/packages/types/src/signUp.ts +++ b/packages/types/src/signUp.ts @@ -113,6 +113,11 @@ export interface SignUpResource extends ClerkResource { * @internal */ __internal_future: SignUpFutureResource; + + /** + * @experimental + */ + __experimental_getEnterpriseConnections: () => Promise; } /** @@ -121,5 +126,4 @@ export interface SignUpResource extends ClerkResource { export interface SignUpEnterpriseConnectionResource extends ClerkResource { id: string; name: string; - logoPublicUrl: string | null; } From 9d2e414292b8aec3f412a5b310693a66112f95bc Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:54:43 -0300 Subject: [PATCH 06/10] Send enterprise connection ID on sign-in/sign-up --- .../common/ChooseEnterpriseConnectionCard.tsx | 6 ++-- .../SignInChooseEnterpriseConnection.tsx | 22 +++++++++--- .../SignUpChooseEnterpriseConnection.tsx | 35 ++++++++++++++----- .../ui/customizables/elementDescriptors.ts | 1 + packages/localizations/src/en-US.ts | 4 +++ packages/types/src/appearance.ts | 1 + packages/types/src/redirects.ts | 5 +++ 7 files changed, 57 insertions(+), 17 deletions(-) diff --git a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx index 309d1a9fda4..1586a84890a 100644 --- a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx +++ b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx @@ -8,7 +8,7 @@ import type { InternalTheme, PropsOfComponent } from '@/ui/styledSystem'; type ChooseEnterpriseConnectionCardProps = { title: LocalizationKey; subtitle: LocalizationKey; - onClick: (id: string) => Promise; + onClick: (id: string) => void; enterpriseConnections: Array<{ id: string; name: string }>; }; @@ -33,7 +33,7 @@ export const ChooseEnterpriseConnectionCard = ({ {card.error} {enterpriseConnections?.map(({ id, name }) => ( @@ -41,7 +41,7 @@ export const ChooseEnterpriseConnectionCard = ({ key={id} id={id} label={name} - onClick={onClick} + onClick={() => onClick(id)} isLoading={card.isLoading} /> ))} diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx index 371e67bfa6d..0041b04d910 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -1,7 +1,8 @@ import type { ComponentType } from 'react'; -import { withRedirect } from '@/ui/common'; -import { useCoreSignIn, useSignInContext } from '@/ui/contexts'; +import { buildSSOCallbackURL, withRedirect } from '@/ui/common'; +import { ChooseEnterpriseConnectionCard } from '@/ui/common/ChooseEnterpriseConnectionCard'; +import { useCoreSignIn, useEnvironment, useSignInContext } from '@/ui/contexts'; import { Flow, localizationKeys } from '@/ui/customizables'; import type { AvailableComponentProps } from '@/ui/types'; @@ -12,6 +13,8 @@ import { hasMultipleEnterpriseConnections } from './shared'; */ const SignInChooseEnterpriseConnectionInternal = () => { const signIn = useCoreSignIn(); + const ctx = useSignInContext(); + const { displayConfig } = useEnvironment(); if (!hasMultipleEnterpriseConnections(signIn.supportedFirstFactors)) { // This should not happen due to the HOC guard, but provides type safety @@ -23,9 +26,18 @@ const SignInChooseEnterpriseConnectionInternal = () => { name: ff.enterpriseConnectionName, })); - const handleEnterpriseSSO = (connectionId: string) => { - // TODO - Post sign-in with enterprise connection ID - console.log('Signing in with enterprise connection:', connectionId); + const handleEnterpriseSSO = (enterpriseConnectionId: string) => { + const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signInUrl); + const redirectUrlComplete = ctx.afterSignInUrl || '/'; + + return signIn.authenticateWithRedirect({ + strategy: 'enterprise_sso', + redirectUrl, + redirectUrlComplete, + oidcPrompt: ctx.oidcPrompt, + continueSignIn: true, + enterpriseConnectionId, + }); }; return ( diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx index b9ad4469bf0..9a015cc0df6 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx @@ -1,5 +1,5 @@ import { ChooseEnterpriseConnectionCard } from '@/ui/common/ChooseEnterpriseConnectionCard'; -import { useCoreSignUp } from '@/ui/contexts'; +import { useCoreSignUp, useSignUpContext } from '@/ui/contexts'; import { Flow, localizationKeys } from '@/ui/customizables'; import { LoadingCard } from '@/ui/elements/LoadingCard'; import { useFetch } from '@/ui/hooks'; @@ -9,28 +9,45 @@ import { useFetch } from '@/ui/hooks'; */ export const SignUpChooseEnterpriseConnection = () => { const signUp = useCoreSignUp(); + const ctx = useSignUpContext(); const { data: enterpriseConnections, isLoading } = useFetch(signUp?.__experimental_getEnterpriseConnections, { signUpId: signUp.id, }); - const handleEnterpriseSSO = (connectionId: string) => { - // TODO - Post sign-up with enterprise connection ID - console.log('Signing up with enterprise connection:', connectionId); + const handleEnterpriseSSO = (enterpriseConnectionId: string) => { + if (!signUp.emailAddress) { + return; + } + + const redirectUrl = ctx.ssoCallbackUrl; + const redirectUrlComplete = ctx.afterSignUpUrl || '/'; + + void signUp.authenticateWithRedirect({ + strategy: 'enterprise_sso', + identifier: signUp.emailAddress, + redirectUrl, + redirectUrlComplete, + continueSignUp: true, + enterpriseConnectionId, + }); }; + if (!isLoading && !enterpriseConnections?.length) { + return null; + } + return ( - /* TODO - Add as a shared component to reuse between sign-in/sign-up */ - {isLoading ? ( - - ) : ( + {enterpriseConnections?.length ? ( - )} + ) : isLoading ? ( + + ) : null} ); }; diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index 14121369845..73231131aae 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -506,6 +506,7 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'subscriptionDetailsDetailRowLabel', 'subscriptionDetailsDetailRowValue', + 'chooseEnterpriseConnectionsRoot', 'chooseEnterpriseConnectionButton', 'chooseEnterpriseConnectionButtonText', ] as const).map(camelize) as (keyof ElementsConfig)[]; diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 410176085bb..f1becd509c5 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -806,6 +806,10 @@ export const enUS: LocalizationResource = { title: 'Create your account', titleCombined: 'Create your account', }, + chooseEnterpriseConnection: { + subtitle: 'Select the enterprise account with which you wish to continue.', + title: 'Choose your enterprise account', + }, }, socialButtonsBlockButton: 'Continue with {{provider|titleize}}', socialButtonsBlockButtonManyInView: '{{provider|titleize}}', diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index f16deff5848..e2a255fc67b 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -641,6 +641,7 @@ export type ElementsConfig = { subscriptionDetailsDetailRowLabel: WithOptions; subscriptionDetailsDetailRowValue: WithOptions; + chooseEnterpriseConnectionsRoot: WithOptions; chooseEnterpriseConnectionButton: WithOptions; chooseEnterpriseConnectionButtonText: WithOptions; }; diff --git a/packages/types/src/redirects.ts b/packages/types/src/redirects.ts index 4b1e3833f22..5f81d7cce2a 100644 --- a/packages/types/src/redirects.ts +++ b/packages/types/src/redirects.ts @@ -91,6 +91,11 @@ export type AuthenticateWithRedirectParams = { * Optional for `oauth_` or `enterprise_sso` strategies. The value to pass to the [OIDC prompt parameter](https://openid.net/specs/openid-connect-core-1_0.html#:~:text=prompt,reauthentication%20and%20consent.) in the generated OAuth redirect URL. */ oidcPrompt?: string; + + /** + * @experimental + */ + enterpriseConnectionId?: string; }; export type AuthenticateWithPopupParams = AuthenticateWithRedirectParams & { popup: Window | null }; From 031fa4ca687e113c00f62bc3aa634bbf636829b0 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 14:57:16 -0300 Subject: [PATCH 07/10] Add changeset --- .changeset/upset-results-win.md | 7 +++++ .../clerk-js/src/core/resources/SignIn.ts | 5 +++- .../clerk-js/src/core/resources/SignUp.ts | 6 +++-- .../SignInChooseEnterpriseConnection.tsx | 5 +++- .../SignUpChooseEnterpriseConnection.tsx | 27 +++++++++++-------- .../src/ui/components/SignUp/SignUpStart.tsx | 2 +- packages/types/src/factors.ts | 4 +++ packages/types/src/signIn.ts | 11 ++++++++ 8 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 .changeset/upset-results-win.md diff --git a/.changeset/upset-results-win.md b/.changeset/upset-results-win.md new file mode 100644 index 00000000000..0b974cadc45 --- /dev/null +++ b/.changeset/upset-results-win.md @@ -0,0 +1,7 @@ +--- +'@clerk/localizations': minor +'@clerk/clerk-js': minor +'@clerk/types': minor +--- + +Introduce experimental step to choose enterprise connection on sign-in/sign-up diff --git a/packages/clerk-js/src/core/resources/SignIn.ts b/packages/clerk-js/src/core/resources/SignIn.ts index e72b9490851..cb7832dbb90 100644 --- a/packages/clerk-js/src/core/resources/SignIn.ts +++ b/packages/clerk-js/src/core/resources/SignIn.ts @@ -222,6 +222,7 @@ export class SignIn extends BaseResource implements SignInResource { redirectUrl: params.redirectUrl, actionCompleteRedirectUrl: params.actionCompleteRedirectUrl, oidcPrompt: params.oidcPrompt, + enterpriseConnectionId: params.enterpriseConnectionId, } as EnterpriseSSOConfig; break; default: @@ -308,7 +309,8 @@ export class SignIn extends BaseResource implements SignInResource { params: AuthenticateWithRedirectParams, navigateCallback: (url: URL | string) => void, ): Promise => { - const { strategy, redirectUrlComplete, identifier, oidcPrompt, continueSignIn } = params || {}; + const { strategy, redirectUrlComplete, identifier, oidcPrompt, continueSignIn, enterpriseConnectionId } = + params || {}; const actionCompleteRedirectUrl = redirectUrlComplete; const redirectUrl = SignIn.clerk.buildUrlWithAuth(params.redirectUrl); @@ -328,6 +330,7 @@ export class SignIn extends BaseResource implements SignInResource { redirectUrl, actionCompleteRedirectUrl, oidcPrompt, + enterpriseConnectionId, }); } diff --git a/packages/clerk-js/src/core/resources/SignUp.ts b/packages/clerk-js/src/core/resources/SignUp.ts index b63a12fdd38..fbc697a210f 100644 --- a/packages/clerk-js/src/core/resources/SignUp.ts +++ b/packages/clerk-js/src/core/resources/SignUp.ts @@ -387,6 +387,7 @@ export class SignUp extends BaseResource implements SignUpResource { emailAddress, legalAccepted, oidcPrompt, + enterpriseConnectionId, } = params; const redirectUrlWithAuthToken = SignUp.clerk.buildUrlWithAuth(redirectUrl); @@ -400,6 +401,7 @@ export class SignUp extends BaseResource implements SignUpResource { emailAddress, legalAccepted, oidcPrompt, + enterpriseConnectionId, }; return continueSignUp && this.id ? this.update(authParams) : this.create(authParams); }; @@ -554,7 +556,7 @@ export class SignUp extends BaseResource implements SignUpResource { return false; } - public __experimental_getEnterpriseConnections(): Promise { + __experimental_getEnterpriseConnections = (): Promise => { return BaseResource._fetch({ path: `/client/sign_ups/${this.id}/enterprise_connections`, method: 'GET', @@ -563,7 +565,7 @@ export class SignUp extends BaseResource implements SignUpResource { return enterpriseConnections.map(enterpriseConnection => new SignUpEnterpriseConnection(enterpriseConnection)); }); - } + }; } class SignUpFuture implements SignUpFutureResource { diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx index 0041b04d910..5e441296a26 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -1,3 +1,4 @@ +import { useClerk } from '@clerk/shared/react/index'; import type { ComponentType } from 'react'; import { buildSSOCallbackURL, withRedirect } from '@/ui/common'; @@ -12,10 +13,12 @@ import { hasMultipleEnterpriseConnections } from './shared'; * @experimental */ const SignInChooseEnterpriseConnectionInternal = () => { - const signIn = useCoreSignIn(); const ctx = useSignInContext(); const { displayConfig } = useEnvironment(); + const clerk = useClerk(); + const signIn = clerk.client.signIn; + if (!hasMultipleEnterpriseConnections(signIn.supportedFirstFactors)) { // This should not happen due to the HOC guard, but provides type safety return null; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx index 9a015cc0df6..f24ca1fb6f8 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx @@ -1,30 +1,28 @@ +import { useClerk } from '@clerk/shared/react/index'; + +import { withRedirectToAfterSignUp } from '@/ui/common'; import { ChooseEnterpriseConnectionCard } from '@/ui/common/ChooseEnterpriseConnectionCard'; -import { useCoreSignUp, useSignUpContext } from '@/ui/contexts'; +import { useSignUpContext } from '@/ui/contexts'; import { Flow, localizationKeys } from '@/ui/customizables'; +import { withCardStateProvider } from '@/ui/elements/contexts'; import { LoadingCard } from '@/ui/elements/LoadingCard'; import { useFetch } from '@/ui/hooks'; -/** - * @experimental - */ -export const SignUpChooseEnterpriseConnection = () => { - const signUp = useCoreSignUp(); +const SignUpChooseEnterpriseConnectionInternal = () => { + const clerk = useClerk(); const ctx = useSignUpContext(); + + const signUp = clerk.client.signUp; const { data: enterpriseConnections, isLoading } = useFetch(signUp?.__experimental_getEnterpriseConnections, { signUpId: signUp.id, }); const handleEnterpriseSSO = (enterpriseConnectionId: string) => { - if (!signUp.emailAddress) { - return; - } - const redirectUrl = ctx.ssoCallbackUrl; const redirectUrlComplete = ctx.afterSignUpUrl || '/'; void signUp.authenticateWithRedirect({ strategy: 'enterprise_sso', - identifier: signUp.emailAddress, redirectUrl, redirectUrlComplete, continueSignUp: true, @@ -51,3 +49,10 @@ export const SignUpChooseEnterpriseConnection = () => { ); }; + +/** + * @experimental + */ +export const SignUpChooseEnterpriseConnection = withRedirectToAfterSignUp( + withCardStateProvider(SignUpChooseEnterpriseConnectionInternal), +); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 5aae24a1c86..efc46a4c958 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -364,7 +364,7 @@ function SignUpStartInternal(): JSX.Element { isClerkAPIResponseError(err) && err.errors?.[0]?.code === 'enterprise_connection_id_is_required_with_multiple_connections' ) { - return navigate('../choose-enterprise-connection'); + return navigate('./choose-enterprise-connection'); } return handleError(err, fieldsToSubmit, card.setError); diff --git a/packages/types/src/factors.ts b/packages/types/src/factors.ts index 72632aadec1..407b5904538 100644 --- a/packages/types/src/factors.ts +++ b/packages/types/src/factors.ts @@ -124,6 +124,10 @@ export type EnterpriseSSOConfig = EnterpriseSSOFactor & { redirectUrl: string; actionCompleteRedirectUrl: string; oidcPrompt?: string; + /** + * @experimental + */ + enterpriseConnectionId?: string; }; export type PhoneCodeSecondFactorConfig = { diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index 6ffa69aa1fd..0281693d7d0 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -79,6 +79,12 @@ export interface SignInResource extends ClerkResource { createEmailLinkFlow: () => CreateEmailLinkFlowReturn; validatePassword: (password: string, callbacks?: ValidatePasswordCallbacks) => void; + + /** + * @experimental + */ + __experimental_getEnterpriseConnections: () => Promise; + /** * @internal */ @@ -106,3 +112,8 @@ export interface SignInJSON extends ClerkResourceJSON { second_factor_verification: VerificationJSON | null; created_session_id: string | null; } + +export interface SignInEnterpriseConnectionResource { + id: string; + name: string; +} From c33ba3691f9c8467e4f4efd9f0c088a1a6a75446 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:33:29 -0300 Subject: [PATCH 08/10] Improve loading state on connection click --- .../common/ChooseEnterpriseConnectionCard.tsx | 22 ++++++++++++------- .../SignInChooseEnterpriseConnection.tsx | 2 +- .../SignUpChooseEnterpriseConnection.tsx | 2 +- packages/types/src/signIn.ts | 11 ---------- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx index 1586a84890a..b6e8189f695 100644 --- a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx +++ b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react'; + import type { LocalizationKey } from '@/ui/customizables'; import { descriptors, Flex, Grid, SimpleButton, Spinner, Text } from '@/ui/customizables'; import { Card } from '@/ui/elements/Card'; @@ -8,7 +10,7 @@ import type { InternalTheme, PropsOfComponent } from '@/ui/styledSystem'; type ChooseEnterpriseConnectionCardProps = { title: LocalizationKey; subtitle: LocalizationKey; - onClick: (id: string) => void; + onClick: (id: string) => Promise; enterpriseConnections: Array<{ id: string; name: string }>; }; @@ -41,8 +43,7 @@ export const ChooseEnterpriseConnectionCard = ({ key={id} id={id} label={name} - onClick={() => onClick(id)} - isLoading={card.isLoading} + onClick={onClick} /> ))} @@ -53,15 +54,20 @@ export const ChooseEnterpriseConnectionCard = ({ ); }; -type ChooseEnterpriseConnectionButtonProps = PropsOfComponent & { +type ChooseEnterpriseConnectionButtonProps = Omit, 'onClick'> & { id: string; label?: string; - isLoading: boolean; - onClick: () => void; + onClick: (id: string) => Promise; }; const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButtonProps): JSX.Element => { - const { isLoading, label, onClick, ...rest } = props; + const { label, onClick, ...rest } = props; + const [isLoading, setIsLoading] = useState(false); + + const handleClick = () => { + setIsLoading(true); + void onClick(props.id).catch(() => setIsLoading(false)); + }; return ( [ { diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx index 5e441296a26..13212944022 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx @@ -67,7 +67,7 @@ const withEnterpriseConnectionsGuard =

(Compo Component, () => !hasMultipleEnterpriseConnections(signIn.supportedFirstFactors), ({ clerk }) => signInCtx.signInUrl || clerk.buildSignInUrl(), - 'There are no enterprise connections available to sign-in. Clerk is redirecting to the `signInUrl` URL instead.', + 'There are no enterprise connections available to sign-in. Clerk is redirecting to the `signInUrl` instead.', )(props); }; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx index f24ca1fb6f8..0fe209a45e4 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx @@ -21,7 +21,7 @@ const SignUpChooseEnterpriseConnectionInternal = () => { const redirectUrl = ctx.ssoCallbackUrl; const redirectUrlComplete = ctx.afterSignUpUrl || '/'; - void signUp.authenticateWithRedirect({ + return signUp.authenticateWithRedirect({ strategy: 'enterprise_sso', redirectUrl, redirectUrlComplete, diff --git a/packages/types/src/signIn.ts b/packages/types/src/signIn.ts index 0281693d7d0..6ffa69aa1fd 100644 --- a/packages/types/src/signIn.ts +++ b/packages/types/src/signIn.ts @@ -79,12 +79,6 @@ export interface SignInResource extends ClerkResource { createEmailLinkFlow: () => CreateEmailLinkFlowReturn; validatePassword: (password: string, callbacks?: ValidatePasswordCallbacks) => void; - - /** - * @experimental - */ - __experimental_getEnterpriseConnections: () => Promise; - /** * @internal */ @@ -112,8 +106,3 @@ export interface SignInJSON extends ClerkResourceJSON { second_factor_verification: VerificationJSON | null; created_session_id: string | null; } - -export interface SignInEnterpriseConnectionResource { - id: string; - name: string; -} From c899e3b4df3f4d03a0e28cb40ac8b923946349ec Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Thu, 9 Oct 2025 16:56:08 -0300 Subject: [PATCH 09/10] Bump bundle limit for sign-up --- packages/clerk-js/bundlewatch.config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/clerk-js/bundlewatch.config.json b/packages/clerk-js/bundlewatch.config.json index eb4717c9b6f..86aed3c552c 100644 --- a/packages/clerk-js/bundlewatch.config.json +++ b/packages/clerk-js/bundlewatch.config.json @@ -16,7 +16,7 @@ { "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" }, { "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" }, { "path": "./dist/signin*.js", "maxSize": "18KB" }, - { "path": "./dist/signup*.js", "maxSize": "8.86KB" }, + { "path": "./dist/signup*.js", "maxSize": "9.5KB" }, { "path": "./dist/userbutton*.js", "maxSize": "5KB" }, { "path": "./dist/userprofile*.js", "maxSize": "16KB" }, { "path": "./dist/userverification*.js", "maxSize": "5KB" }, From d2494ed242dd6ee320111e1b09c2df7918e1d452 Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:07:00 -0300 Subject: [PATCH 10/10] Address review comments --- .../__snapshots__/file-structure.test.ts.snap | 2 ++ .../ui/common/ChooseEnterpriseConnectionCard.tsx | 6 +++--- .../src/ui/components/SignIn/SignInFactorOne.tsx | 4 ++-- ...tsx => SignInFactorOneEnterpriseConnections.tsx} | 13 +++++++------ ...nnection.tsx => SignUpEnterpriseConnections.tsx} | 12 ++++++------ .../src/ui/components/SignUp/SignUpStart.tsx | 2 +- .../clerk-js/src/ui/components/SignUp/index.tsx | 6 +++--- .../src/ui/customizables/elementDescriptors.ts | 6 +++--- .../clerk-js/src/ui/elements/contexts/index.tsx | 2 +- packages/localizations/src/en-US.ts | 4 ++-- packages/types/src/appearance.ts | 6 +++--- packages/types/src/localization.ts | 4 ++-- 12 files changed, 35 insertions(+), 32 deletions(-) rename packages/clerk-js/src/ui/components/SignIn/{SignInChooseEnterpriseConnection.tsx => SignInFactorOneEnterpriseConnections.tsx} (83%) rename packages/clerk-js/src/ui/components/SignUp/{SignUpChooseEnterpriseConnection.tsx => SignUpEnterpriseConnections.tsx} (78%) diff --git a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap index 2a06a2e0d56..8db8c3f416e 100644 --- a/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap +++ b/.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap @@ -127,6 +127,8 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = ` "types/sign-in-signal-value.mdx", "types/sign-out.mdx", "types/sign-up-authenticate-with-metamask-params.mdx", + "types/sign-up-enterprise-connection-json.mdx", + "types/sign-up-enterprise-connection-resource.mdx", "types/sign-up-future-resource.mdx", "types/sign-up-resource.mdx", "types/signed-in-session-resource.mdx", diff --git a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx index b6e8189f695..73f3efb7b3c 100644 --- a/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx +++ b/packages/clerk-js/src/ui/common/ChooseEnterpriseConnectionCard.tsx @@ -35,7 +35,7 @@ export const ChooseEnterpriseConnectionCard = ({ {card.error} {enterpriseConnections?.map(({ id, name }) => ( @@ -71,7 +71,7 @@ const ChooseEnterpriseConnectionButton = (props: ChooseEnterpriseConnectionButto return ( )} ; + return ; } if (showAllStrategies || showForgotPasswordStrategies) { diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOneEnterpriseConnections.tsx similarity index 83% rename from packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx rename to packages/clerk-js/src/ui/components/SignIn/SignInFactorOneEnterpriseConnections.tsx index 13212944022..e5256e3a385 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOneEnterpriseConnections.tsx @@ -5,6 +5,7 @@ import { buildSSOCallbackURL, withRedirect } from '@/ui/common'; import { ChooseEnterpriseConnectionCard } from '@/ui/common/ChooseEnterpriseConnectionCard'; import { useCoreSignIn, useEnvironment, useSignInContext } from '@/ui/contexts'; import { Flow, localizationKeys } from '@/ui/customizables'; +import { withCardStateProvider } from '@/ui/elements/contexts'; import type { AvailableComponentProps } from '@/ui/types'; import { hasMultipleEnterpriseConnections } from './shared'; @@ -12,7 +13,7 @@ import { hasMultipleEnterpriseConnections } from './shared'; /** * @experimental */ -const SignInChooseEnterpriseConnectionInternal = () => { +const SignInFactorOneEnterpriseConnectionsInternal = () => { const ctx = useSignInContext(); const { displayConfig } = useEnvironment(); @@ -44,10 +45,10 @@ const SignInChooseEnterpriseConnectionInternal = () => { }; return ( - + @@ -76,6 +77,6 @@ const withEnterpriseConnectionsGuard =

(Compo return HOC; }; -export const SignInChooseEnterpriseConnection = withEnterpriseConnectionsGuard( - SignInChooseEnterpriseConnectionInternal, +export const SignInFactorOneEnterpriseConnections = withCardStateProvider( + withEnterpriseConnectionsGuard(SignInFactorOneEnterpriseConnectionsInternal), ); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpEnterpriseConnections.tsx similarity index 78% rename from packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx rename to packages/clerk-js/src/ui/components/SignUp/SignUpEnterpriseConnections.tsx index 0fe209a45e4..b2eea839ebb 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpChooseEnterpriseConnection.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpEnterpriseConnections.tsx @@ -8,7 +8,7 @@ import { withCardStateProvider } from '@/ui/elements/contexts'; import { LoadingCard } from '@/ui/elements/LoadingCard'; import { useFetch } from '@/ui/hooks'; -const SignUpChooseEnterpriseConnectionInternal = () => { +const SignUpEnterpriseConnectionsInternal = () => { const clerk = useClerk(); const ctx = useSignUpContext(); @@ -35,11 +35,11 @@ const SignUpChooseEnterpriseConnectionInternal = () => { } return ( - + {enterpriseConnections?.length ? ( @@ -53,6 +53,6 @@ const SignUpChooseEnterpriseConnectionInternal = () => { /** * @experimental */ -export const SignUpChooseEnterpriseConnection = withRedirectToAfterSignUp( - withCardStateProvider(SignUpChooseEnterpriseConnectionInternal), +export const SignUpEnterpriseConnections = withRedirectToAfterSignUp( + withCardStateProvider(SignUpEnterpriseConnectionsInternal), ); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index efc46a4c958..a178468d6b2 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -364,7 +364,7 @@ function SignUpStartInternal(): JSX.Element { isClerkAPIResponseError(err) && err.errors?.[0]?.code === 'enterprise_connection_id_is_required_with_multiple_connections' ) { - return navigate('./choose-enterprise-connection'); + return navigate('./enterprise-connections'); } return handleError(err, fieldsToSubmit, card.setError); diff --git a/packages/clerk-js/src/ui/components/SignUp/index.tsx b/packages/clerk-js/src/ui/components/SignUp/index.tsx index dd740178449..e5167b6743e 100644 --- a/packages/clerk-js/src/ui/components/SignUp/index.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/index.tsx @@ -9,8 +9,8 @@ import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowC import { SignUpContext, useSignUpContext, withCoreSessionSwitchGuard } from '../../contexts'; import { Flow } from '../../customizables'; import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router'; -import { SignUpChooseEnterpriseConnection } from './SignUpChooseEnterpriseConnection'; import { SignUpContinue } from './SignUpContinue'; +import { SignUpEnterpriseConnections } from './SignUpEnterpriseConnections'; import { SignUpSSOCallback } from './SignUpSSOCallback'; import { SignUpStart } from './SignUpStart'; import { SignUpVerifyEmail } from './SignUpVerifyEmail'; @@ -83,8 +83,8 @@ function SignUpRoutes(): JSX.Element { - - + + diff --git a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts index 73231131aae..206bc658c73 100644 --- a/packages/clerk-js/src/ui/customizables/elementDescriptors.ts +++ b/packages/clerk-js/src/ui/customizables/elementDescriptors.ts @@ -506,9 +506,9 @@ export const APPEARANCE_KEYS = containsAllElementsConfigKeys([ 'subscriptionDetailsDetailRowLabel', 'subscriptionDetailsDetailRowValue', - 'chooseEnterpriseConnectionsRoot', - 'chooseEnterpriseConnectionButton', - 'chooseEnterpriseConnectionButtonText', + 'enterpriseConnectionsRoot', + 'enterpriseConnectionButton', + 'enterpriseConnectionButtonText', ] as const).map(camelize) as (keyof ElementsConfig)[]; type TargettableClassname = `${typeof CLASS_PREFIX}${K}`; diff --git a/packages/clerk-js/src/ui/elements/contexts/index.tsx b/packages/clerk-js/src/ui/elements/contexts/index.tsx index 23e0f17d97c..aa6c5227d48 100644 --- a/packages/clerk-js/src/ui/elements/contexts/index.tsx +++ b/packages/clerk-js/src/ui/elements/contexts/index.tsx @@ -124,7 +124,7 @@ export type FlowMetadata = { | 'complete' | 'accountSwitcher' | 'chooseOrganization' - | 'chooseEnterpriseConnection'; + | 'enterpriseConnections'; }; const [FlowMetadataCtx, useFlowMetadata] = createContextAndHook('FlowMetadata'); diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index f1becd509c5..4b7089a8f7a 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -714,7 +714,7 @@ export const enUS: LocalizationResource = { subtitle: 'To continue, please enter the verification code generated by your authenticator app', title: 'Two-step verification', }, - chooseEnterpriseConnection: { + enterpriseConnections: { subtitle: 'Select the enterprise account with which you wish to continue.', title: 'Choose your enterprise account', }, @@ -806,7 +806,7 @@ export const enUS: LocalizationResource = { title: 'Create your account', titleCombined: 'Create your account', }, - chooseEnterpriseConnection: { + enterpriseConnections: { subtitle: 'Select the enterprise account with which you wish to continue.', title: 'Choose your enterprise account', }, diff --git a/packages/types/src/appearance.ts b/packages/types/src/appearance.ts index e2a255fc67b..e10f74badea 100644 --- a/packages/types/src/appearance.ts +++ b/packages/types/src/appearance.ts @@ -641,9 +641,9 @@ export type ElementsConfig = { subscriptionDetailsDetailRowLabel: WithOptions; subscriptionDetailsDetailRowValue: WithOptions; - chooseEnterpriseConnectionsRoot: WithOptions; - chooseEnterpriseConnectionButton: WithOptions; - chooseEnterpriseConnectionButtonText: WithOptions; + enterpriseConnectionsRoot: WithOptions; + enterpriseConnectionButton: WithOptions; + enterpriseConnectionButtonText: WithOptions; }; export type Elements = { diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index c22ba05556a..f007114741d 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -361,7 +361,7 @@ export type __internal_LocalizationResource = { label__onlyTermsOfService: LocalizationValue<'termsOfServiceLink'>; }; }; - chooseEnterpriseConnection: { + enterpriseConnections: { title: LocalizationValue; subtitle: LocalizationValue; }; @@ -519,7 +519,7 @@ export type __internal_LocalizationResource = { action__addAccount: LocalizationValue; action__signOutAll: LocalizationValue; }; - chooseEnterpriseConnection: { + enterpriseConnections: { title: LocalizationValue; subtitle: LocalizationValue; };