diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 62e7839b21f0..f5d4655c4861 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -20,7 +20,6 @@ import {updateLastRoute} from './libs/actions/App'; import * as EmojiPickerAction from './libs/actions/EmojiPickerAction'; import * as Report from './libs/actions/Report'; import * as User from './libs/actions/User'; -import {handleHybridAppOnboarding} from './libs/actions/Welcome'; import * as ActiveClientManager from './libs/ActiveClientManager'; import FS from './libs/Fullstory'; import * as Growl from './libs/Growl'; @@ -99,7 +98,6 @@ function Expensify({ const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [session] = useOnyx(ONYXKEYS.SESSION); const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE); - const [tryNewDotData] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT); const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false); useEffect(() => { @@ -118,14 +116,6 @@ function Expensify({ setAttemptedToOpenPublicRoom(true); }, [isCheckingPublicRoom]); - useEffect(() => { - if (splashScreenState !== CONST.BOOT_SPLASH_STATE.HIDDEN || tryNewDotData === undefined) { - return; - } - - handleHybridAppOnboarding(); - }, [splashScreenState, tryNewDotData]); - const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]); const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]); diff --git a/src/components/ExplanationModal.tsx b/src/components/ExplanationModal.tsx index 73290c43d39a..d846dd4d28ba 100644 --- a/src/components/ExplanationModal.tsx +++ b/src/components/ExplanationModal.tsx @@ -1,30 +1,12 @@ -import React, {useCallback} from 'react'; +import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import Navigation from '@libs/Navigation/Navigation'; -import variables from '@styles/variables'; import * as Welcome from '@userActions/Welcome'; -import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; import CONST from '@src/CONST'; import FeatureTrainingModal from './FeatureTrainingModal'; function ExplanationModal() { const {translate} = useLocalize(); - const onClose = useCallback(() => { - Welcome.completeHybridAppOnboarding(); - - // We need to check if standard NewDot onboarding is completed. - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => { - setTimeout(() => { - Navigation.isNavigationReady().then(() => { - OnboardingFlow.startOnboardingFlow(); - }); - }, variables.welcomeVideoDelay); - }, - }); - }, []); - return ( ); } diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 322f28aa246d..b2068bfe1b1b 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -21,7 +21,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; -import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import Performance from '@libs/Performance'; @@ -47,7 +47,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${optionItem?.reportID || -1}`); const [isFirstTimeNewExpensifyUser] = useOnyx(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER); - const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasCompletedGuidedSetupFlowSelector, }); const [shouldHideGBRTooltip] = useOnyx(ONYXKEYS.NVP_SHOULD_HIDE_GBR_TOOLTIP, {initialValue: true}); @@ -171,12 +171,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > { + if (NativeModules.HybridAppModule) { + // When user is transitioning from OldDot to NewDot, we usually show the explanation modal + if (isHybridAppOnboardingCompleted === false) { + Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT); + } + + // But if the hybrid app onboarding is completed, but NewDot onboarding is not completed, we start NewDot onboarding flow + // This is a special case when user created an account from NewDot without finishing the onboarding flow and then logged in from OldDot + if (isHybridAppOnboardingCompleted === true && isOnboardingCompleted === false) { + OnboardingFlow.startOnboardingFlow(); + } + } + + // If the user is not transitioning from OldDot to NewDot, we should start NewDot onboarding flow if it's not completed yet + if (!NativeModules.HybridAppModule && isOnboardingCompleted === false) { + OnboardingFlow.startOnboardingFlow(); + } + }, [isOnboardingCompleted, isHybridAppOnboardingCompleted]); + + return {isOnboardingCompleted, isHybridAppOnboardingCompleted}; +} + +export default useOnboardingFlowRouter; diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index dee84a4f201f..ccc8290a93c3 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -9,6 +9,7 @@ import OptionsListContextProvider from '@components/OptionListContextProvider'; import {SearchContextProvider} from '@components/Search/SearchContext'; import SearchRouter from '@components/Search/SearchRouter/SearchRouter'; import useActiveWorkspace from '@hooks/useActiveWorkspace'; +import useOnboardingFlowRouter from '@hooks/useOnboardingFlow'; import usePermissions from '@hooks/usePermissions'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -237,7 +238,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie ); const modal = useRef({}); const [didPusherInit, setDidPusherInit] = useState(false); - + const {isOnboardingCompleted} = useOnboardingFlowRouter(); let initialReportID: string | undefined; const isInitialRender = useRef(true); if (isInitialRender.current) { @@ -526,17 +527,19 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie options={onboardingModalScreenOptions} component={WelcomeVideoModalNavigator} /> - { - Modal.setDisableDismissOnEscape(true); - }, - beforeRemove: () => Modal.setDisableDismissOnEscape(false), - }} - /> + {isOnboardingCompleted === false && ( + { + Modal.setDisableDismissOnEscape(true); + }, + beforeRemove: () => Modal.setDisableDismissOnEscape(false), + }} + /> + )} (); function OnboardingModalNavigator() { const styles = useThemeStyles(); const {onboardingIsMediumOrLargerScreenWidth} = useResponsiveLayout(); - const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { - selector: hasCompletedGuidedSetupFlowSelector, - }); - const {shouldUseNarrowLayout} = useResponsiveLayout(); - - useEffect(() => { - if (!hasCompletedGuidedSetupFlow) { - return; - } - Navigation.isNavigationReady().then(() => { - // On small screens, pop all navigation states and go back to HOME. - // On large screens, need to go back to previous route and then redirect to Concierge, - // otherwise going back on Concierge will go to onboarding and then redirected to Concierge again - if (shouldUseNarrowLayout) { - Navigation.setShouldPopAllStateOnUP(true); - Navigation.goBack(ROUTES.HOME, true, true); - } else { - Navigation.goBack(); - Report.navigateToConciergeChat(); - } - }); - }, [hasCompletedGuidedSetupFlow, shouldUseNarrowLayout]); - const outerViewRef = React.useRef(null); const handleOuterClick = useCallback(() => { @@ -58,9 +29,6 @@ function OnboardingModalNavigator() { useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, handleOuterClick, {shouldBubble: true}); - if (hasCompletedGuidedSetupFlow) { - return null; - } return ( diff --git a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx index 5befa446f6f9..d718de95861a 100644 --- a/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx +++ b/src/libs/Navigation/AppNavigator/createCustomBottomTabNavigator/BottomTabBar.tsx @@ -1,6 +1,5 @@ -import {useNavigation} from '@react-navigation/native'; import React, {memo, useCallback, useEffect, useState} from 'react'; -import {NativeModules, View} from 'react-native'; +import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -11,11 +10,9 @@ import useActiveWorkspace from '@hooks/useActiveWorkspace'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Session from '@libs/actions/Session'; import interceptAnonymousUser from '@libs/interceptAnonymousUser'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList, RootStackParamList, State} from '@libs/Navigation/types'; -import {isCentralPaneName} from '@libs/NavigationUtils'; import * as PolicyUtils from '@libs/PolicyUtils'; import * as SearchUtils from '@libs/SearchUtils'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; @@ -24,10 +21,7 @@ import navigationRef from '@navigation/navigationRef'; import BottomTabAvatar from '@pages/home/sidebar/BottomTabAvatar'; import BottomTabBarFloatingActionButton from '@pages/home/sidebar/BottomTabBarFloatingActionButton'; import variables from '@styles/variables'; -import * as Welcome from '@userActions/Welcome'; -import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow'; import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; @@ -69,9 +63,7 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const navigation = useNavigation(); const {activeWorkspaceID} = useActiveWorkspace(); - const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); const transactionViolations = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [chatTabBrickRoad, setChatTabBrickRoad] = useState(getChatTabBrickRoad(activeWorkspaceID)); @@ -79,28 +71,6 @@ function BottomTabBar({selectedTab}: BottomTabBarProps) { setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID)); }, [activeWorkspaceID, transactionViolations]); - useEffect(() => { - const navigationState = navigation.getState() as State | undefined; - const routes = navigationState?.routes; - const currentRoute = routes?.[navigationState?.index ?? 0]; - // When we are redirected to the Settings tab from the OldDot, we don't want to call the Welcome.show() method. - // To prevent this, the value of the bottomTabRoute?.name is checked here - if (!!(currentRoute && currentRoute.name !== NAVIGATORS.BOTTOM_TAB_NAVIGATOR && !isCentralPaneName(currentRoute.name)) || Session.isAnonymousUser()) { - return; - } - - // HybridApp has own entry point when we decide whether to display onboarding and explanation modal. - if (NativeModules.HybridAppModule) { - return; - } - - Welcome.isOnboardingFlowCompleted({ - onNotCompleted: () => OnboardingFlow.startOnboardingFlow(), - }); - - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [isLoadingApp]); - const navigateToChats = useCallback(() => { if (selectedTab === SCREENS.HOME) { return; diff --git a/src/libs/Navigation/NavigationRoot.tsx b/src/libs/Navigation/NavigationRoot.tsx index a1aa53bc0b7e..39e3dfa697f1 100644 --- a/src/libs/Navigation/NavigationRoot.tsx +++ b/src/libs/Navigation/NavigationRoot.tsx @@ -11,8 +11,8 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import Firebase from '@libs/Firebase'; import {FSPage} from '@libs/Fullstory'; -import hasCompletedGuidedSetupFlowSelector from '@libs/hasCompletedGuidedSetupFlowSelector'; import Log from '@libs/Log'; +import {hasCompletedGuidedSetupFlowSelector} from '@libs/onboardingSelectors'; import {getPathFromURL} from '@libs/Url'; import {updateLastVisitedPath} from '@userActions/App'; import {updateOnboardingLastVisitedPath} from '@userActions/Welcome'; @@ -92,7 +92,7 @@ function NavigationRoot({authenticated, lastVisitedPath, initialUrl, onReady, sh const {setActiveWorkspaceID} = useActiveWorkspace(); const [user] = useOnyx(ONYXKEYS.USER); - const [hasCompletedGuidedSetupFlow] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { + const [isOnboardingCompleted = true] = useOnyx(ONYXKEYS.NVP_ONBOARDING, { selector: hasCompletedGuidedSetupFlowSelector, }); @@ -103,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 (!NativeModules.HybridAppModule && !hasCompletedGuidedSetupFlow && authenticated && !shouldShowRequire2FAModal) { + if (!NativeModules.HybridAppModule && !isOnboardingCompleted && authenticated && !shouldShowRequire2FAModal) { const {adaptedState} = getAdaptedStateFromPath(getOnboardingInitialPath(), linkingConfig.config); return adaptedState; } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 6a1c83656686..01d71eb09331 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -3580,6 +3580,11 @@ function completeOnboarding( key: ONYXKEYS.NVP_INTRO_SELECTED, value: {choice: engagementChoice}, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_ONBOARDING, + value: {hasCompletedGuidedSetupFlow: true}, + }, ); const successData: OnyxUpdate[] = [...tasksForSuccessData]; @@ -3635,6 +3640,11 @@ function completeOnboarding( key: ONYXKEYS.NVP_INTRO_SELECTED, value: {choice: null}, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_ONBOARDING, + value: {hasCompletedGuidedSetupFlow: false}, + }, ); const guidedSetupData: GuidedSetupData = [ diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index ab209e9bf928..4d6ba6cfa774 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -482,7 +482,7 @@ function signUpUser() { function signInAfterTransitionFromOldDot(transitionURL: string) { const [route, queryParams] = transitionURL.split('?'); - const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart} = Object.fromEntries( + const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding} = Object.fromEntries( queryParams.split('&').map((param) => { const [key, value] = param.split('='); return [key, value]; @@ -493,6 +493,7 @@ function signInAfterTransitionFromOldDot(transitionURL: string) { Onyx.multiSet({ [ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)}, [ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword}, + [ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}}, }).then(App.openApp); }; diff --git a/src/libs/actions/Welcome/index.ts b/src/libs/actions/Welcome/index.ts index fc921b16f4cf..b15c5a6cc39c 100644 --- a/src/libs/actions/Welcome/index.ts +++ b/src/libs/actions/Welcome/index.ts @@ -4,12 +4,8 @@ import Onyx from 'react-native-onyx'; import * as API from '@libs/API'; import {SIDE_EFFECT_REQUEST_COMMANDS} from '@libs/API/types'; import Log from '@libs/Log'; -import Navigation from '@libs/Navigation/Navigation'; -import variables from '@styles/variables'; import type {OnboardingPurposeType} from '@src/CONST'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; import type Onboarding from '@src/types/onyx/Onboarding'; import type TryNewDot from '@src/types/onyx/TryNewDot'; import * as OnboardingFlow from './OnboardingFlow'; @@ -26,11 +22,6 @@ type HasCompletedOnboardingFlowProps = { onCanceled?: () => void; }; -type HasOpenedForTheFirstTimeFromHybridAppProps = { - onFirstTimeInHybridApp?: () => void; - onSubsequentRuns?: () => void; -}; - let resolveIsReadyPromise: (value?: Promise) => void | undefined; let isServerDataReadyPromise = new Promise((resolve) => { resolveIsReadyPromise = resolve; @@ -42,9 +33,6 @@ let isOnboardingFlowStatusKnownPromise = new Promise((resolve) => { }); let resolveTryNewDotStatus: (value?: Promise) => void | undefined; -const tryNewDotStatusPromise = new Promise((resolve) => { - resolveTryNewDotStatus = resolve; -}); function onServerDataReady(): Promise { return isServerDataReadyPromise; @@ -68,49 +56,6 @@ function isOnboardingFlowCompleted({onCompleted, onNotCompleted, onCanceled}: Ha }); } -/** - * Determines whether the application is being launched for the first time by a hybrid app user, - * and executes corresponding callback functions. - */ -function isFirstTimeHybridAppUser({onFirstTimeInHybridApp, onSubsequentRuns}: HasOpenedForTheFirstTimeFromHybridAppProps) { - tryNewDotStatusPromise.then(() => { - let completedHybridAppOnboarding = tryNewDotData?.classicRedirect?.completedHybridAppOnboarding; - // Backend might return strings instead of booleans - if (typeof completedHybridAppOnboarding === 'string') { - completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; - } - - if (NativeModules.HybridAppModule && !completedHybridAppOnboarding) { - onFirstTimeInHybridApp?.(); - return; - } - - onSubsequentRuns?.(); - }); -} - -/** - * Handles HybridApp onboarding flow if it's possible and necessary. - */ -function handleHybridAppOnboarding() { - if (!NativeModules.HybridAppModule) { - return; - } - - isFirstTimeHybridAppUser({ - // When user opens New Expensify for the first time from HybridApp we always want to show explanation modal first. - onFirstTimeInHybridApp: () => Navigation.navigate(ROUTES.EXPLANATION_MODAL_ROOT), - // In other scenarios we need to check if onboarding was completed. - onSubsequentRuns: () => - isOnboardingFlowCompleted({ - onNotCompleted: () => - setTimeout(() => { - OnboardingFlow.startOnboardingFlow(); - }, variables.explanationModalDelay), - }), - }); -} - /** * Check if report data are loaded */ @@ -165,6 +110,10 @@ function updateOnboardingLastVisitedPath(path: string) { } function completeHybridAppOnboarding() { + if (!NativeModules.HybridAppModule) { + return; + } + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -177,27 +126,15 @@ function completeHybridAppOnboarding() { }, ]; - const failureData: OnyxUpdate[] = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.NVP_TRYNEWDOT, - value: { - classicRedirect: { - completedHybridAppOnboarding: false, - }, - }, - }, - ]; - // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData, failureData}).then((response) => { + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING, {}, {optimisticData}).then((response) => { if (!response) { return; } - // if the call succeeded HybridApp onboarding is finished, otherwise it's not - Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true, {completedHybridAppOnboarding: response?.jsonCode === CONST.JSON_CODE.SUCCESS}); - NativeModules.HybridAppModule.completeOnboarding(response?.jsonCode === CONST.JSON_CODE.SUCCESS); + // No matter what the response is, we want to mark the onboarding as completed (user saw the explanation modal) + Log.info(`[HybridApp] Onboarding status has changed. Propagating new value to OldDot`, true); + NativeModules.HybridAppModule.completeOnboarding(true); }); } @@ -247,6 +184,5 @@ export { setOnboardingAdminsChatReportID, setOnboardingPolicyID, completeHybridAppOnboarding, - handleHybridAppOnboarding, setOnboardingErrorMessage, }; diff --git a/src/libs/hasCompletedGuidedSetupFlowSelector.ts b/src/libs/hasCompletedGuidedSetupFlowSelector.ts deleted file mode 100644 index 4934c83d7773..000000000000 --- a/src/libs/hasCompletedGuidedSetupFlowSelector.ts +++ /dev/null @@ -1,13 +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 ?? true; -} - -export default hasCompletedGuidedSetupFlowSelector; diff --git a/src/libs/onboardingSelectors.ts b/src/libs/onboardingSelectors.ts new file mode 100644 index 000000000000..efa67d2aed48 --- /dev/null +++ b/src/libs/onboardingSelectors.ts @@ -0,0 +1,38 @@ +import type {OnyxValue} from 'react-native-onyx'; +import type ONYXKEYS from '@src/ONYXKEYS'; + +/** + * Selector to get the value of hasCompletedGuidedSetupFlow from the Onyx store + * + * `undefined` means the value is not loaded yet + * `true` means the user has completed the NewDot onboarding flow + * `false` means the user has not completed the NewDot onboarding flow + */ +function hasCompletedGuidedSetupFlowSelector(onboarding: OnyxValue): boolean | undefined { + // Onboarding is an array for old accounts and accounts created from OldDot + if (Array.isArray(onboarding)) { + return true; + } + + return onboarding?.hasCompletedGuidedSetupFlow; +} + +/** + * Selector to get the value of completedHybridAppOnboarding from the Onyx store + * + * `undefined` means the value is not loaded yet + * `true` means the user has completed the hybrid app onboarding flow + * `false` means the user has not completed the hybrid app onboarding flow + */ +function hasCompletedHybridAppOnboardingFlowSelector(tryNewDotData: OnyxValue): boolean | undefined { + let completedHybridAppOnboarding = tryNewDotData?.classicRedirect?.completedHybridAppOnboarding; + + // Backend might return strings instead of booleans + if (typeof completedHybridAppOnboarding === 'string') { + completedHybridAppOnboarding = completedHybridAppOnboarding === 'true'; + } + + return completedHybridAppOnboarding; +} + +export {hasCompletedGuidedSetupFlowSelector, hasCompletedHybridAppOnboardingFlowSelector}; diff --git a/src/styles/variables.ts b/src/styles/variables.ts index db2febc11c57..82d443928c7a 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -213,8 +213,6 @@ export default { restrictedActionIllustrationHeight: 136, photoUploadPopoverWidth: 335, onboardingModalWidth: 500, - welcomeVideoDelay: 1000, - explanationModalDelay: 2000, // The height of the empty list is 14px (2px for borders and 12px for vertical padding) // This is calculated based on the values specified in the 'getGoogleListViewStyle' function of the 'StyleUtils' utility