diff --git a/src/App.tsx b/src/App.tsx index 0e247d5faa53..a2d353a026af 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackgro import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; +import InitialURLContextProvider from './components/InitialURLContextProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; @@ -30,12 +31,11 @@ import {WindowDimensionsProvider} from './components/withWindowDimensions'; import Expensify from './Expensify'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; -import InitialUrlContext from './libs/InitialUrlContext'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; type AppProps = { - /** If we have an authToken this is true */ + /** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ url?: Route; }; @@ -52,7 +52,7 @@ function App({url}: AppProps) { useDefaultDragAndDrop(); OnyxUpdateManager(); return ( - + - + ); } diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx new file mode 100644 index 000000000000..a3df93844ca9 --- /dev/null +++ b/src/components/InitialURLContextProvider.tsx @@ -0,0 +1,33 @@ +import React, {createContext, useEffect, useState} from 'react'; +import type {ReactNode} from 'react'; +import {Linking} from 'react-native'; +import type {Route} from '@src/ROUTES'; + +/** Initial url that will be opened when NewDot is embedded into Hybrid App. */ +const InitialURLContext = createContext(undefined); + +type InitialURLContextProviderProps = { + /** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */ + url?: Route; + + /** Children passed to the context provider */ + children: ReactNode; +}; + +function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) { + const [initialURL, setInitialURL] = useState(url); + useEffect(() => { + if (initialURL) { + return; + } + Linking.getInitialURL().then((initURL) => { + setInitialURL(initURL as Route); + }); + }, [initialURL]); + return {children}; +} + +InitialURLContextProvider.displayName = 'InitialURLContextProvider'; + +export default InitialURLContextProvider; +export {InitialURLContext}; diff --git a/src/libs/InitialUrlContext/index.ts b/src/libs/InitialUrlContext/index.ts deleted file mode 100644 index a87417fe4cc6..000000000000 --- a/src/libs/InitialUrlContext/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import {createContext} from 'react'; -import type {Route} from '@src/ROUTES'; - -/** Initial url that will be opened when NewDot is embedded into Hybrid App. */ -const InitialUrlContext = createContext(undefined); - -export default InitialUrlContext; diff --git a/src/libs/Navigation/AppNavigator/index.tsx b/src/libs/Navigation/AppNavigator/index.tsx index 24f0f42c5d64..9729a2f812ce 100644 --- a/src/libs/Navigation/AppNavigator/index.tsx +++ b/src/libs/Navigation/AppNavigator/index.tsx @@ -1,6 +1,6 @@ import React, {useContext, useEffect} from 'react'; import {NativeModules} from 'react-native'; -import InitialUrlContext from '@libs/InitialUrlContext'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; import Navigation from '@libs/Navigation/Navigation'; type AppNavigatorProps = { @@ -9,7 +9,7 @@ type AppNavigatorProps = { }; function AppNavigator({authenticated}: AppNavigatorProps) { - const initUrl = useContext(InitialUrlContext); + const initUrl = useContext(InitialURLContext); useEffect(() => { if (!NativeModules.HybridAppModule || !initUrl) { diff --git a/src/pages/LogOutPreviousUserPage.tsx b/src/pages/LogOutPreviousUserPage.tsx index 3df7a4cdb9f3..37402ea8b048 100644 --- a/src/pages/LogOutPreviousUserPage.tsx +++ b/src/pages/LogOutPreviousUserPage.tsx @@ -1,22 +1,27 @@ import type {StackScreenProps} from '@react-navigation/stack'; -import React, {useEffect} from 'react'; -import {Linking} from 'react-native'; +import React, {useContext, useEffect} from 'react'; +import {NativeModules} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import Navigation from '@libs/Navigation/Navigation'; +import {InitialURLContext} from '@components/InitialURLContextProvider'; import * as SessionUtils from '@libs/SessionUtils'; +import Navigation from '@navigation/Navigation'; import type {AuthScreensParamList} from '@navigation/types'; import * as SessionActions from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type {Session} from '@src/types/onyx'; type LogOutPreviousUserPageOnyxProps = { /** The data about the current session which will be set once the user is authenticated and we return to this component as an AuthScreen */ session: OnyxEntry; + + /** Is the account loading? */ + isAccountLoading: boolean; }; type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreenProps; @@ -25,42 +30,55 @@ type LogOutPreviousUserPageProps = LogOutPreviousUserPageOnyxProps & StackScreen // out if the transition is for another user. // // This component should not do any other navigation as that handled in App.setUpPoliciesAndNavigate -function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { +function LogOutPreviousUserPage({session, route, isAccountLoading}: LogOutPreviousUserPageProps) { + const initialURL = useContext(InitialURLContext); useEffect(() => { - Linking.getInitialURL().then((transitionURL) => { - const sessionEmail = session?.email; - const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); - const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; + const sessionEmail = session?.email; + const transitionURL = NativeModules.HybridAppModule ? `${CONST.DEEPLINK_BASE_URL}${initialURL ?? ''}` : initialURL; + const isLoggingInAsNewUser = SessionUtils.isLoggingInAsNewUser(transitionURL ?? undefined, sessionEmail); + const isSupportalLogin = route.params.authTokenType === CONST.AUTH_TOKEN_TYPES.SUPPORT; - if (isLoggingInAsNewUser) { - SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); - } + if (isLoggingInAsNewUser) { + SessionActions.signOutAndRedirectToSignIn(false, isSupportalLogin); + return; + } - if (isSupportalLogin) { - SessionActions.signInWithSupportAuthToken(route.params.shortLivedAuthToken ?? ''); - Navigation.isNavigationReady().then(() => { - // We must call goBack() to remove the /transition route from history - Navigation.goBack(); - Navigation.navigate(ROUTES.HOME); - }); - return; - } + if (isSupportalLogin) { + SessionActions.signInWithSupportAuthToken(route.params.shortLivedAuthToken ?? ''); + Navigation.isNavigationReady().then(() => { + // We must call goBack() to remove the /transition route from history + Navigation.goBack(); + Navigation.navigate(ROUTES.HOME); + }); + return; + } - // We need to signin and fetch a new authToken, if a user was already authenticated in NewDot, and was redirected to OldDot - // and their authToken stored in Onyx becomes invalid. - // This workflow is triggered while setting up VBBA. User is redirected from NewDot to OldDot to set up 2FA, and then redirected back to NewDot - // On Enabling 2FA, authToken stored in Onyx becomes expired and hence we need to fetch new authToken - const shouldForceLogin = route.params.shouldForceLogin === 'true'; - if (shouldForceLogin) { - const email = route.params.email ?? ''; - const shortLivedAuthToken = route.params.shortLivedAuthToken ?? ''; - SessionActions.signInWithShortLivedAuthToken(email, shortLivedAuthToken); - } - }); + // We need to signin and fetch a new authToken, if a user was already authenticated in NewDot, and was redirected to OldDot + // and their authToken stored in Onyx becomes invalid. + // This workflow is triggered while setting up VBBA. User is redirected from NewDot to OldDot to set up 2FA, and then redirected back to NewDot + // On Enabling 2FA, authToken stored in Onyx becomes expired and hence we need to fetch new authToken + const shouldForceLogin = route.params.shouldForceLogin === 'true'; + if (shouldForceLogin) { + const email = route.params.email ?? ''; + const shortLivedAuthToken = route.params.shortLivedAuthToken ?? ''; + SessionActions.signInWithShortLivedAuthToken(email, shortLivedAuthToken); + } + const exitTo = route.params.exitTo as Route | null; + // We don't want to navigate to the exitTo route when creating a new workspace from a deep link, + // because we already handle creating the optimistic policy and navigating to it in App.setUpPoliciesAndNavigate, + // which is already called when AuthScreens mounts. + if (exitTo && exitTo !== ROUTES.WORKSPACE_NEW && !isAccountLoading && !isLoggingInAsNewUser) { + Navigation.isNavigationReady().then(() => { + // remove this screen and navigate to exit route + const exitUrl = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : exitTo; + Navigation.goBack(); + Navigation.navigate(exitUrl); + }); + } // We only want to run this effect once on mount (when the page first loads after transitioning from OldDot) // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [initialURL, isAccountLoading]); return ; } @@ -68,6 +86,10 @@ function LogOutPreviousUserPage({session, route}: LogOutPreviousUserPageProps) { LogOutPreviousUserPage.displayName = 'LogOutPreviousUserPage'; export default withOnyx({ + isAccountLoading: { + key: ONYXKEYS.ACCOUNT, + selector: (account) => account?.isLoading ?? false, + }, session: { key: ONYXKEYS.SESSION, },