diff --git a/src/CONST.ts b/src/CONST.ts index b809bdaacaf6..b8ecbe57aad7 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5265,10 +5265,6 @@ const CONST = { DATE: 'date', LIST: 'dropdown', }, - - NAVIGATION_ACTIONS: { - RESET: 'RESET', - }, } as const; type Country = keyof typeof CONST.ALL_COUNTRIES; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index bd4b294a6d68..8cb684b983cb 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -320,9 +320,6 @@ const ONYXKEYS = { /** Onboarding Purpose selected by the user during Onboarding flow */ ONBOARDING_PURPOSE_SELECTED: 'onboardingPurposeSelected', - /** Onboarding error message to be displayed to the user */ - ONBOARDING_ERROR_MESSAGE: 'onboardingErrorMessage', - /** Onboarding policyID selected by the user during Onboarding flow */ ONBOARDING_POLICY_ID: 'onboardingPolicyID', @@ -797,7 +794,6 @@ type OnyxValuesMapping = { [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; [ONYXKEYS.MAX_CANVAS_WIDTH]: number; [ONYXKEYS.ONBOARDING_PURPOSE_SELECTED]: string; - [ONYXKEYS.ONBOARDING_ERROR_MESSAGE]: string; [ONYXKEYS.ONBOARDING_POLICY_ID]: string; [ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID]: string; [ONYXKEYS.IS_SEARCHING_FOR_REPORTS]: boolean; diff --git a/src/languages/en.ts b/src/languages/en.ts index 1ac9684ac22e..b39ddd504088 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1463,7 +1463,6 @@ export default { title: 'What do you want to do today?', errorSelection: 'Please make a selection to continue.', errorContinue: 'Please press continue to get set up.', - errorBackButton: 'Please finish the setup questions to start using the app.', [CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Get paid back by my employer', [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: "Manage my team's expenses", [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Track and budget expenses', diff --git a/src/languages/es.ts b/src/languages/es.ts index ea9186daee78..129f53eeb1fa 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1471,7 +1471,6 @@ export default { title: '¿Qué quieres hacer hoy?', errorSelection: 'Por favor selecciona una opción para continuar.', errorContinue: 'Por favor, haz click en continuar para configurar tu cuenta.', - errorBackButton: 'Por favor, finaliza las preguntas de configuración para empezar a utilizar la aplicación.', [CONST.ONBOARDING_CHOICES.EMPLOYER]: 'Cobrar de mi empresa', [CONST.ONBOARDING_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo', [CONST.ONBOARDING_CHOICES.PERSONAL_SPEND]: 'Controlar y presupuestar gastos', diff --git a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx index 61adcd77da76..29a2205b2e37 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/OnboardingModalNavigator.tsx @@ -4,11 +4,9 @@ import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import NoDropZone from '@components/DragAndDrop/NoDropZone'; import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen'; -import useDisableModalDismissOnEscape from '@hooks/useDisableModalDismissOnEscape'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; -import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector'; import OnboardingModalNavigatorScreenOptions from '@libs/Navigation/AppNavigator/OnboardingModalNavigatorScreenOptions'; import Navigation from '@libs/Navigation/Navigation'; import type {OnboardingModalNavigatorParamList} from '@libs/Navigation/types'; @@ -28,11 +26,15 @@ function OnboardingModalNavigator() { const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useOnboardingLayout(); const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, + selector: (onboarding) => { + // onboarding is an array for old accounts and accounts created from olddot + if (Array.isArray(onboarding)) { + return true; + } + return onboarding?.hasCompletedGuidedSetupFlow; + }, }); - useDisableModalDismissOnEscape(); - useEffect(() => { if (!hasCompletedGuidedSetupFlow) { return; diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx index 2e1c4c012156..8c531a918af8 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar/index.website.tsx @@ -15,9 +15,7 @@ import * as Session from '@libs/actions/Session'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; -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 {getChatTabBrickRoad} from '@libs/WorkspacesSettingsUtils'; @@ -55,12 +53,7 @@ function BottomTabBar({isLoadingApp = false}: PurposeForUsingExpensifyModalProps return; } - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => { - const {adaptedState} = getAdaptedStateFromPath(ROUTES.ONBOARDING_ROOT, linkingConfig.config); - navigationRef.resetRoot(adaptedState); - }, - }); + Welcome.isOnboardingFlowCompleted({onNotCompleted: () => Navigation.navigate(ROUTES.ONBOARDING_ROOT)}); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isLoadingApp]); diff --git a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts index 5b3cefb63a2d..a1768df5e0d6 100644 --- a/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts +++ b/src/libs/Navigation/AppNavigator/createCustomStackNavigator/CustomRouter.ts @@ -1,16 +1,13 @@ -import type {CommonActions, RouterConfigOptions, StackActionType, StackNavigationState} from '@react-navigation/native'; -import {findFocusedRoute, getPathFromState, StackRouter} from '@react-navigation/native'; +import type {RouterConfigOptions, StackNavigationState} from '@react-navigation/native'; +import {getPathFromState, StackRouter} from '@react-navigation/native'; import type {ParamListBase} from '@react-navigation/routers'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; -import * as Localize from '@libs/Localize'; import getTopmostBottomTabRoute from '@libs/Navigation/getTopmostBottomTabRoute'; import getTopmostCentralPaneRoute from '@libs/Navigation/getTopmostCentralPaneRoute'; import linkingConfig from '@libs/Navigation/linkingConfig'; import getAdaptedStateFromPath from '@libs/Navigation/linkingConfig/getAdaptedStateFromPath'; import type {NavigationPartialRoute, RootStackParamList, State} from '@libs/Navigation/types'; -import {isCentralPaneName, isOnboardingFlowName} from '@libs/NavigationUtils'; -import * as Welcome from '@userActions/Welcome'; -import CONST from '@src/CONST'; +import {isCentralPaneName} from '@libs/NavigationUtils'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; import type {ResponsiveStackNavigatorRouterOptions} from './types'; @@ -100,23 +97,6 @@ function compareAndAdaptState(state: StackNavigationState) { } } -function shouldPreventReset(state: StackNavigationState, action: CommonActions.Action | StackActionType) { - if (action.type !== CONST.NAVIGATION_ACTIONS.RESET || !action?.payload) { - return false; - } - const currentFocusedRoute = findFocusedRoute(state); - const targetFocusedRoute = findFocusedRoute(action?.payload); - - // We want to prevent the user from navigating back to a non-onboarding screen if they are currently on an onboarding screen - if (isOnboardingFlowName(currentFocusedRoute?.name) && !isOnboardingFlowName(targetFocusedRoute?.name)) { - Welcome.setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton')); - // We reset the URL as the browser sets it in a way that doesn't match the navigation state - // eslint-disable-next-line no-restricted-globals - history.replaceState({}, '', getPathFromState(state, linkingConfig.config)); - return true; - } -} - function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { const stackRouter = StackRouter(options); @@ -127,12 +107,6 @@ function CustomRouter(options: ResponsiveStackNavigatorRouterOptions) { const state = stackRouter.getRehydratedState(partialState, {routeNames, routeParamList, routeGetIdList}); return state; }, - getStateForAction(state: StackNavigationState, action: CommonActions.Action | StackActionType, configOptions: RouterConfigOptions) { - if (shouldPreventReset(state, action)) { - return state; - } - return stackRouter.getStateForAction(state, action, configOptions); - }, }; } diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index a225831b56ff..63792be4f79f 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -1,7 +1,6 @@ import type {NavigationState} from '@react-navigation/native'; import {DefaultTheme, findFocusedRoute, NavigationContainer} from '@react-navigation/native'; import React, {useContext, useEffect, useMemo, useRef} from 'react'; -import {useOnyx} from 'react-native-onyx'; import HybridAppMiddleware from '@components/HybridAppMiddleware'; import {ScrollOffsetContext} from '@components/ScrollOffsetContextProvider'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; @@ -9,14 +8,11 @@ import useCurrentReportID from '@hooks/useCurrentReportID'; import useTheme from '@hooks/useTheme'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {FSPage} from '@libs/Fullstory'; -import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector'; import Log from '@libs/Log'; import {getPathFromURL} from '@libs/Url'; import {updateLastVisitedPath} from '@userActions/App'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; -import ROUTES from '@src/ROUTES'; import AppNavigator from './AppNavigator'; import getPolicyIDFromState from './getPolicyIDFromState'; import linkingConfig from './linkingConfig'; @@ -81,37 +77,25 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady}: N const {isSmallScreenWidth} = useWindowDimensions(); const {setActiveWorkspaceID} = useActiveWorkspace(); - const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); + const initialState = useMemo( + () => { + if (!lastVisitedPath) { + return undefined; + } - const initialState = useMemo(() => { - // If the user haven't completed the flow, we want to always redirect them to the onboarding flow. - if (!hasCompletedGuidedSetupFlow) { - const {adaptedState} = getAdaptedStateFromPath(ROUTES.ONBOARDING_ROOT, linkingConfig.config); - return adaptedState; - } - - // If there is no lastVisitedPath, we can do early return. We won't modify the default behavior. - if (!lastVisitedPath) { - return undefined; - } - - const path = initialUrl ? getPathFromURL(initialUrl) : null; + const path = initialUrl ? getPathFromURL(initialUrl) : null; - // If the user opens the root of app "/" it will be parsed to empty string "". - // If the path is defined and different that empty string we don't want to modify the default behavior. - if (path) { - return; - } - - // Otherwise we want to redirect the user to the last visited path. - const {adaptedState} = getAdaptedStateFromPath(lastVisitedPath, linkingConfig.config); - return adaptedState; + // For non-nullable paths we don't want to set initial state + if (path) { + return; + } - // The initialState value is relevant only on the first render. + const {adaptedState} = getAdaptedStateFromPath(lastVisitedPath, linkingConfig.config); + return adaptedState; + }, // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, []); + [], + ); // https://reactnavigation.org/docs/themes const navigationTheme = useMemo( diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index fc67fe6b8cc0..51cc6958a286 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1208,8 +1208,6 @@ type FullScreenName = keyof FullScreenNavigatorParamList; type CentralPaneName = keyof CentralPaneScreensParamList; -type OnboardingFlowName = keyof OnboardingModalNavigatorParamList; - type SwitchPolicyIDParams = { policyID?: string; route?: Routes; @@ -1240,7 +1238,6 @@ export type { NewChatNavigatorParamList, NewTaskNavigatorParamList, OnboardingModalNavigatorParamList, - OnboardingFlowName, ParticipantsNavigatorParamList, PrivateNotesNavigatorParamList, ProfileNavigatorParamList, diff --git a/src/libs/NavigationUtils.ts b/src/libs/NavigationUtils.ts index aa26268977a2..34fc0b971ef6 100644 --- a/src/libs/NavigationUtils.ts +++ b/src/libs/NavigationUtils.ts @@ -1,7 +1,7 @@ import cloneDeep from 'lodash/cloneDeep'; import SCREENS from '@src/SCREENS'; import getTopmostBottomTabRoute from './Navigation/getTopmostBottomTabRoute'; -import type {CentralPaneName, OnboardingFlowName, RootStackParamList, State} from './Navigation/types'; +import type {CentralPaneName, RootStackParamList, State} from './Navigation/types'; const CENTRAL_PANE_SCREEN_NAMES = new Set([ SCREENS.SETTINGS.WORKSPACES, @@ -17,8 +17,6 @@ const CENTRAL_PANE_SCREEN_NAMES = new Set([ SCREENS.REPORT, ]); -const ONBOARDING_SCREEN_NAMES = new Set([SCREENS.ONBOARDING.PERSONAL_DETAILS, SCREENS.ONBOARDING.PURPOSE, SCREENS.ONBOARDING.WORK, SCREENS.ONBOARDING_MODAL.ONBOARDING]); - function isCentralPaneName(screen: string | undefined): screen is CentralPaneName { if (!screen) { return false; @@ -27,14 +25,6 @@ function isCentralPaneName(screen: string | undefined): screen is CentralPaneNam return CENTRAL_PANE_SCREEN_NAMES.has(screen as CentralPaneName); } -function isOnboardingFlowName(screen: string | undefined): screen is OnboardingFlowName { - if (!screen) { - return false; - } - - return ONBOARDING_SCREEN_NAMES.has(screen as OnboardingFlowName); -} - const removePolicyIDParamFromState = (state: State) => { const stateCopy = cloneDeep(state); const bottomTabRoute = getTopmostBottomTabRoute(stateCopy); @@ -44,4 +34,4 @@ const removePolicyIDParamFromState = (state: State) => { return stateCopy; }; -export {isCentralPaneName, removePolicyIDParamFromState, isOnboardingFlowName}; +export {isCentralPaneName, removePolicyIDParamFromState}; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 3060f53f12c3..026ce45146d3 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1,4 +1,3 @@ -import {findFocusedRoute} from '@react-navigation/native'; import {format as timezoneFormat, utcToZonedTime} from 'date-fns-tz'; import {Str} from 'expensify-common'; import isEmpty from 'lodash/isEmpty'; @@ -56,13 +55,11 @@ import {prepareDraftComment} from '@libs/DraftCommentUtils'; import * as EmojiUtils from '@libs/EmojiUtils'; import * as Environment from '@libs/Environment/Environment'; import * as ErrorUtils from '@libs/ErrorUtils'; -import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector'; import isPublicScreenRoute from '@libs/isPublicScreenRoute'; import * as Localize from '@libs/Localize'; import Log from '@libs/Log'; import {registerPaginationConfig} from '@libs/Middleware/Pagination'; -import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; -import {isOnboardingFlowName} from '@libs/NavigationUtils'; +import Navigation from '@libs/Navigation/Navigation'; import type {NetworkStatus} from '@libs/NetworkConnection'; import LocalNotification from '@libs/Notification/LocalNotification'; import Parser from '@libs/Parser'; @@ -2544,47 +2541,28 @@ function openReportFromDeepLink(url: string) { // Navigate to the report after sign-in/sign-up. InteractionManager.runAfterInteractions(() => { Session.waitForUserSignIn().then(() => { - Onyx.connect({ - key: ONYXKEYS.NVP_ONBOARDING, - callback: (onboarding) => { - Navigation.waitForProtectedRoutes().then(() => { - if (route && Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(route)) { - Session.signOutAndRedirectToSignIn(true); - return; - } - - // 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 (new URL(url).searchParams.get('exitTo') === ROUTES.WORKSPACE_NEW) { - return; - } - - if (shouldSkipDeepLinkNavigation(route)) { - return; - } - - const state = navigationRef.getRootState(); - const currentFocusedRoute = findFocusedRoute(state); - const hasCompletedGuidedSetupFlow = hasCompletedGuidedSetupFlowSelector(onboarding); - - // We need skip deeplinking if the user hasn't completed the guided setup flow. - if (!hasCompletedGuidedSetupFlow) { - return; - } - - if (isOnboardingFlowName(currentFocusedRoute?.name)) { - Welcome.setOnboardingErrorMessage(Localize.translateLocal('onboarding.purpose.errorBackButton')); - return; - } - - if (isAuthenticated) { - return; - } - - Navigation.navigate(route as Route, CONST.NAVIGATION.ACTION_TYPE.PUSH); - }); - }, + Navigation.waitForProtectedRoutes().then(() => { + if (route && Session.isAnonymousUser() && !Session.canAnonymousUserAccessRoute(route)) { + Session.signOutAndRedirectToSignIn(true); + return; + } + + // 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 (new URL(url).searchParams.get('exitTo') === ROUTES.WORKSPACE_NEW) { + return; + } + + if (shouldSkipDeepLinkNavigation(route)) { + return; + } + + if (isAuthenticated) { + return; + } + + Navigation.navigate(route as Route, CONST.NAVIGATION.ACTION_TYPE.PUSH); }); }); }); diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index b592424cfcdf..a90c386d02b6 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -11,8 +11,7 @@ import ROUTES from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; import type TryNewDot from '@src/types/onyx/TryNewDot'; -type OnboardingData = Onboarding | [] | undefined; - +let onboarding: Onboarding | [] | undefined; let isLoadingReportData = true; let tryNewDotData: TryNewDot | undefined; @@ -31,8 +30,8 @@ let isServerDataReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); -let resolveOnboardingFlowStatus: (value?: OnboardingData) => void; -let isOnboardingFlowStatusKnownPromise = new Promise((resolve) => { +let resolveOnboardingFlowStatus: (value?: Promise) => void | undefined; +let isOnboardingFlowStatusKnownPromise = new Promise((resolve) => { resolveOnboardingFlowStatus = resolve; }); @@ -46,7 +45,7 @@ function onServerDataReady(): Promise { } function isOnboardingFlowCompleted({onCompleted, onNotCompleted}: HasCompletedOnboardingFlowProps) { - isOnboardingFlowStatusKnownPromise.then((onboarding) => { + isOnboardingFlowStatusKnownPromise.then(() => { if (Array.isArray(onboarding) || onboarding?.hasCompletedGuidedSetupFlow === undefined) { return; } @@ -103,7 +102,23 @@ function handleHybridAppOnboarding() { } /** - * Check if report data are loaded + * Check that a few requests have completed so that the welcome action can proceed: + * + * - Whether we are a first time new expensify user + * - Whether we have loaded all policies the server knows about + * - Whether we have loaded all reports the server knows about + * Check if onboarding data is ready in order to check if the user has completed onboarding or not + */ +function checkOnboardingDataReady() { + if (onboarding === undefined) { + return; + } + + resolveOnboardingFlowStatus?.(); +} + +/** + * Check if user dismissed modal and if report data are loaded */ function checkServerDataReady() { if (isLoadingReportData) { @@ -128,10 +143,6 @@ function setOnboardingPurposeSelected(value: OnboardingPurposeType) { Onyx.set(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, value ?? null); } -function setOnboardingErrorMessage(value: string) { - Onyx.set(ONYXKEYS.ONBOARDING_ERROR_MESSAGE, value ?? null); -} - function setOnboardingAdminsChatReportID(adminsChatReportID?: string) { Onyx.set(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID, adminsChatReportID ?? null); } @@ -175,7 +186,9 @@ Onyx.connect({ return; } - resolveOnboardingFlowStatus(value); + onboarding = value; + + checkOnboardingDataReady(); }, }); @@ -200,9 +213,10 @@ function resetAllChecks() { isServerDataReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; }); - isOnboardingFlowStatusKnownPromise = new Promise((resolve) => { + isOnboardingFlowStatusKnownPromise = new Promise((resolve) => { resolveOnboardingFlowStatus = resolve; }); + onboarding = undefined; isLoadingReportData = true; } @@ -215,5 +229,4 @@ export { setOnboardingPolicyID, completeHybridAppOnboarding, handleHybridAppOnboarding, - setOnboardingErrorMessage, }; diff --git a/src/libs/hasCompletedGuidedSetupFlowSelector.ts b/src/libs/hasCompletedGuidedSetupFlowSelector.ts deleted file mode 100644 index 83cde0a0be8c..000000000000 --- a/src/libs/hasCompletedGuidedSetupFlowSelector.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type {OnyxValue} from 'react-native-onyx'; -import type ONYXKEYS from '@src/ONYXKEYS'; - -function hasCompletedGuidedSetupFlowSelector(onboarding: OnyxValue): boolean { - // onboarding is an array for old accounts and accounts created from olddot - if (Array.isArray(onboarding)) { - return true; - } - return onboarding?.hasCompletedGuidedSetupFlow ?? false; -} - -export default hasCompletedGuidedSetupFlowSelector; diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index 52e2d817e6db..f5bd14ed7aa1 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import FormProvider from '@components/Form/FormProvider'; @@ -12,6 +12,7 @@ import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; +import useDisableModalDismissOnEscape from '@hooks/useDisableModalDismissOnEscape'; import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -45,9 +46,7 @@ function BaseOnboardingPersonalDetails({ const [shouldValidateOnChange, setShouldValidateOnChange] = useState(false); const {accountID} = useSession(); - useEffect(() => { - Welcome.setOnboardingErrorMessage(''); - }, []); + useDisableModalDismissOnEscape(); const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingPersonalDetailsForm'>) => { diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 7304c1822ae9..03a4b790bc5f 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -2,7 +2,7 @@ import {useIsFocused} from '@react-navigation/native'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; -import {useOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FormAlertWithSubmitButton from '@components/FormAlertWithSubmitButton'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Icon from '@components/Icon'; @@ -13,6 +13,7 @@ import MenuItemList from '@components/MenuItemList'; import OfflineIndicator from '@components/OfflineIndicator'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import Text from '@components/Text'; +import useDisableModalDismissOnEscape from '@hooks/useDisableModalDismissOnEscape'; import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useTheme from '@hooks/useTheme'; @@ -27,8 +28,7 @@ import type {OnboardingPurposeType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import type {BaseOnboardingPurposeProps} from './types'; +import type {BaseOnboardingPurposeOnyxProps, BaseOnboardingPurposeProps} from './types'; const menuIcons = { [CONST.ONBOARDING_CHOICES.EMPLOYER]: Illustrations.ReceiptUpload, @@ -38,15 +38,15 @@ const menuIcons = { [CONST.ONBOARDING_CHOICES.LOOKING_AROUND]: Illustrations.Binoculars, }; -function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: BaseOnboardingPurposeProps) { +function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, onboardingPurposeSelected}: BaseOnboardingPurposeProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {shouldUseNarrowLayout} = useOnboardingLayout(); const [selectedPurpose, setSelectedPurpose] = useState(undefined); const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); const theme = useTheme(); - const [onboardingPurposeSelected, onboardingPurposeSelectedResult] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); - const [onboardingErrorMessage, onboardingErrorMessageResult] = useOnyx(ONYXKEYS.ONBOARDING_ERROR_MESSAGE); + + useDisableModalDismissOnEscape(); const PurposeFooterInstance = ; @@ -83,6 +83,8 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: B Navigation.navigate(ROUTES.ONBOARDING_PERSONAL_DETAILS); }, [selectedPurpose]); + const [errorMessage, setErrorMessage] = useState(''); + const menuItems: MenuItemProps[] = Object.values(CONST.ONBOARDING_CHOICES).map((choice) => { const translationKey = `onboarding.purpose.${choice}` as const; const isSelected = selectedPurpose === choice; @@ -101,7 +103,7 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: B numberOfLinesTitle: 0, onPress: () => { Welcome.setOnboardingPurposeSelected(choice); - Welcome.setOnboardingErrorMessage(''); + setErrorMessage(''); }, }; }); @@ -109,18 +111,15 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: B const handleOuterClick = useCallback(() => { if (!selectedPurpose) { - Welcome.setOnboardingErrorMessage(translate('onboarding.purpose.errorSelection')); + setErrorMessage(translate('onboarding.purpose.errorSelection')); } else { - Welcome.setOnboardingErrorMessage(translate('onboarding.purpose.errorContinue')); + setErrorMessage(translate('onboarding.purpose.errorContinue')); } - }, [selectedPurpose, translate]); + }, [selectedPurpose, setErrorMessage, translate]); const onboardingLocalRef = useRef(null); useImperativeHandle(isFocused ? OnboardingRefManager.ref : onboardingLocalRef, () => ({handleOuterClick}), [handleOuterClick]); - if (isLoadingOnyxValue(onboardingPurposeSelectedResult, onboardingErrorMessageResult)) { - return null; - } return ( {({safeAreaPaddingBottomStyle}) => ( @@ -149,14 +148,14 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: B buttonText={translate('common.continue')} onSubmit={() => { if (!selectedPurpose) { - Welcome.setOnboardingErrorMessage(translate('onboarding.purpose.errorSelection')); + setErrorMessage(translate('onboarding.purpose.errorSelection')); return; } - Welcome.setOnboardingErrorMessage(''); + setErrorMessage(''); saveAndNavigate(); }} - message={onboardingErrorMessage} - isAlertVisible={!!onboardingErrorMessage} + message={errorMessage} + isAlertVisible={!!errorMessage} containerStyles={[styles.w100, styles.mb5, styles.mh0, paddingHorizontal]} /> @@ -167,6 +166,10 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight}: B BaseOnboardingPurpose.displayName = 'BaseOnboardingPurpose'; -export default BaseOnboardingPurpose; +export default withOnyx({ + onboardingPurposeSelected: { + key: ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, + }, +})(BaseOnboardingPurpose); export type {BaseOnboardingPurposeProps}; diff --git a/src/pages/OnboardingPurpose/types.ts b/src/pages/OnboardingPurpose/types.ts index 17970dbab9a6..8c8f11503f1a 100644 --- a/src/pages/OnboardingPurpose/types.ts +++ b/src/pages/OnboardingPurpose/types.ts @@ -1,11 +1,20 @@ -type OnboardingPurposeProps = Record; +import type {OnyxEntry} from 'react-native-onyx'; +import type {OnboardingPurposeType} from '@src/CONST'; -type BaseOnboardingPurposeProps = OnboardingPurposeProps & { - /* Whether to use native styles tailored for native devices */ - shouldUseNativeStyles: boolean; +type OnboardingPurposeProps = Record; - /** Whether to use the maxHeight (true) or use the 100% of the height (false) */ - shouldEnableMaxHeight: boolean; +type BaseOnboardingPurposeOnyxProps = { + /** Saved onboarding purpose selected by the user */ + onboardingPurposeSelected: OnyxEntry; }; -export type {BaseOnboardingPurposeProps, OnboardingPurposeProps}; +type BaseOnboardingPurposeProps = OnboardingPurposeProps & + BaseOnboardingPurposeOnyxProps & { + /* Whether to use native styles tailored for native devices */ + shouldUseNativeStyles: boolean; + + /** Whether to use the maxHeight (true) or use the 100% of the height (false) */ + shouldEnableMaxHeight: boolean; + }; + +export type {BaseOnboardingPurposeOnyxProps, BaseOnboardingPurposeProps, OnboardingPurposeProps}; diff --git a/src/pages/OnboardingWork/BaseOnboardingWork.tsx b/src/pages/OnboardingWork/BaseOnboardingWork.tsx index 14f9223f6c67..9b8824300d30 100644 --- a/src/pages/OnboardingWork/BaseOnboardingWork.tsx +++ b/src/pages/OnboardingWork/BaseOnboardingWork.tsx @@ -9,6 +9,7 @@ import KeyboardAvoidingView from '@components/KeyboardAvoidingView'; import OfflineIndicator from '@components/OfflineIndicator'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; +import useDisableModalDismissOnEscape from '@hooks/useDisableModalDismissOnEscape'; import useLocalize from '@hooks/useLocalize'; import useOnboardingLayout from '@hooks/useOnboardingLayout'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -32,6 +33,8 @@ function BaseOnboardingWork({shouldUseNativeStyles, onboardingPurposeSelected, o const {isSmallScreenWidth} = useWindowDimensions(); const {shouldUseNarrowLayout} = useOnboardingLayout(); + useDisableModalDismissOnEscape(); + const completeEngagement = useCallback( (values: FormOnyxValues<'onboardingWorkForm'>) => { if (!onboardingPurposeSelected) { diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index ca30eb10b065..b10cae2e7736 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -39,10 +39,6 @@ jest.mock('../../src/components/ConfirmedRoute.tsx'); TestHelper.setupApp(); TestHelper.setupGlobalFetchMock(); -beforeEach(() => { - Onyx.set(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: true}); -}); - function scrollUpToRevealNewMessagesBadge() { const hintText = Localize.translateLocal('sidebarScreen.listOfChatMessages'); fireEvent.scroll(screen.getByLabelText(hintText), {