diff --git a/.changeset/fix-factor-two-infinite-spinner.md b/.changeset/fix-factor-two-infinite-spinner.md new file mode 100644 index 00000000000..284b7617613 --- /dev/null +++ b/.changeset/fix-factor-two-infinite-spinner.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Fix infinite loading spinner when navigating to factor-two sign-in route without an active 2FA session diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx index 61b94f57262..a082a03b183 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx @@ -1,3 +1,4 @@ +import { useClerk } from '@clerk/shared/react'; import type { SignInFactor } from '@clerk/shared/types'; import React from 'react'; @@ -6,6 +7,7 @@ import { LoadingCard } from '@/ui/elements/LoadingCard'; import { withRedirectToAfterSignIn, withRedirectToSignInTask } from '../../common'; import { useCoreSignIn } from '../../contexts'; +import { useRouter } from '../../router'; import { SignInFactorTwoAlternativeMethods } from './SignInFactorTwoAlternativeMethods'; import { SignInFactorTwoBackupCodeCard } from './SignInFactorTwoBackupCodeCard'; import { SignInFactorTwoEmailCodeCard } from './SignInFactorTwoEmailCodeCard'; @@ -26,7 +28,9 @@ const factorKey = (factor: SignInFactor | null | undefined) => { }; function SignInFactorTwoInternal(): JSX.Element { + const { __internal_setActiveInProgress } = useClerk(); const signIn = useCoreSignIn(); + const router = useRouter(); const availableFactors = signIn.supportedSecondFactors; const lastPreparedFactorKeyRef = React.useRef(''); @@ -45,6 +49,19 @@ function SignInFactorTwoInternal(): JSX.Element { toggleAllStrategies(); }; + React.useEffect(() => { + if (__internal_setActiveInProgress) { + return; + } + + // If the sign-in was reset or doesn't exist, redirect back to the start. + // Don't redirect for 'complete' status - setActive will handle navigation. + if (signIn.status === null || signIn.status === 'needs_identifier' || signIn.status === 'needs_first_factor') { + void router.navigate('../'); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- Match SignInFactorOne pattern: only run on mount and when setActiveInProgress changes + }, [__internal_setActiveInProgress]); + if (!currentFactor) { return ; }