diff --git a/.changeset/fix-signin-factor-two-spinner.md b/.changeset/fix-signin-factor-two-spinner.md new file mode 100644 index 00000000000..a55b2122ffd --- /dev/null +++ b/.changeset/fix-signin-factor-two-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/packages/ui/src/components/SignIn/SignInFactorTwo.tsx b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx index caa771ed0e3..4cced739797 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,18 @@ function SignInFactorTwoInternal(): JSX.Element { toggleAllStrategies, } = useSecondFactorSelection(signIn.supportedSecondFactors); + React.useEffect(() => { + if (__internal_setActiveInProgress) { + return; + } + + // If the sign-in doesn't require second factor verification, + // redirect back to the start of the sign-in flow + if (signIn.status !== 'needs_second_factor') { + void router.navigate('../'); + } + }, [__internal_setActiveInProgress, signIn.status, router]); + if (!currentFactor) { return ; } diff --git a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx index 3d944954df9..63f9e05cf80 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx @@ -18,8 +18,17 @@ describe('SignInFactorTwo', () => { }); describe('Navigation', () => { - //This isn't yet implemented in the component - it.todo('navigates to SignInStart component if user lands on SignInFactorTwo page but they should not'); + it('navigates to SignInStart component if sign-in status is not needs_second_factor', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withEmailAddress(); + // Note: We do NOT call f.startSignInFactorTwo() here, so the sign-in status + // will be null/needs_identifier, not 'needs_second_factor' + }); + render(, { wrapper }); + await waitFor(() => { + expect(fixtures.router.navigate).toHaveBeenCalledWith('../'); + }); + }); }); describe('Submitting', () => {