Skip to content

Commit 99e0888

Browse files
authored
Merge pull request Expensify#67348 from callstack-internal/bugfix/start-test-drive-migrated-accounts
[NoQA] Show the TestDrive demo to users if they migrated from Classic
2 parents f9ff2cf + 9044135 commit 99e0888

8 files changed

Lines changed: 135 additions & 77 deletions

File tree

src/components/TestDrive/TestDriveDemo.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOffli
44
import EmbeddedDemo from '@components/EmbeddedDemo';
55
import Modal from '@components/Modal';
66
import SafeAreaConsumer from '@components/SafeAreaConsumer';
7+
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
78
import useOnboardingMessages from '@hooks/useOnboardingMessages';
89
import useOnyx from '@hooks/useOnyx';
910
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1011
import useThemeStyles from '@hooks/useThemeStyles';
1112
import {completeTestDriveTask} from '@libs/actions/Task';
1213
import Navigation from '@libs/Navigation/Navigation';
14+
import {isPaidGroupPolicy, isUserPolicyAdmin} from '@libs/PolicyUtils';
1315
import {isAdminRoom} from '@libs/ReportUtils';
1416
import {getTestDriveURL} from '@libs/TourUtils';
1517
import CONST from '@src/CONST';
@@ -25,6 +27,11 @@ function TestDriveDemo() {
2527
const [onboardingReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${onboarding?.chatReportID}`, {canBeMissing: true});
2628
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true});
2729
const {testDrive} = useOnboardingMessages();
30+
const currentUserPersonalDetails = useCurrentUserPersonalDetails();
31+
const [isPolicyAdmin = false] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
32+
canBeMissing: true,
33+
selector: (policies) => Object.values(policies ?? {}).some((policy) => isPaidGroupPolicy(policy) && isUserPolicyAdmin(policy, currentUserPersonalDetails.login)),
34+
});
2835

2936
useEffect(() => {
3037
InteractionManager.runAfterInteractions(() => {
@@ -62,7 +69,7 @@ function TestDriveDemo() {
6269
<TestDriveBanner onPress={closeModal} />
6370
<FullPageOfflineBlockingView>
6471
<EmbeddedDemo
65-
url={getTestDriveURL(shouldUseNarrowLayout, introSelected)}
72+
url={getTestDriveURL(shouldUseNarrowLayout, introSelected, isPolicyAdmin)}
6673
iframeTitle={testDrive.EMBEDDED_DEMO_IFRAME_TITLE}
6774
/>
6875
</FullPageOfflineBlockingView>

src/hooks/useOnboardingFlow.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import {useEffect, useRef} from 'react';
22
import {InteractionManager} from 'react-native';
33
import {startOnboardingFlow} from '@libs/actions/Welcome/OnboardingFlow';
44
import getCurrentUrl from '@libs/Navigation/currentUrl';
5-
import Navigation from '@libs/Navigation/Navigation';
5+
import Navigation, {navigationRef} from '@libs/Navigation/Navigation';
66
import {hasCompletedGuidedSetupFlowSelector, tryNewDotOnyxSelector} from '@libs/onboardingSelectors';
77
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
88
import {isLoggingInAsNewUser} from '@libs/SessionUtils';
99
import isProductTrainingElementDismissed from '@libs/TooltipUtils';
1010
import CONFIG from '@src/CONFIG';
11+
import NAVIGATORS from '@src/NAVIGATORS';
1112
import ONYXKEYS from '@src/ONYXKEYS';
1213
import ROUTES from '@src/ROUTES';
1314
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
@@ -61,11 +62,17 @@ function useOnboardingFlowRouter() {
6162
if (CONFIG.IS_HYBRID_APP && isLoadingOnyxValue(isSingleNewDotEntryMetadata)) {
6263
return;
6364
}
65+
6466
if (hasBeenAddedToNudgeMigration && !isProductTrainingElementDismissed('migratedUserWelcomeModal', dismissedProductTraining)) {
65-
const defaultCannedQuery = buildCannedSearchQuery();
66-
const query = defaultCannedQuery;
67-
Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query}));
68-
Navigation.navigate(ROUTES.MIGRATED_USER_WELCOME_MODAL.getRoute(true));
67+
const navigationState = navigationRef.getRootState();
68+
const lastRoute = navigationState.routes.at(-1);
69+
// Prevent duplicate navigation if the migrated user modal is already shown.
70+
if (lastRoute?.name !== NAVIGATORS.MIGRATED_USER_MODAL_NAVIGATOR) {
71+
const defaultCannedQuery = buildCannedSearchQuery();
72+
const query = defaultCannedQuery;
73+
Navigation.navigate(ROUTES.SEARCH_ROOT.getRoute({query}));
74+
Navigation.navigate(ROUTES.MIGRATED_USER_WELCOME_MODAL.getRoute(true));
75+
}
6976
return;
7077
}
7178

src/libs/PolicyUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ function isExpensifyTeam(email: string | undefined): boolean {
311311
/**
312312
* Checks if the user with login is an admin of the policy.
313313
*/
314-
const isUserPolicyAdmin = (policy: OnyxInputOrEntry<Policy>, login?: string) => !!(policy && policy.employeeList && login && policy.employeeList[login]?.role === CONST.POLICY.ROLE.ADMIN);
314+
const isUserPolicyAdmin = (policy: OnyxInputOrEntry<Policy>, login?: string) => getPolicyRole(policy, login) === CONST.POLICY.ROLE.ADMIN;
315315

316316
/**
317317
* Checks if the current user is of the role "user" on the policy.

src/libs/TourUtils.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
import type {OnyxEntry} from 'react-native-onyx';
12
import CONST from '@src/CONST';
23
import type {IntroSelected} from './actions/Report';
34

4-
function getTestDriveURL(shouldUseNarrowLayout: boolean, introSelected?: IntroSelected) {
5-
if (introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE) {
6-
return shouldUseNarrowLayout ? CONST.STORYLANE.EMPLOYEE_TOUR_MOBILE : CONST.STORYLANE.EMPLOYEE_TOUR;
5+
function getTestDriveURL(shouldUseNarrowLayout: boolean, introSelected: OnyxEntry<IntroSelected>, isUserPolicyAdmin: boolean): string {
6+
if (introSelected) {
7+
if (introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE) {
8+
return shouldUseNarrowLayout ? CONST.STORYLANE.EMPLOYEE_TOUR_MOBILE : CONST.STORYLANE.EMPLOYEE_TOUR;
9+
}
10+
11+
if (introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE) {
12+
return shouldUseNarrowLayout ? CONST.STORYLANE.TRACK_WORKSPACE_TOUR_MOBILE : CONST.STORYLANE.TRACK_WORKSPACE_TOUR;
13+
}
14+
15+
return shouldUseNarrowLayout ? CONST.STORYLANE.ADMIN_TOUR_MOBILE : CONST.STORYLANE.ADMIN_TOUR;
716
}
817

9-
if (introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE) {
10-
return shouldUseNarrowLayout ? CONST.STORYLANE.TRACK_WORKSPACE_TOUR_MOBILE : CONST.STORYLANE.TRACK_WORKSPACE_TOUR;
18+
// Migrated users don't have the introSelected NVP, so we must check if they are an Admin of any Workspace in order
19+
// to show the Admin demo.
20+
if (isUserPolicyAdmin) {
21+
return shouldUseNarrowLayout ? CONST.STORYLANE.ADMIN_TOUR_MOBILE : CONST.STORYLANE.ADMIN_TOUR;
1122
}
1223

13-
return shouldUseNarrowLayout ? CONST.STORYLANE.ADMIN_TOUR_MOBILE : CONST.STORYLANE.ADMIN_TOUR;
24+
return shouldUseNarrowLayout ? CONST.STORYLANE.EMPLOYEE_TOUR_MOBILE : CONST.STORYLANE.EMPLOYEE_TOUR;
1425
}
1526

1627
// eslint-disable-next-line import/prefer-default-export

src/libs/actions/Tour.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {InteractionManager} from 'react-native';
2+
import Navigation from '@libs/Navigation/Navigation';
3+
import CONST from '@src/CONST';
4+
import ROUTES from '@src/ROUTES';
5+
import type {IntroSelected} from './Report';
6+
import {completeTestDriveTask} from './Task';
7+
8+
function startTestDrive(introSelected: IntroSelected | undefined, shouldUpdateSelfTourViewedOnlyLocally = false, hasUserBeenAddedToNudgeMigration = false) {
9+
InteractionManager.runAfterInteractions(() => {
10+
if (
11+
hasUserBeenAddedToNudgeMigration ||
12+
introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ||
13+
introSelected?.choice === CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER ||
14+
introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE ||
15+
(introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE)
16+
) {
17+
completeTestDriveTask(shouldUpdateSelfTourViewedOnlyLocally);
18+
Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT);
19+
} else {
20+
Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route);
21+
}
22+
});
23+
}
24+
25+
// eslint-disable-next-line import/prefer-default-export
26+
export {startTestDrive};

src/pages/Search/EmptySearchView.tsx

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, {useMemo, useRef, useState} from 'react';
22
// eslint-disable-next-line no-restricted-imports
33
import type {GestureResponderEvent, ImageStyle, Text as RNText, TextStyle, ViewStyle} from 'react-native';
4-
import {InteractionManager, Linking, View} from 'react-native';
4+
import {Linking, View} from 'react-native';
55
import type {OnyxCollection} from 'react-native-onyx';
66
import type {ValueOf} from 'type-fest';
77
import BookTravelButton from '@components/BookTravelButton';
@@ -28,10 +28,10 @@ import useThemeStyles from '@hooks/useThemeStyles';
2828
import {startMoneyRequest} from '@libs/actions/IOU';
2929
import {openOldDotLink} from '@libs/actions/Link';
3030
import {createNewReport} from '@libs/actions/Report';
31-
import {completeTestDriveTask} from '@libs/actions/Task';
31+
import {startTestDrive} from '@libs/actions/Tour';
3232
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
3333
import Navigation from '@libs/Navigation/Navigation';
34-
import {hasSeenTourSelector} from '@libs/onboardingSelectors';
34+
import {hasSeenTourSelector, tryNewDotOnyxSelector} from '@libs/onboardingSelectors';
3535
import {areAllGroupPoliciesExpenseChatDisabled, getGroupPaidPoliciesWithExpenseChatEnabled, isPaidGroupPolicy} from '@libs/PolicyUtils';
3636
import {generateReportID} from '@libs/ReportUtils';
3737
import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils';
@@ -90,6 +90,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
9090
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
9191
canBeMissing: true,
9292
});
93+
const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {selector: tryNewDotOnyxSelector, canBeMissing: true});
9394

9495
const groupPoliciesWithChatEnabled = getGroupPaidPoliciesWithExpenseChatEnabled();
9596

@@ -194,20 +195,8 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
194195
}
195196
}
196197

197-
const startTestDrive = () => {
198-
InteractionManager.runAfterInteractions(() => {
199-
if (
200-
introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ||
201-
introSelected?.choice === CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER ||
202-
introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE ||
203-
(introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE)
204-
) {
205-
completeTestDriveTask();
206-
Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT);
207-
} else {
208-
Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route);
209-
}
210-
});
198+
const startTestDriveAction = () => {
199+
startTestDrive(introSelected, false, tryNewDot?.hasBeenAddedToNudgeMigration);
211200
};
212201

213202
// If we are grouping by reports, show a custom message rather than a type-specific message
@@ -229,7 +218,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
229218
? [
230219
{
231220
buttonText: translate('emptySearchView.takeATestDrive'),
232-
buttonAction: startTestDrive,
221+
buttonAction: startTestDriveAction,
233222
},
234223
]
235224
: []),
@@ -296,7 +285,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
296285
? [
297286
{
298287
buttonText: translate('emptySearchView.takeATestDrive'),
299-
buttonAction: startTestDrive,
288+
buttonAction: startTestDriveAction,
300289
},
301290
]
302291
: []),
@@ -327,7 +316,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
327316
? [
328317
{
329318
buttonText: translate('emptySearchView.takeATestDrive'),
330-
buttonAction: startTestDrive,
319+
buttonAction: startTestDriveAction,
331320
},
332321
]
333322
: []),
@@ -369,8 +358,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
369358
styles.emptyStateFolderWebStyles,
370359
styles.textAlignLeft,
371360
styles.tripEmptyStateLottieWebView,
372-
introSelected?.choice,
373-
introSelected?.inviteType,
361+
introSelected,
374362
hasResults,
375363
defaultViewItemHeader,
376364
hasSeenTour,
@@ -381,6 +369,7 @@ function EmptySearchView({hash, type, groupBy, hasResults}: EmptySearchViewProps
381369
tripViewChildren,
382370
shouldRedirectToExpensifyClassic,
383371
transactions,
372+
tryNewDot?.hasBeenAddedToNudgeMigration,
384373
]);
385374

386375
return (

src/pages/home/sidebar/FloatingActionButtonAndPopover.tsx

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {Str} from 'expensify-common';
33
import type {ImageContentFit} from 'expo-image';
44
import type {ForwardedRef} from 'react';
55
import React, {forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
6-
import {InteractionManager, View} from 'react-native';
6+
import {View} from 'react-native';
77
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
88
import ConfirmModal from '@components/ConfirmModal';
99
import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider';
@@ -27,13 +27,13 @@ import {openOldDotLink} from '@libs/actions/Link';
2727
import {navigateToQuickAction} from '@libs/actions/QuickActionNavigation';
2828
import {createNewReport, startNewChat} from '@libs/actions/Report';
2929
import {isAnonymousUser} from '@libs/actions/Session';
30-
import {completeTestDriveTask} from '@libs/actions/Task';
30+
import {startTestDrive} from '@libs/actions/Tour';
3131
import getIconForAction from '@libs/getIconForAction';
3232
import interceptAnonymousUser from '@libs/interceptAnonymousUser';
3333
import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute';
3434
import navigateAfterInteraction from '@libs/Navigation/navigateAfterInteraction';
3535
import Navigation from '@libs/Navigation/Navigation';
36-
import {hasSeenTourSelector} from '@libs/onboardingSelectors';
36+
import {hasSeenTourSelector, tryNewDotOnyxSelector} from '@libs/onboardingSelectors';
3737
import {openTravelDotLink, shouldOpenTravelDotLinkWeb} from '@libs/openTravelDotLink';
3838
import {
3939
areAllGroupPoliciesExpenseChatDisabled,
@@ -135,6 +135,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT
135135
selector: hasSeenTourSelector,
136136
canBeMissing: true,
137137
});
138+
const [tryNewDot] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {selector: tryNewDotOnyxSelector, canBeMissing: true});
138139

139140
const groupPoliciesWithChatEnabled = getGroupPaidPoliciesWithExpenseChatEnabled();
140141

@@ -525,22 +526,7 @@ function FloatingActionButtonAndPopover({onHideCreateMenu, onShowCreateMenu, isT
525526
iconStyles: styles.popoverIconCircle,
526527
iconFill: theme.icon,
527528
text: translate('testDrive.quickAction.takeATwoMinuteTestDrive'),
528-
onSelected: () =>
529-
interceptAnonymousUser(() => {
530-
InteractionManager.runAfterInteractions(() => {
531-
if (
532-
introSelected?.choice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ||
533-
introSelected?.choice === CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER ||
534-
introSelected?.choice === CONST.ONBOARDING_CHOICES.TRACK_WORKSPACE ||
535-
(introSelected?.choice === CONST.ONBOARDING_CHOICES.SUBMIT && introSelected.inviteType === CONST.ONBOARDING_INVITE_TYPES.WORKSPACE)
536-
) {
537-
completeTestDriveTask(isAnonymousUser());
538-
Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT);
539-
} else {
540-
Navigation.navigate(ROUTES.TEST_DRIVE_MODAL_ROOT.route);
541-
}
542-
});
543-
}),
529+
onSelected: () => interceptAnonymousUser(() => startTestDrive(introSelected, isAnonymousUser(), tryNewDot?.hasBeenAddedToNudgeMigration)),
544530
},
545531
]
546532
: []),

0 commit comments

Comments
 (0)