diff --git a/.changeset/fix-factor-two-infinite-spinner.md b/.changeset/fix-factor-two-infinite-spinner.md new file mode 100644 index 00000000000..a55b2122ffd --- /dev/null +++ b/.changeset/fix-factor-two-infinite-spinner.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Fix infinite loading spinner when navigating to factor-two sign-in route without an active 2FA session diff --git a/commitlint.config.ts b/commitlint.config.ts index 5448a7e00d0..58cd2776348 100644 --- a/commitlint.config.ts +++ b/commitlint.config.ts @@ -31,7 +31,7 @@ const Configuration = { 'body-max-line-length': [1, 'always', '150'], 'scope-empty': [2, 'never'], 'scope-enum': [2, 'always', [...getPackageNames(), 'repo', 'release', 'e2e', '*', 'ci']], - 'subject-case': [2, 'always', ['camel-case', 'lower-case', 'sentence-case']], + 'subject-case': [1, 'always', ['camel-case', 'lower-case', 'sentence-case']], }, }; diff --git a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx index caa771ed0e3..b3c2688f97c 100644 --- a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx +++ b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx @@ -1,8 +1,12 @@ +import { useClerk } from '@clerk/shared/react'; +import React from 'react'; + import { withCardStateProvider } from '@/ui/elements/contexts'; 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'; @@ -12,7 +16,9 @@ import { SignInFactorTwoTOTPCard } from './SignInFactorTwoTOTPCard'; import { useSecondFactorSelection } from './useSecondFactorSelection'; function SignInFactorTwoInternal(): JSX.Element { + const { __internal_setActiveInProgress } = useClerk(); const signIn = useCoreSignIn(); + const router = useRouter(); const { currentFactor, factorAlreadyPrepared, @@ -22,6 +28,19 @@ function SignInFactorTwoInternal(): JSX.Element { toggleAllStrategies, } = useSecondFactorSelection(signIn.supportedSecondFactors); + 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 ; }