diff --git a/src/pages/ValidateLoginPage/index.web.tsx b/src/pages/ValidateLoginPage/index.web.tsx index 11fdec054209..2d3f045b170e 100644 --- a/src/pages/ValidateLoginPage/index.web.tsx +++ b/src/pages/ValidateLoginPage/index.web.tsx @@ -62,7 +62,11 @@ function ValidateLoginPage({ credentials?.validateCode === validateCode && credentials?.accountID === Number(accountID) && (autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN || autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.FAILED); - const isUserClickedSignIn = !login && isSignedIn && (autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.SIGNING_IN || autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN); + // Exclude `exitTo` deep links (e.g. a workspace-chat invite link): those carry a specific + // destination that `handleExitToNavigation` owns, so this flag's Home redirect (focus effect + // below) must not fire and clobber it. + const isUserClickedSignIn = + !login && !exitTo && isSignedIn && (autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.SIGNING_IN || autoAuthStateWithDefault === CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN); const shouldStartSignInWithValidateCode = !isUserClickedSignIn && !isSignedIn && (!!login || !!exitTo) && isValidValidateCode(validateCode); const isNavigatingToExitTo = isSignedIn && !!exitTo; // Fresh-session magic-link sign-in. Not gated on `isSignedIn` because `autoAuthState` lands diff --git a/tests/ui/ValidateLoginPageTest.tsx b/tests/ui/ValidateLoginPageTest.tsx index 787f07239622..554e72e4ff82 100644 --- a/tests/ui/ValidateLoginPageTest.tsx +++ b/tests/ui/ValidateLoginPageTest.tsx @@ -156,6 +156,38 @@ describe('ValidateLoginPage', () => { expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.HOME, {forceReplace: true}); }); + it('Should hand off to exitTo (not redirect Home) for a first-time invitee opening an exitTo magic link', async () => { + // Regression for #94549: an invited member opens `/v//?exitTo=` with no + // cached `login` and JUST_SIGNED_IN. Before the fix `isUserClickedSignIn` matched this exactly and + // its focus effect force-redirected Home, clobbering the exitTo navigation. Excluding `exitTo` + // keeps that Home redirect from firing so `handleExitToNavigation` owns the deep-link destination. + await act(async () => { + await Onyx.set(ONYXKEYS.SESSION, { + authToken: 'abcd', + autoAuthState: CONST.AUTO_AUTH_STATE.JUST_SIGNED_IN, + }); + await Onyx.set(ONYXKEYS.CREDENTIALS, { + accountID: 1, + validateCode: '123456', + }); + }); + + renderPage({accountID: '1', validateCode: '123456', exitTo: 'concierge'}); + await waitForBatchedUpdatesWithAct(); + + // The deferred destination handoff is registered with the exitTo route... + expect(handleExitToNavigation).toHaveBeenCalledWith('concierge'); + + // ...and even once protected routes become available, the competing Home redirect must not fire. + await act(async () => { + mockWaitForProtectedRoutes.resolve(); + await Promise.resolve(); + }); + await waitForBatchedUpdatesWithAct(); + + expect(Navigation.navigate).not.toHaveBeenCalledWith(ROUTES.HOME, {forceReplace: true}); + }); + it('Should not navigate to home when a signed-in session opens /v/ to view the code (autoAuthState !== JUST_SIGNED_IN)', async () => { await act(async () => { await Onyx.set(ONYXKEYS.SESSION, {