Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor NewDot and HybridApp onboarding flow #49586

Merged
merged 29 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
00f6568
Refactor completeOnboarding function to update onboarding status
blazejkustra Sep 23, 2024
67ef4d2
Set hasCompletedGuidedSetupFlow to false
blazejkustra Sep 23, 2024
4ae48e5
Clean navigation to onboarding logic
blazejkustra Sep 23, 2024
eda9b65
Remove unused imports
blazejkustra Sep 23, 2024
1ab2303
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 24, 2024
f07f55e
Add useful comments
blazejkustra Sep 24, 2024
647622b
Remove onboarding logic from bottom tabs
blazejkustra Sep 24, 2024
a94e8b5
Adjust onboardinf flow with hybrid app considerations
blazejkustra Sep 24, 2024
bb1cd88
Fix the logic and remove console.log
blazejkustra Sep 24, 2024
2bad563
Add comments for selectors
blazejkustra Sep 24, 2024
11b901d
Refactor onboarding logic and improve logging
blazejkustra Sep 24, 2024
c1aecb0
Remove unused import
blazejkustra Sep 24, 2024
e4dd280
Add comments to useOnboardingFlow hook
blazejkustra Sep 24, 2024
4ce6479
Send the completeHybridAppOnboarding request only if HybridApp module…
blazejkustra Sep 24, 2024
13c9c7d
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 24, 2024
baf8497
Remove unused useOnyx
blazejkustra Sep 24, 2024
e2a855c
Fix imports
blazejkustra Sep 24, 2024
ab69af2
Init HybridApp with information about HybridApp onboarding
mateuuszzzzz Sep 25, 2024
cdfe1c7
Make the hybrid app flow weven easier
blazejkustra Sep 26, 2024
15a0eda
Remove unsued variables
blazejkustra Sep 26, 2024
3794824
Merge branch 'fix/onboarding-offline' of github.com:software-mansion-…
blazejkustra Sep 26, 2024
7c746c3
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 26, 2024
9938392
Refactor useOnboardingFlow to use useOnboardingFlowRouter
blazejkustra Sep 26, 2024
12b4a39
Change names to be more consistent
blazejkustra Sep 26, 2024
d452435
Fix prettier
blazejkustra Sep 26, 2024
6d70f43
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 27, 2024
907dcc3
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 30, 2024
03c94dc
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 30, 2024
075af14
Merge branch 'main' into fix/onboarding-offline
blazejkustra Sep 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(() => {
Expand All @@ -118,14 +116,6 @@ function Expensify({
setAttemptedToOpenPublicRoom(true);
}, [isCheckingPublicRoom]);

useEffect(() => {
if (splashScreenState !== CONST.BOOT_SPLASH_STATE.HIDDEN || tryNewDotData === undefined) {
return;
}

handleHybridAppOnboarding();
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
}, [splashScreenState, tryNewDotData]);

const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]);
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);

Expand Down
22 changes: 2 additions & 20 deletions src/components/ExplanationModal.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,20 @@
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 (
<FeatureTrainingModal
title={translate('onboarding.explanationModal.title')}
description={translate('onboarding.explanationModal.description')}
secondaryDescription={translate('onboarding.explanationModal.secondaryDescription')}
confirmText={translate('footer.getStarted')}
videoURL={CONST.WELCOME_VIDEO_URL}
onClose={onClose}
onClose={Welcome.completeHybridAppOnboarding}
/>
);
}
Expand Down
11 changes: 3 additions & 8 deletions src/components/LHNOptionsList/OptionRowLHN.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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});
Expand Down Expand Up @@ -171,12 +171,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti
>
<EducationalTooltip
shouldRender={
isFirstTimeNewExpensifyUser &&
!shouldHideGBRTooltip &&
hasCompletedGuidedSetupFlow &&
isScreenFocused &&
shouldUseNarrowLayout &&
ReportUtils.isConciergeChatReport(report)
isFirstTimeNewExpensifyUser && !shouldHideGBRTooltip && isOnboardingCompleted && isScreenFocused && shouldUseNarrowLayout && ReportUtils.isConciergeChatReport(report)
}
renderTooltipContent={renderGBRTooltip}
anchorAlignment={{
Expand Down
46 changes: 46 additions & 0 deletions src/hooks/useOnboardingFlow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {useEffect} from 'react';
import {NativeModules} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Navigation from '@libs/Navigation/Navigation';
import {hasCompletedGuidedSetupFlowSelector, hasCompletedHybridAppOnboardingFlowSelector} from '@libs/onboardingSelectors';
import * as OnboardingFlow from '@userActions/Welcome/OnboardingFlow';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';

/**
* Hook to handle redirection to the onboarding flow based on the user's onboarding status
*
* Warning: This hook should be used only once in the app
*/
function useOnboardingFlowRouter() {
const [isOnboardingCompleted] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {
selector: hasCompletedGuidedSetupFlowSelector,
});
const [isHybridAppOnboardingCompleted] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT, {
selector: hasCompletedHybridAppOnboardingFlowSelector,
});

useEffect(() => {
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;
27 changes: 15 additions & 12 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -237,7 +238,7 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
);
const modal = useRef<OnyxTypes.Modal>({});
const [didPusherInit, setDidPusherInit] = useState(false);

const {isOnboardingCompleted} = useOnboardingFlowRouter();
let initialReportID: string | undefined;
const isInitialRender = useRef(true);
if (isInitialRender.current) {
Expand Down Expand Up @@ -526,17 +527,19 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
options={onboardingModalScreenOptions}
component={WelcomeVideoModalNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR}
options={onboardingScreenOptions}
component={OnboardingModalNavigator}
listeners={{
focus: () => {
Modal.setDisableDismissOnEscape(true);
},
beforeRemove: () => Modal.setDisableDismissOnEscape(false),
}}
/>
{isOnboardingCompleted === false && (
<RootStack.Screen
name={NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR}
options={onboardingScreenOptions}
component={OnboardingModalNavigator}
listeners={{
focus: () => {
Modal.setDisableDismissOnEscape(true);
},
beforeRemove: () => Modal.setDisableDismissOnEscape(false),
}}
/>
)}
<RootStack.Screen
name={SCREENS.WORKSPACE_JOIN_USER}
options={{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import {createStackNavigator} from '@react-navigation/stack';
import React, {useCallback, useEffect} from 'react';
import React, {useCallback} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import FocusTrapForScreens from '@components/FocusTrap/FocusTrapForScreen';
import useKeyboardShortcut from '@hooks/useKeyboardShortcut';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
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';
import OnboardingRefManager from '@libs/OnboardingRefManager';
import OnboardingPersonalDetails from '@pages/OnboardingPersonalDetails';
import OnboardingPurpose from '@pages/OnboardingPurpose';
import OnboardingWork from '@pages/OnboardingWork';
import * as Report from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import SCREENS from '@src/SCREENS';
import Overlay from './Overlay';

Expand All @@ -27,29 +21,6 @@ const Stack = createStackNavigator<OnboardingModalNavigatorParamList>();
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<View>(null);

const handleOuterClick = useCallback(() => {
Expand All @@ -58,9 +29,6 @@ function OnboardingModalNavigator() {

useKeyboardShortcut(CONST.KEYBOARD_SHORTCUTS.ESCAPE, handleOuterClick, {shouldBubble: true});

if (hasCompletedGuidedSetupFlow) {
return null;
}
return (
<NoDropZone>
<Overlay />
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -69,38 +63,14 @@ 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<BrickRoad>(getChatTabBrickRoad(activeWorkspaceID));

useEffect(() => {
setChatTabBrickRoad(getChatTabBrickRoad(activeWorkspaceID));
}, [activeWorkspaceID, transactionViolations]);

useEffect(() => {
const navigationState = navigation.getState() as State<RootStackParamList> | 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;
Expand Down
6 changes: 3 additions & 3 deletions src/libs/Navigation/NavigationRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
});

Expand All @@ -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;
}
Expand Down
10 changes: 10 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3579,6 +3579,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];
Expand Down Expand Up @@ -3634,6 +3639,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 = [
blazejkustra marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading
Loading