Skip to content

[Test Drive][Phase 1][FE] Show the Test Drive modal during onboarding and via task #60085

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c587096
feat(testdrive): show test drive modal after onboarding
pac-guerreiro Apr 11, 2025
8604e26
feat(testdrive): replace self guided tour task with test drive
pac-guerreiro Apr 11, 2025
be7cfcb
feat(testdrive): implement test drive embedded ui
pac-guerreiro Apr 11, 2025
8c0fc1b
Merge branch 'pac-guerreiro/feature/test-drive-admin-modal' into pac-…
pac-guerreiro Apr 14, 2025
26c9e2a
Merge branch 'pac-guerreiro/feature/show-test-drive-modal-during-onbo…
pac-guerreiro Apr 14, 2025
a719d8b
fix(testdrive): test drive modal not shown after onboarding on mobile…
pac-guerreiro Apr 14, 2025
941843e
chore(testdrive): update tests
pac-guerreiro Apr 14, 2025
caef117
Merge branch 'pac-guerreiro/feature/test-drive-admin-modal' into pac-…
pac-guerreiro Apr 14, 2025
4c4328e
chore(testdrive): fix broken test
pac-guerreiro Apr 14, 2025
8ba7ae9
Merge branch 'pac-guerreiro/feature/show-test-drive-modal-during-onbo…
pac-guerreiro Apr 14, 2025
60af31b
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 16, 2025
fa72f5d
Merge branch 'pac-guerreiro/feature/show-test-drive-modal-during-onbo…
pac-guerreiro Apr 16, 2025
431ff3f
chore: fix eslint issues
pac-guerreiro Apr 16, 2025
44037e0
Merge branch 'pac-guerreiro/feature/implement-test-drive-embedded-ui'…
pac-guerreiro Apr 17, 2025
75536c8
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 17, 2025
dad1cef
chore(testdrive): apply suggestions
pac-guerreiro Apr 18, 2025
31294a5
chore(testdrive): add unit tests
pac-guerreiro Apr 21, 2025
ff881e5
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 21, 2025
b169f13
chore(testdrive): apply linter
pac-guerreiro Apr 21, 2025
72fad8f
chore: fix broken test
pac-guerreiro Apr 21, 2025
7960bf1
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 22, 2025
f6c578e
refactor(testdrive): apply suggestions
pac-guerreiro Apr 22, 2025
dd5dcee
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 22, 2025
80d84b1
fix(testdrive): demo banner overlapped by desktop header
pac-guerreiro Apr 22, 2025
86dde22
fix(testdrive): task not getting completed
pac-guerreiro Apr 22, 2025
9c35f21
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 22, 2025
30d7d5b
fix(testdrive): demo modal not showing
pac-guerreiro Apr 23, 2025
da7a4e5
refactor: apply prettier
pac-guerreiro Apr 23, 2025
5fecc4b
refactor(testdrive): apply suggestion
pac-guerreiro Apr 23, 2025
4e388ef
refactor: remove unused imports
pac-guerreiro Apr 23, 2025
56ea4e6
refactor(testdrive): parse task name markdown to html to replicate re…
pac-guerreiro Apr 23, 2025
f3dd308
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 24, 2025
ad39571
Merge branch 'main' into pac-guerreiro/feature/show-test-drive-modal-…
pac-guerreiro Apr 24, 2025
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
29 changes: 27 additions & 2 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ const selfGuidedTourTask: OnboardingTask = {
description: ({navatticURL}) => `[Take a self-guided product tour](${navatticURL}) and learn about everything Expensify has to offer.`,
};

const getTestDriveTaskName = (testDriveURL?: string) => (testDriveURL ? `Take a [test drive](${testDriveURL})` : 'Take a test drive');
const testDriveTask: OnboardingTask = {
type: 'viewTour',
autoCompleted: false,
mediaAttributes: {},
title: ({testDriveURL}) => getTestDriveTaskName(testDriveURL),
description: ({testDriveURL}) => `[Take a quick product tour](${testDriveURL}) to see why Expensify is the fastest way to do your expenses.`,
};

const createWorkspaceTask: OnboardingTask = {
type: 'createWorkspace',
autoCompleted: true,
Expand Down Expand Up @@ -290,6 +299,7 @@ type OnboardingTaskLinks = Partial<{
workspaceMembersLink: string;
workspaceAccountingLink: string;
navatticURL: string;
testDriveURL: string;
corporateCardLink: string;
}>;

Expand Down Expand Up @@ -1098,6 +1108,12 @@ const CONST = {
EMPLOYEE_TOUR_STAGING: 'https://expensify.navattic.com/cf15002s',
COMPLETED: 'completed',
},
STORYLANE: {
ADMIN_TOUR_PRODUCTION: 'https://app.storylane.io/demo/0bhwdna0isb3?embed=inline',
ADMIN_TOUR_MOBILE_PRODUCTION: 'https://app.storylane.io/demo/sfzzu3s6l3ov?embed=inline',
ADMIN_TOUR_STAGING: 'https://app.storylane.io/demo/0bhwdna0isb3?embed=inline',
ADMIN_TOUR_MOBILE_STAGING: 'https://app.storylane.io/demo/sfzzu3s6l3ov?embed=inline',
},
OLD_DOT_PUBLIC_URLS: {
TERMS_URL: `${EXPENSIFY_URL}/terms`,
PRIVACY_URL: `${EXPENSIFY_URL}/privacy`,
Expand Down Expand Up @@ -1497,6 +1513,7 @@ const CONST = {
BOTTOM_DOCKED: 'bottom_docked',
POPOVER: 'popover',
RIGHT_DOCKED: 'right_docked',
FULLSCREEN: 'fullscreen',
},
ANCHOR_ORIGIN_VERTICAL: {
TOP: 'top',
Expand Down Expand Up @@ -5360,8 +5377,7 @@ const CONST = {
message: ({onboardingCompanySize: companySize}) => `Here is a task list I’d recommend for a company of your size with ${companySize} submitters:`,
tasks: [
createWorkspaceTask,
selfGuidedTourTask,

testDriveTask,
{
type: 'addAccountingIntegration',
autoCompleted: false,
Expand Down Expand Up @@ -6991,6 +7007,12 @@ const CONST = {
BILLING: {
TYPE_FAILED_2018: 'failed_2018',
},

TEST_DRIVE: {
ONBOARDING_TASK_NAME: getTestDriveTaskName(),
EMBEDDED_DEMO_WHITELIST: ['http://', 'https://', 'about:'] as string[],
EMBEDDED_DEMO_IFRAME_TITLE: 'Test Drive',
},
} as const;

type Country = keyof typeof CONST.ALL_COUNTRIES;
Expand All @@ -7010,6 +7032,7 @@ export type {
IOUType,
OnboardingPurpose,
OnboardingCompanySize,
OnboardingTaskLinks,
IOURequestType,
SubscriptionType,
FeedbackSurveyOptionID,
Expand All @@ -7019,4 +7042,6 @@ export type {
IOUActionParams,
};

export {getTestDriveTaskName};

export default CONST;
1 change: 1 addition & 0 deletions src/NAVIGATORS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default {
EXPLANATION_MODAL_NAVIGATOR: 'ExplanationModalNavigator',
MIGRATED_USER_MODAL_NAVIGATOR: 'MigratedUserModalNavigator',
TEST_DRIVE_MODAL_NAVIGATOR: 'TestDriveModalNavigator',
TEST_DRIVE_DEMO_NAVIGATOR: 'TestDriveDemoNavigator',
REPORTS_SPLIT_NAVIGATOR: 'ReportsSplitNavigator',
SETTINGS_SPLIT_NAVIGATOR: 'SettingsSplitNavigator',
WORKSPACE_SPLIT_NAVIGATOR: 'WorkspaceSplitNavigator',
Expand Down
1 change: 1 addition & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,7 @@ const ROUTES = {
WELCOME_VIDEO_ROOT: 'onboarding/welcome-video',
EXPLANATION_MODAL_ROOT: 'onboarding/explanation',
TEST_DRIVE_MODAL_ROOT: 'onboarding/test-drive',
TEST_DRIVE_DEMO_ROOT: 'onboarding/test-drive/demo',
WORKSPACE_CONFIRMATION: {
route: 'workspace/confirmation',
getRoute: (backTo?: string) => getUrlWithBackToParam(`workspace/confirmation`, backTo),
Expand Down
4 changes: 4 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,10 @@ const SCREENS = {
ROOT: 'TestDrive_Modal_Root',
},

TEST_DRIVE_DEMO: {
ROOT: 'TestDrive_Demo_Root',
},

I_KNOW_A_TEACHER: 'I_Know_A_Teacher',
INTRO_SCHOOL_PRINCIPAL: 'Intro_School_Principal',
I_AM_A_TEACHER: 'I_Am_A_Teacher',
Expand Down
23 changes: 23 additions & 0 deletions src/components/EmbeddedDemo/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import WebView from 'react-native-webview';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import type EmbeddedDemoProps from './types';

function EmbeddedDemo({url, webViewProps}: EmbeddedDemoProps) {
const styles = useThemeStyles();

return (
<WebView
source={{uri: url}}
originWhitelist={CONST.TEST_DRIVE.EMBEDDED_DEMO_WHITELIST}
style={styles.flex1}
// eslint-disable-next-line react/jsx-props-no-spreading
{...webViewProps}
/>
);
}

EmbeddedDemo.displayName = 'EmbeddedDemo';

export default EmbeddedDemo;
21 changes: 21 additions & 0 deletions src/components/EmbeddedDemo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import useThemeStyles from '@hooks/useThemeStyles';
import type EmbeddedDemoProps from './types';

function EmbeddedDemo({url, iframeTitle, iframeProps}: EmbeddedDemoProps) {
const styles = useThemeStyles();

return (
<iframe
title={iframeTitle}
src={url}
style={styles.embeddedDemoIframe}
// eslint-disable-next-line react/jsx-props-no-spreading
{...iframeProps}
/>
);
}

EmbeddedDemo.displayName = 'EmbeddedDemo';

export default EmbeddedDemo;
18 changes: 18 additions & 0 deletions src/components/EmbeddedDemo/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type {DetailedHTMLProps, IframeHTMLAttributes} from 'react';
import type {WebViewProps} from 'react-native-webview';

type EmbeddedDemoProps = {
/** Embedded demo URL */
url: string;

/** **(web/desktop)** Description for screenreaders */
iframeTitle?: string;

/** **(web/desktop)** Additional iframe props */
iframeProps?: DetailedHTMLProps<IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement> & Record<string, unknown>;

/** **(native)** Additional WebView props */
webViewProps?: WebViewProps;
};

export default EmbeddedDemoProps;
42 changes: 42 additions & 0 deletions src/components/TestDrive/TestDriveBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import HeaderGap from '@components/HeaderGap';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';

type TestDriveBannerProps = {
/** Callback to finish the test drive */
onPress: () => void;
};

function TestDriveBanner({onPress}: TestDriveBannerProps) {
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const {translate} = useLocalize();

return (
<View style={styles.highlightBG}>
<HeaderGap styles={styles.testDriveBannerGap} />
<View style={[styles.gap2, styles.alignItemsCenter, styles.flexRow, styles.justifyContentCenter, styles.h10]}>
<Text>
{shouldUseNarrowLayout
? translate('testDrive.banner.currentlyTestDrivingExpensify')
: `${translate('testDrive.banner.currentlyTestDrivingExpensify')}. ${translate('testDrive.banner.readyForTheRealThing')}`}
</Text>
<Button
text={translate('testDrive.banner.getStarted')}
small
success
onPress={onPress}
/>
</View>
</View>
);
}

TestDriveBanner.displayName = 'TestDriveBanner';

export default TestDriveBanner;
61 changes: 61 additions & 0 deletions src/components/TestDrive/TestDriveDemo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, {useCallback, useEffect, useState} from 'react';
import {InteractionManager} from 'react-native';
import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView';
import EmbeddedDemo from '@components/EmbeddedDemo';
import Modal from '@components/Modal';
import SafeAreaConsumer from '@components/SafeAreaConsumer';
import useEnvironment from '@hooks/useEnvironment';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import {completeTestDriveTask} from '@libs/actions/Task';
import Navigation from '@libs/Navigation/Navigation';
import {getTestDriveURL} from '@libs/TourUtils';
import CONST from '@src/CONST';
import TestDriveBanner from './TestDriveBanner';

function TestDriveDemo() {
const {environment} = useEnvironment();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const [isVisible, setIsVisible] = useState(false);
const styles = useThemeStyles();

useEffect(() => {
InteractionManager.runAfterInteractions(() => {
setIsVisible(true);
completeTestDriveTask();
});
}, []);

const closeModal = useCallback(() => {
setIsVisible(false);
InteractionManager.runAfterInteractions(() => {
Navigation.goBack();
});
}, []);

return (
<SafeAreaConsumer>
{({paddingTop, paddingBottom}) => (
<Modal
isVisible={isVisible}
onClose={closeModal}
type={CONST.MODAL.MODAL_TYPE.FULLSCREEN}
style={styles.backgroundWhite}
innerContainerStyle={{...styles.flex1, marginTop: paddingTop, marginBottom: paddingBottom}}
>
<TestDriveBanner onPress={closeModal} />
<FullPageOfflineBlockingView>
<EmbeddedDemo
url={getTestDriveURL(environment, shouldUseNarrowLayout)}
iframeTitle={CONST.TEST_DRIVE.EMBEDDED_DEMO_IFRAME_TITLE}
/>
</FullPageOfflineBlockingView>
</Modal>
)}
</SafeAreaConsumer>
);
}

TestDriveDemo.displayName = 'TestDriveDemo';

export default TestDriveDemo;
8 changes: 7 additions & 1 deletion src/components/TestDriveModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React from 'react';
import {InteractionManager} from 'react-native';
import FastTrack from '@assets/images/fast-track-cover.jpg';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import FeatureTrainingModal from './FeatureTrainingModal';

function TestDriveModal() {
Expand All @@ -14,7 +16,11 @@ function TestDriveModal() {
Navigation.dismissModal();
};

const navigateTestDriveDemo = () => {};
const navigateTestDriveDemo = () => {
InteractionManager.runAfterInteractions(() => {
Navigation.navigate(ROUTES.TEST_DRIVE_DEMO_ROOT);
});
};

return (
<FeatureTrainingModal
Expand Down
5 changes: 5 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6254,6 +6254,11 @@ const translations = {
confirmText: 'Start test drive',
helpText: 'Skip',
},
banner: {
currentlyTestDrivingExpensify: "You're currently test driving Expensify",
readyForTheRealThing: 'Ready for the real thing?',
getStarted: 'Get started',
},
},
};

Expand Down
5 changes: 5 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6779,6 +6779,11 @@ const translations = {
confirmText: 'Iniciar prueba',
helpText: 'Omitir',
},
banner: {
currentlyTestDrivingExpensify: 'Actualmente estás probando Expensify',
readyForTheRealThing: '¿Listo para la versión real?',
getStarted: 'Comenzar',
},
},
};

Expand Down
6 changes: 6 additions & 0 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import OnboardingModalNavigator from './Navigators/OnboardingModalNavigator';
import RightModalNavigator from './Navigators/RightModalNavigator';
import TestDriveModalNavigator from './Navigators/TestDriveModalNavigator';
import WelcomeVideoModalNavigator from './Navigators/WelcomeVideoModalNavigator';
import TestDriveDemoNavigator from './TestDriveDemoNavigator';
import useRootNavigatorScreenOptions from './useRootNavigatorScreenOptions';

type AuthScreensProps = {
Expand Down Expand Up @@ -642,6 +643,11 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
options={rootNavigatorScreenOptions.basicModalNavigator}
component={TestDriveModalNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR}
options={rootNavigatorScreenOptions.basicModalNavigator}
component={TestDriveDemoNavigator}
/>
<RootStack.Screen
name={NAVIGATORS.FEATURE_TRANING_MODAL_NAVIGATOR}
options={rootNavigatorScreenOptions.basicModalNavigator}
Expand Down
29 changes: 29 additions & 0 deletions src/libs/Navigation/AppNavigator/TestDriveDemoNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import {View} from 'react-native';
import NoDropZone from '@components/DragAndDrop/NoDropZone';
import TestDriveDemo from '@components/TestDrive/TestDriveDemo';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import Animations from '@libs/Navigation/PlatformStackNavigation/navigationOptions/animation';
import type {TestDriveDemoNavigatorParamList} from '@libs/Navigation/types';
import SCREENS from '@src/SCREENS';

const Stack = createPlatformStackNavigator<TestDriveDemoNavigatorParamList>();

function TestDriveDemoNavigator() {
return (
<NoDropZone>
<View>
<Stack.Navigator screenOptions={{headerShown: false, animation: Animations.SLIDE_FROM_RIGHT}}>
<Stack.Screen
name={SCREENS.TEST_DRIVE_DEMO.ROOT}
component={TestDriveDemo}
/>
</Stack.Navigator>
</View>
</NoDropZone>
);
}

TestDriveDemoNavigator.displayName = 'TestDriveDemoNavigator';

export default TestDriveDemoNavigator;
9 changes: 9 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ const config: LinkingOptions<RootNavigatorParamList>['config'] = {
},
},

[NAVIGATORS.TEST_DRIVE_DEMO_NAVIGATOR]: {
screens: {
[SCREENS.TEST_DRIVE_DEMO.ROOT]: {
path: ROUTES.TEST_DRIVE_DEMO_ROOT,
exact: true,
},
},
},

[NAVIGATORS.ONBOARDING_MODAL_NAVIGATOR]: {
// Don't set the initialRouteName, because when the user continues from the last visited onboarding page,
// the onboarding purpose page will be briefly visible.
Expand Down
Loading