diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index b7b6cf53a176f..49eb58f9ea63b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -336,6 +336,9 @@ const ONYXKEYS = { /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_ADMINS_CHAT_REPORT_ID: 'onboardingAdminsChatReportID', + // Stores onboarding last visited path + ONBOARDING_LAST_VISITED_PATH: 'onboardingLastVisitedPath', + // Max width supported for HTML element MAX_CANVAS_WIDTH: 'maxCanvasWidth', @@ -862,6 +865,7 @@ type OnyxValuesMapping = { [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; + [ONYXKEYS.ONBOARDING_LAST_VISITED_PATH]: string; [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean; [ONYXKEYS.LAST_VISITED_PATH]: string | undefined; [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; diff --git a/src/components/ExplanationModal.tsx b/src/components/ExplanationModal.tsx index c6294f6009934..bf6bd04d42778 100644 --- a/src/components/ExplanationModal.tsx +++ b/src/components/ExplanationModal.tsx @@ -4,7 +4,6 @@ import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; -import ROUTES from '@src/ROUTES'; import FeatureTrainingModal from './FeatureTrainingModal'; function ExplanationModal() { @@ -18,7 +17,7 @@ function ExplanationModal() { onNotCompleted: () => { setTimeout(() => { Navigation.isNavigationReady().then(() => { - Navigation.navigate(ROUTES.ONBOARDING_ROOT.route); + Welcome.startOnboardingFlow(); }); }, variables.welcomeVideoDelay); }, diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index f023e94bec9b6..3a9f3c28c843e 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -12,9 +12,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@libs/actions/Session'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; -import linkingConfig from '@libs/Navigation/linkingConfig'; -import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath'; -import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; +import Navigation from '@libs/Navigation/Navigation'; import type {RootStackParamList, State} from '@libs/Navigation/types'; import {isCentralPaneName} from '@libs/NavigationUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; @@ -66,10 +64,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { } Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => { - const {adaptedState} = getAdaptedStateFromPath(ROUTES.ONBOARDING_ROOT.route, linkingConfig.config); - navigationRef.resetRoot(adaptedState); - }, + onNotCompleted: () => Welcome.startOnboardingFlow(), }); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index 152594ba6b3e0..df65e55e96a03 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -14,10 +14,12 @@ import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFl import Log from '@libs/Log'; import {getPathFromURL} from '@libs/Url'; import {updateLastVisitedPath} from '@userActions/App'; +import {updateOnboardingLastVisitedPath} from '@userActions/Welcome'; +import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import AppNavigator from './AppNavigator'; import getPolicyIDFromState from './getPolicyIDFromState'; import linkingConfig from './linkingConfig'; @@ -58,6 +60,9 @@ function parseAndLogRoute(state: NavigationState) { if (focusedRoute && !CONST.EXCLUDE_FROM_LAST_VISITED_PATH.includes(focusedRoute?.name)) { updateLastVisitedPath(currentPath); + if (currentPath.startsWith(`/${ROUTES.ONBOARDING_ROOT.route}`)) { + updateOnboardingLastVisitedPath(currentPath); + } } // Don't log the route transitions from OldDot because they contain authTokens @@ -98,7 +103,7 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh // If the user haven't completed the flow, we want to always redirect them to the onboarding flow. // We also make sure that the user is authenticated. if (!hasCompletedGuidedSetupFlow && authenticated && !shouldShowRequire2FAModal) { - const {adaptedState} = getAdaptedStateFromPath(ROUTES.ONBOARDING_ROOT.route, linkingConfig.config); + const {adaptedState} = getAdaptedStateFromPath(Welcome.getOnboardingInitialPath(), linkingConfig.config); return adaptedState; } diff --git a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts index 611831544bdc2..16bb1c89b6e87 100644 --- a/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts +++ b/src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts @@ -47,7 +47,7 @@ type GetAdaptedStateReturnType = { metainfo: Metainfo; }; -type GetAdaptedStateFromPath = (...args: Parameters) => GetAdaptedStateReturnType; +type GetAdaptedStateFromPath = (...args: [...Parameters, shouldReplacePathInNestedState?: boolean]) => GetAdaptedStateReturnType; // The function getPathFromState that we are using in some places isn't working correctly without defined index. const getRoutesWithIndex = (routes: NavigationPartialRoute[]): PartialState => ({routes, index: routes.length - 1}); @@ -365,7 +365,7 @@ function getAdaptedState(state: PartialState }; } -const getAdaptedStateFromPath: GetAdaptedStateFromPath = (path, options) => { +const getAdaptedStateFromPath: GetAdaptedStateFromPath = (path, options, shouldReplacePathInNestedState = true) => { const normalizedPath = !path.startsWith('/') ? `/${path}` : path; const pathWithoutPolicyID = getPathWithoutPolicyID(normalizedPath); const isAnonymous = isAnonymousUser(); @@ -374,7 +374,9 @@ const getAdaptedStateFromPath: GetAdaptedStateFromPath = (path, options) => { const policyID = isAnonymous ? undefined : extractPolicyIDFromPath(path); const state = getStateFromPath(pathWithoutPolicyID, options) as PartialState>; - replacePathInNestedState(state, path); + if (shouldReplacePathInNestedState) { + replacePathInNestedState(state, path); + } if (state === undefined) { throw new Error('Unable to parse path'); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7eb7e01d6edd6..9a8f41ef9c3a8 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2700,7 +2700,9 @@ function openReportFromDeepLink(url: string) { // We need skip deeplinking if the user hasn't completed the guided setup flow. if (!hasCompletedGuidedSetupFlow) { - Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_ROOT.getRoute())}); + Welcome.isOnboardingFlowCompleted({ + onNotCompleted: () => Welcome.startOnboardingFlow(), + }); return; } diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index d54314ae6f05c..5bc5151844642 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -3,11 +3,16 @@ import type {OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import {WRITE_COMMANDS} from '@libs/API/types'; -import Navigation from '@libs/Navigation/Navigation'; +import linkingConfig from '@libs/Navigation/linkingConfig'; +import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; +import getStateFromPath from '@navigation/getStateFromPath'; +import getAdaptedStateFromPath from '@navigation/linkingConfig/getAdaptedStateFromPath'; import variables from '@styles/variables'; import type {OnboardingPurposeType} from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type {Route} from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; import type TryNewDot from '@src/types/onyx/TryNewDot'; @@ -15,6 +20,7 @@ type OnboardingData = Onboarding | [] | undefined; let isLoadingReportData = true; let tryNewDotData: TryNewDot | undefined; +let onboardingInitialPath = ''; let onboarding: OnboardingData; type HasCompletedOnboardingFlowProps = { @@ -46,6 +52,7 @@ function onServerDataReady(): Promise { return isServerDataReadyPromise; } +let isOnboardingInProgress = false; function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOnboardingFlowProps) { isOnboardingFlowStatusKnownPromise.then(() => { if (Array.isArray(onboarding) || onboarding?.hasCompletedGuidedSetupFlow === undefined) { @@ -53,8 +60,10 @@ function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOn } if (onboarding?.hasCompletedGuidedSetupFlow) { + isOnboardingInProgress = false; onCompleted?.(); - } else { + } else if (!isOnboardingInProgress) { + isOnboardingInProgress = true; onNotCompleted?.(); } }); @@ -97,7 +106,7 @@ function handleHybridAppOnboarding() { isOnboardingFlowCompleted({ onNotCompleted: () => setTimeout(() => { - Navigation.navigate(ROUTES.ONBOARDING_ROOT.route); + startOnboardingFlow(); }, variables.explanationModalDelay), }), }); @@ -152,6 +161,19 @@ function setOnboardingPolicyID(policyID?: string) { Onyx.set(ONYXKEYS.ONBOARDING_POLICY_ID, policyID ?? null); } +function updateOnboardingLastVisitedPath(path: string) { + Onyx.merge(ONYXKEYS.ONBOARDING_LAST_VISITED_PATH, path); +} + +function getOnboardingInitialPath(): Route { + const state = getStateFromPath(onboardingInitialPath as Route); + if (state?.routes?.at(-1)?.name !== NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR) { + return ROUTES.ONBOARDING_ROOT.route as Route; + } + + return onboardingInitialPath as Route; +} + function completeHybridAppOnboarding() { const optimisticData: OnyxUpdate[] = [ { @@ -180,6 +202,11 @@ function completeHybridAppOnboarding() { API.write(WRITE_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData, failureData}); } +function startOnboardingFlow() { + const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(), linkingConfig.config, false); + navigationRef.resetRoot(adaptedState); +} + Onyx.connect({ key: ONYXKEYS.NVP_ONBOARDING, callback: (value) => { @@ -188,6 +215,18 @@ Onyx.connect({ }, }); +const onboardingLastVisitedPathConnection = Onyx.connect({ + key: ONYXKEYS.ONBOARDING_LAST_VISITED_PATH, + callback: (value) => { + if (value === undefined) { + return; + } + + onboardingInitialPath = value.substring(1); + Onyx.disconnect(onboardingLastVisitedPathConnection); + }, +}); + Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, @@ -213,16 +252,21 @@ function resetAllChecks() { resolveOnboardingFlowStatus = resolve; }); isLoadingReportData = true; + onboardingInitialPath = ''; + isOnboardingInProgress = false; } export { onServerDataReady, isOnboardingFlowCompleted, setOnboardingPurposeSelected, + getOnboardingInitialPath, + updateOnboardingLastVisitedPath, resetAllChecks, setOnboardingAdminsChatReportID, setOnboardingPolicyID, completeHybridAppOnboarding, handleHybridAppOnboarding, setOnboardingErrorMessage, + startOnboardingFlow, }; diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 4e3ebd6d05783..04dce7f826abb 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -75,7 +75,9 @@ function BaseOnboardingPersonalDetails({ Welcome.setOnboardingAdminsChatReportID(); Welcome.setOnboardingPolicyID(); - Navigation.dismissModal(); + // Navigate to HOME instead of dismissModal, because there is bug in small screen + // where the onboarding puropose page will be disaplayed briefly + Navigation.navigate(ROUTES.HOME); // Only navigate to concierge chat when central pane is visible // Otherwise stay on the chats screen.