Skip to content

Commit

Permalink
Merge pull request #32154 from Expensify/stites-purposeForExpensify
Browse files Browse the repository at this point in the history
Add full screen engagement modal to NewDot
  • Loading branch information
stitesExpensify authored Jan 23, 2024
2 parents 82fff8c + da884be commit 74f1163
Show file tree
Hide file tree
Showing 24 changed files with 419 additions and 57 deletions.
10 changes: 10 additions & 0 deletions assets/images/scan.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3130,6 +3130,13 @@ const CONST = {
REPORT: 'REPORT',
},

INTRO_CHOICES: {
TRACK: 'newDotTrack',
SUBMIT: 'newDotSubmit',
MANAGE_TEAM: 'newDotManageTeam',
CHAT_SPLIT: 'newDotSplitChat',
},

MINI_CONTEXT_MENU_MAX_ITEMS: 4,

REPORT_FIELD_TITLE_FIELD_ID: 'text_title',
Expand Down
8 changes: 8 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ const ONYXKEYS = {
/** This NVP holds to most recent waypoints that a person has used when creating a distance request */
NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints',

/** This NVP will be `true` if the user has ever dismissed the engagement modal on either OldDot or NewDot. If it becomes true it should stay true forever. */
NVP_HAS_DISMISSED_IDLE_PANEL: 'hasDismissedIdlePanel',

/** This NVP contains the choice that the user made on the engagement modal */
NVP_INTRO_SELECTED: 'introSelected',

/** Does this user have push notifications enabled for this device? */
PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled',

Expand Down Expand Up @@ -395,6 +401,8 @@ type OnyxValues = {
[ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean;
[ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record<string, string>;
[ONYXKEYS.NVP_RECENT_WAYPOINTS]: OnyxTypes.RecentWaypoint[];
[ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL]: boolean;
[ONYXKEYS.NVP_INTRO_SELECTED]: OnyxTypes.IntroSelected;
[ONYXKEYS.PUSH_NOTIFICATIONS_ENABLED]: boolean;
[ONYXKEYS.PLAID_DATA]: OnyxTypes.PlaidData;
[ONYXKEYS.IS_PLAID_DISABLED]: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/components/AttachmentModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ function AttachmentModal(props) {
shouldShowThreeDotsButton={shouldShowThreeDotsButton}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)}
threeDotsMenuItems={threeDotsMenuItems}
shouldOverlay
shouldOverlayDots
/>
<View style={styles.imageModalImageCenterContainer}>
{!_.isEmpty(props.report) && !props.isReceiptAttachment ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ const propTypes = {

/** Whether we should navigate to report page when the route have a topMostReport */
shouldNavigateToTopMostReport: PropTypes.bool,

/** Whether we should overlay the 3 dots menu */
shouldOverlayDots: PropTypes.bool,
};

export default propTypes;
7 changes: 4 additions & 3 deletions src/components/HeaderWithBackButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {Keyboard, View} from 'react-native';
import {Keyboard, StyleSheet, View} from 'react-native';
import AvatarWithDisplayName from '@components/AvatarWithDisplayName';
import Header from '@components/Header';
import Icon from '@components/Icon';
Expand Down Expand Up @@ -52,6 +52,7 @@ function HeaderWithBackButton({
threeDotsMenuItems = [],
shouldEnableDetailPageNavigation = false,
children = null,
shouldOverlayDots = false,
shouldOverlay = false,
singleExecution = (func) => func,
shouldNavigateToTopMostReport = false,
Expand All @@ -69,7 +70,7 @@ function HeaderWithBackButton({
// Hover on some part of close icons will not work on Electron if dragArea is true
// https://github.com/Expensify/App/issues/29598
dataSet={{dragArea: false}}
style={[styles.headerBar, shouldShowBorderBottom && styles.borderBottom, shouldShowBackButton && styles.pl2]}
style={[styles.headerBar, shouldShowBorderBottom && styles.borderBottom, shouldShowBackButton && styles.pl2, shouldOverlay && StyleSheet.absoluteFillObject]}
>
<View style={[styles.dFlex, styles.flexRow, styles.alignItemsCenter, styles.flexGrow1, styles.justifyContentBetween, styles.overflowHidden]}>
{shouldShowBackButton && (
Expand Down Expand Up @@ -163,7 +164,7 @@ function HeaderWithBackButton({
menuItems={threeDotsMenuItems}
onIconPress={onThreeDotsButtonPress}
anchorPosition={threeDotsAnchorPosition}
shouldOverlay={shouldOverlay}
shouldOverlay={shouldOverlayDots}
/>
)}
{shouldShowCloseButton && (
Expand Down
3 changes: 3 additions & 0 deletions src/components/HeaderWithBackButton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ type HeaderWithBackButtonProps = Partial<ChildrenProps> & {

/** Whether we should enable detail page navigation */
shouldEnableDetailPageNavigation?: boolean;

/** Whether we should overlay the 3 dots menu */
shouldOverlayDots?: boolean;
};

export type {ThreeDotsMenuItem};
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import ReceiptSearch from '@assets/images/receipt-search.svg';
import Receipt from '@assets/images/receipt.svg';
import Rotate from '@assets/images/rotate-image.svg';
import RotateLeft from '@assets/images/rotate-left.svg';
import Scan from '@assets/images/scan.svg';
import Send from '@assets/images/send.svg';
import Shield from '@assets/images/shield.svg';
import AppleLogo from '@assets/images/signIn/apple-logo.svg';
Expand Down Expand Up @@ -243,6 +244,7 @@ export {
ReceiptSearch,
Rotate,
RotateLeft,
Scan,
Send,
Shield,
Sync,
Expand Down
5 changes: 4 additions & 1 deletion src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay';
import useNativeDriver from '@libs/useNativeDriver';
import variables from '@styles/variables';
import * as Modal from '@userActions/Modal';
Expand Down Expand Up @@ -38,6 +39,7 @@ function BaseModal(
onLayout,
avoidKeyboard = false,
children,
shouldUseCustomBackdrop = false,
}: BaseModalProps,
ref: React.ForwardedRef<View>,
) {
Expand Down Expand Up @@ -185,7 +187,7 @@ function BaseModal(
swipeDirection={swipeDirection}
isVisible={isVisible}
backdropColor={theme.overlay}
backdropOpacity={hideBackdrop ? 0 : variables.overlayOpacity}
backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity}
backdropTransitionOutTiming={0}
hasBackdrop={fullscreen}
coverScreen={fullscreen}
Expand All @@ -201,6 +203,7 @@ function BaseModal(
statusBarTranslucent={statusBarTranslucent}
onLayout={onLayout}
avoidKeyboard={avoidKeyboard}
customBackdrop={shouldUseCustomBackdrop ? <Overlay onPress={handleBackdropPress} /> : undefined}
>
<View
style={[styles.defaultModalContainer, modalContainerStyle, modalPaddingStyles, !isVisible && styles.pointerEventsNone]}
Expand Down
3 changes: 3 additions & 0 deletions src/components/Modal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type BaseModalProps = Partial<ModalProps> & {
* See: https://github.com/react-native-modal/react-native-modal/pull/116
* */
hideModalContentWhileAnimating?: boolean;

/** Should we use a custom backdrop for the modal? (This prevents focus issues on desktop) */
shouldUseCustomBackdrop?: boolean;
};

export default BaseModalProps;
Expand Down
178 changes: 178 additions & 0 deletions src/components/PurposeForUsingExpensifyModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import {useNavigation} from '@react-navigation/native';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {ScrollView, View} from 'react-native';
import type {ValueOf} from 'type-fest';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Report from '@userActions/Report';
import * as Welcome from '@userActions/Welcome';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import SCREENS from '@src/SCREENS';
import HeaderWithBackButton from './HeaderWithBackButton';
import * as Expensicons from './Icon/Expensicons';
import Lottie from './Lottie';
import LottieAnimations from './LottieAnimations';
import type {MenuItemProps} from './MenuItem';
import MenuItemList from './MenuItemList';
import Modal from './Modal';
import Text from './Text';

// This is not translated because it is a message coming from concierge, which only supports english
const messageCopy = {
[CONST.INTRO_CHOICES.TRACK]:
'Great! To track your expenses, I suggest you create a workspace to keep everything contained:\n' +
'\n' +
'1. Press your avatar icon\n' +
'2. Choose Workspaces\n' +
'3. Choose New Workspace\n' +
'4. Name your workspace something meaningful (eg, "My Business Expenses")\n' +
'\n' +
'Once you have your workspace set up, you can add expenses to it as follows:\n' +
'\n' +
'1. Choose My Business Expenses (or whatever you named it) in the list of chat rooms\n' +
'2. Choose the + button in the chat compose window\n' +
'3. Choose Request money\n' +
"4. Choose what kind of expense you'd like to log, whether a manual expense, scanned receipt, or tracked distance.\n" +
'\n' +
"That'll be stored in your My Business Expenses room for your later access. Thanks for asking, and let me know how it goes!",
[CONST.INTRO_CHOICES.SUBMIT]:
'Hi there, to submit expenses for reimbursement, please:\n' +
'\n' +
'1. Press the big green + button\n' +
'2. Choose Request money\n' +
'3. Indicate how much to request, either manually, by scanning a receipt, or by tracking distance\n' +
'4. Enter the email address or phone number of your boss\n' +
'\n' +
"And we'll take it from there to get you paid back. Please give it a shot and let me know how it goes!",
[CONST.INTRO_CHOICES.MANAGE_TEAM]:
"Great! To manage your team's expenses, create a workspace to keep everything contained:\n" +
'\n' +
'1. Press your avatar icon\n' +
'2. Choose Workspaces\n' +
'3. Choose New Workspace\n' +
'4. Name your workspace something meaningful (eg, "Galaxy Food Inc.")\n' +
'\n' +
'Once you have your workspace set up, you can invite your team to it via the Members pane and connect a business bank account to reimburse them!',
[CONST.INTRO_CHOICES.CHAT_SPLIT]:
'Hi there, to split an expense such as with a friend, please:\n' +
'\n' +
'Press the big green + button\n' +
'Choose *Request money*\n' +
'Indicate how much was spent, either manually, by scanning a receipt, or by tracking distance\n' +
'Enter the email address or phone number of your friend\n' +
'Press *Split* next to their name\n' +
'Repeat as many times as you like for each of your friends\n' +
'Press *Add to split* when done adding friends\n' +
'Press Split to split the bill\n' +
'\n' +
"This will send a money request to each of your friends for however much they owe you, and we'll take care of getting you paid back. Thanks for asking, and let me know how it goes!",
};

const menuIcons = {
[CONST.INTRO_CHOICES.TRACK]: Expensicons.ReceiptSearch,
[CONST.INTRO_CHOICES.SUBMIT]: Expensicons.Scan,
[CONST.INTRO_CHOICES.MANAGE_TEAM]: Expensicons.MoneyBag,
[CONST.INTRO_CHOICES.CHAT_SPLIT]: Expensicons.Briefcase,
};

function PurposeForUsingExpensifyModal() {
const {translate} = useLocalize();
const StyleUtils = useStyleUtils();
const styles = useThemeStyles();
const {isSmallScreenWidth, windowHeight} = useWindowDimensions();
const navigation = useNavigation();
const [isModalOpen, setIsModalOpen] = useState(false);
const theme = useTheme();

useEffect(() => {
const navigationState = navigation.getState();
const routes = navigationState.routes;
const currentRoute = routes[navigationState.index];
if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRoute.name && currentRoute.name !== SCREENS.HOME) {
return;
}

Welcome.show(routes, () => setIsModalOpen(true));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const closeModal = useCallback(() => {
Report.dismissEngagementModal();
setIsModalOpen(false);
}, []);

const completeModalAndClose = useCallback((message: string, choice: ValueOf<typeof CONST.INTRO_CHOICES>) => {
Report.completeEngagementModal(message, choice);
setIsModalOpen(false);
Report.navigateToConciergeChat();
}, []);

const menuItems: MenuItemProps[] = useMemo(
() =>
Object.values(CONST.INTRO_CHOICES).map((choice) => {
const translationKey = `purposeForExpensify.${choice}` as const;
return {
key: translationKey,
title: translate(translationKey),
icon: menuIcons[choice],
iconRight: Expensicons.ArrowRight,
onPress: () => completeModalAndClose(messageCopy[choice], choice),
shouldShowRightIcon: true,
numberOfLinesTitle: 2,
};
}),
[completeModalAndClose, translate],
);

return (
<Modal
type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED}
isVisible={isModalOpen}
onClose={closeModal}
innerContainerStyle={styles.pt0}
shouldUseCustomBackdrop
>
<View style={{maxHeight: windowHeight}}>
<ScrollView>
<View style={StyleUtils.getBackgroundColorStyle(theme.PAGE_THEMES[SCREENS.SETTINGS.WORKSPACES].backgroundColor)}>
<Lottie
source={LottieAnimations.Hands}
style={styles.w100}
webStyle={styles.w100}
autoPlay
loop
/>
<HeaderWithBackButton
shouldShowCloseButton
shouldShowBackButton={false}
onCloseButtonPress={closeModal}
shouldOverlay
iconFill={theme.iconColorfulBackground}
/>
</View>
<View style={[styles.w100, styles.ph5, styles.pv5]}>
<Text
style={[styles.textHeadline, styles.preWrap, styles.mb2]}
numberOfLines={2}
>
{translate('purposeForExpensify.welcomeMessage')}
</Text>
<Text>{translate('purposeForExpensify.welcomeSubtitle')}</Text>
</View>
<MenuItemList
menuItems={menuItems}
shouldUseSingleExecution
/>
</ScrollView>
</View>
</Modal>
);
}

PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal';

export default PurposeForUsingExpensifyModal;
8 changes: 8 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2074,6 +2074,14 @@ export default {
},
copyReferralLink: 'Copy invite link',
},
purposeForExpensify: {
[CONST.INTRO_CHOICES.TRACK]: 'Track business spend for taxes',
[CONST.INTRO_CHOICES.SUBMIT]: 'Get paid back by my employer',
[CONST.INTRO_CHOICES.MANAGE_TEAM]: "Manage my team's expenses",
[CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chat and split bills with friends',
welcomeMessage: 'Welcome to Expensify',
welcomeSubtitle: 'What would you like to do?',
},
violations: {
allTagLevelsRequired: 'All tags required',
autoReportedRejectedExpense: ({rejectReason, rejectedBy}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rejected this expense with the comment "${rejectReason}"`,
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2561,6 +2561,14 @@ export default {
},
copyReferralLink: 'Copiar enlace de invitación',
},
purposeForExpensify: {
[CONST.INTRO_CHOICES.TRACK]: 'Seguimiento de los gastos de empresa para fines fiscales',
[CONST.INTRO_CHOICES.SUBMIT]: 'Reclamar gastos a mi empleador',
[CONST.INTRO_CHOICES.MANAGE_TEAM]: 'Gestionar los gastos de mi equipo',
[CONST.INTRO_CHOICES.CHAT_SPLIT]: 'Chatea y divide gastos con tus amigos',
welcomeMessage: 'Bienvenido a Expensify',
welcomeSubtitle: '¿Qué te gustaría hacer?',
},
violations: {
allTagLevelsRequired: 'Todas las etiquetas son obligatorias',
autoReportedRejectedExpense: ({rejectedBy, rejectReason}: ViolationsAutoReportedRejectedExpenseParams) => `${rejectedBy} rechazó la solicitud y comentó "${rejectReason}"`,
Expand Down
Loading

0 comments on commit 74f1163

Please sign in to comment.