From 4d299d6af14a810b62784d8c4debcc8358294bc9 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 16:24:43 +0700 Subject: [PATCH 001/146] fix: attachment modal is not closed when click notification --- ...bscribeToReportCommentPushNotifications.js | 23 +++++++++++-------- src/libs/actions/Report.js | 11 ++++++++- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js index 04fd34bf6075..84d6c4ef6514 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.js @@ -2,6 +2,7 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import Visibility from '@libs/Visibility'; +import * as Modal from '@userActions/Modal'; import ROUTES from '@src/ROUTES'; import backgroundRefresh from './backgroundRefresh'; import PushNotification from './index'; @@ -24,17 +25,19 @@ export default function subscribeToReportCommentPushNotifications() { Log.info('[PushNotification] onSelected() - called', false, {reportID, reportActionID}); Navigation.isNavigationReady().then(() => { - try { - // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back - if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { - Navigation.goBack(ROUTES.HOME); - } + Modal.close(() => { + try { + // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back + if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { + Navigation.goBack(ROUTES.HOME); + } - Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); - } catch (error) { - Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: error.message}); - } + Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)); + } catch (error) { + Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: error.message}); + } + }); }); }); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 1de15c1184cb..9d6688882486 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -22,6 +22,7 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; +import * as Modal from '@userActions/Modal'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -1815,7 +1816,15 @@ function showReportActionNotification(reportID, reportAction) { const notificationParams = { report, reportAction, - onClick: () => Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(reportID)), + onClick: () => { + Modal.close(() => { + const reportRoute = ROUTES.REPORT_WITH_ID.getRoute(reportID); + if (Navigation.isActiveRoute(reportRoute)) { + return; + } + Navigation.navigate(reportRoute); + }); + }, }; if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { LocalNotification.showModifiedExpenseNotification(notificationParams); From e2cc597f44a726885355ec8ebd5923739d2408fc Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 7 Nov 2023 17:04:57 +0700 Subject: [PATCH 002/146] fix lint --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9d6688882486..672ff1d97cd7 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -22,11 +22,11 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as UserUtils from '@libs/UserUtils'; import Visibility from '@libs/Visibility'; -import * as Modal from '@userActions/Modal'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import * as Modal from './Modal'; import * as Session from './Session'; import * as Welcome from './Welcome'; From 3e5c8b094bdec621983d5d075719ba2903bc031f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 20 Nov 2023 17:17:26 +0100 Subject: [PATCH 003/146] ref: started migrating LHNOptionsList module --- .../{LHNOptionsList.js => LHNOptionsList.tsx} | 20 ++- .../{OptionRowLHN.js => OptionRowLHN.tsx} | 117 +++++++----------- ...tionRowLHNData.js => OptionRowLHNData.tsx} | 0 3 files changed, 60 insertions(+), 77 deletions(-) rename src/components/LHNOptionsList/{LHNOptionsList.js => LHNOptionsList.tsx} (90%) rename src/components/LHNOptionsList/{OptionRowLHN.js => OptionRowLHN.tsx} (76%) rename src/components/LHNOptionsList/{OptionRowLHNData.js => OptionRowLHNData.tsx} (100%) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.tsx similarity index 90% rename from src/components/LHNOptionsList/LHNOptionsList.js rename to src/components/LHNOptionsList/LHNOptionsList.tsx index 0d300c5e2179..5def414f9010 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -2,8 +2,9 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -17,6 +18,7 @@ import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; const propTypes = { @@ -82,6 +84,20 @@ const defaultProps = { const keyExtractor = (item) => `report_${item}`; +type LHNOptionsListProps = { + style?: StyleProp; + contentContainerStyles: StyleProp; + data: string[]; + onSelectRow: (reportID: string) => void; + optionMode: ValueOf; + shouldDisableFocusOptions?: boolean; + policy: OnyxEntry; + reports: OnyxEntry>; + reportActions: OnyxEntry; + preferredLocale: OnyxEntry>; + personalDetails: OnyxEntry>; +}; + function LHNOptionsList({ style, contentContainerStyles, diff --git a/src/components/LHNOptionsList/OptionRowLHN.js b/src/components/LHNOptionsList/OptionRowLHN.tsx similarity index 76% rename from src/components/LHNOptionsList/OptionRowLHN.js rename to src/components/LHNOptionsList/OptionRowLHN.tsx index 8420f3db7a1e..89133fe27d72 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.js +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,8 +1,10 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import React, {RefObject, useCallback, useRef, useState} from 'react'; +import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; import _ from 'underscore'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; @@ -30,54 +32,26 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Beta} from '@src/types/onyx'; -const propTypes = { - /** Style for hovered state */ - // eslint-disable-next-line react/forbid-prop-types - hoverStyle: PropTypes.object, - - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - - /** The ID of the report that the option is for */ - reportID: PropTypes.string.isRequired, - - /** Whether this option is currently in focus so we can modify its style */ - isFocused: PropTypes.bool, - - /** A function that is called when an option is selected. Selected option is passed as a param */ - onSelectRow: PropTypes.func, - - /** Toggle between compact and default view */ - viewMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)), - - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - - /** The item that should be rendered */ - // eslint-disable-next-line react/forbid-prop-types - optionItem: PropTypes.object, -}; - -const defaultProps = { - hoverStyle: undefined, - viewMode: 'default', - onSelectRow: () => {}, - style: null, - optionItem: null, - isFocused: false, - betas: [], +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: unknown; }; - -function OptionRowLHN(props) { +function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const isFocusedRef = useRef(true); const {isSmallScreenWidth} = useWindowDimensions(); const {translate} = useLocalize(); - - const optionItem = props.optionItem; const [isContextMenuActive, setIsContextMenuActive] = useState(false); useFocusEffect( @@ -94,30 +68,28 @@ function OptionRowLHN(props) { } const isHidden = optionItem.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; - if (isHidden && !props.isFocused && !optionItem.isPinned) { + if (isHidden && !isFocused && !optionItem.isPinned) { return null; } - const textStyle = props.isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; + const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], props.style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); const alternateTextStyle = StyleUtils.combineStyles( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - props.style, + style, ); const contentContainerStyles = - props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( - props.viewMode === CONST.OPTION_MODE.COMPACT + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); const hoveredBackgroundColor = - (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - : theme.sidebar; + (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -139,9 +111,9 @@ function OptionRowLHN(props) { event, '', popoverAnchor, - props.reportID, + reportID, '0', - props.reportID, + reportID, '', () => {}, () => setIsContextMenuActive(false), @@ -152,15 +124,14 @@ function OptionRowLHN(props) { ); }; - const emojiCode = lodashGet(optionItem, 'status.emojiCode', ''); - const statusText = lodashGet(optionItem, 'status.text', ''); - const statusClearAfterDate = lodashGet(optionItem, 'status.clearAfter', ''); + const emojiCode = optionItem.status.emojiCode ?? ''; + const statusText = optionItem.status.text ?? ''; + const statusClearAfterDate = optionItem.status.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(props.betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - const isGroupChat = - optionItem.type === CONST.REPORT.TYPE.CHAT && _.isEmpty(optionItem.chatType) && !optionItem.isThread && lodashGet(optionItem, 'displayNamesWithTooltips.length', 0) > 2; + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; return ( @@ -180,7 +151,7 @@ function OptionRowLHN(props) { } // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); - props.onSelectRow(optionItem, popoverAnchor); + onSelectRow(optionItem, popoverAnchor); }} onMouseDown={(e) => { // Allow composer blur on right click @@ -196,7 +167,7 @@ function OptionRowLHN(props) { showPopover(e); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - DomUtils.getActiveElement().blur(); + DomUtils.getActiveElement()?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -208,32 +179,32 @@ function OptionRowLHN(props) { styles.sidebarLink, styles.sidebarLinkInner, StyleUtils.getBackgroundColorStyle(theme.sidebar), - props.isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !props.isFocused ? props.hoverStyle || styles.sidebarLinkHover : null, + isFocused ? styles.sidebarLinkActive : null, + (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : null, ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={props.optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} > {!_.isEmpty(optionItem.icons) && (optionItem.shouldShowSubscript ? ( ) : ( @@ -321,10 +292,6 @@ function OptionRowLHN(props) { ); } -OptionRowLHN.propTypes = propTypes; -OptionRowLHN.defaultProps = defaultProps; OptionRowLHN.displayName = 'OptionRowLHN'; export default React.memo(OptionRowLHN); - -export {propTypes, defaultProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.tsx similarity index 100% rename from src/components/LHNOptionsList/OptionRowLHNData.js rename to src/components/LHNOptionsList/OptionRowLHNData.tsx From 2418cd131d94790de2b152edfac532ef0d344c9b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 16:16:51 +0100 Subject: [PATCH 004/146] ref: working on migration to typescript --- .../LHNOptionsList/LHNOptionsList.tsx | 135 ++++++------------ .../LHNOptionsList/OptionRowLHN.tsx | 12 +- .../LHNOptionsList/OptionRowLHNData.tsx | 103 +++++++------ 3 files changed, 112 insertions(+), 138 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 5def414f9010..160f1330cfa8 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,102 +1,60 @@ -import {FlashList} from '@shopify/flash-list'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; -import _ from 'underscore'; -import participantPropTypes from '@components/participantPropTypes'; -import transactionPropTypes from '@components/transactionPropTypes'; -import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; +import withCurrentReportID, {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; -import reportPropTypes from '@pages/reportPropTypes'; -import stylePropTypes from '@styles/stylePropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -const propTypes = { +const keyExtractor = (item) => `report_${item}`; + +type LHNOptionsListProps = { /** Wrapper style for the section list */ - style: stylePropTypes, + style?: StyleProp; /** Extra styles for the section list container */ - contentContainerStyles: stylePropTypes.isRequired, + contentContainerStyles?: ContentStyle; /** Sections for the section list */ - data: PropTypes.arrayOf(PropTypes.string).isRequired, + data: string[]; /** Callback to fire when a row is selected */ - onSelectRow: PropTypes.func.isRequired, + onSelectRow: (reportID: string) => void; /** Toggle between compact and default view of the option */ - optionMode: PropTypes.oneOf(_.values(CONST.OPTION_MODE)).isRequired, + optionMode: ValueOf; /** Whether to allow option focus or not */ - shouldDisableFocusOptions: PropTypes.bool, + shouldDisableFocusOptions?: boolean; /** The policy which the user has access to and which the report could be tied to */ - policy: PropTypes.shape({ - /** The ID of the policy */ - id: PropTypes.string, - /** Name of the policy */ - name: PropTypes.string, - /** Avatar of the policy */ - avatar: PropTypes.string, - }), + policy: OnyxEntry>; /** All reports shared with the user */ - reports: PropTypes.objectOf(reportPropTypes), + reports: OnyxEntry>; /** Array of report actions for this report */ - reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), + reportActions: OnyxEntry>; /** Indicates which locale the user currently has selected */ - preferredLocale: PropTypes.string, + preferredLocale: OnyxEntry>; /** List of users' personal details */ - personalDetails: PropTypes.objectOf(participantPropTypes), + personalDetails: OnyxEntry>; /** The transaction from the parent report action */ - transactions: PropTypes.objectOf(transactionPropTypes), - /** List of draft comments */ - draftComments: PropTypes.objectOf(PropTypes.string), - ...withCurrentReportIDPropTypes, -}; - -const defaultProps = { - style: undefined, - shouldDisableFocusOptions: false, - reportActions: {}, - reports: {}, - policy: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - personalDetails: {}, - transactions: {}, - draftComments: {}, - ...withCurrentReportIDDefaultProps, -}; + transactions: OnyxEntry>; -const keyExtractor = (item) => `report_${item}`; - -type LHNOptionsListProps = { - style?: StyleProp; - contentContainerStyles: StyleProp; - data: string[]; - onSelectRow: (reportID: string) => void; - optionMode: ValueOf; - shouldDisableFocusOptions?: boolean; - policy: OnyxEntry; - reports: OnyxEntry>; - reportActions: OnyxEntry; - preferredLocale: OnyxEntry>; - personalDetails: OnyxEntry>; -}; + /** List of draft comments */ + draftComments: OnyxEntry>; +} & CurrentReportIDContextValue; function LHNOptionsList({ style, @@ -104,36 +62,31 @@ function LHNOptionsList({ data, onSelectRow, optionMode, - shouldDisableFocusOptions, - reports, - reportActions, - policy, - preferredLocale, - personalDetails, - transactions, - draftComments, - currentReportID, -}) { + shouldDisableFocusOptions = false, + reports = {}, + reportActions = {}, + policy = {}, + preferredLocale = CONST.LOCALES.DEFAULT, + personalDetails = {}, + transactions = {}, + draftComments = {}, + currentReportID = '', +}: LHNOptionsListProps) { const styles = useThemeStyles(); /** * Function which renders a row in the list - * - * @param {Object} params - * @param {Object} params.item - * - * @return {Component} */ const renderItem = useCallback( - ({item: reportID}) => { - const itemFullReport = reports[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] || {}; - const itemReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport.parentReportID}`] || {}; - const itemParentReportAction = itemParentReportActions[itemFullReport.parentReportActionID] || {}; - const itemPolicy = policy[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport.policyID}`] || {}; - const transactionID = lodashGet(itemParentReportAction, ['originalMessage', 'IOUTransactionID'], ''); - const itemTransaction = transactionID ? transactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; - const itemComment = draftComments[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] || ''; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport.participantAccountIDs, personalDetails); + ({item: reportID}: {item: string}) => { + const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; + const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); return ( + ; /** The preferred language for the app */ - preferredLocale: PropTypes.string, + preferredLocale: string; /** The full data of the report */ - // eslint-disable-next-line react/forbid-prop-types - fullReport: PropTypes.object, + fullReport: Report; /** The policy which the user has access to and which the report could be tied to */ - policy: PropTypes.shape({ - /** The ID of the policy */ - id: PropTypes.string, - /** Name of the policy */ - name: PropTypes.string, - /** Avatar of the policy */ - avatar: PropTypes.string, - }), + policy: Policy; /** The action from the parent report */ - parentReportAction: PropTypes.shape(reportActionPropTypes), + parentReportAction: ReportAction; /** The transaction from the parent report action */ - transaction: transactionPropTypes, - - ...basePropTypes, -}; - -const defaultProps = { - isFocused: false, - personalDetails: {}, - fullReport: {}, - policy: {}, - parentReportAction: {}, - transaction: {}, - preferredLocale: CONST.LOCALES.DEFAULT, - ...baseDefaultProps, -}; + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; +} & LHNOptionsListProps; /* * This component gets the data from onyx for the actual @@ -74,7 +97,7 @@ function OptionRowLHNData({ parentReportAction, transaction, ...propsToForward -}) { +}: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; const optionItemRef = useRef(); @@ -116,8 +139,6 @@ function OptionRowLHNData({ ); } -OptionRowLHNData.propTypes = propTypes; -OptionRowLHNData.defaultProps = defaultProps; OptionRowLHNData.displayName = 'OptionRowLHNData'; /** From 2e6f52a91aa81c4c8ab11703f8f20fc29770f0e9 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 21 Nov 2023 20:21:41 +0100 Subject: [PATCH 005/146] ref: contuniue migration --- .../LHNOptionsList/LHNOptionsList.tsx | 44 +-------- .../LHNOptionsList/OptionRowLHN.tsx | 29 ++---- .../LHNOptionsList/OptionRowLHNData.tsx | 46 ++------- src/components/LHNOptionsList/types.ts | 95 +++++++++++++++++++ src/components/SubscriptAvatar.tsx | 32 +++---- src/libs/SidebarUtils.ts | 2 + 6 files changed, 127 insertions(+), 121 deletions(-) create mode 100644 src/components/LHNOptionsList/types.ts diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 160f1330cfa8..64acf390e6a6 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -12,50 +12,10 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; +import {LHNOptionsListProps} from './types'; const keyExtractor = (item) => `report_${item}`; -type LHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; - - /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry>; - - /** All reports shared with the user */ - reports: OnyxEntry>; - - /** Array of report actions for this report */ - reportActions: OnyxEntry>; - - /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry>; - - /** List of users' personal details */ - personalDetails: OnyxEntry>; - - /** The transaction from the parent report action */ - transactions: OnyxEntry>; - - /** List of draft comments */ - draftComments: OnyxEntry>; -} & CurrentReportIDContextValue; - function LHNOptionsList({ style, contentContainerStyles, @@ -69,8 +29,8 @@ function LHNOptionsList({ preferredLocale = CONST.LOCALES.DEFAULT, personalDetails = {}, transactions = {}, - draftComments = {}, currentReportID = '', + draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); /** diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 72add9bc8ae5..24d006ae7555 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,11 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {RefObject, useCallback, useRef, useState} from 'react'; -import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import _ from 'underscore'; +import React, {useCallback, useRef, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -32,18 +27,8 @@ import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; -import {Beta} from '@src/types/onyx'; +import {OptionRowLHNProps} from './types'; -type OptionRowLHNProps = { - hoverStyle?: StyleProp; - betas?: Beta[]; - reportID: string; - isFocused?: boolean; - onSelectRow?: (optionItem: unknown, popoverAnchor: RefObject) => void; - viewMode?: ValueOf; - style?: StyleProp; - optionItem?: unknown; -}; function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); @@ -184,7 +169,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ]} role={CONST.ACCESSIBILITY_ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} - needsOffscreenAlphaCompositing={optionItem.icons.length >= 2} + needsOffscreenAlphaCompositing={(optionItem?.icons?.length ?? 0) >= 2} > @@ -192,13 +177,13 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe (optionItem.shouldShowSubscript ? ( ) : ( ; - - /** The preferred language for the app */ - preferredLocale: string; - - /** The full data of the report */ - fullReport: Report; - - /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; - - /** The action from the parent report */ - parentReportAction: ReportAction; - - /** The transaction from the parent report action */ - transaction: Transaction; - - comment: string; - - receiptTransactions: Transaction[]; -} & LHNOptionsListProps; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. @@ -100,10 +72,10 @@ function OptionRowLHNData({ }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(); + const optionItemRef = useRef(); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const lastReportAction = _.first(sortedReportActions); + const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); @@ -114,7 +86,9 @@ function OptionRowLHNData({ if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } - optionItemRef.current = item; + if (item) { + optionItemRef.current = item; + } return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed @@ -122,10 +96,10 @@ function OptionRowLHNData({ }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); useEffect(() => { - if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { + if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; } - Report.setReportWithDraft(reportID, true); + ReportLib.setReportWithDraft(reportID, true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts new file mode 100644 index 000000000000..2f7bfc1005c0 --- /dev/null +++ b/src/components/LHNOptionsList/types.ts @@ -0,0 +1,95 @@ +import {ContentStyle} from '@shopify/flash-list'; +import {Transaction} from 'electron'; +import {RefObject} from 'react'; +import {StyleProp, ViewStyle} from 'react-native'; +import {OnyxEntry} from 'react-native-onyx'; +import {ValueOf} from 'type-fest'; +import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {OptionData} from '@libs/SidebarUtils'; +import CONST from '@src/CONST'; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; + +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; + + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; + + /** The policy which the user has access to and which the report could be tied to */ + policy: OnyxEntry>; + + /** All reports shared with the user */ + reports: OnyxEntry>; + + /** Array of report actions for this report */ + reportActions: OnyxEntry>; + + /** Indicates which locale the user currently has selected */ + preferredLocale: OnyxEntry>; + + /** List of users' personal details */ + personalDetails: OnyxEntry>; + + /** The transaction from the parent report action */ + transactions: OnyxEntry>; + + /** List of draft comments */ + draftComments: OnyxEntry>; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + +type OptionRowLHNDataProps = { + /** Whether row should be focused */ + isFocused: boolean; + + /** List of users' personal details */ + personalDetails: Record; + + /** The preferred language for the app */ + preferredLocale: string; + + /** The full data of the report */ + fullReport: Report; + + /** The policy which the user has access to and which the report could be tied to */ + policy: Policy; + + /** The action from the parent report */ + parentReportAction: ReportAction; + + /** The transaction from the parent report action */ + transaction: Transaction; + + comment: string; + + receiptTransactions: Transaction[]; + + reportID: string; +} & CustomLHNOptionsListProps; + +type OptionRowLHNProps = { + hoverStyle?: StyleProp; + betas?: Beta[]; + reportID: string; + isFocused?: boolean; + onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + viewMode?: ValueOf; + style?: StyleProp; + optionItem?: OptionData; +}; + +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index ab9f0dec8e57..8b77565e25ff 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -1,37 +1,20 @@ import React, {memo} from 'react'; import {View} from 'react-native'; import {ValueOf} from 'type-fest'; -import type {AvatarSource} from '@libs/UserUtils'; import * as StyleUtils from '@styles/StyleUtils'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; +import {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; -type SubAvatar = { - /** Avatar source to display */ - source?: AvatarSource; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - - /** Avatar id */ - id?: number | string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: AvatarSource; -}; - type SubscriptAvatarProps = { /** Avatar URL or icon */ - mainAvatar?: SubAvatar; + mainAvatar?: Icon; /** Subscript avatar URL or icon */ - secondaryAvatar?: SubAvatar; + secondaryAvatar?: Icon; /** Set the size of avatars */ size?: ValueOf; @@ -46,7 +29,14 @@ type SubscriptAvatarProps = { showTooltip?: boolean; }; -function SubscriptAvatar({mainAvatar = {}, secondaryAvatar = {}, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) { +function SubscriptAvatar({ + mainAvatar = {} as Icon, + secondaryAvatar = {} as Icon, + size = CONST.AVATAR_SIZE.DEFAULT, + backgroundColor, + noMargin = false, + showTooltip = true, +}: SubscriptAvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const isSmall = size === CONST.AVATAR_SIZE.SMALL; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 58c4a124335d..8aa01543ddae 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -527,3 +527,5 @@ export default { isSidebarLoadedReady, resetIsSidebarLoadedReadyPromise, }; + +export type {OptionData}; From 6c72cd965b0620907d4a75e8fe42ae874ce069d5 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 13:09:20 +0100 Subject: [PATCH 006/146] fix: add few type fixes --- .../LHNOptionsList/LHNOptionsList.tsx | 63 ++++++++++++------- .../LHNOptionsList/OptionRowLHNData.tsx | 50 +++------------ src/components/LHNOptionsList/types.ts | 62 +++++++++--------- src/libs/SidebarUtils.ts | 22 ++++--- 4 files changed, 96 insertions(+), 101 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 64acf390e6a6..967525b0ff6a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,20 +1,17 @@ -import {ContentStyle, FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import withCurrentReportID, {CurrentReportIDContextValue} from '@components/withCurrentReportID'; +import {FlatList, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails, Policy, Report, ReportActions, Transaction} from '@src/types/onyx'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListProps} from './types'; +import {LHNOptionsListOnyxProps, LHNOptionsListProps} from './types'; -const keyExtractor = (item) => `report_${item}`; +const keyExtractor = (item: string) => `report_${item}`; function LHNOptionsList({ style, @@ -33,21 +30,41 @@ function LHNOptionsList({ draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + + /** + * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization + * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large + * lists. + * + * @param itemData - This is the same as the data we pass into the component + * @param index the current item's index in the set of data + */ + const getItemLayout = useCallback( + // eslint-disable-next-line @typescript-eslint/naming-convention + (_, index: number) => { + const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; + return { + length: optionHeight, + offset: index * optionHeight, + index, + }; + }, + [optionMode], + ); /** * Function which renders a row in the list */ const renderItem = useCallback( ({item: reportID}: {item: string}) => { - const itemFullReport: Report | undefined = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]; - const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`]; - const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? '']; - const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`]; - const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID : ''; - const itemTransaction = transactionID ? transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] : {}; + const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; + const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; + const itemParentReportAction = itemParentReportActions?.[itemFullReport?.parentReportActionID ?? ''] ?? null; + const itemPolicy = policy?.[`${ONYXKEYS.COLLECTION.POLICY}${itemFullReport?.policyID}`] ?? null; + const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; + const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); - return ( - ); @@ -90,8 +109,7 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; export default compose( - withCurrentReportID, - withOnyx({ + withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, }, @@ -114,6 +132,7 @@ export default compose( key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, }), + withCurrentReportID, )(LHNOptionsList); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index de2e3742a817..4abc69828791 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -4,42 +4,10 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils, {OptionData} from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import * as ReportLib from '@userActions/Report'; +import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const propTypes = { -// /** Whether row should be focused */ -// isFocused: PropTypes.bool, - -// /** List of users' personal details */ -// personalDetails: PropTypes.objectOf(participantPropTypes), - -// /** The preferred language for the app */ -// preferredLocale: PropTypes.string, - -// /** The full data of the report */ -// // eslint-disable-next-line react/forbid-prop-types -// fullReport: PropTypes.object, - -// /** The policy which the user has access to and which the report could be tied to */ -// policy: PropTypes.shape({ -// /** The ID of the policy */ -// id: PropTypes.string, -// /** Name of the policy */ -// name: PropTypes.string, -// /** Avatar of the policy */ -// avatar: PropTypes.string, -// }), - -// /** The action from the parent report */ -// parentReportAction: PropTypes.shape(reportActionPropTypes), - -// /** The transaction from the parent report action */ -// transaction: transactionPropTypes, - -// ...basePropTypes, -// }; - // const defaultProps = { // isFocused: false, // personalDetails: {}, @@ -58,16 +26,16 @@ import {OptionRowLHNDataProps} from './types'; * re-render if the data really changed. */ function OptionRowLHNData({ - isFocused, - fullReport, + isFocused = false, + fullReport = null, reportActions, - personalDetails, - preferredLocale, + personalDetails = {}, + preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy, + policy = null, receiptTransactions, - parentReportAction, - transaction, + parentReportAction = null, + transaction = null, ...propsToForward }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; @@ -78,7 +46,7 @@ function OptionRowLHNData({ const lastReportAction = sortedReportActions[0]; return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions, reportActions]); + }, [fullReport?.reportID, receiptTransactions, reportActions]); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 2f7bfc1005c0..5a736e57721d 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,5 +1,4 @@ import {ContentStyle} from '@shopify/flash-list'; -import {Transaction} from 'electron'; import {RefObject} from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import {OnyxEntry} from 'react-native-onyx'; @@ -7,27 +6,9 @@ import {ValueOf} from 'type-fest'; import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import {OptionData} from '@libs/SidebarUtils'; import CONST from '@src/CONST'; -import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions} from '@src/types/onyx'; - -type CustomLHNOptionsListProps = { - /** Wrapper style for the section list */ - style?: StyleProp; - - /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; - - /** Sections for the section list */ - data: string[]; - - /** Callback to fire when a row is selected */ - onSelectRow: (reportID: string) => void; - - /** Toggle between compact and default view of the option */ - optionMode: ValueOf; - - /** Whether to allow option focus or not */ - shouldDisableFocusOptions?: boolean; +import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxEntry>; @@ -49,8 +30,27 @@ type CustomLHNOptionsListProps = { /** List of draft comments */ draftComments: OnyxEntry>; }; +type CustomLHNOptionsListProps = { + /** Wrapper style for the section list */ + style?: StyleProp; + + /** Extra styles for the section list container */ + contentContainerStyles?: ContentStyle; + + /** Sections for the section list */ + data: string[]; + + /** Callback to fire when a row is selected */ + onSelectRow: (reportID: string) => void; -type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue; + /** Toggle between compact and default view of the option */ + optionMode: ValueOf; + + /** Whether to allow option focus or not */ + shouldDisableFocusOptions?: boolean; +}; + +type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextValue & LHNOptionsListOnyxProps; type OptionRowLHNDataProps = { /** Whether row should be focused */ @@ -60,26 +60,28 @@ type OptionRowLHNDataProps = { personalDetails: Record; /** The preferred language for the app */ - preferredLocale: string; + preferredLocale: OnyxEntry>; /** The full data of the report */ - fullReport: Report; + fullReport: OnyxEntry; /** The policy which the user has access to and which the report could be tied to */ - policy: Policy; + policy: OnyxEntry; /** The action from the parent report */ - parentReportAction: ReportAction; + parentReportAction: OnyxEntry; /** The transaction from the parent report action */ - transaction: Transaction; + transaction: OnyxEntry; comment: string; - receiptTransactions: Transaction[]; + receiptTransactions: OnyxEntry>; reportID: string; -} & CustomLHNOptionsListProps; + + reportActions: OnyxEntry; +}; type OptionRowLHNProps = { hoverStyle?: StyleProp; @@ -92,4 +94,4 @@ type OptionRowLHNProps = { optionItem?: OptionData; }; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps}; +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d3bc3e609486..824e397376ab 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -219,6 +219,12 @@ function getOrderedReportIDs( return LHNReports; } +type Status = { + text: string; + emojiCode: string; + clearAfter: string; +}; + type OptionData = { text?: string | null; alternateText?: string | null; @@ -235,7 +241,7 @@ type OptionData = { managerID?: number | null; reportID?: string | null; policyID?: string | null; - status?: string | null; + status?: Status | null; type?: string | null; stateNum?: ValueOf | null; statusNum?: ValueOf | null; @@ -292,12 +298,12 @@ type Icon = { * Gets all the data necessary for rendering an OptionRowLHN component */ function getOptionData( - report: Report, - reportActions: Record, - personalDetails: Record, - preferredLocale: ValueOf, - policy: Policy, - parentReportAction: ReportAction, + report: OnyxEntry, + reportActions: OnyxEntry>, + personalDetails: OnyxEntry>, + preferredLocale: OnyxEntry>, + policy: OnyxEntry, + parentReportAction: OnyxEntry, ): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do From 2bf40c8f31db5342889c0b2845ec63f87c7a95ec Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 22 Nov 2023 19:37:24 +0100 Subject: [PATCH 007/146] fix: types --- src/components/DisplayNames/types.ts | 2 +- .../LHNOptionsList/OptionRowLHN.tsx | 43 +++++++++++-------- .../LHNOptionsList/OptionRowLHNData.tsx | 11 ----- src/components/LHNOptionsList/types.ts | 2 +- src/libs/SidebarUtils.ts | 12 ++---- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts index 94e4fc7c39c6..307c28bd2df3 100644 --- a/src/components/DisplayNames/types.ts +++ b/src/components/DisplayNames/types.ts @@ -12,7 +12,7 @@ type DisplayNameWithTooltip = { login?: string; /** The avatar for the tooltip fallback */ - avatar: AvatarSource; + avatar?: AvatarSource; }; type DisplayNamesProps = { diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 24d006ae7555..e2db0cba498a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleProp, StyleSheet, TextStyle, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -59,22 +59,24 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style); + const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style ?? {}); const alternateTextStyle = StyleUtils.combineStyles( viewMode === CONST.OPTION_MODE.COMPACT ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - style, + style ?? {}, ); const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, optionRowStyles.compactContentContainerStyles] : [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten( - viewMode === CONST.OPTION_MODE.COMPACT - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], - ); + const sidebarInnerRowStyle = StyleSheet.flatten([ + styles.chatLinkRowPressable, + styles.flexGrow1, + styles.optionItemAvatarNameWrapper, + viewMode === CONST.OPTION_MODE.COMPACT ? styles.optionRowCompact : styles.optionRow, + styles.justifyContentCenter, + ]); const hoveredBackgroundColor = - (!!hoverStyle || styles.sidebarLinkHover) && (hoverStyle || styles.sidebarLinkHover).backgroundColor ? (hoverStyle || styles.sidebarLinkHover).backgroundColor : theme.sidebar; + !!hoverStyle && 'backgroundColor' in hoverStyle && 'backgroundColor' in styles.sidebarLinkHover ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -84,7 +86,7 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe /** * Show the ReportActionContextMenu modal popover. * - * @param {Object} [event] - A press event. + * @param [event] - A press event. */ const showPopover = (event) => { if (!isFocusedRef.current && isSmallScreenWidth) { @@ -114,11 +116,10 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem.reportID)); - - const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips.length ?? 0) > 2; - const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem.reportID)) : optionItem.text; + const isStatusVisible = Permissions.canUseCustomStatus(betas) && !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem?.reportID ?? '')); + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && !optionItem.chatType && !optionItem.isThread && (optionItem?.displayNamesWithTooltips?.length ?? 0) > 2; + const fullTitle = isGroupChat ? getGroupChatName(ReportUtils.getReport(optionItem?.reportID ?? '')) : optionItem.text; return ( {isStatusVisible && ( @@ -226,9 +231,9 @@ function OptionRowLHN({hoverStyle, betas = [], reportID, isFocused = false, onSe ) : null} - {optionItem.descriptiveText ? ( + {optionItem?.descriptiveText ? ( - {optionItem.descriptiveText} + {optionItem?.descriptiveText} ) : null} {hasBrickError && ( diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 4abc69828791..b48454fb7ec9 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -8,17 +8,6 @@ import CONST from '@src/CONST'; import OptionRowLHN from './OptionRowLHN'; import {OptionRowLHNDataProps} from './types'; -// const defaultProps = { -// isFocused: false, -// personalDetails: {}, -// fullReport: {}, -// policy: {}, -// parentReportAction: {}, -// transaction: {}, -// preferredLocale: CONST.LOCALES.DEFAULT, -// ...baseDefaultProps, -// }; - /* * This component gets the data from onyx for the actual * OptionRowLHN component. diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 5a736e57721d..fa0e48a413c4 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -90,7 +90,7 @@ type OptionRowLHNProps = { isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; - style?: StyleProp; + style?: ViewStyle | ViewStyle[]; optionItem?: OptionData; }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 824e397376ab..5432662dcc94 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -231,7 +231,7 @@ type OptionData = { pendingAction?: OnyxCommon.PendingAction | null; allReportErrors?: OnyxCommon.Errors | null; brickRoadIndicator?: typeof CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR | '' | null; - icons?: Icon[] | null; + icons?: OnyxCommon.Icon[] | null; tooltipText?: string | null; ownerAccountID?: number | null; subtitle?: string | null; @@ -272,11 +272,12 @@ type OptionData = { notificationPreference?: string | number | null; displayNamesWithTooltips?: DisplayNamesWithTooltip[] | null; chatType?: ValueOf | null; + descriptiveText?: string; }; type DisplayNamesWithTooltip = { displayName?: string; - avatar?: string; + avatar?: UserUtils.AvatarSource; login?: string; accountID?: number; pronouns?: string; @@ -287,13 +288,6 @@ type ActorDetails = { accountID?: number; }; -type Icon = { - source?: string; - id?: number; - type?: string; - name?: string; -}; - /** * Gets all the data necessary for rendering an OptionRowLHN component */ From e205c9d9d416107e2fe8d8e1a3ca500c35d61b40 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 24 Nov 2023 12:48:21 +0100 Subject: [PATCH 008/146] fix: few type issues --- .../LHNOptionsList/LHNOptionsList.tsx | 33 ++++--------------- src/components/LHNOptionsList/types.ts | 3 +- src/styles/StyleUtils.ts | 4 +-- 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 967525b0ff6a..d71fe1db535d 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,6 @@ +import {FlashList} from '@shopify/flash-list'; import React, {useCallback} from 'react'; -import {FlatList, View} from 'react-native'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; @@ -31,26 +32,6 @@ function LHNOptionsList({ }: LHNOptionsListProps) { const styles = useThemeStyles(); - /** - * This function is used to compute the layout of any given item in our list. Since we know that each item will have the exact same height, this is a performance optimization - * so that the heights can be determined before the options are rendered. Otherwise, the heights are determined when each option is rendering and it causes a lot of overhead on large - * lists. - * - * @param itemData - This is the same as the data we pass into the component - * @param index the current item's index in the set of data - */ - const getItemLayout = useCallback( - // eslint-disable-next-line @typescript-eslint/naming-convention - (_, index: number) => { - const optionHeight = optionMode === CONST.OPTION_MODE.COMPACT ? variables.optionRowHeightCompact : variables.optionRowHeight; - return { - length: optionHeight, - offset: index * optionHeight, - index, - }; - }, - [optionMode], - ); /** * Function which renders a row in the list */ @@ -88,19 +69,17 @@ function LHNOptionsList({ return ( - ); diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index fa0e48a413c4..bd6fb9a1a0a0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -85,13 +85,12 @@ type OptionRowLHNDataProps = { type OptionRowLHNProps = { hoverStyle?: StyleProp; - betas?: Beta[]; reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; style?: ViewStyle | ViewStyle[]; - optionItem?: OptionData; + optionItem?: OptionData | null; }; export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; diff --git a/src/styles/StyleUtils.ts b/src/styles/StyleUtils.ts index 4b998f940244..695570f2669f 100644 --- a/src/styles/StyleUtils.ts +++ b/src/styles/StyleUtils.ts @@ -425,7 +425,7 @@ function getAutoGrowHeightInputStyle(textInputHeight: number, maxHeight: number) /** * Returns a style with backgroundColor and borderColor set to the same color */ -function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { +function getBackgroundAndBorderStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, borderColor: backgroundColor, @@ -435,7 +435,7 @@ function getBackgroundAndBorderStyle(backgroundColor: string): ViewStyle { /** * Returns a style with the specified backgroundColor */ -function getBackgroundColorStyle(backgroundColor: string): ViewStyle { +function getBackgroundColorStyle(backgroundColor: ColorValue): ViewStyle { return { backgroundColor, }; From 71aaa5aff5721ed25f670fcf990f7c48629008ac Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 13:15:45 -0700 Subject: [PATCH 009/146] Create new component --- .../PurposeForUsingExpensifyModal.js | 116 ++++++++++++++++++ src/pages/home/ReportScreen.js | 38 +++--- 2 files changed, 138 insertions(+), 16 deletions(-) create mode 100644 src/components/PurposeForUsingExpensifyModal.js diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js new file mode 100644 index 000000000000..c629442086aa --- /dev/null +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -0,0 +1,116 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import {withOnyx} from 'react-native-onyx'; +import useLocalize from '@hooks/useLocalize'; +import compose from '@libs/compose'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import useThemeStyles from "@styles/useThemeStyles"; +import useWindowDimensions from "@hooks/useWindowDimensions"; +import {useState} from "@types/react"; +import withLocalize, {withLocalizePropTypes} from "@components/withLocalize"; +import ScreenWrapper from "./ScreenWrapper"; +import MenuItemList from "./MenuItemList"; +import Header from "./Header"; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import Modal from "./Modal"; +import HeaderGap from "./HeaderGap"; +import * as Expensicons from './Icon/Expensicons'; + +const propTypes = { + + /** Session info for the currently logged in user. */ + session: PropTypes.shape({ + /** Currently logged in user accountID */ + accountID: PropTypes.number, + }), + + ...withLocalizePropTypes, + + ...windowDimensionsPropTypes, +}; + +const defaultProps = { + session: {}, +}; + +function PurposeForUsingExpensifyModal() { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + const [isModalOpen, setIsModalOpen] = useState(true); + + const menuItems = [ + { + key: 'twoFactorAuth.headerTitle', + title: translate('twoFactorAuth.headerTitle'), + icon: Expensicons.ReceiptSearch, + iconRight: Expensicons.ArrowRight, + onPress: () => {debugger;}, + shouldShowRightIcon: true, + }, + { + key: 'twoFactorAuth.headerTitle', + title: translate('twoFactorAuth.headerTitle'), + icon: Expensicons.ReceiptSearch, + iconRight: Expensicons.ArrowRight, + onPress: () => {debugger;}, + shouldShowRightIcon: true, + }, + { + key: 'twoFactorAuth.headerTitle', + title: translate('twoFactorAuth.headerTitle'), + icon: Expensicons.MoneyBag, + iconRight: Expensicons.ArrowRight, + onPress: () => {debugger;}, + shouldShowRightIcon: true, + }, + { + key: 'twoFactorAuth.headerTitle', + title: translate('twoFactorAuth.headerTitle'), + icon: Expensicons.Briefcase, + iconRight: Expensicons.ArrowRight, + onPress: () => {debugger;}, + shouldShowRightIcon: true, + }, + ]; + + return ( + + + {isSmallScreenWidth && } +
setIsModalOpen(false)} + /> + + + + ); +} + +PurposeForUsingExpensifyModal.propTypes = propTypes; +PurposeForUsingExpensifyModal.defaultProps = defaultProps; +PurposeForUsingExpensifyModal.displayName = 'AddPaymentMethodMenu'; + +export default compose( + withWindowDimensions, + withLocalize, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(PurposeForUsingExpensifyModal); diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 33646e7129cd..bc5b63e08bc3 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -32,6 +32,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import PurposeForUsingExpensifyModal from '@components/PurposeForUsingExpensifyModal'; import HeaderView from './HeaderView'; import reportActionPropTypes from './report/reportActionPropTypes'; import ReportActionsView from './report/ReportActionsView'; @@ -135,22 +136,22 @@ function getReportID(route) { } function ReportScreen({ - betas, - route, - report, - reportMetadata, - reportActions, - accountManagerReportID, - personalDetails, - markReadyForHydration, - policies, - isSidebarLoaded, - viewportOffsetTop, - isComposerFullSize, - errors, - userLeavingStatus, - currentReportID, -}) { + betas, + route, + report, + reportMetadata, + reportActions, + accountManagerReportID, + personalDetails, + markReadyForHydration, + policies, + isSidebarLoaded, + viewportOffsetTop, + isComposerFullSize, + errors, + userLeavingStatus, + currentReportID, + }) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); @@ -452,6 +453,11 @@ function ReportScreen({ )} + {}} + onItemSelected={() => {}} + /> From 38f9c59303f2367b038ea0beb6eb9edd3bc53312 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 13:29:31 -0700 Subject: [PATCH 010/146] Fix imports --- src/components/PurposeForUsingExpensifyModal.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index c629442086aa..ac8fadf1b0e1 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React from 'react'; +import React, {useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; @@ -7,7 +7,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import useThemeStyles from "@styles/useThemeStyles"; import useWindowDimensions from "@hooks/useWindowDimensions"; -import {useState} from "@types/react"; import withLocalize, {withLocalizePropTypes} from "@components/withLocalize"; import ScreenWrapper from "./ScreenWrapper"; import MenuItemList from "./MenuItemList"; From d387417995c68b9e29c47efc84de91a8af39d1b9 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 13:50:23 -0700 Subject: [PATCH 011/146] Fix header --- src/components/PurposeForUsingExpensifyModal.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index ac8fadf1b0e1..6ef2fb2a33a4 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -10,7 +10,7 @@ import useWindowDimensions from "@hooks/useWindowDimensions"; import withLocalize, {withLocalizePropTypes} from "@components/withLocalize"; import ScreenWrapper from "./ScreenWrapper"; import MenuItemList from "./MenuItemList"; -import Header from "./Header"; +import HeaderWithBackButton from "./HeaderWithBackButton"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import HeaderGap from "./HeaderGap"; @@ -78,6 +78,7 @@ function PurposeForUsingExpensifyModal() { {isSmallScreenWidth && } -
setIsModalOpen(false)} + shouldShowBackButton={false} + onCloseButtonPress={() => setIsModalOpen(false)} /> Date: Tue, 28 Nov 2023 13:55:13 -0700 Subject: [PATCH 012/146] Add english copy and fix key errors --- src/components/PurposeForUsingExpensifyModal.js | 16 ++++++++-------- src/languages/en.ts | 6 ++++++ src/languages/es.ts | 6 ++++++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 6ef2fb2a33a4..5f591e7c8ccf 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -41,32 +41,32 @@ function PurposeForUsingExpensifyModal() { const menuItems = [ { - key: 'twoFactorAuth.headerTitle', - title: translate('twoFactorAuth.headerTitle'), + key: 'purposeForExpensify.track', + title: translate('purposeForExpensify.track'), icon: Expensicons.ReceiptSearch, iconRight: Expensicons.ArrowRight, onPress: () => {debugger;}, shouldShowRightIcon: true, }, { - key: 'twoFactorAuth.headerTitle', - title: translate('twoFactorAuth.headerTitle'), + key: 'purposeForExpensify.submit', + title: translate('purposeForExpensify.submit'), icon: Expensicons.ReceiptSearch, iconRight: Expensicons.ArrowRight, onPress: () => {debugger;}, shouldShowRightIcon: true, }, { - key: 'twoFactorAuth.headerTitle', - title: translate('twoFactorAuth.headerTitle'), + key: 'purposeForExpensify.VSB', + title: translate('purposeForExpensify.VSB'), icon: Expensicons.MoneyBag, iconRight: Expensicons.ArrowRight, onPress: () => {debugger;}, shouldShowRightIcon: true, }, { - key: 'twoFactorAuth.headerTitle', - title: translate('twoFactorAuth.headerTitle'), + key: 'purposeForExpensify.SMB', + title: translate('purposeForExpensify.SMB'), icon: Expensicons.Briefcase, iconRight: Expensicons.ArrowRight, onPress: () => {debugger;}, diff --git a/src/languages/en.ts b/src/languages/en.ts index 7bc9c985ad66..f95e964c3b54 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1974,4 +1974,10 @@ export default { }, copyReferralLink: 'Copy referral link', }, + purposeForExpensify: { + track: 'Keep track of personal receipts and expenses', + submit: 'Scan and submit receipts to my manager', + VSB: 'Manage my business expenses to file taxes', + SMB: 'Explore Expensify for my own company', + } } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index 6ea01dc4bd14..1779de80a471 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2459,4 +2459,10 @@ export default { }, copyReferralLink: 'Copiar enlace de invitación', }, + purposeForExpensify: { + track: 'Keep track of personal receipts and expenses', + submit: 'Scan and submit receipts to my manager', + VSB: 'Manage my business expenses to file taxes', + SMB: 'Explore Expensify for my own company', + } } satisfies EnglishTranslation; From 01629a22ae3cbd912511d053d5dfe5125521ba09 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 14:13:00 -0700 Subject: [PATCH 013/146] Switch to IllustratedHeaderPageLayout --- .../PurposeForUsingExpensifyModal.js | 28 +++++++++++-------- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 5f591e7c8ccf..25770f724329 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -11,10 +11,16 @@ import withLocalize, {withLocalizePropTypes} from "@components/withLocalize"; import ScreenWrapper from "./ScreenWrapper"; import MenuItemList from "./MenuItemList"; import HeaderWithBackButton from "./HeaderWithBackButton"; +import Header from "./Header"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import HeaderGap from "./HeaderGap"; import * as Expensicons from './Icon/Expensicons'; +import * as StyleUtils from "@styles/StyleUtils"; +import IllustratedHeaderPageLayout from "@components/IllustratedHeaderPageLayout"; +import SCREENS from "@src/SCREENS"; +import LottieAnimations from "@components/LottieAnimations"; +import useTheme from '@styles/themes/useTheme'; const propTypes = { @@ -38,6 +44,7 @@ function PurposeForUsingExpensifyModal() { const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const [isModalOpen, setIsModalOpen] = useState(true); + const theme = useTheme(); const menuItems = [ { @@ -80,23 +87,22 @@ function PurposeForUsingExpensifyModal() { isVisible={isModalOpen} fullscreen > - setIsModalOpen(false)} > - {isSmallScreenWidth && } - setIsModalOpen(false)} +
- + ); } diff --git a/src/languages/en.ts b/src/languages/en.ts index f95e964c3b54..0dc082bb23ec 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1979,5 +1979,7 @@ export default { submit: 'Scan and submit receipts to my manager', VSB: 'Manage my business expenses to file taxes', SMB: 'Explore Expensify for my own company', + welcomeMessage: 'Welcome to Expensify', + welcomeSubtitle: 'What\'s your main purpose for using Expensify?', } } satisfies TranslationBase; diff --git a/src/languages/es.ts b/src/languages/es.ts index 1779de80a471..49458c794c90 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2464,5 +2464,7 @@ export default { submit: 'Scan and submit receipts to my manager', VSB: 'Manage my business expenses to file taxes', SMB: 'Explore Expensify for my own company', + welcomeMessage: 'Welcome to Expensify', + welcomeSubtitle: 'What\'s your main purpose for using Expensify?', } } satisfies EnglishTranslation; From 96dc71ea7cec3b0ffd313d60f8a83cb6f2d4225f Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 15:33:18 -0700 Subject: [PATCH 014/146] Use text instead of the header component for better styling --- src/components/PurposeForUsingExpensifyModal.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 25770f724329..a8eb921fe664 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -21,6 +21,8 @@ import IllustratedHeaderPageLayout from "@components/IllustratedHeaderPageLayout import SCREENS from "@src/SCREENS"; import LottieAnimations from "@components/LottieAnimations"; import useTheme from '@styles/themes/useTheme'; +import Text from "@components/Text"; +import {View} from "react-native"; const propTypes = { @@ -94,10 +96,15 @@ function PurposeForUsingExpensifyModal() { shouldShowBackButton={false} onCloseButtonPress={() => setIsModalOpen(false)} > -
+ + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + Date: Tue, 28 Nov 2023 15:35:31 -0700 Subject: [PATCH 015/146] Fix imports --- .../PurposeForUsingExpensifyModal.js | 21 ++++++------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index a8eb921fe664..1221afa59767 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -5,24 +5,18 @@ import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from "@src/SCREENS"; import useThemeStyles from "@styles/useThemeStyles"; +import useTheme from '@styles/themes/useTheme'; import useWindowDimensions from "@hooks/useWindowDimensions"; -import withLocalize, {withLocalizePropTypes} from "@components/withLocalize"; -import ScreenWrapper from "./ScreenWrapper"; +import {View} from "react-native"; +import IllustratedHeaderPageLayout from "./IllustratedHeaderPageLayout"; +import LottieAnimations from "./LottieAnimations"; +import Text from "./Text"; import MenuItemList from "./MenuItemList"; -import HeaderWithBackButton from "./HeaderWithBackButton"; -import Header from "./Header"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; -import HeaderGap from "./HeaderGap"; import * as Expensicons from './Icon/Expensicons'; -import * as StyleUtils from "@styles/StyleUtils"; -import IllustratedHeaderPageLayout from "@components/IllustratedHeaderPageLayout"; -import SCREENS from "@src/SCREENS"; -import LottieAnimations from "@components/LottieAnimations"; -import useTheme from '@styles/themes/useTheme'; -import Text from "@components/Text"; -import {View} from "react-native"; const propTypes = { @@ -32,8 +26,6 @@ const propTypes = { accountID: PropTypes.number, }), - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, }; @@ -120,7 +112,6 @@ PurposeForUsingExpensifyModal.displayName = 'AddPaymentMethodMenu'; export default compose( withWindowDimensions, - withLocalize, withOnyx({ session: { key: ONYXKEYS.SESSION, From 39b3593a9325331c0155e13db428e712a71c51d2 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 16:30:25 -0700 Subject: [PATCH 016/146] Change illustration --- src/components/PurposeForUsingExpensifyModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 1221afa59767..b2ed7f10c6b6 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -80,10 +80,11 @@ function PurposeForUsingExpensifyModal() { type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} isVisible={isModalOpen} fullscreen + onClose={() => setIsModalOpen(false)} > setIsModalOpen(false)} From 2c21559074951acfbe19a24fe9668f7601c45810 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 28 Nov 2023 18:48:55 -0700 Subject: [PATCH 017/146] add scan asset and update row --- assets/images/scan.svg | 10 ++++++++++ src/components/Icon/Expensicons.ts | 2 ++ src/components/PurposeForUsingExpensifyModal.js | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 assets/images/scan.svg diff --git a/assets/images/scan.svg b/assets/images/scan.svg new file mode 100644 index 000000000000..629dc3823a12 --- /dev/null +++ b/assets/images/scan.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 3d4f0edb1656..4505e6f31de7 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -105,6 +105,7 @@ import Receipt from '@assets/images/receipt.svg'; import Rotate from '@assets/images/rotate-image.svg'; import RotateLeft from '@assets/images/rotate-left.svg'; import Send from '@assets/images/send.svg'; +import Scan from '@assets/images/scan.svg'; import Shield from '@assets/images/shield.svg'; import AppleLogo from '@assets/images/signIn/apple-logo.svg'; import GoogleLogo from '@assets/images/signIn/google-logo.svg'; @@ -238,6 +239,7 @@ export { ReceiptSearch, Rotate, RotateLeft, + Scan, Send, Shield, Sync, diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index b2ed7f10c6b6..e7651bd69315 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -52,7 +52,7 @@ function PurposeForUsingExpensifyModal() { { key: 'purposeForExpensify.submit', title: translate('purposeForExpensify.submit'), - icon: Expensicons.ReceiptSearch, + icon: Expensicons.Scan, iconRight: Expensicons.ArrowRight, onPress: () => {debugger;}, shouldShowRightIcon: true, From 7f32222b8ec887d5961cbd771b427c87edac40b3 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 4 Dec 2023 15:31:13 -0700 Subject: [PATCH 018/146] ReplaceillustratedHeaderPageLayout with views to make it work on mobile --- .../PurposeForUsingExpensifyModal.js | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index e7651bd69315..ef48513e68bd 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -5,18 +5,22 @@ import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import SCREENS from "@src/SCREENS"; import useThemeStyles from "@styles/useThemeStyles"; import useTheme from '@styles/themes/useTheme'; import useWindowDimensions from "@hooks/useWindowDimensions"; -import {View} from "react-native"; -import IllustratedHeaderPageLayout from "./IllustratedHeaderPageLayout"; +import {View, StyleSheet} from "react-native"; import LottieAnimations from "./LottieAnimations"; import Text from "./Text"; import MenuItemList from "./MenuItemList"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import * as Expensicons from './Icon/Expensicons'; +import Lottie from "@components/Lottie"; +import * as Illustrations from '@components/Icon/Illustrations'; +import HeaderWithBackButton from "@components/HeaderWithBackButton"; +import Image from "@components/Image"; +import * as StyleUtils from "@styles/StyleUtils"; +import SCREENS from "@src/SCREENS"; const propTypes = { @@ -82,14 +86,22 @@ function PurposeForUsingExpensifyModal() { fullscreen onClose={() => setIsModalOpen(false)} > - setIsModalOpen(false)} - > - + + setIsModalOpen(false)} + /> + + + + - + ); } From 1d2ff01ed586247f743cd23d7dd76236a53a9052 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 4 Dec 2023 18:33:05 -0700 Subject: [PATCH 019/146] Add new method for completing engagement modal --- src/libs/actions/Report.js | 118 +++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index a03488429405..f585985ad6fa 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2400,6 +2400,124 @@ function getReportPrivateNote(reportID) { ); } +/** + * Add up to two report actions to a report. This method can be called for the following situations: + * + * - Adding one comment + * - Adding one attachment + * - Add both a comment and attachment simultaneously + * + * @param {String} text + * @param {String} choice + */ +function completeEngagementModal( text , choice) { + const commandName = 'completeEngagementModal'; + const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text); + const reportCommentAction = reportComment.reportAction; + const reportCommentText = reportComment.commentText; + const currentTime = DateUtils.getDBTime(); + const lastCommentText = ReportUtils.formatReportLastMessageText(reportCommentAction.message[0].text); + + const optimisticReport = { + lastVisibleActionCreated: currentTime, + lastMessageTranslationKey: lodashGet(reportCommentAction, 'message[0].translationKey', ''), + lastMessageText: lastCommentText, + lastMessageHtml: lastCommentText, + lastActorAccountID: currentUserAccountID, + lastReadTime: currentTime, + }; + + if (ReportUtils.getReportNotificationPreference(ReportUtils.getReport(conciergeChatReportID)) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; + } + + // Optimistically add the new actions to the store before waiting to save them to the server + const optimisticReportActions = {}; + optimisticReportActions[reportCommentAction.reportActionID] = reportCommentAction; + + + const parameters = { + reportID: conciergeChatReportID, + reportActionID: reportCommentAction.reportActionID, + reportComment: reportCommentText, + engagementChoice: choice, + + }; + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`, + value: optimisticReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, + value: optimisticReportActions, + }, + ]; + + const successData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, + value: _.mapObject(optimisticReportActions, () => ({pendingAction: null})), + }, + ]; + + let failureReport = { + lastMessageTranslationKey: '', + lastMessageText: '', + lastVisibleActionCreated: '', + }; + const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportActionsUtils.getLastVisibleMessage(conciergeChatReportID); + if (lastMessageText || lastMessageTranslationKey) { + const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(conciergeChatReportID); + const lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); + const lastActorAccountID = lodashGet(lastVisibleAction, 'actorAccountID'); + failureReport = { + lastMessageTranslationKey, + lastMessageText, + lastVisibleActionCreated, + lastActorAccountID, + }; + } + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`, + value: failureReport, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, + value: _.mapObject(optimisticReportActions, (action) => ({ + ...action, + errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), + })), + }, + ]; + + // Update the timezone if it's been 5 minutes from the last time the user added a comment + if (DateUtils.canUpdateTimezone()) { + const timezone = DateUtils.getCurrentTimezone(); + parameters.timezone = JSON.stringify(timezone); + optimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: {[currentUserAccountID]: {timezone}}, + }); + DateUtils.setTimezoneUpdated(); + } + + API.write(commandName, parameters, { + optimisticData, + successData, + failureData, + }); + notifyNewAction(conciergeChatReportID, reportCommentAction.actorAccountID, reportCommentAction.reportActionID); +} + /** * Loads necessary data for rendering the RoomMembersPage * From 5fb78b815057aacad51050e91162192db32870c8 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 5 Dec 2023 10:16:27 -0700 Subject: [PATCH 020/146] Add new nvp keys and add to the optimistic data --- src/ONYXKEYS.ts | 4 ++++ src/libs/actions/Report.js | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5576eb64736d..8b499ff6d98b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -110,6 +110,10 @@ const ONYXKEYS = { /** This NVP holds to most recent waypoints that a person has used when creating a distance request */ NVP_RECENT_WAYPOINTS: 'expensify_recentWaypoints', + NVP_HAS_DISMISSED_IDLE_PANEL: 'hasDismissedIdlePanel', + + NVP_INTRO_SELECTED: 'introSelected', + /** Does this user have push notifications enabled for this device? */ PUSH_NOTIFICATIONS_ENABLED: 'pushNotificationsEnabled', diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index f585985ad6fa..42257654b30e 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2455,6 +2455,11 @@ function completeEngagementModal( text , choice) { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, value: optimisticReportActions, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.NVP_INTRO_SELECTED, + value: {choice} + }, ]; const successData = [ @@ -2462,7 +2467,7 @@ function completeEngagementModal( text , choice) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, value: _.mapObject(optimisticReportActions, () => ({pendingAction: null})), - }, + } ]; let failureReport = { From 877efb9d3fc117e5b2da804c8815fc42a4f47505 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 5 Dec 2023 11:52:38 -0700 Subject: [PATCH 021/146] Add copy that concierge will send --- .../PurposeForUsingExpensifyModal.js | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index ef48513e68bd..4bf0b4bea865 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -44,6 +44,51 @@ function PurposeForUsingExpensifyModal() { const [isModalOpen, setIsModalOpen] = useState(true); const theme = useTheme(); + const messageCopy = { + track: 'Great! To track your expenses, I suggest you create a workspace to keep everything contained:
' + + '
' + + '1. Press your avatar icon
' + + '2. Choose Workspaces
' + + '3. Choose New Workspace
' + + '4. Name your workspace something meaningful (eg, "My Business Expenses")
' + + '
' + + 'Once you have your workspace set up, you can add expenses to it as follows:
' + + '
' + + '1. Choose My Business Expenses (or whatever you named it) in the list of chat rooms
' + + '2. Choose the + button in the chat compose window
' + + '3. Choose Request money
' + + '4. Choose what kind of expense you\'d like to log, whether a manual expense, scanned receipt, or tracked distance.
' + + '
' + + 'That\'ll be stored in your My Business Expenses room for your later access. Thanks for asking, and let me know how it goes!', + submit: 'Hi there, to submit expenses for reimbursement, please:
' + + '
' + + '1. Press the big green + button
' + + '2. Choose Request money
' + + '3. Indicate how much to request, either manually, by scanning a receipt, or by tracking distance
' + + '4. Enter the email address or phone number of your boss
' + + '
' + + 'And we\'ll take it from there to get you paid back. Please give it a shot and let me know how it goes!', + manage: 'Great! To manage your team\'s expenses, create a workspace to keep everything contained:
' + + '
' + + '1. Press your avatar icon
' + + '2. Choose Workspaces
' + + '3. Choose New Workspace
' + + '4. Name your workspace something meaningful (eg, "Galaxy Food Inc.")
' + + '
' + + '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!', + chat: 'Hi there, to split an expense such as with a friend, please:
' + + '
' + + 'Press the big green + button
' + + 'Choose *Request money*
' + + 'Indicate how much was spent, either manually, by scanning a receipt, or by tracking distance
' + + 'Enter the email address or phone number of your friend
' + + 'Press *Split* next to their name
' + + 'Repeat as many times as you like for each of your friends
' + + 'Press *Add to split* when done adding friends
' + + 'Press Split to split the bill
' + + '
' + + 'This will send an 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 menuItems = [ { key: 'purposeForExpensify.track', From 5f921af4c91706f29e0cfd213e4a7f30a9524850 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 5 Dec 2023 14:14:52 -0700 Subject: [PATCH 022/146] Update copy and send message --- .../PurposeForUsingExpensifyModal.js | 24 ++++++++++--------- src/languages/en.ts | 8 +++---- src/languages/es.ts | 8 +++---- src/libs/actions/Report.js | 1 + 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 4bf0b4bea865..22097df59902 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -11,6 +11,7 @@ import useWindowDimensions from "@hooks/useWindowDimensions"; import {View, StyleSheet} from "react-native"; import LottieAnimations from "./LottieAnimations"; import Text from "./Text"; +import * as Report from '../libs/actions/Report'; import MenuItemList from "./MenuItemList"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; @@ -44,6 +45,7 @@ function PurposeForUsingExpensifyModal() { const [isModalOpen, setIsModalOpen] = useState(true); const theme = useTheme(); + // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { track: 'Great! To track your expenses, I suggest you create a workspace to keep everything contained:
' + '
' + @@ -68,7 +70,7 @@ function PurposeForUsingExpensifyModal() { '4. Enter the email address or phone number of your boss
' + '
' + 'And we\'ll take it from there to get you paid back. Please give it a shot and let me know how it goes!', - manage: 'Great! To manage your team\'s expenses, create a workspace to keep everything contained:
' + + business: 'Great! To manage your team\'s expenses, create a workspace to keep everything contained:
' + '
' + '1. Press your avatar icon
' + '2. Choose Workspaces
' + @@ -76,7 +78,7 @@ function PurposeForUsingExpensifyModal() { '4. Name your workspace something meaningful (eg, "Galaxy Food Inc.")
' + '
' + '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!', - chat: 'Hi there, to split an expense such as with a friend, please:
' + + chatSplit: 'Hi there, to split an expense such as with a friend, please:
' + '
' + 'Press the big green + button
' + 'Choose *Request money*
' + @@ -87,7 +89,7 @@ function PurposeForUsingExpensifyModal() { 'Press *Add to split* when done adding friends
' + 'Press Split to split the bill
' + '
' + - 'This will send an 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!' + + '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 menuItems = [ { @@ -95,7 +97,7 @@ function PurposeForUsingExpensifyModal() { title: translate('purposeForExpensify.track'), icon: Expensicons.ReceiptSearch, iconRight: Expensicons.ArrowRight, - onPress: () => {debugger;}, + onPress: () => Report.completeEngagementModal(messageCopy.track, 'trackNewDot'), shouldShowRightIcon: true, }, { @@ -103,23 +105,23 @@ function PurposeForUsingExpensifyModal() { title: translate('purposeForExpensify.submit'), icon: Expensicons.Scan, iconRight: Expensicons.ArrowRight, - onPress: () => {debugger;}, + onPress: () => Report.completeEngagementModal(messageCopy.submit, 'submitNewDot'), shouldShowRightIcon: true, }, { - key: 'purposeForExpensify.VSB', - title: translate('purposeForExpensify.VSB'), + key: 'purposeForExpensify.business', + title: translate('purposeForExpensify.business'), icon: Expensicons.MoneyBag, iconRight: Expensicons.ArrowRight, - onPress: () => {debugger;}, + onPress: () => Report.completeEngagementModal(messageCopy.business, 'businessNewDot'), shouldShowRightIcon: true, }, { - key: 'purposeForExpensify.SMB', - title: translate('purposeForExpensify.SMB'), + key: 'purposeForExpensify.chatSplit', + title: translate('purposeForExpensify.chatSplit'), icon: Expensicons.Briefcase, iconRight: Expensicons.ArrowRight, - onPress: () => {debugger;}, + onPress: () => Report.completeEngagementModal(messageCopy.chatSplit, 'chatSplitNewDot'), shouldShowRightIcon: true, }, ]; diff --git a/src/languages/en.ts b/src/languages/en.ts index d10600d3bd3a..bd454480e471 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1976,10 +1976,10 @@ export default { copyReferralLink: 'Copy referral link', }, purposeForExpensify: { - track: 'Keep track of personal receipts and expenses', - submit: 'Scan and submit receipts to my manager', - VSB: 'Manage my business expenses to file taxes', - SMB: 'Explore Expensify for my own company', + track: 'Track my business expenses to file taxes', + submit: 'Submit expenses to my employer to get paid back', + business: 'Manage my team\'s expenses', + chatSplit: 'Chat and split expenses with friends', welcomeMessage: 'Welcome to Expensify', welcomeSubtitle: 'What\'s your main purpose for using Expensify?', } diff --git a/src/languages/es.ts b/src/languages/es.ts index 6a60c7a402fe..ce2ac0894c4f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2461,10 +2461,10 @@ export default { copyReferralLink: 'Copiar enlace de invitación', }, purposeForExpensify: { - track: 'Keep track of personal receipts and expenses', - submit: 'Scan and submit receipts to my manager', - VSB: 'Manage my business expenses to file taxes', - SMB: 'Explore Expensify for my own company', + track: 'Track my business expenses to file taxes', + submit: 'Submit expenses to my employer to get paid back', + business: 'Manage my team\'s expenses', + chatSplit: 'Chat and split expenses with friends', welcomeMessage: 'Welcome to Expensify', welcomeSubtitle: 'What\'s your main purpose for using Expensify?', } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 42257654b30e..ceec4039cf0c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2683,6 +2683,7 @@ export { hasErrorInPrivateNotes, getOlderActions, getNewerActions, + completeEngagementModal, openRoomMembersPage, savePrivateNotesDraft, getDraftPrivateNote, From cfad7ccf3af0098752304e3fe37261d6a985de40 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 5 Dec 2023 14:37:08 -0700 Subject: [PATCH 023/146] Aso close the modal when submitting an option --- .../PurposeForUsingExpensifyModal.js | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 22097df59902..83ab667a2109 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useState} from 'react'; +import React, {useState, useCallback} from 'react'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; @@ -91,13 +91,23 @@ function PurposeForUsingExpensifyModal() { '
' + '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 closeModal = useCallback(() => { + setIsModalOpen(false); + }, []); + + const completeModalAndClose = (message, choice) => { + Report.completeEngagementModal(message, choice); + closeModal(); + } + const menuItems = [ { key: 'purposeForExpensify.track', title: translate('purposeForExpensify.track'), icon: Expensicons.ReceiptSearch, iconRight: Expensicons.ArrowRight, - onPress: () => Report.completeEngagementModal(messageCopy.track, 'trackNewDot'), + onPress: () => completeModalAndClose(messageCopy.track, 'trackNewDot'), shouldShowRightIcon: true, }, { @@ -105,7 +115,7 @@ function PurposeForUsingExpensifyModal() { title: translate('purposeForExpensify.submit'), icon: Expensicons.Scan, iconRight: Expensicons.ArrowRight, - onPress: () => Report.completeEngagementModal(messageCopy.submit, 'submitNewDot'), + onPress: () => completeModalAndClose(messageCopy.submit, 'submitNewDot'), shouldShowRightIcon: true, }, { @@ -113,7 +123,7 @@ function PurposeForUsingExpensifyModal() { title: translate('purposeForExpensify.business'), icon: Expensicons.MoneyBag, iconRight: Expensicons.ArrowRight, - onPress: () => Report.completeEngagementModal(messageCopy.business, 'businessNewDot'), + onPress: () => completeModalAndClose(messageCopy.business, 'businessNewDot'), shouldShowRightIcon: true, }, { @@ -121,7 +131,7 @@ function PurposeForUsingExpensifyModal() { title: translate('purposeForExpensify.chatSplit'), icon: Expensicons.Briefcase, iconRight: Expensicons.ArrowRight, - onPress: () => Report.completeEngagementModal(messageCopy.chatSplit, 'chatSplitNewDot'), + onPress: () => completeModalAndClose(messageCopy.chatSplit, 'chatSplitNewDot'), shouldShowRightIcon: true, }, ]; @@ -131,7 +141,7 @@ function PurposeForUsingExpensifyModal() { type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} isVisible={isModalOpen} fullscreen - onClose={() => setIsModalOpen(false)} + onClose={closeModal} > Date: Tue, 5 Dec 2023 16:04:25 -0700 Subject: [PATCH 024/146] Re-order elements, add overlay param, and rename poorly named prop --- src/components/AttachmentModal.js | 2 +- src/components/HeaderWithBackButton/index.js | 7 ++++--- src/components/PurposeForUsingExpensifyModal.js | 15 ++++++++------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 356e75b1e1bb..56a3b2244ffa 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -440,7 +440,7 @@ function AttachmentModal(props) { shouldShowThreeDotsButton={shouldShowThreeDotsButton} threeDotsAnchorPosition={styles.threeDotsPopoverOffsetAttachmentModal(windowWidth)} threeDotsMenuItems={threeDotsMenuItems} - shouldOverlay + shouldOverlayDots /> {!_.isEmpty(props.report) ? ( diff --git a/src/components/HeaderWithBackButton/index.js b/src/components/HeaderWithBackButton/index.js index 051e18ed675e..57897d17ecfd 100755 --- a/src/components/HeaderWithBackButton/index.js +++ b/src/components/HeaderWithBackButton/index.js @@ -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'; @@ -51,6 +51,7 @@ function HeaderWithBackButton({ threeDotsMenuItems = [], shouldEnableDetailPageNavigation = false, children = null, + shouldOverlayDots = false, shouldOverlay = false, singleExecution = (func) => func, shouldNavigateToTopMostReport = false, @@ -65,7 +66,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]} > {shouldShowBackButton && ( @@ -159,7 +160,7 @@ function HeaderWithBackButton({ menuItems={threeDotsMenuItems} onIconPress={onThreeDotsButtonPress} anchorPosition={threeDotsAnchorPosition} - shouldOverlay={shouldOverlay} + shouldOverlay={shouldOverlayDots} /> )} {shouldShowCloseButton && ( diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 83ab667a2109..544dfa035e58 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -144,12 +144,7 @@ function PurposeForUsingExpensifyModal() { onClose={closeModal} > - setIsModalOpen(false)} - /> - + - + setIsModalOpen(false)} + shouldOverlay + /> + Date: Tue, 5 Dec 2023 16:38:56 -0700 Subject: [PATCH 025/146] Add padding and remove unnecessary view --- .../PurposeForUsingExpensifyModal.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 544dfa035e58..0cf1267c6dce 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -144,22 +144,20 @@ function PurposeForUsingExpensifyModal() { onClose={closeModal} > - - - + setIsModalOpen(false)} shouldOverlay /> - + Date: Tue, 5 Dec 2023 19:05:29 -0700 Subject: [PATCH 026/146] Fix copy --- .../PurposeForUsingExpensifyModal.js | 78 +++++++++---------- src/libs/actions/Report.js | 16 ---- 2 files changed, 39 insertions(+), 55 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 0cf1267c6dce..d9ff33c62a09 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -47,48 +47,48 @@ function PurposeForUsingExpensifyModal() { // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { - track: 'Great! To track your expenses, I suggest you create a workspace to keep everything contained:
' + - '
' + - '1. Press your avatar icon
' + - '2. Choose Workspaces
' + - '3. Choose New Workspace
' + - '4. Name your workspace something meaningful (eg, "My Business Expenses")
' + - '
' + - 'Once you have your workspace set up, you can add expenses to it as follows:
' + - '
' + - '1. Choose My Business Expenses (or whatever you named it) in the list of chat rooms
' + - '2. Choose the + button in the chat compose window
' + - '3. Choose Request money
' + - '4. Choose what kind of expense you\'d like to log, whether a manual expense, scanned receipt, or tracked distance.
' + - '
' + + 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!', - submit: 'Hi there, to submit expenses for reimbursement, please:
' + - '
' + - '1. Press the big green + button
' + - '2. Choose Request money
' + - '3. Indicate how much to request, either manually, by scanning a receipt, or by tracking distance
' + - '4. Enter the email address or phone number of your boss
' + - '
' + + 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!', - business: 'Great! To manage your team\'s expenses, create a workspace to keep everything contained:
' + - '
' + - '1. Press your avatar icon
' + - '2. Choose Workspaces
' + - '3. Choose New Workspace
' + - '4. Name your workspace something meaningful (eg, "Galaxy Food Inc.")
' + - '
' + + business: '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!', - chatSplit: 'Hi there, to split an expense such as with a friend, please:
' + - '
' + - 'Press the big green + button
' + - 'Choose *Request money*
' + - 'Indicate how much was spent, either manually, by scanning a receipt, or by tracking distance
' + - 'Enter the email address or phone number of your friend
' + - 'Press *Split* next to their name
' + - 'Repeat as many times as you like for each of your friends
' + - 'Press *Add to split* when done adding friends
' + - 'Press Split to split the bill
' + - '
' + + chatSplit: '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!', } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index ceec4039cf0c..9ea671a1b43a 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2487,21 +2487,6 @@ function completeEngagementModal( text , choice) { lastActorAccountID, }; } - const failureData = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`, - value: failureReport, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, - value: _.mapObject(optimisticReportActions, (action) => ({ - ...action, - errors: ErrorUtils.getMicroSecondOnyxError('report.genericAddCommentFailureMessage'), - })), - }, - ]; // Update the timezone if it's been 5 minutes from the last time the user added a comment if (DateUtils.canUpdateTimezone()) { @@ -2518,7 +2503,6 @@ function completeEngagementModal( text , choice) { API.write(commandName, parameters, { optimisticData, successData, - failureData, }); notifyNewAction(conciergeChatReportID, reportCommentAction.actorAccountID, reportCommentAction.reportActionID); } From 91120f905cb05fb1737192c943d59c10fcb91938 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 5 Dec 2023 19:05:50 -0700 Subject: [PATCH 027/146] Always get the conciergeChatReport because sometimes it won't be set --- src/components/PurposeForUsingExpensifyModal.js | 1 + src/libs/actions/Report.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index d9ff33c62a09..52aa25390604 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -97,6 +97,7 @@ function PurposeForUsingExpensifyModal() { }, []); const completeModalAndClose = (message, choice) => { + debugger; Report.completeEngagementModal(message, choice); closeModal(); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9ea671a1b43a..7d4a558a310e 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2427,6 +2427,11 @@ function completeEngagementModal( text , choice) { lastReadTime: currentTime, }; + const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); + const conciergeChatReport = ReportUtils.getChatByParticipants(conciergeAccountID); + conciergeChatReportID = conciergeChatReport.reportID; + debugger; + if (ReportUtils.getReportNotificationPreference(ReportUtils.getReport(conciergeChatReportID)) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; } From 0def96ff1fc06f11dfc0e5d3909b99e1e0aec6c1 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 6 Dec 2023 06:47:26 -0700 Subject: [PATCH 028/146] Fix coloring --- src/components/PurposeForUsingExpensifyModal.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 52aa25390604..a18f91cd11a4 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -141,10 +141,9 @@ function PurposeForUsingExpensifyModal() { - + setIsModalOpen(false)} shouldOverlay /> + - ); } From e5ef2b4b2a70e27070f76b19e87fde8bd92b1fe3 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 6 Dec 2023 06:52:43 -0700 Subject: [PATCH 029/146] Fix x color --- src/components/PurposeForUsingExpensifyModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index a18f91cd11a4..125d226d652f 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -156,6 +156,7 @@ function PurposeForUsingExpensifyModal() { shouldShowBackButton={false} onCloseButtonPress={() => setIsModalOpen(false)} shouldOverlay + iconFill={theme.iconColorfulBackground} />
From 73fb91986d4b6c675c28df46bfc3ec61de8dfe97 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 6 Dec 2023 07:36:06 -0700 Subject: [PATCH 030/146] Lint --- .../PurposeForUsingExpensifyModal.js | 15 +++++++-------- src/libs/actions/Report.js | 18 ------------------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 125d226d652f..135da1710db0 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -8,20 +8,19 @@ import ONYXKEYS from '@src/ONYXKEYS'; import useThemeStyles from "@styles/useThemeStyles"; import useTheme from '@styles/themes/useTheme'; import useWindowDimensions from "@hooks/useWindowDimensions"; -import {View, StyleSheet} from "react-native"; +import * as Report from '@userActions/Report'; +import * as StyleUtils from "@styles/StyleUtils"; +import SCREENS from "@src/SCREENS"; +import {View} from "react-native"; import LottieAnimations from "./LottieAnimations"; import Text from "./Text"; -import * as Report from '../libs/actions/Report'; +import Lottie from "./Lottie"; +import HeaderWithBackButton from "./HeaderWithBackButton"; + import MenuItemList from "./MenuItemList"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import * as Expensicons from './Icon/Expensicons'; -import Lottie from "@components/Lottie"; -import * as Illustrations from '@components/Icon/Illustrations'; -import HeaderWithBackButton from "@components/HeaderWithBackButton"; -import Image from "@components/Image"; -import * as StyleUtils from "@styles/StyleUtils"; -import SCREENS from "@src/SCREENS"; const propTypes = { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index c85f10a2750e..55c48a8302a6 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2466,24 +2466,6 @@ function completeEngagementModal( text , choice) { } ]; - let failureReport = { - lastMessageTranslationKey: '', - lastMessageText: '', - lastVisibleActionCreated: '', - }; - const {lastMessageText = '', lastMessageTranslationKey = ''} = ReportActionsUtils.getLastVisibleMessage(conciergeChatReportID); - if (lastMessageText || lastMessageTranslationKey) { - const lastVisibleAction = ReportActionsUtils.getLastVisibleAction(conciergeChatReportID); - const lastVisibleActionCreated = lodashGet(lastVisibleAction, 'created'); - const lastActorAccountID = lodashGet(lastVisibleAction, 'actorAccountID'); - failureReport = { - lastMessageTranslationKey, - lastMessageText, - lastVisibleActionCreated, - lastActorAccountID, - }; - } - // Update the timezone if it's been 5 minutes from the last time the user added a comment if (DateUtils.canUpdateTimezone()) { const timezone = DateUtils.getCurrentTimezone(); From 01ce5e3ec4236db9d31f2d85fb085c734dac405b Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 6 Dec 2023 08:58:17 -0700 Subject: [PATCH 031/146] fix background color after merge --- src/components/PurposeForUsingExpensifyModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 135da1710db0..ec55ba94875e 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -142,7 +142,7 @@ function PurposeForUsingExpensifyModal() { isVisible={isModalOpen} onClose={closeModal} > - + Date: Wed, 6 Dec 2023 15:26:22 -0700 Subject: [PATCH 032/146] add modal to set dismissed NVP --- .../PurposeForUsingExpensifyModal.js | 6 +++--- src/libs/actions/Report.js | 21 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index ec55ba94875e..e3065730dc58 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -92,13 +92,13 @@ function PurposeForUsingExpensifyModal() { } const closeModal = useCallback(() => { + Report.dismissEngagementModal(); setIsModalOpen(false); }, []); const completeModalAndClose = (message, choice) => { - debugger; Report.completeEngagementModal(message, choice); - closeModal(); + setIsModalOpen(false) } const menuItems = [ @@ -153,7 +153,7 @@ function PurposeForUsingExpensifyModal() { setIsModalOpen(false)} + onCloseButtonPress={closeModal} shouldOverlay iconFill={theme.iconColorfulBackground} /> diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 55c48a8302a6..c5627ff6845c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2485,6 +2485,26 @@ function completeEngagementModal( text , choice) { notifyNewAction(conciergeChatReportID, reportCommentAction.actorAccountID, reportCommentAction.reportActionID); } +function dismissEngagementModal() { + const commandName = 'SetNameValuePair'; + const parameters = { + name: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, + value: true, + }; + + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: 'ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL', + value: true, + }, + ]; + + API.write(commandName, parameters, { + optimisticData, + }); +} + /** * Loads necessary data for rendering the RoomMembersPage * @@ -2653,6 +2673,7 @@ export { getOlderActions, getNewerActions, completeEngagementModal, + dismissEngagementModal, openRoomMembersPage, savePrivateNotesDraft, getDraftPrivateNote, From 60c13dca923c6843aa71ab7a84930c1dcc959abd Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 6 Dec 2023 15:26:46 -0700 Subject: [PATCH 033/146] Use the concierge accountID so that the message comes from them --- src/libs/ReportUtils.ts | 9 +++++---- src/libs/actions/Report.js | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b50b4611a249..c0ea2bf80556 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2376,12 +2376,13 @@ function getParsedComment(text: string): string { return text.length <= CONST.MAX_MARKUP_LENGTH ? parser.replace(text) : lodashEscape(text); } -function buildOptimisticAddCommentReportAction(text?: string, file?: File & {source: string; uri: string}): OptimisticReportAction { +function buildOptimisticAddCommentReportAction(text?: string, file?: File & {source: string; uri: string}, actorAccountID?: number): OptimisticReportAction { const parser = new ExpensiMark(); const commentText = getParsedComment(text ?? ''); const isAttachment = !text && file !== undefined; const attachmentInfo = isAttachment ? file : {}; const htmlForNewComment = isAttachment ? CONST.ATTACHMENT_UPLOADING_MESSAGE_HTML : commentText; + const accountID = actorAccountID ?? currentUserAccountID; // Remove HTML from text when applying optimistic offline comment const textForNewComment = isAttachment ? CONST.ATTACHMENT_MESSAGE_TEXT : parser.htmlToText(htmlForNewComment); @@ -2390,16 +2391,16 @@ function buildOptimisticAddCommentReportAction(text?: string, file?: File & {sou reportAction: { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorAccountID: currentUserAccountID, + actorAccountID: accountID, person: [ { style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? -1]?.displayName ?? currentUserEmail, + text: allPersonalDetails?.[accountID ?? -1]?.displayName ?? currentUserEmail, type: 'TEXT', }, ], automatic: false, - avatar: allPersonalDetails?.[currentUserAccountID ?? -1]?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: allPersonalDetails?.[accountID ?? -1]?.avatar ?? UserUtils.getDefaultAvatarURL(accountID), created: DateUtils.getDBTime(), message: [ { diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index c5627ff6845c..fd43b6814317 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -2402,8 +2402,9 @@ function getReportPrivateNote(reportID) { * @param {String} choice */ function completeEngagementModal( text , choice) { - const commandName = 'completeEngagementModal'; - const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text); + const commandName = 'CompleteEngagementModal'; + const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE])[0]; + const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, null, conciergeAccountID); const reportCommentAction = reportComment.reportAction; const reportCommentText = reportComment.commentText; const currentTime = DateUtils.getDBTime(); @@ -2418,8 +2419,7 @@ function completeEngagementModal( text , choice) { lastReadTime: currentTime, }; - const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); - const conciergeChatReport = ReportUtils.getChatByParticipants(conciergeAccountID); + const conciergeChatReport = ReportUtils.getChatByParticipants([conciergeAccountID]); conciergeChatReportID = conciergeChatReport.reportID; debugger; From 6855f21151a7333531dbeed6180bbdf94b68edf7 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 7 Dec 2023 10:13:34 -0700 Subject: [PATCH 034/146] Remove auto-show for create menu --- .../FloatingActionButtonAndPopover.js | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 21e712c418e6..d6c38de0c101 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -149,19 +149,7 @@ function FloatingActionButtonAndPopover(props) { } }; - useEffect(() => { - const navigationState = props.navigation.getState(); - const routes = lodashGet(navigationState, 'routes', []); - const currentRoute = routes[navigationState.index]; - if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { - return; - } - if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { - return; - } - Welcome.show({routes, showCreateMenu}); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.isLoading]); + useEffect(() => { if (!didScreenBecomeInactive()) { From bb36f6ee7a5caf35359548266e0cf7c4f2f28bc1 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 7 Dec 2023 10:15:04 -0700 Subject: [PATCH 035/146] Move the engagement modal to the sidebarScreen so that it is in the same place for both mobile and web --- src/pages/home/ReportScreen.js | 5 ----- src/pages/home/sidebar/SidebarScreen/index.js | 2 ++ src/pages/home/sidebar/SidebarScreen/index.native.js | 2 ++ 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 1944028f1aa4..98bad2d3b167 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -456,11 +456,6 @@ function ReportScreen({ )} - {}} - onItemSelected={() => {}} - /> diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 0b4c520c78a2..9c099da75d7d 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -4,6 +4,7 @@ import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; +import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; function SidebarScreen(props) { const popoverModal = useRef(null); @@ -44,6 +45,7 @@ function SidebarScreen(props) { onShowCreateMenu={createDragoverListener} onHideCreateMenu={removeDragoverListener} /> + ); diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index 36724c02d278..683afd9168e7 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -4,6 +4,7 @@ import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; +import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; function SidebarScreen(props) { const {isSmallScreenWidth} = useWindowDimensions(); @@ -14,6 +15,7 @@ function SidebarScreen(props) { {...props} > + ); From f7dcb5e0e6611914e8fbe999c78b364438552c27 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 7 Dec 2023 15:58:53 -0700 Subject: [PATCH 036/146] Update modal to use welcome.ts --- .../PurposeForUsingExpensifyModal.js | 24 ++++++++++++-- src/libs/actions/Welcome.ts | 32 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index e3065730dc58..1f294a2a246c 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -1,5 +1,5 @@ import PropTypes from 'prop-types'; -import React, {useState, useCallback} from 'react'; +import React, {useState, useCallback, useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; @@ -16,11 +16,14 @@ import LottieAnimations from "./LottieAnimations"; import Text from "./Text"; import Lottie from "./Lottie"; import HeaderWithBackButton from "./HeaderWithBackButton"; - import MenuItemList from "./MenuItemList"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import * as Expensicons from './Icon/Expensicons'; +import lodashGet from "lodash/get"; +import NAVIGATORS from "@src/NAVIGATORS"; +import * as Welcome from "@userActions/Welcome"; +import withNavigation from "@components/withNavigation"; const propTypes = { @@ -37,13 +40,27 @@ const defaultProps = { session: {}, }; -function PurposeForUsingExpensifyModal() { +function PurposeForUsingExpensifyModal(props) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const [isModalOpen, setIsModalOpen] = useState(true); const theme = useTheme(); + useEffect(() => { + const navigationState = props.navigation.getState(); + const routes = lodashGet(navigationState, 'routes', []); + const currentRoute = routes[navigationState.index]; + if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { + return; + } + if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { + return; + } + Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [props.isLoading]); + // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { track: 'Great! To track your expenses, I suggest you create a workspace to keep everything contained:\n' + @@ -181,6 +198,7 @@ PurposeForUsingExpensifyModal.displayName = 'AddPaymentMethodMenu'; export default compose( withWindowDimensions, + withNavigation, withOnyx({ session: { key: ONYXKEYS.SESSION, diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 7fd7adeafa96..7b5a092014fa 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -25,7 +25,7 @@ type Route = { type ShowParams = { routes: Route[]; - showCreateMenu?: () => void; + showEngagementModal?: () => void; showPopoverMenu?: () => boolean; }; @@ -57,6 +57,32 @@ Onyx.connect({ }, }); +Onyx.connect({ + key: ONYXKEYS.NVP_INTRO_SELECTED, + initWithStoredValues: false, + callback: (value) => { + // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic + // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 + + isFirstTimeNewExpensifyUser = value ?? undefined; + + checkOnReady(); + }, +}); + +Onyx.connect({ + key: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, + initWithStoredValues: false, + callback: (value) => { + // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic + // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 + + isFirstTimeNewExpensifyUser = value ?? undefined; + + checkOnReady(); + }, +}); + Onyx.connect({ key: ONYXKEYS.IS_LOADING_REPORT_DATA, initWithStoredValues: false, @@ -110,7 +136,7 @@ Onyx.connect({ /** * Shows a welcome action on first login */ -function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false}: ShowParams) { +function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => false}: ShowParams) { isReadyPromise.then(() => { if (!isFirstTimeNewExpensifyUser) { return; @@ -158,7 +184,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the create menu. if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute) { - showCreateMenu(); + showEngagementModal(); } // Update isFirstTimeNewExpensifyUser so the Welcome logic doesn't run again From 35100b600268a3f695da9aef0bc68d6e2cb73268 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 8 Dec 2023 14:59:45 +0700 Subject: [PATCH 037/146] fix lint --- .../subscribeToReportCommentPushNotifications.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index 53092bccb5f7..1b9cb00e8afe 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -2,8 +2,8 @@ import Onyx from 'react-native-onyx'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import Visibility from '@libs/Visibility'; -import ROUTES from '@src/ROUTES'; import * as Modal from '@userActions/Modal'; +import ROUTES from '@src/ROUTES'; import backgroundRefresh from './backgroundRefresh'; import PushNotification from './index'; @@ -33,7 +33,7 @@ export default function subscribeToReportCommentPushNotifications() { if (Navigation.getActiveRoute().slice(1, 2) === ROUTES.REPORT && !Navigation.isActiveRoute(`r/${reportID}`)) { Navigation.goBack(ROUTES.HOME); } - + Log.info('[PushNotification] onSelected() - Navigation is ready. Navigating...', false, {reportID, reportActionID}); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(String(reportID))); } catch (error) { @@ -41,10 +41,10 @@ export default function subscribeToReportCommentPushNotifications() { if (error instanceof Error) { errorMessage = error.message; } - + Log.alert('[PushNotification] onSelected() - failed', {reportID, reportActionID, error: errorMessage}); } - }) + }); }); }); } From c9378b473c260e6a6d3f7c9c8871c540b419cd8b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 12 Dec 2023 10:19:28 +0100 Subject: [PATCH 038/146] fix: conflicts --- src/components/LHNOptionsList/LHNOptionsList.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index d71fe1db535d..ea608ae7ff78 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,6 +5,7 @@ import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -45,7 +46,9 @@ function LHNOptionsList({ const transactionID = itemParentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU ? itemParentReportAction.originalMessage.IOUTransactionID ?? '' : ''; const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(itemFullReport?.participantAccountIDs ?? [], personalDetails); + const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport?.ownerAccountID]; + + const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); return ( Date: Tue, 12 Dec 2023 17:18:07 -0700 Subject: [PATCH 039/146] Fix import of style utils --- src/components/PurposeForUsingExpensifyModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 1f294a2a246c..e6d323dac29e 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -9,7 +9,7 @@ import useThemeStyles from "@styles/useThemeStyles"; import useTheme from '@styles/themes/useTheme'; import useWindowDimensions from "@hooks/useWindowDimensions"; import * as Report from '@userActions/Report'; -import * as StyleUtils from "@styles/StyleUtils"; +import useStyleUtils from "@styles/useStyleUtils"; import SCREENS from "@src/SCREENS"; import {View} from "react-native"; import LottieAnimations from "./LottieAnimations"; @@ -42,6 +42,7 @@ const defaultProps = { function PurposeForUsingExpensifyModal(props) { const {translate} = useLocalize(); + const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); const [isModalOpen, setIsModalOpen] = useState(true); From 0a815733ddfc84f10cbacaeb3f0c916b16d23344 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 13 Dec 2023 17:03:26 -0700 Subject: [PATCH 040/146] Update for typescript --- src/libs/actions/Report.ts | 53 ++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index fa9d0a4cc1af..e3ae9d9415e8 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2438,22 +2438,20 @@ function getReportPrivateNote(reportID: string) { * - Adding one comment * - Adding one attachment * - Add both a comment and attachment simultaneously - * - * @param {String} text - * @param {String} choice */ -function completeEngagementModal( text , choice) { +function completeEngagementModal(text: string, choice: string) { const commandName = 'CompleteEngagementModal'; const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE])[0]; - const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, null, conciergeAccountID); - const reportCommentAction = reportComment.reportAction; + const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, undefined, conciergeAccountID); + const reportCommentAction: Partial = reportComment.reportAction; + const lastComment = reportCommentAction?.message?.[0]; + const lastCommentText = ReportUtils.formatReportLastMessageText(lastComment?.text ?? ''); const reportCommentText = reportComment.commentText; const currentTime = DateUtils.getDBTime(); - const lastCommentText = ReportUtils.formatReportLastMessageText(reportCommentAction.message[0].text); - const optimisticReport = { + const optimisticReport: Partial = { lastVisibleActionCreated: currentTime, - lastMessageTranslationKey: lodashGet(reportCommentAction, 'message[0].translationKey', ''), + lastMessageTranslationKey: lastComment?.translationKey ?? '', lastMessageText: lastCommentText, lastMessageHtml: lastCommentText, lastActorAccountID: currentUserAccountID, @@ -2461,27 +2459,38 @@ function completeEngagementModal( text , choice) { }; const conciergeChatReport = ReportUtils.getChatByParticipants([conciergeAccountID]); - conciergeChatReportID = conciergeChatReport.reportID; - debugger; + conciergeChatReportID = conciergeChatReport?.reportID; + + const report = ReportUtils.getReport(conciergeChatReportID); - if (ReportUtils.getReportNotificationPreference(ReportUtils.getReport(conciergeChatReportID)) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + if (isNotEmptyObject(report) && ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; } // Optimistically add the new actions to the store before waiting to save them to the server - const optimisticReportActions = {}; - optimisticReportActions[reportCommentAction.reportActionID] = reportCommentAction; + const optimisticReportActions: OnyxCollection> = {}; + if (reportCommentAction?.reportActionID) { + optimisticReportActions[reportCommentAction.reportActionID] = reportCommentAction; + } + type CompleteEngagementParameters = { + reportID: string; + reportActionID?: string; + commentReportActionID?: string | null; + reportComment?: string; + engagementChoice: string, + timezone?: string; + }; - const parameters = { - reportID: conciergeChatReportID, + const parameters: CompleteEngagementParameters = { + reportID: conciergeChatReportID ?? '', reportActionID: reportCommentAction.reportActionID, reportComment: reportCommentText, engagementChoice: choice, }; - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${conciergeChatReportID}`, @@ -2499,11 +2508,11 @@ function completeEngagementModal( text , choice) { }, ]; - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, - value: _.mapObject(optimisticReportActions, () => ({pendingAction: null})), + value: {[reportCommentAction.reportActionID]: {pendingAction: null}}, } ]; @@ -2523,7 +2532,7 @@ function completeEngagementModal( text , choice) { optimisticData, successData, }); - notifyNewAction(conciergeChatReportID, reportCommentAction.actorAccountID, reportCommentAction.reportActionID); + notifyNewAction(conciergeChatReportID ?? '', reportCommentAction.actorAccountID, reportCommentAction.reportActionID); } function dismissEngagementModal() { @@ -2533,10 +2542,10 @@ function dismissEngagementModal() { value: true, }; - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, - key: 'ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL', + key: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, value: true, }, ]; From 0773d9e5be8a3bf89516036b2e7106f66f35dfd6 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 13 Dec 2023 19:59:24 -0700 Subject: [PATCH 041/146] Create new type and fix welcome.ts --- src/ONYXKEYS.ts | 2 ++ src/libs/actions/Welcome.ts | 12 ++++++------ src/types/onyx/IntroSelected.ts | 11 +++++++++++ src/types/onyx/index.ts | 2 ++ 4 files changed, 21 insertions(+), 6 deletions(-) create mode 100644 src/types/onyx/IntroSelected.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index e0c04e07f3f4..92e4b062bf29 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -397,6 +397,8 @@ type OnyxValues = { [ONYXKEYS.FOCUS_MODE_NOTIFICATION]: boolean; [ONYXKEYS.NVP_LAST_PAYMENT_METHOD]: Record; [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; diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 40652b9f8994..100653c14f6a 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -15,6 +15,8 @@ let isReadyPromise = new Promise((resolve) => { }); let isFirstTimeNewExpensifyUser: boolean | undefined; +let hasDismissedModal: boolean | undefined; +let hasSelectedChoice: boolean | undefined; let isLoadingReportData = true; let currentUserAccountID: number | undefined; @@ -59,12 +61,11 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.NVP_INTRO_SELECTED, - initWithStoredValues: false, + initWithStoredValues: true, callback: (value) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 - - isFirstTimeNewExpensifyUser = value ?? undefined; + hasSelectedChoice = !!value; checkOnReady(); }, @@ -72,12 +73,11 @@ Onyx.connect({ Onyx.connect({ key: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, - initWithStoredValues: false, + initWithStoredValues: true, callback: (value) => { // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 - - isFirstTimeNewExpensifyUser = value ?? undefined; + hasDismissedModal = value ?? false; checkOnReady(); }, diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts new file mode 100644 index 000000000000..d0a9def4f49a --- /dev/null +++ b/src/types/onyx/IntroSelected.ts @@ -0,0 +1,11 @@ +import * as OnyxCommon from './OnyxCommon'; + +type IntroSelected = { + /** The choice that the user selected in the engagement modal */ + choice: string; +}; + +type LoginList = Record; + +export default IntroSelected; +export type {LoginList}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 110bdb024a8c..ca09e300fbf4 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -12,6 +12,7 @@ import Download from './Download'; import Form, {AddDebitCardForm, DateOfBirthForm} from './Form'; import FrequentlyUsedEmoji from './FrequentlyUsedEmoji'; import Fund, {FundList} from './Fund'; +import IntroSelected from './IntroSelected'; import IOU from './IOU'; import Locale from './Locale'; import Login, {LoginList} from './Login'; @@ -75,6 +76,7 @@ export type { FrequentlyUsedEmoji, Fund, FundList, + IntroSelected, IOU, Locale, Login, From e52c81e8ff63afa80ac9aa6e360d613e217feb56 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 13 Dec 2023 19:59:46 -0700 Subject: [PATCH 042/146] Don't check for isLoading and default to hidden --- src/components/PurposeForUsingExpensifyModal.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index e6d323dac29e..6de5043ea8c0 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -45,7 +45,7 @@ function PurposeForUsingExpensifyModal(props) { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const {isSmallScreenWidth} = useWindowDimensions(); - const [isModalOpen, setIsModalOpen] = useState(true); + const [isModalOpen, setIsModalOpen] = useState(false); const theme = useTheme(); useEffect(() => { @@ -58,9 +58,10 @@ function PurposeForUsingExpensifyModal(props) { if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { return; } + debugger; Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.isLoading]); + }, []); // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { From 08e88435946231adcfadd6d8095f8f477e4b3fd8 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 13 Dec 2023 20:00:03 -0700 Subject: [PATCH 043/146] Also check for nvps before showing modal --- src/libs/actions/Welcome.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 100653c14f6a..216b5b2425e3 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -39,7 +39,8 @@ type ShowParams = { * - Whether we have loaded all reports the server knows about */ function checkOnReady() { - if (isFirstTimeNewExpensifyUser === undefined || isLoadingReportData) { + debugger; + if (isFirstTimeNewExpensifyUser === undefined || isLoadingReportData || hasSelectedChoice === undefined || hasDismissedModal === undefined) { return; } @@ -181,9 +182,10 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f return; } + debugger; // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the create menu. - if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute) { + if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal) { showEngagementModal(); } From 3025cde4b23da5d6782ba0f1a966665c407cff06 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 18:48:04 +0100 Subject: [PATCH 044/146] fix: types --- .../LHNOptionsList/LHNOptionsList.tsx | 5 +- .../LHNOptionsList/OptionRowLHN.tsx | 65 ++++++++++--------- .../LHNOptionsList/OptionRowLHNData.tsx | 5 +- src/components/LHNOptionsList/types.ts | 30 ++++----- src/components/OfflineWithFeedback.tsx | 2 +- .../types.ts | 2 +- src/libs/GroupChatUtils.ts | 4 +- src/libs/ReportUtils.ts | 5 +- src/libs/SidebarUtils.ts | 12 ++-- src/styles/utils/index.ts | 2 +- 10 files changed, 67 insertions(+), 65 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index ea608ae7ff78..76c7106ff9d2 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -3,10 +3,10 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -32,7 +32,6 @@ function LHNOptionsList({ draftComments = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); - /** * Function which renders a row in the list */ @@ -62,7 +61,7 @@ function LHNOptionsList({ viewMode={optionMode} isFocused={!shouldDisableFocusOptions && reportID === currentReportID} onSelectRow={onSelectRow} - preferredLocale={preferredLocale} + preferredLocale={preferredLocale ?? CONST.LOCALES.DEFAULT} comment={itemComment} /> ); diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index e957d83c1607..38dcaff7b22c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import {GestureResponderEvent, StyleProp, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -18,18 +18,20 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import DateUtils from '@libs/DateUtils'; import DomUtils from '@libs/DomUtils'; +import {getGroupChatName} from '@libs/GroupChatUtils'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportUtils from '@libs/ReportUtils'; import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; +import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); - const popoverAnchor = useRef(null); + const popoverAnchor = useRef(null); const StyleUtils = useStyleUtils(); const isFocusedRef = useRef(true); const {isSmallScreenWidth} = useWindowDimensions(); @@ -65,16 +67,14 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () style ?? {}, ); const contentContainerStyles = - props.viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; + viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( - props.viewMode === CONST.OPTION_MODE.COMPACT - ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] - : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], + viewMode === CONST.OPTION_MODE.COMPACT + ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) + : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), ); const hoveredBackgroundColor = - (props.hoverStyle || styles.sidebarLinkHover) && (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - ? (props.hoverStyle || styles.sidebarLinkHover).backgroundColor - : theme.sidebar; + (!!hoverStyle || !!styles.sidebarLinkHover) && 'backgroundColor' in (hoverStyle ?? styles.sidebarLinkHover) ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; @@ -86,7 +86,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () * * @param [event] - A press event. */ - const showPopover = (event) => { + const showPopover = (event: MouseEvent | GestureResponderEvent) => { if (!isFocusedRef.current && isSmallScreenWidth) { return; } @@ -109,19 +109,22 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () ); }; - const emojiCode = optionItem.status?.emojiCode ?? ''; - const statusText = optionItem.status?.text ?? ''; - const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; - const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); + const emojiCode = typeof optionItem.status === 'object' ? optionItem.status?.emojiCode : ''; + const statusText = typeof optionItem.status === 'object' ? optionItem.status?.text : ''; + const statusClearAfterDate = typeof optionItem.status === 'object' ? optionItem.status?.clearAfter : ''; + const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate ?? ''); const statusContent = formattedDate ? `${statusText} (${formattedDate})` : statusText; - const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(ReportUtils.getReport(optionItem?.reportID ?? '')); + const report = ReportUtils.getReport(optionItem.reportID ?? ''); + const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); - const subscriptAvatarBorderColor = props.isFocused ? focusedBackgroundColor : theme.sidebar; + const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips?.length ?? 0) > 2; + const fullTitle = isGroupChat ? getGroupChatName(isNotEmptyObject(report) ? report : null) : optionItem.text; + const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar; return ( @@ -129,29 +132,27 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () {(hovered) => ( { - if (e) { - e.preventDefault(); - } + onPress={(event) => { + event?.preventDefault(); // Enable Composer to focus on clicking the same chat after opening the context menu. ReportActionComposeFocusManager.focus(); onSelectRow(optionItem, popoverAnchor); }} - onMouseDown={(e) => { + onMouseDown={(event) => { // Allow composer blur on right click - if (!e) { + if (!event) { return; } // Prevent composer blur on left click - e.preventDefault(); + event.preventDefault(); }} testID={optionItem.reportID} - onSecondaryInteraction={(e) => { - showPopover(e); + onSecondaryInteraction={(event) => { + showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - DomUtils.getActiveElement()?.blur(); + (DomUtils.getActiveElement() as HTMLElement | TextInput)?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -164,7 +165,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () styles.sidebarLinkInnerLHN, StyleUtils.getBackgroundColorStyle(theme.sidebar), isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : null, + (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : styles.sidebarLinkHover, ]} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} @@ -175,10 +176,10 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () {(optionItem.icons?.length ?? 0) > 0 && (optionItem.shouldShowSubscript ? ( ) : ( { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index bd6fb9a1a0a0..3d2920a2bf2a 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,22 +1,22 @@ -import {ContentStyle} from '@shopify/flash-list'; -import {RefObject} from 'react'; -import {StyleProp, ViewStyle} from 'react-native'; -import {OnyxEntry} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; -import {CurrentReportIDContextValue} from '@components/withCurrentReportID'; -import {OptionData} from '@libs/SidebarUtils'; +import type {ContentStyle} from '@shopify/flash-list'; +import type {RefObject} from 'react'; +import {type StyleProp, View, type ViewStyle} from 'react-native'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; +import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; -import {Beta, PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {OptionData} from '@src/libs/ReportUtils'; +import type {PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry>; + policy: OnyxCollection; /** All reports shared with the user */ - reports: OnyxEntry>; + reports: OnyxCollection; /** Array of report actions for this report */ - reportActions: OnyxEntry>; + reportActions: OnyxCollection; /** Indicates which locale the user currently has selected */ preferredLocale: OnyxEntry>; @@ -25,10 +25,10 @@ type LHNOptionsListOnyxProps = { personalDetails: OnyxEntry>; /** The transaction from the parent report action */ - transactions: OnyxEntry>; + transactions: OnyxCollection; /** List of draft comments */ - draftComments: OnyxEntry>; + draftComments: OnyxCollection; }; type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ @@ -76,7 +76,7 @@ type OptionRowLHNDataProps = { comment: string; - receiptTransactions: OnyxEntry>; + receiptTransactions: OnyxCollection; reportID: string; @@ -87,7 +87,7 @@ type OptionRowLHNProps = { hoverStyle?: StyleProp; reportID: string; isFocused?: boolean; - onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; style?: ViewStyle | ViewStyle[]; optionItem?: OptionData | null; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 5fcf1fe7442b..f9787d86abf7 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -18,7 +18,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction: OnyxCommon.PendingAction; + pendingAction: OnyxCommon.PendingAction | undefined; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index cf286afcb63a..33eb27afbe57 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -42,7 +42,7 @@ type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & activeOpacity?: number; /** Used to apply styles to the Pressable */ - style?: StyleProp; + style?: StyleProp; /** Whether the long press with hover behavior is enabled */ enableLongPressWithHover?: boolean; diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 862c50700c0c..a44c6d12627b 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -13,8 +13,8 @@ Onyx.connect({ /** * Returns the report name if the report is a group chat */ -function getGroupChatName(report: Report): string | undefined { - const participants = report.participantAccountIDs ?? []; +function getGroupChatName(report: OnyxEntry): string | undefined { + const participants = report?.participantAccountIDs ?? []; const isMultipleParticipantReport = participants.length > 1; const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, allPersonalDetails ?? {}); // @ts-expect-error Error will gone when OptionsListUtils will be migrated to Typescript diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8729bb1bf957..f805e06c8557 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,7 +16,7 @@ import CONST from '@src/CONST'; import {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; +import {Beta, CustomStatusDraft, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyTags, Report, ReportAction, Session, Transaction} from '@src/types/onyx'; import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import {IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; import {NotificationPreference} from '@src/types/onyx/Report'; @@ -320,7 +320,7 @@ type OptionData = { subtitle?: string | null; login?: string | null; accountID?: number | null; - status?: string | null; + status?: CustomStatusDraft | string | null; phoneNumber?: string | null; isUnread?: boolean | null; isUnreadWithMention?: boolean | null; @@ -338,6 +338,7 @@ type OptionData = { isTaskReport?: boolean | null; parentReportAction?: ReportAction; displayNamesWithTooltips?: DisplayNameWithTooltips | null; + descriptiveText?: string; } & Report; type OnyxDataTaskAssigneeChat = { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 1813d4f0a795..380ff7586815 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -1,6 +1,6 @@ /* eslint-disable rulesdir/prefer-underscore-method */ import Str from 'expensify-common/lib/str'; -import Onyx, {OnyxCollection} from 'react-native-onyx'; +import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -230,12 +230,12 @@ type ActorDetails = { * Gets all the data necessary for rendering an OptionRowLHN component */ function getOptionData( - report: Report, - reportActions: Record, + report: OnyxEntry, + reportActions: OnyxEntry, personalDetails: Record, preferredLocale: ValueOf, - policy: Policy, - parentReportAction: ReportAction, + policy: OnyxEntry, + parentReportAction: OnyxEntry, ): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do @@ -278,7 +278,7 @@ function getOptionData( result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); result.isTaskReport = ReportUtils.isTaskReport(report); - result.parentReportAction = parentReportAction; + result.parentReportAction = parentReportAction ?? undefined; result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isExpenseRequest = ReportUtils.isExpenseRequest(report); diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index f0056f1fbd72..a0e495299252 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -1,5 +1,5 @@ import {CSSProperties} from 'react'; -import {Animated, DimensionValue, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {Animated, ColorValue, DimensionValue, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; import {EdgeInsets} from 'react-native-safe-area-context'; import {ValueOf} from 'type-fest'; import * as Browser from '@libs/Browser'; From e436a6c98eadea70607176787eee4c9c6e259590 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 19:29:41 +0100 Subject: [PATCH 045/146] fix: removed unused prop --- src/components/LHNOptionsList/OptionRowLHN.tsx | 8 +++----- src/components/LHNOptionsList/types.ts | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 38dcaff7b22c..6f20c625938c 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -28,7 +28,7 @@ import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; -function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -73,14 +73,12 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), ); - const hoveredBackgroundColor = - (!!hoverStyle || !!styles.sidebarLinkHover) && 'backgroundColor' in (hoverStyle ?? styles.sidebarLinkHover) ? (hoverStyle ?? styles.sidebarLinkHover).backgroundColor : theme.sidebar; + const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; const hasBrickError = optionItem.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR; const defaultSubscriptSize = optionItem.isExpenseRequest ? CONST.AVATAR_SIZE.SMALL_NORMAL : CONST.AVATAR_SIZE.DEFAULT; const shouldShowGreenDotIndicator = !hasBrickError && ReportUtils.requiresAttentionFromCurrentUser(optionItem, optionItem.parentReportAction); - /** * Show the ReportActionContextMenu modal popover. * @@ -165,7 +163,7 @@ function OptionRowLHN({hoverStyle, reportID, isFocused = false, onSelectRow = () styles.sidebarLinkInnerLHN, StyleUtils.getBackgroundColorStyle(theme.sidebar), isFocused ? styles.sidebarLinkActive : null, - (hovered || isContextMenuActive) && !isFocused ? hoverStyle ?? styles.sidebarLinkHover : styles.sidebarLinkHover, + (hovered || isContextMenuActive) && !isFocused ? styles.sidebarLinkHover : null, ]} role={CONST.ROLE.BUTTON} accessibilityLabel={translate('accessibilityHints.navigatesToChat')} diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 3d2920a2bf2a..ae8513c652cf 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -84,7 +84,6 @@ type OptionRowLHNDataProps = { }; type OptionRowLHNProps = { - hoverStyle?: StyleProp; reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; From 01f2aa72364fb486be468427f28ad1152b67bffa Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 19:47:11 +0100 Subject: [PATCH 046/146] fix: linter --- .../LHNOptionsList/LHNOptionsList.tsx | 6 +++--- src/components/LHNOptionsList/types.ts | 4 +++- .../types.ts | 2 +- src/components/SubscriptAvatar.tsx | 20 +------------------ 4 files changed, 8 insertions(+), 24 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 76c7106ff9d2..2eb42080ef6a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,5 @@ import {FlashList} from '@shopify/flash-list'; -import React, {useCallback} from 'react'; +import React, {ReactElement, useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -11,7 +11,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListOnyxProps, LHNOptionsListProps} from './types'; +import {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; @@ -36,7 +36,7 @@ function LHNOptionsList({ * Function which renders a row in the list */ const renderItem = useCallback( - ({item: reportID}: {item: string}) => { + ({item: reportID}: RenderItemProps): ReactElement => { const itemFullReport = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; const itemReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`] ?? null; const itemParentReportActions = reportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${itemFullReport?.parentReportID}`] ?? null; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index ae8513c652cf..c6aa3ac2930e 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -92,4 +92,6 @@ type OptionRowLHNProps = { optionItem?: OptionData | null; }; -export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps}; +type RenderItemProps = {item: string}; + +export type {LHNOptionsListProps, OptionRowLHNDataProps, OptionRowLHNProps, LHNOptionsListOnyxProps, RenderItemProps}; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index 33eb27afbe57..2e9867ba4545 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -1,4 +1,4 @@ -import {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; import ChildrenProps from '@src/types/utils/ChildrenProps'; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 71fe4423f7aa..1ab2b4238213 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -4,29 +4,11 @@ import {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {AvatarSource} from '@libs/UserUtils'; import CONST from '@src/CONST'; -import {AvatarType} from '@src/types/onyx/OnyxCommon'; +import {Icon} from '@src/types/onyx/OnyxCommon'; import Avatar from './Avatar'; import UserDetailsTooltip from './UserDetailsTooltip'; -type SubAvatar = { - /** Avatar source to display */ - source?: AvatarSource; - - /** Denotes whether it is an avatar or a workspace avatar */ - type?: AvatarType; - - /** Owner of the avatar. If user, displayName. If workspace, policy name */ - name?: string; - - /** Avatar id */ - id?: number | string; - - /** A fallback avatar icon to display when there is an error on loading avatar from remote URL. */ - fallbackIcon?: AvatarSource; -}; - type SubscriptAvatarProps = { /** Avatar URL or icon */ mainAvatar?: Icon; From 4d7030368ec3720432fc6432cbbb3055cf21ee8b Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 18 Dec 2023 17:59:26 -0700 Subject: [PATCH 047/146] fix import order --- src/components/PurposeForUsingExpensifyModal.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 12342016b943..fe446c4adb33 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -5,13 +5,17 @@ import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import useThemeStyles from "@styles/useThemeStyles"; -import useTheme from '@styles/themes/useTheme'; +import useThemeStyles from "@hooks/useThemeStyles"; +import useTheme from '@hooks/useTheme'; import useWindowDimensions from "@hooks/useWindowDimensions"; import * as Report from '@userActions/Report'; -import useStyleUtils from "@styles/useStyleUtils"; +import useStyleUtils from "@hooks/useStyleUtils"; import SCREENS from "@src/SCREENS"; import {View} from "react-native"; +import NAVIGATORS from "@src/NAVIGATORS"; +import * as Welcome from "@userActions/Welcome"; +import lodashGet from "lodash/get"; +import withNavigation from "./withNavigation"; import LottieAnimations from "./LottieAnimations"; import Text from "./Text"; import Lottie from "./Lottie"; @@ -20,10 +24,6 @@ import MenuItemList from "./MenuItemList"; import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import Modal from "./Modal"; import * as Expensicons from './Icon/Expensicons'; -import lodashGet from "lodash/get"; -import NAVIGATORS from "@src/NAVIGATORS"; -import * as Welcome from "@userActions/Welcome"; -import withNavigation from "@components/withNavigation"; const propTypes = { From c68d7285522052a1d2754083ffdcd199ff141c4f Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 12:15:39 -0700 Subject: [PATCH 048/146] Check for number of policies --- src/libs/actions/Welcome.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 17ffc3821457..daf024f885b6 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -6,6 +6,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import OnyxPolicy from '@src/types/onyx/Policy'; +import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; import Report from '@src/types/onyx/Report'; import * as Policy from './Policy'; @@ -39,9 +40,6 @@ type ShowParams = { * - Whether we have loaded all reports the server knows about */ function checkOnReady() { - if (allPolicies) { - debugger; - } if (isFirstTimeNewExpensifyUser === undefined || isLoadingReportData || hasSelectedChoice === undefined || hasDismissedModal === undefined) { return; } @@ -108,7 +106,7 @@ Onyx.connect({ }, }); -const allPolicies: OnyxCollection = {}; +const allPolicies: OnyxCollection | EmptyObject = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.POLICY, callback: (val, key) => { @@ -186,7 +184,7 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the engagement modal. - if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal) { + if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal && Object.keys(allPolicies ?? {}).length === 1) { showEngagementModal(); } From 17dbbe6a56afe74dccbd4d236d2d5c7ee7265336 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 15:38:42 -0700 Subject: [PATCH 049/146] Style --- .../PurposeForUsingExpensifyModal.js | 26 +++++++++---------- src/libs/actions/Welcome.ts | 2 +- src/pages/home/ReportScreen.js | 1 - .../FloatingActionButtonAndPopover.js | 4 --- src/pages/home/sidebar/SidebarScreen/index.js | 2 +- .../sidebar/SidebarScreen/index.native.js | 2 +- src/types/onyx/IntroSelected.ts | 5 ---- 7 files changed, 16 insertions(+), 26 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index fe446c4adb33..593e3086a2e6 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -176,19 +176,19 @@ function PurposeForUsingExpensifyModal(props) { iconFill={theme.iconColorfulBackground} /> - - - {translate('purposeForExpensify.welcomeMessage')} - - {translate('purposeForExpensify.welcomeSubtitle')} - - + + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + + ); } diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index daf024f885b6..838e9e200899 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -6,7 +6,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import OnyxPolicy from '@src/types/onyx/Policy'; -import {EmptyObject, isEmptyObject} from '@src/types/utils/EmptyObject'; +import {EmptyObject} from '@src/types/utils/EmptyObject'; import Report from '@src/types/onyx/Report'; import * as Policy from './Policy'; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index f2daec6145fe..d6249c0a28eb 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -35,7 +35,6 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import PurposeForUsingExpensifyModal from '@components/PurposeForUsingExpensifyModal'; import HeaderView from './HeaderView'; import reportActionPropTypes from './report/reportActionPropTypes'; import ReportActionsView from './report/ReportActionsView'; diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index e63e7e522f7d..9c2028c63677 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -1,4 +1,3 @@ -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; @@ -20,12 +19,9 @@ import * as IOU from '@userActions/IOU'; import * as Policy from '@userActions/Policy'; import * as Session from '@userActions/Session'; import * as Task from '@userActions/Task'; -import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; /** * @param {Object} [policy] diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 9c099da75d7d..00136826e13c 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -2,9 +2,9 @@ import React, {useCallback, useRef} from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; +import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; -import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; function SidebarScreen(props) { const popoverModal = useRef(null); diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index 683afd9168e7..ddda0019ea5f 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -2,9 +2,9 @@ import React from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; import BaseSidebarScreen from './BaseSidebarScreen'; +import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; -import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; function SidebarScreen(props) { const {isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index d0a9def4f49a..f7446cdd7a4c 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -1,11 +1,6 @@ -import * as OnyxCommon from './OnyxCommon'; - type IntroSelected = { /** The choice that the user selected in the engagement modal */ choice: string; }; -type LoginList = Record; - export default IntroSelected; -export type {LoginList}; From 356db316c7efcae0abb1ecf78ad73776c0a2ddfb Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 15:44:53 -0700 Subject: [PATCH 050/146] import order --- src/pages/home/sidebar/SidebarScreen/index.js | 2 +- src/pages/home/sidebar/SidebarScreen/index.native.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 00136826e13c..6b390c524aad 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -1,8 +1,8 @@ import React, {useCallback, useRef} from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; -import BaseSidebarScreen from './BaseSidebarScreen'; import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; +import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index ddda0019ea5f..e69aa00c72f6 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -1,8 +1,8 @@ import React from 'react'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; -import BaseSidebarScreen from './BaseSidebarScreen'; import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; +import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; From 2ba2966975ec916e2db1d40a3916068c68ad140e Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 15:54:04 -0700 Subject: [PATCH 051/146] Prettier --- src/components/Icon/Expensicons.ts | 2 +- .../PurposeForUsingExpensifyModal.js | 65 ++++++++++--------- src/languages/en.ts | 4 +- src/languages/es.ts | 4 +- src/libs/actions/Report.ts | 9 ++- src/libs/actions/Welcome.ts | 2 +- src/pages/home/ReportScreen.js | 32 ++++----- .../FloatingActionButtonAndPopover.js | 2 - src/pages/home/sidebar/SidebarScreen/index.js | 2 +- .../sidebar/SidebarScreen/index.native.js | 2 +- 10 files changed, 62 insertions(+), 62 deletions(-) diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 082b5b545cf8..a01ac4b62382 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -105,8 +105,8 @@ 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 Send from '@assets/images/send.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'; import GoogleLogo from '@assets/images/signIn/google-logo.svg'; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 593e3086a2e6..dd698a731f28 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -1,32 +1,31 @@ +import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useState, useCallback, useEffect} from 'react'; +import React, {useCallback, useEffect, useState} from 'react'; +import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; 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 compose from '@libs/compose'; +import * as Report from '@userActions/Report'; +import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; import ONYXKEYS from '@src/ONYXKEYS'; -import useThemeStyles from "@hooks/useThemeStyles"; -import useTheme from '@hooks/useTheme'; -import useWindowDimensions from "@hooks/useWindowDimensions"; -import * as Report from '@userActions/Report'; -import useStyleUtils from "@hooks/useStyleUtils"; -import SCREENS from "@src/SCREENS"; -import {View} from "react-native"; -import NAVIGATORS from "@src/NAVIGATORS"; -import * as Welcome from "@userActions/Welcome"; -import lodashGet from "lodash/get"; -import withNavigation from "./withNavigation"; -import LottieAnimations from "./LottieAnimations"; -import Text from "./Text"; -import Lottie from "./Lottie"; -import HeaderWithBackButton from "./HeaderWithBackButton"; -import MenuItemList from "./MenuItemList"; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; -import Modal from "./Modal"; +import SCREENS from '@src/SCREENS'; +import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; +import Lottie from './Lottie'; +import LottieAnimations from './LottieAnimations'; +import MenuItemList from './MenuItemList'; +import Modal from './Modal'; +import Text from './Text'; +import withNavigation from './withNavigation'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; const propTypes = { - /** Session info for the currently logged in user. */ session: PropTypes.shape({ /** Currently logged in user accountID */ @@ -64,7 +63,8 @@ function PurposeForUsingExpensifyModal(props) { // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { - track: 'Great! To track your expenses, I suggest you create a workspace to keep everything contained:\n' + + 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' + @@ -76,18 +76,20 @@ function PurposeForUsingExpensifyModal(props) { '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' + + "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!', - submit: 'Hi there, to submit expenses for reimbursement, please:\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!", + 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!', - business: 'Great! To manage your team\'s expenses, create a workspace to keep everything contained:\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!", + business: + "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' + @@ -95,7 +97,8 @@ function PurposeForUsingExpensifyModal(props) { '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!', - chatSplit: 'Hi there, to split an expense such as with a friend, please:\n' + + chatSplit: + 'Hi there, to split an expense such as with a friend, please:\n' + '\n' + 'Press the big green + button\n' + 'Choose *Request money*\n' + @@ -106,8 +109,8 @@ function PurposeForUsingExpensifyModal(props) { '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!', - } + "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 closeModal = useCallback(() => { Report.dismissEngagementModal(); @@ -116,8 +119,8 @@ function PurposeForUsingExpensifyModal(props) { const completeModalAndClose = (message, choice) => { Report.completeEngagementModal(message, choice); - setIsModalOpen(false) - } + setIsModalOpen(false); + }; const menuItems = [ { diff --git a/src/languages/en.ts b/src/languages/en.ts index 7ff0cc736464..c5e6b79817fe 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2024,10 +2024,10 @@ export default { purposeForExpensify: { track: 'Track my business expenses to file taxes', submit: 'Submit expenses to my employer to get paid back', - business: 'Manage my team\'s expenses', + business: "Manage my team's expenses", chatSplit: 'Chat and split expenses with friends', welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: 'What\'s your main purpose for using Expensify?', + welcomeSubtitle: "What's your main purpose for using Expensify?", }, violations: { allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired', diff --git a/src/languages/es.ts b/src/languages/es.ts index d28eea1fbae0..4b3a55e9d8c3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2511,10 +2511,10 @@ export default { purposeForExpensify: { track: 'Track my business expenses to file taxes', submit: 'Submit expenses to my employer to get paid back', - business: 'Manage my team\'s expenses', + business: "Manage my team's expenses", chatSplit: 'Chat and split expenses with friends', welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: 'What\'s your main purpose for using Expensify?', + welcomeSubtitle: "What's your main purpose for using Expensify?", }, violations: { allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired', diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index b910c69903d9..8030111d9d69 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2475,7 +2475,7 @@ function completeEngagementModal(text: string, choice: string) { reportActionID?: string; commentReportActionID?: string | null; reportComment?: string; - engagementChoice: string, + engagementChoice: string; timezone?: string; }; @@ -2484,7 +2484,6 @@ function completeEngagementModal(text: string, choice: string) { reportActionID: reportCommentAction.reportActionID, reportComment: reportCommentText, engagementChoice: choice, - }; const optimisticData: OnyxUpdate[] = [ @@ -2501,7 +2500,7 @@ function completeEngagementModal(text: string, choice: string) { { onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.NVP_INTRO_SELECTED, - value: {choice} + value: {choice}, }, ]; @@ -2509,8 +2508,8 @@ function completeEngagementModal(text: string, choice: string) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, - value: {[reportCommentAction.reportActionID]: {pendingAction: null}}, - } + value: {[reportCommentAction.reportActionID]: {pendingAction: null}}, + }, ]; // Update the timezone if it's been 5 minutes from the last time the user added a comment diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 838e9e200899..380436b761c1 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -6,8 +6,8 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import OnyxPolicy from '@src/types/onyx/Policy'; -import {EmptyObject} from '@src/types/utils/EmptyObject'; import Report from '@src/types/onyx/Report'; +import {EmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index f2d8c60a821c..161b8aa8889d 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -138,22 +138,22 @@ function getReportID(route) { } function ReportScreen({ - betas, - route, - report, - reportMetadata, - reportActions, - accountManagerReportID, - personalDetails, - markReadyForHydration, - policies, - isSidebarLoaded, - viewportOffsetTop, - isComposerFullSize, - errors, - userLeavingStatus, - currentReportID, - }) { + betas, + route, + report, + reportMetadata, + reportActions, + accountManagerReportID, + personalDetails, + markReadyForHydration, + policies, + isSidebarLoaded, + viewportOffsetTop, + isComposerFullSize, + errors, + userLeavingStatus, + currentReportID, +}) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 9c2028c63677..a1b4789bcdef 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -146,8 +146,6 @@ function FloatingActionButtonAndPopover(props) { } }; - - useEffect(() => { if (!didScreenBecomeInactive()) { return; diff --git a/src/pages/home/sidebar/SidebarScreen/index.js b/src/pages/home/sidebar/SidebarScreen/index.js index 6b390c524aad..e823d24b87fe 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.js +++ b/src/pages/home/sidebar/SidebarScreen/index.js @@ -1,7 +1,7 @@ import React, {useCallback, useRef} from 'react'; +import PurposeForUsingExpensifyModal from '@components/PurposeForUsingExpensifyModal'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; -import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; diff --git a/src/pages/home/sidebar/SidebarScreen/index.native.js b/src/pages/home/sidebar/SidebarScreen/index.native.js index e69aa00c72f6..7f36e4ebfa22 100755 --- a/src/pages/home/sidebar/SidebarScreen/index.native.js +++ b/src/pages/home/sidebar/SidebarScreen/index.native.js @@ -1,7 +1,7 @@ import React from 'react'; +import PurposeForUsingExpensifyModal from '@components/PurposeForUsingExpensifyModal'; import useWindowDimensions from '@hooks/useWindowDimensions'; import FreezeWrapper from '@libs/Navigation/FreezeWrapper'; -import PurposeForUsingExpensifyModal from "@components/PurposeForUsingExpensifyModal"; import BaseSidebarScreen from './BaseSidebarScreen'; import FloatingActionButtonAndPopover from './FloatingActionButtonAndPopover'; import sidebarPropTypes from './sidebarPropTypes'; From 1f3420cb676ee581c91cb0a797da8f6a32d66e72 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 16:01:34 -0700 Subject: [PATCH 052/146] Remove unnecessary file --- docs/universal-darwin21 | 1 - 1 file changed, 1 deletion(-) delete mode 120000 docs/universal-darwin21 diff --git a/docs/universal-darwin21 b/docs/universal-darwin21 deleted file mode 120000 index 06de9bd9290c..000000000000 --- a/docs/universal-darwin21 +++ /dev/null @@ -1 +0,0 @@ -universal-darwin22 \ No newline at end of file From 874543fc062083268dd54d5d6332915e750c23b6 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 19 Dec 2023 16:27:05 -0700 Subject: [PATCH 053/146] Add prop type --- .../HeaderWithBackButton/headerWithBackButtonPropTypes.js | 3 +++ src/components/PurposeForUsingExpensifyModal.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js b/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js index 109e60adf672..2f7ac48b558b 100644 --- a/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js +++ b/src/components/HeaderWithBackButton/headerWithBackButtonPropTypes.js @@ -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; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index dd698a731f28..ad0f23d6081c 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -162,6 +162,8 @@ function PurposeForUsingExpensifyModal(props) { type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} isVisible={isModalOpen} onClose={closeModal} + shouldAddTopSafeAreaMargin={false} + shouldAddTopSafeAreaPadding={false} > Date: Tue, 19 Dec 2023 16:56:02 -0700 Subject: [PATCH 054/146] Fix typescript --- src/components/PurposeForUsingExpensifyModal.js | 2 -- src/libs/actions/Report.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index ad0f23d6081c..dd698a731f28 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -162,8 +162,6 @@ function PurposeForUsingExpensifyModal(props) { type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} isVisible={isModalOpen} onClose={closeModal} - shouldAddTopSafeAreaMargin={false} - shouldAddTopSafeAreaPadding={false} > Date: Tue, 19 Dec 2023 17:28:07 -0700 Subject: [PATCH 055/146] Remove white padding --- src/components/PurposeForUsingExpensifyModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index dd698a731f28..f5f417a8225d 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -162,6 +162,7 @@ function PurposeForUsingExpensifyModal(props) { type={isSmallScreenWidth ? CONST.MODAL.MODAL_TYPE.BOTTOM_DOCKED : CONST.MODAL.MODAL_TYPE.RIGHT_DOCKED} isVisible={isModalOpen} onClose={closeModal} + innerContainerStyle={styles.pt0} > Date: Tue, 19 Dec 2023 17:49:16 -0700 Subject: [PATCH 056/146] Redirect to concierge chat after completing modal --- src/components/PurposeForUsingExpensifyModal.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index f5f417a8225d..8845bbe5dafa 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -120,6 +120,7 @@ function PurposeForUsingExpensifyModal(props) { const completeModalAndClose = (message, choice) => { Report.completeEngagementModal(message, choice); setIsModalOpen(false); + Report.navigateToConciergeChat(); }; const menuItems = [ From ab774c9a781a25dbc74fe031081f8540dc7a45cc Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 09:41:52 +0100 Subject: [PATCH 057/146] chore: added todos --- src/components/LHNOptionsList/LHNOptionsList.tsx | 1 + src/components/LHNOptionsList/OptionRowLHN.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 2eb42080ef6a..c61c8d711533 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -55,6 +55,7 @@ function LHNOptionsList({ reportActions={itemReportActions} parentReportAction={itemParentReportAction} policy={itemPolicy} + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. personalDetails={participantsPersonalDetails} transaction={itemTransaction} receiptTransactions={transactions} diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 6f20c625938c..c97d5ddfa8c7 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -93,6 +93,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti ContextMenuActions.CONTEXT_MENU_TYPES.REPORT, event, '', + // @ts-expect-error TODO: Remove this once ReportActionContextMenu (https://github.com/Expensify/App/pull/32670) is migrated to TypeScript. popoverAnchor, reportID, '0', From 91e7c92ea6ea8e34f89914de04afb492242459a2 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 13:49:50 +0100 Subject: [PATCH 058/146] fix: adjust type --- src/components/LHNOptionsList/types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c6aa3ac2930e..c934f7b54db0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetails, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -22,7 +22,7 @@ type LHNOptionsListOnyxProps = { preferredLocale: OnyxEntry>; /** List of users' personal details */ - personalDetails: OnyxEntry>; + personalDetails: OnyxEntry; /** The transaction from the parent report action */ transactions: OnyxCollection; From 18352a72c7b9759e9d13268f6265cc445ddbb251 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 20 Dec 2023 15:35:42 +0100 Subject: [PATCH 059/146] Refactor imports and types in LHNOptionsList, OfflineWithFeedback, PressableWithSecondaryInteraction, and SidebarUtils --- src/components/LHNOptionsList/types.ts | 4 ++-- src/components/OfflineWithFeedback.tsx | 2 +- src/components/PressableWithSecondaryInteraction/types.ts | 2 +- src/libs/SidebarUtils.ts | 8 ++++---- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index c934f7b54db0..7cafbbe2ead0 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -57,7 +57,7 @@ type OptionRowLHNDataProps = { isFocused: boolean; /** List of users' personal details */ - personalDetails: Record; + personalDetails: PersonalDetailsList; /** The preferred language for the app */ preferredLocale: OnyxEntry>; diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index f9787d86abf7..4522595826af 100644 --- a/src/components/OfflineWithFeedback.tsx +++ b/src/components/OfflineWithFeedback.tsx @@ -18,7 +18,7 @@ import MessagesRow from './MessagesRow'; type OfflineWithFeedbackProps = ChildrenProps & { /** The type of action that's pending */ - pendingAction: OnyxCommon.PendingAction | undefined; + pendingAction?: OnyxCommon.PendingAction; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index 2f5abfe5bb6e..bf999e9692b5 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -1,4 +1,4 @@ -import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; import {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; import type {ParsableStyle} from '@styles/utils/types'; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0f2e8ae2230c..16a8fb09d21e 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -4,9 +4,8 @@ import Onyx, {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails} from '@src/types/onyx'; +import {PersonalDetails, PersonalDetailsList} from '@src/types/onyx'; import Beta from '@src/types/onyx/Beta'; -import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; @@ -232,7 +231,7 @@ type ActorDetails = { function getOptionData( report: OnyxEntry, reportActions: OnyxEntry, - personalDetails: Record, + personalDetails: PersonalDetailsList, preferredLocale: ValueOf, policy: OnyxEntry, parentReportAction: OnyxEntry, @@ -285,7 +284,8 @@ function getOptionData( result.isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined; - result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; + // @ts-expect-error TODO: Remove this once OptionsListUtils (https://github.com/Expensify/App/issues/24921) is migrated to TypeScript. + result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions); result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; From 489b14806b7d50586a3c56e6194d36e0009e9357 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 20 Dec 2023 09:17:44 -0700 Subject: [PATCH 060/146] Add new param to show backdrop for modals --- src/components/Modal/BaseModal.tsx | 3 ++- src/components/Modal/types.ts | 3 +++ src/components/PurposeForUsingExpensifyModal.js | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 89640c56f5ef..620828290d9c 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -38,6 +38,7 @@ function BaseModal( onLayout, avoidKeyboard = false, children, + shouldShowBackdrop = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -185,7 +186,7 @@ function BaseModal( swipeDirection={swipeDirection} isVisible={isVisible} backdropColor={theme.overlay} - backdropOpacity={hideBackdrop ? 0 : variables.overlayOpacity} + backdropOpacity={!shouldShowBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} backdropTransitionOutTiming={0} hasBackdrop={fullscreen} coverScreen={fullscreen} diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 172965b45fb2..1675114b4571 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -61,6 +61,9 @@ type BaseModalProps = WindowDimensionsProps & * See: https://github.com/react-native-modal/react-native-modal/pull/116 * */ hideModalContentWhileAnimating?: boolean; + + /** Should we show the background overlay around the modal */ + shouldShowBackdrop?: boolean; }; export default BaseModalProps; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 8845bbe5dafa..f2565f6e0326 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -164,6 +164,7 @@ function PurposeForUsingExpensifyModal(props) { isVisible={isModalOpen} onClose={closeModal} innerContainerStyle={styles.pt0} + shouldShowBackdrop > Date: Wed, 20 Dec 2023 09:41:11 -0700 Subject: [PATCH 061/146] Allow all options to be on 2 lines --- src/components/PurposeForUsingExpensifyModal.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index f2565f6e0326..657b3eef85dc 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -131,6 +131,7 @@ function PurposeForUsingExpensifyModal(props) { iconRight: Expensicons.ArrowRight, onPress: () => completeModalAndClose(messageCopy.track, 'trackNewDot'), shouldShowRightIcon: true, + numberOfLinesTitle: 2, }, { key: 'purposeForExpensify.submit', @@ -139,6 +140,7 @@ function PurposeForUsingExpensifyModal(props) { iconRight: Expensicons.ArrowRight, onPress: () => completeModalAndClose(messageCopy.submit, 'submitNewDot'), shouldShowRightIcon: true, + numberOfLinesTitle: 2, }, { key: 'purposeForExpensify.business', @@ -147,6 +149,7 @@ function PurposeForUsingExpensifyModal(props) { iconRight: Expensicons.ArrowRight, onPress: () => completeModalAndClose(messageCopy.business, 'businessNewDot'), shouldShowRightIcon: true, + numberOfLinesTitle: 2, }, { key: 'purposeForExpensify.chatSplit', @@ -155,6 +158,7 @@ function PurposeForUsingExpensifyModal(props) { iconRight: Expensicons.ArrowRight, onPress: () => completeModalAndClose(messageCopy.chatSplit, 'chatSplitNewDot'), shouldShowRightIcon: true, + numberOfLinesTitle: 2, }, ]; From cf1a9ec5687873cd9d14e0a313d11fceabe537b0 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 20 Dec 2023 14:18:44 -0700 Subject: [PATCH 062/146] Revert background changes --- src/components/Modal/BaseModal.tsx | 3 +-- src/components/Modal/types.ts | 3 --- src/components/PurposeForUsingExpensifyModal.js | 1 - 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 620828290d9c..89640c56f5ef 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -38,7 +38,6 @@ function BaseModal( onLayout, avoidKeyboard = false, children, - shouldShowBackdrop = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -186,7 +185,7 @@ function BaseModal( swipeDirection={swipeDirection} isVisible={isVisible} backdropColor={theme.overlay} - backdropOpacity={!shouldShowBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} + backdropOpacity={hideBackdrop ? 0 : variables.overlayOpacity} backdropTransitionOutTiming={0} hasBackdrop={fullscreen} coverScreen={fullscreen} diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 1675114b4571..172965b45fb2 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -61,9 +61,6 @@ type BaseModalProps = WindowDimensionsProps & * See: https://github.com/react-native-modal/react-native-modal/pull/116 * */ hideModalContentWhileAnimating?: boolean; - - /** Should we show the background overlay around the modal */ - shouldShowBackdrop?: boolean; }; export default BaseModalProps; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 657b3eef85dc..2c7b61ff5bcf 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -168,7 +168,6 @@ function PurposeForUsingExpensifyModal(props) { isVisible={isModalOpen} onClose={closeModal} innerContainerStyle={styles.pt0} - shouldShowBackdrop > Date: Wed, 20 Dec 2023 14:19:30 -0700 Subject: [PATCH 063/146] Fix name --- src/components/PurposeForUsingExpensifyModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 2c7b61ff5bcf..8452edc6c8da 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -204,7 +204,7 @@ function PurposeForUsingExpensifyModal(props) { PurposeForUsingExpensifyModal.propTypes = propTypes; PurposeForUsingExpensifyModal.defaultProps = defaultProps; -PurposeForUsingExpensifyModal.displayName = 'AddPaymentMethodMenu'; +PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal'; export default compose( withWindowDimensions, From 4e0eb0df0652ffd0aeef450fce227fe21d165520 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Wed, 20 Dec 2023 15:28:27 -0700 Subject: [PATCH 064/146] Re-add backdrop --- src/components/Modal/BaseModal.tsx | 3 ++- src/components/Modal/types.ts | 3 +++ src/components/PurposeForUsingExpensifyModal.js | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 89640c56f5ef..620828290d9c 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -38,6 +38,7 @@ function BaseModal( onLayout, avoidKeyboard = false, children, + shouldShowBackdrop = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -185,7 +186,7 @@ function BaseModal( swipeDirection={swipeDirection} isVisible={isVisible} backdropColor={theme.overlay} - backdropOpacity={hideBackdrop ? 0 : variables.overlayOpacity} + backdropOpacity={!shouldShowBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} backdropTransitionOutTiming={0} hasBackdrop={fullscreen} coverScreen={fullscreen} diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 172965b45fb2..1675114b4571 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -61,6 +61,9 @@ type BaseModalProps = WindowDimensionsProps & * See: https://github.com/react-native-modal/react-native-modal/pull/116 * */ hideModalContentWhileAnimating?: boolean; + + /** Should we show the background overlay around the modal */ + shouldShowBackdrop?: boolean; }; export default BaseModalProps; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 8452edc6c8da..aaf352bae53e 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -168,6 +168,7 @@ function PurposeForUsingExpensifyModal(props) { isVisible={isModalOpen} onClose={closeModal} innerContainerStyle={styles.pt0} + shouldShowBackdrop > Date: Thu, 21 Dec 2023 10:37:58 +0100 Subject: [PATCH 065/146] Refactor OptionRowLHN component and update types Fix type error in SidebarUtils --- src/components/LHNOptionsList/OptionRowLHN.tsx | 11 +++++------ src/components/LHNOptionsList/types.ts | 14 +++++++++----- src/libs/SidebarUtils.ts | 2 +- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index b4a824071e89..869721b3997a 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -59,13 +59,12 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const textStyle = isFocused ? styles.sidebarLinkActiveText : styles.sidebarLinkText; const textUnreadStyle = optionItem?.isUnread ? [textStyle, styles.sidebarLinkTextBold] : [textStyle]; - const displayNameStyle = StyleUtils.combineStyles([styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, ...textUnreadStyle], style ?? {}); - const alternateTextStyle = StyleUtils.combineStyles( + const displayNameStyle = [styles.optionDisplayName, styles.optionDisplayNameCompact, styles.pre, textUnreadStyle, style]; + const alternateTextStyle = viewMode === CONST.OPTION_MODE.COMPACT - ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2] - : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting], - style ?? {}, - ); + ? [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, styles.optionAlternateTextCompact, styles.ml2, style] + : [textStyle, styles.optionAlternateText, styles.pre, styles.textLabelSupporting, style]; + const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; const sidebarInnerRowStyle = StyleSheet.flatten( diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 7cafbbe2ead0..95cca3316ee1 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,12 +1,12 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; -import {type StyleProp, View, type ViewStyle} from 'react-native'; +import {type StyleProp, TextStyle, View, type ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ @@ -19,7 +19,7 @@ type LHNOptionsListOnyxProps = { reportActions: OnyxCollection; /** Indicates which locale the user currently has selected */ - preferredLocale: OnyxEntry>; + preferredLocale: OnyxEntry; /** List of users' personal details */ personalDetails: OnyxEntry; @@ -60,7 +60,7 @@ type OptionRowLHNDataProps = { personalDetails: PersonalDetailsList; /** The preferred language for the app */ - preferredLocale: OnyxEntry>; + preferredLocale: OnyxEntry; /** The full data of the report */ fullReport: OnyxEntry; @@ -74,12 +74,16 @@ type OptionRowLHNDataProps = { /** The transaction from the parent report action */ transaction: OnyxEntry; + /** Comment added to report */ comment: string; + /** The receipt transaction from the parent report action */ receiptTransactions: OnyxCollection; + /** The reportID of the report */ reportID: string; + /** Array of report actions for this report */ reportActions: OnyxEntry; }; @@ -88,7 +92,7 @@ type OptionRowLHNProps = { isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; viewMode?: ValueOf; - style?: ViewStyle | ViewStyle[]; + style?: StyleProp; optionItem?: OptionData | null; }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 16a8fb09d21e..16d568e37ee6 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -231,7 +231,7 @@ type ActorDetails = { function getOptionData( report: OnyxEntry, reportActions: OnyxEntry, - personalDetails: PersonalDetailsList, + personalDetails: OnyxEntry, preferredLocale: ValueOf, policy: OnyxEntry, parentReportAction: OnyxEntry, From 8b5fe4b34c3c7908fb4338e10eda8e74c02ead1e Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 13:52:18 +0100 Subject: [PATCH 066/146] Update OptionData types and fix SubscriptAvatar props --- .../LHNOptionsList/OptionRowLHN.tsx | 14 ++++----- src/components/LHNOptionsList/types.ts | 8 +++-- src/components/SubscriptAvatar.tsx | 29 +++++++------------ src/libs/ReportUtils.ts | 4 +-- src/libs/SidebarUtils.ts | 4 +-- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 869721b3997a..abd18ffd63e6 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,6 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {GestureResponderEvent, StyleProp, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; +import {GestureResponderEvent, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -28,7 +28,7 @@ import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; import {OptionRowLHNProps} from './types'; -function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem = null, viewMode = 'default', style}: OptionRowLHNProps) { +function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); const styles = useThemeStyles(); const popoverAnchor = useRef(null); @@ -67,10 +67,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const contentContainerStyles = viewMode === CONST.OPTION_MODE.COMPACT ? [styles.flex1, styles.flexRow, styles.overflowHidden, StyleUtils.getCompactContentContainerStyles()] : [styles.flex1]; - const sidebarInnerRowStyle = StyleSheet.flatten( + const sidebarInnerRowStyle = StyleSheet.flatten( viewMode === CONST.OPTION_MODE.COMPACT - ? ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] as StyleProp) - : ([styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter] as StyleProp), + ? [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRowCompact, styles.justifyContentCenter] + : [styles.chatLinkRowPressable, styles.flexGrow1, styles.optionItemAvatarNameWrapper, styles.optionRow, styles.justifyContentCenter], ); const hoveredBackgroundColor = !!styles.sidebarLinkHover && 'backgroundColor' in styles.sidebarLinkHover ? styles.sidebarLinkHover.backgroundColor : theme.sidebar; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; @@ -121,7 +121,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti return ( @@ -170,7 +170,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > - {(optionItem.icons?.length ?? 0) > 0 && + {optionItem.icons?.length && (optionItem.shouldShowSubscript ? ( ; + type LHNOptionsListOnyxProps = { /** The policy which the user has access to and which the report could be tied to */ policy: OnyxCollection; @@ -44,7 +46,7 @@ type CustomLHNOptionsListProps = { onSelectRow: (reportID: string) => void; /** Toggle between compact and default view of the option */ - optionMode: ValueOf; + optionMode: OptionMode; /** Whether to allow option focus or not */ shouldDisableFocusOptions?: boolean; @@ -91,9 +93,9 @@ type OptionRowLHNProps = { reportID: string; isFocused?: boolean; onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; - viewMode?: ValueOf; + viewMode?: OptionMode; style?: StyleProp; - optionItem?: OptionData | null; + optionItem?: OptionData; }; type RenderItemProps = {item: string}; diff --git a/src/components/SubscriptAvatar.tsx b/src/components/SubscriptAvatar.tsx index 1ab2b4238213..2bc4df4fa70b 100644 --- a/src/components/SubscriptAvatar.tsx +++ b/src/components/SubscriptAvatar.tsx @@ -29,14 +29,7 @@ type SubscriptAvatarProps = { showTooltip?: boolean; }; -function SubscriptAvatar({ - mainAvatar = {} as Icon, - secondaryAvatar = {} as Icon, - size = CONST.AVATAR_SIZE.DEFAULT, - backgroundColor, - noMargin = false, - showTooltip = true, -}: SubscriptAvatarProps) { +function SubscriptAvatar({mainAvatar, secondaryAvatar, size = CONST.AVATAR_SIZE.DEFAULT, backgroundColor, noMargin = false, showTooltip = true}: SubscriptAvatarProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -48,23 +41,23 @@ function SubscriptAvatar({ diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 29197e51aaa9..6a504ad40348 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -313,7 +313,7 @@ type DisplayNameWithTooltips = Array; displayNamesWithTooltips?: DisplayNameWithTooltips | null; descriptiveText?: string; } & Report; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 16d568e37ee6..7e93f6fc9e5d 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,7 +245,7 @@ function getOptionData( const result: ReportUtils.OptionData = { alternateText: null, - allReportErrors: null, + allReportErrors: undefined, brickRoadIndicator: null, tooltipText: null, subtitle: null, @@ -277,7 +277,7 @@ function getOptionData( result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); result.isTaskReport = ReportUtils.isTaskReport(report); - result.parentReportAction = parentReportAction ?? undefined; + result.parentReportAction = parentReportAction; result.isArchivedRoom = ReportUtils.isArchivedRoom(report); result.isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); result.isExpenseRequest = ReportUtils.isExpenseRequest(report); From 882d8d98841a178204449967e0c71ee31e0a8d06 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 21 Dec 2023 14:13:53 +0100 Subject: [PATCH 067/146] fix: adjust code based on the comments --- src/components/LHNOptionsList/LHNOptionsList.tsx | 2 +- src/components/LHNOptionsList/OptionRowLHNData.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index c61c8d711533..4789536c90f1 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -46,8 +46,8 @@ function LHNOptionsList({ const itemTransaction = transactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? null; const itemComment = draftComments?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`] ?? ''; const participants = [...ReportUtils.getParticipantsIDs(itemFullReport), itemFullReport?.ownerAccountID]; - const participantsPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); + return ( (); + const optionItemRef = useRef(undefined); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); const lastReportAction = sortedReportActions[0]; @@ -44,9 +44,9 @@ function OptionRowLHNData({ if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } - if (item) { - optionItemRef.current = item; - } + + optionItemRef.current = item; + return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed From 37e1e788305e2babc0984c4dc4e4ee07ccd9a204 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 08:22:07 -0700 Subject: [PATCH 068/146] Add custom backdrop --- src/components/Modal/BaseModal.tsx | 6 ++++-- src/components/Modal/types.ts | 4 ++-- src/components/PurposeForUsingExpensifyModal.js | 2 +- src/libs/Navigation/AppNavigator/Navigators/Overlay.tsx | 6 ++++-- src/styles/index.ts | 4 ++++ 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 620828290d9c..538daf0eb3cb 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -13,6 +13,7 @@ import useNativeDriver from '@libs/useNativeDriver'; import variables from '@styles/variables'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; +import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; import BaseModalProps from './types'; function BaseModal( @@ -38,7 +39,7 @@ function BaseModal( onLayout, avoidKeyboard = false, children, - shouldShowBackdrop = false, + shouldUseCustomBackdrop = false, }: BaseModalProps, ref: React.ForwardedRef, ) { @@ -186,7 +187,7 @@ function BaseModal( swipeDirection={swipeDirection} isVisible={isVisible} backdropColor={theme.overlay} - backdropOpacity={!shouldShowBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} + backdropOpacity={!shouldUseCustomBackdrop && hideBackdrop ? 0 : variables.overlayOpacity} backdropTransitionOutTiming={0} hasBackdrop={fullscreen} coverScreen={fullscreen} @@ -202,6 +203,7 @@ function BaseModal( statusBarTranslucent={statusBarTranslucent} onLayout={onLayout} avoidKeyboard={avoidKeyboard} + customBackdrop={shouldUseCustomBackdrop ? : undefined} > diff --git a/src/styles/index.ts b/src/styles/index.ts index 905c25f4f7d8..8ad7be0a893a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -2858,6 +2858,10 @@ const styles = (theme: ThemeColors) => outlineStyle: 'none', }, + boxShadowNone: { + boxShadow: 'none', + }, + cardStyleNavigator: { overflow: 'hidden', height: '100%', From fdf8429acf823d55824c0a72441dba5cad6b6fb1 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 09:20:34 -0700 Subject: [PATCH 069/146] Add proper spanish translations --- src/languages/en.ts | 8 ++++---- src/languages/es.ts | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 08023948326f..174bebaf96f4 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2023,12 +2023,12 @@ export default { copyReferralLink: 'Copy invite link', }, purposeForExpensify: { - track: 'Track my business expenses to file taxes', - submit: 'Submit expenses to my employer to get paid back', + track: 'Track business spend for taxes', + submit: 'Get paid back by my employer', business: "Manage my team's expenses", - chatSplit: 'Chat and split expenses with friends', + chatSplit: 'Chat and split bills with friends', welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: "What's your main purpose for using Expensify?", + welcomeSubtitle: 'What would you like to do?', }, violations: { allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired', diff --git a/src/languages/es.ts b/src/languages/es.ts index e754f0554701..61d1a7bed3b4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2510,12 +2510,12 @@ export default { copyReferralLink: 'Copiar enlace de invitación', }, purposeForExpensify: { - track: 'Track my business expenses to file taxes', - submit: 'Submit expenses to my employer to get paid back', - business: "Manage my team's expenses", - chatSplit: 'Chat and split expenses with friends', - welcomeMessage: 'Welcome to Expensify', - welcomeSubtitle: "What's your main purpose for using Expensify?", + track: 'Seguimiento de los gastos de empresa para fines fiscales' , + submit: 'Reclamar gastos a mi empleador', + business: 'Gestionar los gastos de mi equipo', + chatSplit: 'Chatea y divide gastos con tus amigos', + welcomeMessage: 'Bienvenido a Expensify', + welcomeSubtitle: '¿Qué te gustaría hacer?', }, violations: { allTagLevelsRequired: 'dummy.violations.allTagLevelsRequired', From 23b3f8911cb8a58a0380dffd8bd9af84b3530601 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 13:29:10 -0700 Subject: [PATCH 070/146] only use custom backdrop for large screens --- src/components/PurposeForUsingExpensifyModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index c53117dca6d0..3522ab8586dd 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -168,7 +168,7 @@ function PurposeForUsingExpensifyModal(props) { isVisible={isModalOpen} onClose={closeModal} innerContainerStyle={styles.pt0} - shouldUseCustomBackdrop + shouldUseCustomBackdrop={!isSmallScreenWidth} > Date: Thu, 21 Dec 2023 15:00:43 -0700 Subject: [PATCH 071/146] Shrink image when screen is small --- .../PurposeForUsingExpensifyModal.js | 23 +++++++++++++++---- .../utils/generators/ModalStyleUtils.ts | 1 + 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 3522ab8586dd..52774c315d86 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -9,6 +9,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; +import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; @@ -58,6 +59,7 @@ function PurposeForUsingExpensifyModal(props) { return; } Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); + setIsModalOpen(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -162,19 +164,32 @@ function PurposeForUsingExpensifyModal(props) { }, ]; + let containerStyle; + let lottieContainerStyle; + let lottieStyle; + if (isSmallScreenWidth) { + containerStyle = {...styles.pt0, ...styles.flex1, marginTop: variables.contentHeaderHeight}; + lottieContainerStyle = [StyleUtils.getBackgroundColorStyle(theme.PAGE_THEMES[SCREENS.SETTINGS.WORKSPACES].backgroundColor), styles.flex1]; + lottieStyle = {width: undefined, ...styles.h100, ...styles.alignSelfCenter}; + } else { + containerStyle = styles.pt0; + lottieContainerStyle = StyleUtils.getBackgroundColorStyle(theme.PAGE_THEMES[SCREENS.SETTINGS.WORKSPACES].backgroundColor); + lottieStyle = styles.w100; + } + return ( - + diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts index 67faec504d61..1ea57ebaf54d 100644 --- a/src/styles/utils/generators/ModalStyleUtils.ts +++ b/src/styles/utils/generators/ModalStyleUtils.ts @@ -187,6 +187,7 @@ const createModalStyleUtils: StyleUtilGenerator = ({the swipeDirection = undefined; animationIn = 'slideInUp'; animationOut = 'slideOutDown'; + shouldAddTopSafeAreaMargin = true; break; case CONST.MODAL.MODAL_TYPE.POPOVER: modalStyle = { From 9adf07d738fa552d7ea1a7ae449c383e955b5bbc Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 15:23:53 -0700 Subject: [PATCH 072/146] Revert resizing changes --- .../PurposeForUsingExpensifyModal.js | 23 ++++--------------- .../utils/generators/ModalStyleUtils.ts | 1 - 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 52774c315d86..3522ab8586dd 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -9,7 +9,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import compose from '@libs/compose'; -import variables from '@styles/variables'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; @@ -59,7 +58,6 @@ function PurposeForUsingExpensifyModal(props) { return; } Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); - setIsModalOpen(true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -164,32 +162,19 @@ function PurposeForUsingExpensifyModal(props) { }, ]; - let containerStyle; - let lottieContainerStyle; - let lottieStyle; - if (isSmallScreenWidth) { - containerStyle = {...styles.pt0, ...styles.flex1, marginTop: variables.contentHeaderHeight}; - lottieContainerStyle = [StyleUtils.getBackgroundColorStyle(theme.PAGE_THEMES[SCREENS.SETTINGS.WORKSPACES].backgroundColor), styles.flex1]; - lottieStyle = {width: undefined, ...styles.h100, ...styles.alignSelfCenter}; - } else { - containerStyle = styles.pt0; - lottieContainerStyle = StyleUtils.getBackgroundColorStyle(theme.PAGE_THEMES[SCREENS.SETTINGS.WORKSPACES].backgroundColor); - lottieStyle = styles.w100; - } - return ( - + diff --git a/src/styles/utils/generators/ModalStyleUtils.ts b/src/styles/utils/generators/ModalStyleUtils.ts index 1ea57ebaf54d..67faec504d61 100644 --- a/src/styles/utils/generators/ModalStyleUtils.ts +++ b/src/styles/utils/generators/ModalStyleUtils.ts @@ -187,7 +187,6 @@ const createModalStyleUtils: StyleUtilGenerator = ({the swipeDirection = undefined; animationIn = 'slideInUp'; animationOut = 'slideOutDown'; - shouldAddTopSafeAreaMargin = true; break; case CONST.MODAL.MODAL_TYPE.POPOVER: modalStyle = { From daf05b61ddf9dc47e11da0e172b8c7521684c093 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 15:44:45 -0700 Subject: [PATCH 073/146] Scrollview --- .../PurposeForUsingExpensifyModal.js | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 3522ab8586dd..6f8fd2b1a238 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; -import {View} from 'react-native'; +import {View, ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -170,35 +170,37 @@ function PurposeForUsingExpensifyModal(props) { innerContainerStyle={styles.pt0} shouldUseCustomBackdrop={!isSmallScreenWidth} > - - + + + + + + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + + - - - - - {translate('purposeForExpensify.welcomeMessage')} - - {translate('purposeForExpensify.welcomeSubtitle')} - - + ); } From 998c2e9fbd15fb780717ebb2ecba7ac2b6c86d75 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 21 Dec 2023 15:49:09 -0700 Subject: [PATCH 074/146] Style --- src/components/Modal/BaseModal.tsx | 4 ++-- src/components/PurposeForUsingExpensifyModal.js | 2 +- src/languages/es.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index 538daf0eb3cb..39132e2af686 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -9,11 +9,11 @@ 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'; import CONST from '@src/CONST'; -import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; import BaseModalProps from './types'; function BaseModal( @@ -203,7 +203,7 @@ function BaseModal( statusBarTranslucent={statusBarTranslucent} onLayout={onLayout} avoidKeyboard={avoidKeyboard} - customBackdrop={shouldUseCustomBackdrop ? : undefined} + customBackdrop={shouldUseCustomBackdrop ? : undefined} > Date: Tue, 2 Jan 2024 14:47:25 +0100 Subject: [PATCH 075/146] fix: resolve comment --- src/components/LHNOptionsList/OptionRowLHNData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 3eca9c5f9913..a0bd4f430c35 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -3,7 +3,7 @@ import React, {useEffect, useMemo, useRef} from 'react'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; -import * as ReportLib from '@userActions/Report'; +import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; @@ -57,7 +57,7 @@ function OptionRowLHNData({ if (!optionItem || !!optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { return; } - ReportLib.setReportWithDraft(reportID, true); + Report.setReportWithDraft(reportID, true); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); From 3ca872ac79691639e756eebe9a1874634ac40c01 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 4 Jan 2024 17:02:03 -0700 Subject: [PATCH 076/146] Change keys' --- src/components/PurposeForUsingExpensifyModal.js | 16 ++++++++-------- src/languages/en.ts | 4 ++-- src/languages/es.ts | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index 38d63214673e..ace35019ed80 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -129,7 +129,7 @@ function PurposeForUsingExpensifyModal(props) { title: translate('purposeForExpensify.track'), icon: Expensicons.ReceiptSearch, iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.track, 'trackNewDot'), + onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), shouldShowRightIcon: true, numberOfLinesTitle: 2, }, @@ -138,25 +138,25 @@ function PurposeForUsingExpensifyModal(props) { title: translate('purposeForExpensify.submit'), icon: Expensicons.Scan, iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.submit, 'submitNewDot'), + onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), shouldShowRightIcon: true, numberOfLinesTitle: 2, }, { - key: 'purposeForExpensify.business', - title: translate('purposeForExpensify.business'), + key: 'purposeForExpensify.manageTeam', + title: translate('purposeForExpensify.manageTeam'), icon: Expensicons.MoneyBag, iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.business, 'businessNewDot'), + onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), shouldShowRightIcon: true, numberOfLinesTitle: 2, }, { - key: 'purposeForExpensify.chatSplit', - title: translate('purposeForExpensify.chatSplit'), + key: 'purposeForExpensify.splitChat', + title: translate('purposeForExpensify.splitChat'), icon: Expensicons.Briefcase, iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.chatSplit, 'chatSplitNewDot'), + onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), shouldShowRightIcon: true, numberOfLinesTitle: 2, }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 9d76fbd92a73..753dc19922d7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2037,8 +2037,8 @@ export default { purposeForExpensify: { track: 'Track business spend for taxes', submit: 'Get paid back by my employer', - business: "Manage my team's expenses", - chatSplit: 'Chat and split bills with friends', + manageTeam: "Manage my team's expenses", + splitChat: 'Chat and split bills with friends', welcomeMessage: 'Welcome to Expensify', welcomeSubtitle: 'What would you like to do?', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 74efd36b4c94..ef9719d04e84 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2524,8 +2524,8 @@ export default { purposeForExpensify: { track: 'Seguimiento de los gastos de empresa para fines fiscales', submit: 'Reclamar gastos a mi empleador', - business: 'Gestionar los gastos de mi equipo', - chatSplit: 'Chatea y divide gastos con tus amigos', + manageTeam: 'Gestionar los gastos de mi equipo', + splitChat: 'Chatea y divide gastos con tus amigos', welcomeMessage: 'Bienvenido a Expensify', welcomeSubtitle: '¿Qué te gustaría hacer?', }, From 5bdc2c6395cc0dc72e15b9f991fc7af420d495dc Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 4 Jan 2024 18:12:01 -0700 Subject: [PATCH 077/146] Style --- src/libs/actions/Welcome.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 83c742da6ca2..3f17ced0ea0e 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -6,9 +6,9 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import type {EmptyObject} from '@src/types/utils/EmptyObject'; import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; +import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; From cf23709193c0b072bd65c2f1064b7525203b2acd Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 4 Jan 2024 18:14:48 -0700 Subject: [PATCH 078/146] Fix props --- src/components/HeaderWithBackButton/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts index 9ffb0b5ef2f3..e5c8860c8fe0 100644 --- a/src/components/HeaderWithBackButton/types.ts +++ b/src/components/HeaderWithBackButton/types.ts @@ -108,6 +108,9 @@ type HeaderWithBackButtonProps = Partial & { /** Whether we should enable detail page navigation */ shouldEnableDetailPageNavigation?: boolean; + + /** Whether we should overlay the 3 dots menu */ + shouldOverlayDots?: boolean; }; export default HeaderWithBackButtonProps; From 01761cc190f07b72053233685c86fb47d8162b05 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Fri, 5 Jan 2024 17:59:28 +0500 Subject: [PATCH 079/146] fix: re-add notification preference condition to unread reports calculation --- src/libs/UnreadIndicatorUpdater/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 4126f2af4e83..832a00fb8f37 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -2,6 +2,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import * as ReportUtils from '@libs/ReportUtils'; import Navigation, {navigationRef} from '@navigation/Navigation'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; import updateUnread from './updateUnread'; @@ -13,7 +14,10 @@ const triggerUnreadUpdate = () => { // We want to keep notification count consistent with what can be accessed from the LHN list const unreadReports = Object.values(allReports ?? {}).filter( - (report) => ReportUtils.isUnread(report) && ReportUtils.shouldReportBeInOptionList(report, currentReportID ?? '', false, [], {}), + (report) => + ReportUtils.isUnread(report) && + ReportUtils.shouldReportBeInOptionList(report, currentReportID ?? '', false, [], {}) && + report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); updateUnread(unreadReports.length); }; From 4df4d3aaa85d864949854068fc3b63a92ddd4255 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 5 Jan 2024 12:12:13 -0700 Subject: [PATCH 080/146] Fix import error --- tests/unit/CalendarPickerTest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/unit/CalendarPickerTest.js b/tests/unit/CalendarPickerTest.js index e1c11fbb8ca8..3aab3a13c1c3 100644 --- a/tests/unit/CalendarPickerTest.js +++ b/tests/unit/CalendarPickerTest.js @@ -7,6 +7,7 @@ import DateUtils from '../../src/libs/DateUtils'; const monthNames = DateUtils.getMonthNames(CONST.LOCALES.EN); jest.mock('@react-navigation/native', () => ({ + ...jest.requireActual('@react-navigation/native'), useNavigation: () => ({navigate: jest.fn()}), createNavigationContainerRef: jest.fn(), })); From 82ce1ce70e9327f6d4c82161c8653d3b2612a79b Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 8 Jan 2024 10:22:41 +0100 Subject: [PATCH 081/146] fix: resolve comments --- .../LHNOptionsList/LHNOptionsList.tsx | 5 ++-- .../LHNOptionsList/OptionRowLHN.tsx | 20 +++++++------- .../LHNOptionsList/OptionRowLHNData.tsx | 6 ++--- src/components/LHNOptionsList/types.ts | 26 ++++++++++++++----- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 4789536c90f1..77940b98bc57 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,5 +1,6 @@ import {FlashList} from '@shopify/flash-list'; -import React, {ReactElement, useCallback} from 'react'; +import type {ReactElement} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -11,7 +12,7 @@ import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import OptionRowLHNData from './OptionRowLHNData'; -import {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; +import type {LHNOptionsListOnyxProps, LHNOptionsListProps, RenderItemProps} from './types'; const keyExtractor = (item: string) => `report_${item}`; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 739ec1410f80..0696c2d8fb13 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import React, {useCallback, useRef, useState} from 'react'; -import {GestureResponderEvent, StyleSheet, TextInput, View, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, ViewStyle} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import DisplayNames from '@components/DisplayNames'; import Hoverable from '@components/Hoverable'; import Icon from '@components/Icon'; @@ -25,7 +26,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; -import {OptionRowLHNProps} from './types'; +import type {OptionRowLHNProps} from './types'; function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { const theme = useTheme(); @@ -90,8 +91,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti CONST.CONTEXT_MENU_TYPES.REPORT, event, '', - // @ts-expect-error TODO: Remove this once ReportActionContextMenu (https://github.com/Expensify/App/pull/32670) is migrated to TypeScript. - popoverAnchor, + popoverAnchor.current, reportID, '0', reportID, @@ -101,14 +101,14 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti false, false, optionItem.isPinned, - optionItem.isUnread, + optionItem.isUnread ?? undefined, ); }; - const emojiCode = typeof optionItem.status === 'object' ? optionItem.status?.emojiCode : ''; - const statusText = typeof optionItem.status === 'object' ? optionItem.status?.text : ''; - const statusClearAfterDate = typeof optionItem.status === 'object' ? optionItem.status?.clearAfter : ''; - const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate ?? ''); + const emojiCode = optionItem.status?.emojiCode ?? ''; + const statusText = optionItem.status?.text ?? ''; + const statusClearAfterDate = optionItem.status?.clearAfter ?? ''; + const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; const report = ReportUtils.getReport(optionItem.reportID ?? ''); const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); @@ -148,7 +148,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - (DomUtils.getActiveElement() as HTMLElement | TextInput)?.blur(); + (DomUtils.getActiveElement() as HTMLElement)?.blur(); } }} withoutFocusOnSecondaryInteraction diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index a0bd4f430c35..741b66d59607 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -7,7 +7,7 @@ import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import OptionRowLHN from './OptionRowLHN'; -import {OptionRowLHNDataProps} from './types'; +import type {OptionRowLHNDataProps} from './types'; /* * This component gets the data from onyx for the actual @@ -17,7 +17,7 @@ import {OptionRowLHNDataProps} from './types'; */ function OptionRowLHNData({ isFocused = false, - fullReport = null, + fullReport, reportActions, personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, @@ -30,7 +30,7 @@ function OptionRowLHNData({ }: OptionRowLHNDataProps) { const reportID = propsToForward.reportID; - const optionItemRef = useRef(undefined); + const optionItemRef = useRef(); const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); const lastReportAction = sortedReportActions[0]; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index f4215fcbf388..6f28edeb79f7 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -1,10 +1,10 @@ import type {ContentStyle} from '@shopify/flash-list'; import type {RefObject} from 'react'; -import {type StyleProp, TextStyle, View, type ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; -import CONST from '@src/CONST'; +import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; @@ -32,6 +32,7 @@ type LHNOptionsListOnyxProps = { /** List of draft comments */ draftComments: OnyxCollection; }; + type CustomLHNOptionsListProps = { /** Wrapper style for the section list */ style?: StyleProp; @@ -56,22 +57,22 @@ type LHNOptionsListProps = CustomLHNOptionsListProps & CurrentReportIDContextVal type OptionRowLHNDataProps = { /** Whether row should be focused */ - isFocused: boolean; + isFocused?: boolean; /** List of users' personal details */ - personalDetails: PersonalDetailsList; + personalDetails?: PersonalDetailsList; /** The preferred language for the app */ - preferredLocale: OnyxEntry; + preferredLocale?: OnyxEntry; /** The full data of the report */ fullReport: OnyxEntry; /** The policy which the user has access to and which the report could be tied to */ - policy: OnyxEntry; + policy?: OnyxEntry; /** The action from the parent report */ - parentReportAction: OnyxEntry; + parentReportAction?: OnyxEntry; /** The transaction from the parent report action */ transaction: OnyxEntry; @@ -90,11 +91,22 @@ type OptionRowLHNDataProps = { }; type OptionRowLHNProps = { + /** The ID of the report that the option is for */ reportID: string; + + /** Whether this option is currently in focus so we can modify its style */ isFocused?: boolean; + + /** A function that is called when an option is selected. Selected option is passed as a param */ onSelectRow?: (optionItem: OptionData, popoverAnchor: RefObject) => void; + + /** Toggle between compact and default view */ viewMode?: OptionMode; + + /** Additional style props */ style?: StyleProp; + + /** The item that should be rendered */ optionItem?: OptionData; }; From c4de9b2085eb29316c92da1949d6e370a2380b8e Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 08:08:36 +0100 Subject: [PATCH 082/146] Fix import and variable names in OptionRowLHN component --- src/components/LHNOptionsList/OptionRowLHN.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 0696c2d8fb13..46161e401228 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -25,7 +25,7 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import * as ReportUtils from '@libs/ReportUtils'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import CONST from '@src/CONST'; -import {isNotEmptyObject} from '@src/types/utils/EmptyObject'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {OptionRowLHNProps} from './types'; function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, optionItem, viewMode = 'default', style}: OptionRowLHNProps) { @@ -101,7 +101,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti false, false, optionItem.isPinned, - optionItem.isUnread ?? undefined, + !!optionItem.isUnread, ); }; @@ -111,10 +111,10 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti const formattedDate = DateUtils.getStatusUntilDate(statusClearAfterDate); const statusContent = formattedDate ? `${statusText ? `${statusText} ` : ''}(${formattedDate})` : statusText; const report = ReportUtils.getReport(optionItem.reportID ?? ''); - const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(isNotEmptyObject(report) ? report : null); + const isStatusVisible = !!emojiCode && ReportUtils.isOneOnOneChat(!isEmptyObject(report) ? report : null); const isGroupChat = optionItem.type === CONST.REPORT.TYPE.CHAT && optionItem.chatType && !optionItem.isThread && (optionItem.displayNamesWithTooltips?.length ?? 0) > 2; - const fullTitle = isGroupChat ? getGroupChatName(isNotEmptyObject(report) ? report : null) : optionItem.text; + const fullTitle = isGroupChat ? getGroupChatName(!isEmptyObject(report) ? report : null) : optionItem.text; const subscriptAvatarBorderColor = isFocused ? focusedBackgroundColor : theme.sidebar; return ( @@ -173,13 +173,13 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti (optionItem.shouldShowSubscript ? ( ) : ( {}, opti {optionItem?.descriptiveText ? ( - {optionItem?.descriptiveText} + {optionItem.descriptiveText} ) : null} {hasBrickError && ( From 7ebbccbbae2d35076208dcf1f5815e152828a37d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 9 Jan 2024 13:01:48 +0100 Subject: [PATCH 083/146] fix: resolve comments --- src/components/LHNOptionsList/LHNOptionsList.tsx | 10 ++++------ src/components/LHNOptionsList/OptionRowLHN.tsx | 10 +++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 77940b98bc57..6fe9a5404718 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -5,7 +5,6 @@ import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; import variables from '@styles/variables'; @@ -63,7 +62,7 @@ function LHNOptionsList({ viewMode={optionMode} isFocused={!shouldDisableFocusOptions && reportID === currentReportID} onSelectRow={onSelectRow} - preferredLocale={preferredLocale ?? CONST.LOCALES.DEFAULT} + preferredLocale={preferredLocale} comment={itemComment} /> ); @@ -91,7 +90,7 @@ function LHNOptionsList({ LHNOptionsList.displayName = 'LHNOptionsList'; -export default compose( +export default withCurrentReportID( withOnyx({ reports: { key: ONYXKEYS.COLLECTION.REPORT, @@ -114,8 +113,7 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - }), - withCurrentReportID, -)(LHNOptionsList); + })(LHNOptionsList), +); export type {LHNOptionsListProps}; diff --git a/src/components/LHNOptionsList/OptionRowLHN.tsx b/src/components/LHNOptionsList/OptionRowLHN.tsx index 46161e401228..1932cf6c6b7f 100644 --- a/src/components/LHNOptionsList/OptionRowLHN.tsx +++ b/src/components/LHNOptionsList/OptionRowLHN.tsx @@ -148,7 +148,7 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti showPopover(event); // Ensure that we blur the composer when opening context menu, so that only one component is focused at a time if (DomUtils.getActiveElement()) { - (DomUtils.getActiveElement() as HTMLElement)?.blur(); + (DomUtils.getActiveElement() as HTMLElement | null)?.blur(); } }} withoutFocusOnSecondaryInteraction @@ -169,17 +169,17 @@ function OptionRowLHN({reportID, isFocused = false, onSelectRow = () => {}, opti > - {optionItem.icons?.length && + {(optionItem.icons?.length ?? 0) > 0 && (optionItem.shouldShowSubscript ? ( ) : ( Date: Tue, 9 Jan 2024 11:08:51 -0700 Subject: [PATCH 084/146] Move message copy outside of component lifecycle --- .../PurposeForUsingExpensifyModal.js | 102 +++++++++--------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index ace35019ed80..d05df72f7649 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -39,6 +39,57 @@ const defaultProps = { session: {}, }; +// This is not translated because it is a message coming from concierge, which only supports english +const messageCopy = { + 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!", + 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!", + business: + "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!', + chatSplit: + '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!", +}; + function PurposeForUsingExpensifyModal(props) { const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); @@ -61,57 +112,6 @@ function PurposeForUsingExpensifyModal(props) { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - // This is not translated because it is a message coming from concierge, which only supports english - const messageCopy = { - 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!", - 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!", - business: - "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!', - chatSplit: - '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 closeModal = useCallback(() => { Report.dismissEngagementModal(); setIsModalOpen(false); From aa6398a4ba4f9525ba6e70b87baf13d8512947a3 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 9 Jan 2024 17:20:01 -0700 Subject: [PATCH 085/146] Add view for max height --- .../PurposeForUsingExpensifyModal.js | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index d05df72f7649..c70dd44ffdfc 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -94,7 +94,7 @@ function PurposeForUsingExpensifyModal(props) { const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); - const {isSmallScreenWidth} = useWindowDimensions(); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); const [isModalOpen, setIsModalOpen] = useState(false); const theme = useTheme(); @@ -163,45 +163,49 @@ function PurposeForUsingExpensifyModal(props) { ]; return ( - - - - - + + + + + + + + + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + + + - - - {translate('purposeForExpensify.welcomeMessage')} - - {translate('purposeForExpensify.welcomeSubtitle')} - - - - + ); } From 37ff12a067b49d04495d606a94d7626fd058012c Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Tue, 9 Jan 2024 17:22:10 -0700 Subject: [PATCH 086/146] Prettier --- .../PurposeForUsingExpensifyModal.js | 82 +++++++++---------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js index c70dd44ffdfc..e80c28e768fa 100644 --- a/src/components/PurposeForUsingExpensifyModal.js +++ b/src/components/PurposeForUsingExpensifyModal.js @@ -163,49 +163,47 @@ function PurposeForUsingExpensifyModal(props) { ]; return ( - - - - - - - - - - {translate('purposeForExpensify.welcomeMessage')} - - {translate('purposeForExpensify.welcomeSubtitle')} - - + + + + - - - + + + + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + + + + + ); } From ef3bba2f8103653bc489e2b4c91ea13efa8dc1bf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 08:10:43 +0100 Subject: [PATCH 087/146] fix: resolve comment --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- src/components/LHNOptionsList/types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 6fe9a5404718..ae98fd776f34 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,7 +1,7 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; import React, {useCallback} from 'react'; -import {View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -75,7 +75,7 @@ function LHNOptionsList({ ; /** Extra styles for the section list container */ - contentContainerStyles?: ContentStyle; + contentContainerStyles?: StyleProp; /** Sections for the section list */ data: string[]; From 0cfe92cd1c48e53526f96f64a5dacfdd801feecf Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 09:02:23 +0100 Subject: [PATCH 088/146] fix: lint --- src/libs/GroupChatUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/GroupChatUtils.ts b/src/libs/GroupChatUtils.ts index 4048d4c72f15..5d925ae1c684 100644 --- a/src/libs/GroupChatUtils.ts +++ b/src/libs/GroupChatUtils.ts @@ -1,3 +1,4 @@ +import type {OnyxEntry} from 'react-native-onyx'; import type {Report} from '@src/types/onyx'; import * as ReportUtils from './ReportUtils'; From 61bc05c570c3262cc6fe250f4fdd0667c82e6811 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Jan 2024 15:10:44 +0700 Subject: [PATCH 089/146] Display merchant in report name of transaction report if exist --- src/libs/ReportUtils.ts | 2 +- src/libs/TransactionUtils.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e619cb3c80dd..80b66225eef6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2039,7 +2039,7 @@ function getTransactionReportName(reportAction: OnyxEntry): string return Localize.translateLocal(ReportActionsUtils.isSentMoneyReportAction(reportAction) ? 'iou.threadSentMoneyReportName' : 'iou.threadRequestReportName', { formattedAmount: CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency, TransactionUtils.isDistanceRequest(transaction)) ?? '', - comment: transactionDetails?.comment ?? '', + comment: !TransactionUtils.isMerchantMissing(transaction) ? transactionDetails?.merchant : transactionDetails?.comment ?? '', }); } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index c34a6753c1d5..8a2e6566931f 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -140,11 +140,12 @@ function hasReceipt(transaction: Transaction | undefined | null): boolean { } function isMerchantMissing(transaction: Transaction) { + if (transaction.modifiedMerchant && transaction.modifiedMerchant !== '') { + return transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; + } const isMerchantEmpty = transaction.merchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.merchant === ''; - const isModifiedMerchantEmpty = !transaction.modifiedMerchant || transaction.modifiedMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT || transaction.modifiedMerchant === ''; - - return isMerchantEmpty && isModifiedMerchantEmpty; + return isMerchantEmpty; } function isAmountMissing(transaction: Transaction) { From e9ca21b5365d9b605d63ca4ea2b0f62edc27f1c2 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Jan 2024 15:19:25 +0700 Subject: [PATCH 090/146] fix ts bug --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 80b66225eef6..1c22b2c16ff2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2039,7 +2039,7 @@ function getTransactionReportName(reportAction: OnyxEntry): string return Localize.translateLocal(ReportActionsUtils.isSentMoneyReportAction(reportAction) ? 'iou.threadSentMoneyReportName' : 'iou.threadRequestReportName', { formattedAmount: CurrencyUtils.convertToDisplayString(transactionDetails?.amount ?? 0, transactionDetails?.currency, TransactionUtils.isDistanceRequest(transaction)) ?? '', - comment: !TransactionUtils.isMerchantMissing(transaction) ? transactionDetails?.merchant : transactionDetails?.comment ?? '', + comment: (!TransactionUtils.isMerchantMissing(transaction) ? transactionDetails?.merchant : transactionDetails?.comment) ?? '', }); } From 9949e483dbc92ebafd13eaa144c462f32fe3b17d Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 11 Jan 2024 16:20:27 +0200 Subject: [PATCH 091/146] Fix invited members for admin threads --- src/libs/ReportUtils.ts | 36 ++++++++++++++++++++++++++++++++-- src/pages/home/ReportScreen.js | 2 +- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1010f8bd82e0..ea658d7e6c59 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,7 +16,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, Report, ReportAction, ReportMetadata, Session, Transaction} from '@src/types/onyx'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; +import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference} from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; @@ -1499,7 +1499,7 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f // This is to check if account is an invite/optimistically created one // and prevent from falling back to 'Hidden', so a correct value is shown // when searching for a new user - if (personalDetails.isOptimisticPersonalDetail === true) { + if (personalDetails.isOptimisticPersonalDetail === true && formattedLogin) { // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing return formattedLogin; } @@ -2201,6 +2201,35 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry) { + const originalMessage = parentReportAction?.originalMessage as ChangeLog | undefined; + // We need to insert in parentReportActionMessage invited members + let actionMessage: string; + const verb = + parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? Localize.translateLocal('workspace.invite.invited') + : Localize.translateLocal('workspace.invite.removed'); + const participantAccountIDs = originalMessage?.targetAccountIDs ?? []; + const participants = Array.from({length: participantAccountIDs.length}, (v, i) => i).map((i) => getDisplayNameForParticipant(participantAccountIDs[i])); + const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; + actionMessage = `${verb} ${users}`; + + const roomName = originalMessage?.roomName ?? ''; + if (roomName) { + const preposition = + parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || + parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM + ? ` ${Localize.translateLocal('workspace.invite.to')}` + : ` ${Localize.translateLocal('workspace.invite.from')}`; + actionMessage += `${preposition} ${roomName}`; + } + return actionMessage; +} + /** * Get the title for a report. */ @@ -2223,6 +2252,9 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu ) { return Localize.translateLocal('parentReportAction.hiddenMessage'); } + if (isAdminRoom(report) || isUserCreatedPolicyRoom(report)) { + return getAdminRoomInvitedParticipants(parentReportAction); + } return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 64e48ecd5509..1d0d56290b8e 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -610,7 +610,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID) { + if (!parentReportActionID || !parentReportActions) { return {}; } return parentReportActions[parentReportActionID]; From 37d532bf52ab4784baf9e9d508e584c08a988056 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 11 Jan 2024 16:47:34 +0200 Subject: [PATCH 092/146] clean up --- src/libs/ReportUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ea658d7e6c59..b5a4555fef60 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2207,14 +2207,13 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry) { const originalMessage = parentReportAction?.originalMessage as ChangeLog | undefined; - // We need to insert in parentReportActionMessage invited members let actionMessage: string; const verb = parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed'); const participantAccountIDs = originalMessage?.targetAccountIDs ?? []; - const participants = Array.from({length: participantAccountIDs.length}, (v, i) => i).map((i) => getDisplayNameForParticipant(participantAccountIDs[i])); + const participants = participantAccountIDs.map((id) => getDisplayNameForParticipant(id)); const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; actionMessage = `${verb} ${users}`; From 995588ca7b98329f82ca757811f7b83c0287df46 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 11 Jan 2024 18:47:22 +0200 Subject: [PATCH 093/146] Code improvements --- src/libs/ReportUtils.ts | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b5a4555fef60..98f65d47592b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2206,27 +2206,25 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry) { - const originalMessage = parentReportAction?.originalMessage as ChangeLog | undefined; - let actionMessage: string; - const verb = - parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? Localize.translateLocal('workspace.invite.invited') - : Localize.translateLocal('workspace.invite.removed'); - const participantAccountIDs = originalMessage?.targetAccountIDs ?? []; + if (!parentReportAction || !parentReportAction.originalMessage) { + return ''; + } + const originalMessage = parentReportAction.originalMessage as ChangeLog; + const actionType = parentReportAction.actionName; + const isInviteAction = actionType === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || actionType === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; + + const verbKey = isInviteAction ? 'workspace.invite.invited' : 'workspace.invite.removed'; + const prepositionKey = isInviteAction ? 'workspace.invite.to' : 'workspace.invite.from'; + + const verb = Localize.translateLocal(verbKey); + const preposition = ` ${Localize.translateLocal(prepositionKey)}`; + + const participantAccountIDs = originalMessage.targetAccountIDs ?? []; const participants = participantAccountIDs.map((id) => getDisplayNameForParticipant(id)); const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; - actionMessage = `${verb} ${users}`; - - const roomName = originalMessage?.roomName ?? ''; - if (roomName) { - const preposition = - parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || - parentReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM - ? ` ${Localize.translateLocal('workspace.invite.to')}` - : ` ${Localize.translateLocal('workspace.invite.from')}`; - actionMessage += `${preposition} ${roomName}`; - } - return actionMessage; + const roomName = originalMessage.roomName ?? ''; + + return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } /** From e42ad5526c7e5efaa3d17edcd8d9705bd6cff121 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 11 Jan 2024 18:51:59 +0200 Subject: [PATCH 094/146] remove not needed space --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 98f65d47592b..ebf7bd1e3bde 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2217,7 +2217,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco const prepositionKey = isInviteAction ? 'workspace.invite.to' : 'workspace.invite.from'; const verb = Localize.translateLocal(verbKey); - const preposition = ` ${Localize.translateLocal(prepositionKey)}`; + const preposition = Localize.translateLocal(prepositionKey); const participantAccountIDs = originalMessage.targetAccountIDs ?? []; const participants = participantAccountIDs.map((id) => getDisplayNameForParticipant(id)); From cbd3db5639b7b631ff0faae257f6223ad0a2ef02 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:03:42 -0700 Subject: [PATCH 095/146] Convert to ts --- .../PurposeForUsingExpensifyModal.tsx | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 src/components/PurposeForUsingExpensifyModal.tsx diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx new file mode 100644 index 000000000000..510f610b1581 --- /dev/null +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -0,0 +1,211 @@ +import lodashGet from 'lodash/get'; +import PropTypes from 'prop-types'; +import React, {useCallback, useEffect, useState} from 'react'; +import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +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 compose from '@libs/compose'; +import * as Report from '@userActions/Report'; +import * as Welcome from '@userActions/Welcome'; +import CONST from '@src/CONST'; +import NAVIGATORS from '@src/NAVIGATORS'; +import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; +import HeaderWithBackButton from './HeaderWithBackButton'; +import * as Expensicons from './Icon/Expensicons'; +import Lottie from './Lottie'; +import LottieAnimations from './LottieAnimations'; +import MenuItemList from './MenuItemList'; +import Modal from './Modal'; +import Text from './Text'; +import withNavigation from './withNavigation'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import type {WindowDimensionsProps} from "@components/withWindowDimensions/types"; + +type PurposeForUsingExpensifyModalProps = WindowDimensionsProps; + +// This is not translated because it is a message coming from concierge, which only supports english +const messageCopy = { + 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!", + 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!", + business: + "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!', + chatSplit: + '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!", +}; + +function PurposeForUsingExpensifyModal(props) { + const {translate} = useLocalize(); + const StyleUtils = useStyleUtils(); + const styles = useThemeStyles(); + const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const [isModalOpen, setIsModalOpen] = useState(false); + const theme = useTheme(); + + useEffect(() => { + const navigationState = props.navigation.getState(); + const routes = lodashGet(navigationState, 'routes', []); + const currentRoute = routes[navigationState.index]; + if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { + return; + } + if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { + return; + } + Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const closeModal = useCallback(() => { + Report.dismissEngagementModal(); + setIsModalOpen(false); + }, []); + + const completeModalAndClose = (message, choice) => { + Report.completeEngagementModal(message, choice); + setIsModalOpen(false); + Report.navigateToConciergeChat(); + }; + + const menuItems = [ + { + key: 'purposeForExpensify.track', + title: translate('purposeForExpensify.track'), + icon: Expensicons.ReceiptSearch, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.submit', + title: translate('purposeForExpensify.submit'), + icon: Expensicons.Scan, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.manageTeam', + title: translate('purposeForExpensify.manageTeam'), + icon: Expensicons.MoneyBag, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.splitChat', + title: translate('purposeForExpensify.splitChat'), + icon: Expensicons.Briefcase, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + ]; + + return ( + + + + + + + + + + {translate('purposeForExpensify.welcomeMessage')} + + {translate('purposeForExpensify.welcomeSubtitle')} + + + + + + ); +} + +PurposeForUsingExpensifyModal.propTypes = propTypes; +PurposeForUsingExpensifyModal.defaultProps = defaultProps; +PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal'; + +export default compose( + withWindowDimensions, + withNavigation, + withOnyx({ + session: { + key: ONYXKEYS.SESSION, + }, + }), +)(PurposeForUsingExpensifyModal); From 69ccc512927c5cbeb1c7973de35715dba2e44667 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:04:40 -0700 Subject: [PATCH 096/146] Remove old file and fix menuitem type --- src/components/MenuItemList.tsx | 2 +- .../PurposeForUsingExpensifyModal.js | 222 ------------------ 2 files changed, 1 insertion(+), 223 deletions(-) delete mode 100644 src/components/PurposeForUsingExpensifyModal.js diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index f83f173a644f..7d7ecd9d41ac 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -15,7 +15,7 @@ type MenuItemWithLink = MenuItemProps & { type MenuItemListProps = { /** An array of props that are pass to individual MenuItem components */ - menuItems: MenuItemWithLink[]; + menuItems: MenuItemWithLink[] | MenuItemProps[]; /** Whether or not to use the single execution hook */ shouldUseSingleExecution?: boolean; diff --git a/src/components/PurposeForUsingExpensifyModal.js b/src/components/PurposeForUsingExpensifyModal.js deleted file mode 100644 index e80c28e768fa..000000000000 --- a/src/components/PurposeForUsingExpensifyModal.js +++ /dev/null @@ -1,222 +0,0 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useState} from 'react'; -import {ScrollView, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -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 compose from '@libs/compose'; -import * as Report from '@userActions/Report'; -import * as Welcome from '@userActions/Welcome'; -import CONST from '@src/CONST'; -import NAVIGATORS from '@src/NAVIGATORS'; -import ONYXKEYS from '@src/ONYXKEYS'; -import SCREENS from '@src/SCREENS'; -import HeaderWithBackButton from './HeaderWithBackButton'; -import * as Expensicons from './Icon/Expensicons'; -import Lottie from './Lottie'; -import LottieAnimations from './LottieAnimations'; -import MenuItemList from './MenuItemList'; -import Modal from './Modal'; -import Text from './Text'; -import withNavigation from './withNavigation'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; - -const propTypes = { - /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user accountID */ - accountID: PropTypes.number, - }), - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - session: {}, -}; - -// This is not translated because it is a message coming from concierge, which only supports english -const messageCopy = { - 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!", - 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!", - business: - "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!', - chatSplit: - '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!", -}; - -function PurposeForUsingExpensifyModal(props) { - const {translate} = useLocalize(); - const StyleUtils = useStyleUtils(); - const styles = useThemeStyles(); - const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); - const [isModalOpen, setIsModalOpen] = useState(false); - const theme = useTheme(); - - useEffect(() => { - const navigationState = props.navigation.getState(); - const routes = lodashGet(navigationState, 'routes', []); - const currentRoute = routes[navigationState.index]; - if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { - return; - } - if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { - return; - } - Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const closeModal = useCallback(() => { - Report.dismissEngagementModal(); - setIsModalOpen(false); - }, []); - - const completeModalAndClose = (message, choice) => { - Report.completeEngagementModal(message, choice); - setIsModalOpen(false); - Report.navigateToConciergeChat(); - }; - - const menuItems = [ - { - key: 'purposeForExpensify.track', - title: translate('purposeForExpensify.track'), - icon: Expensicons.ReceiptSearch, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.submit', - title: translate('purposeForExpensify.submit'), - icon: Expensicons.Scan, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.manageTeam', - title: translate('purposeForExpensify.manageTeam'), - icon: Expensicons.MoneyBag, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.splitChat', - title: translate('purposeForExpensify.splitChat'), - icon: Expensicons.Briefcase, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - ]; - - return ( - - - - - - - - - - {translate('purposeForExpensify.welcomeMessage')} - - {translate('purposeForExpensify.welcomeSubtitle')} - - - - - - ); -} - -PurposeForUsingExpensifyModal.propTypes = propTypes; -PurposeForUsingExpensifyModal.defaultProps = defaultProps; -PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal'; - -export default compose( - withWindowDimensions, - withNavigation, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(PurposeForUsingExpensifyModal); From a02c8aeb0ddb4ce1029b9672e9be579171cb2113 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:29:32 -0700 Subject: [PATCH 097/146] More typescript chages --- .../PurposeForUsingExpensifyModal.tsx | 30 +++++++------------ 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 510f610b1581..f4f037cd24e1 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -8,6 +8,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import {useNavigation} from "@react-navigation/native"; import compose from '@libs/compose'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; @@ -23,10 +24,10 @@ import MenuItemList from './MenuItemList'; import Modal from './Modal'; import Text from './Text'; import withNavigation from './withNavigation'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; -import type {WindowDimensionsProps} from "@components/withWindowDimensions/types"; - -type PurposeForUsingExpensifyModalProps = WindowDimensionsProps; +import withWindowDimensions from './withWindowDimensions'; +import type {WindowDimensionsProps} from "./withWindowDimensions/types"; +import type {StackNavigationProp} from "@react-navigation/stack"; +import type {RootStackParamList} from "@navigation/types"; // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { @@ -79,24 +80,23 @@ const messageCopy = { "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!", }; -function PurposeForUsingExpensifyModal(props) { +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 = props.navigation.getState(); + const navigationState = navigation.getState(); const routes = lodashGet(navigationState, 'routes', []); const currentRoute = routes[navigationState.index]; if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { return; } - if (lodashGet(props.demoInfo, 'money2020.isBeginningDemo', false)) { - return; - } + Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -196,16 +196,6 @@ function PurposeForUsingExpensifyModal(props) { ); } -PurposeForUsingExpensifyModal.propTypes = propTypes; -PurposeForUsingExpensifyModal.defaultProps = defaultProps; PurposeForUsingExpensifyModal.displayName = 'PurposeForUsingExpensifyModal'; -export default compose( - withWindowDimensions, - withNavigation, - withOnyx({ - session: { - key: ONYXKEYS.SESSION, - }, - }), -)(PurposeForUsingExpensifyModal); +export default PurposeForUsingExpensifyModal; From a248b46b11a841cc860bdb6cab0c0030cff1bdcb Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:31:39 -0700 Subject: [PATCH 098/146] remove unused imports --- src/components/PurposeForUsingExpensifyModal.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index f4f037cd24e1..51dbc2856cf1 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,20 +1,16 @@ import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; 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 {useNavigation} from "@react-navigation/native"; -import compose from '@libs/compose'; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; -import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; import HeaderWithBackButton from './HeaderWithBackButton'; import * as Expensicons from './Icon/Expensicons'; @@ -23,11 +19,6 @@ import LottieAnimations from './LottieAnimations'; import MenuItemList from './MenuItemList'; import Modal from './Modal'; import Text from './Text'; -import withNavigation from './withNavigation'; -import withWindowDimensions from './withWindowDimensions'; -import type {WindowDimensionsProps} from "./withWindowDimensions/types"; -import type {StackNavigationProp} from "@react-navigation/stack"; -import type {RootStackParamList} from "@navigation/types"; // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { From 1569ef614cd26bc46120b060e78e2be34fbc0875 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:51:39 -0700 Subject: [PATCH 099/146] Fix background on mobile web --- src/components/PurposeForUsingExpensifyModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 51dbc2856cf1..d566ef9cafd1 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -148,7 +148,7 @@ function PurposeForUsingExpensifyModal() { isVisible={isModalOpen} onClose={closeModal} innerContainerStyle={styles.pt0} - shouldUseCustomBackdrop={!isSmallScreenWidth} + shouldUseCustomBackdrop > From d5719145b5cdfbc4dcb010884f63bb8c5b2a74ce Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 16:51:46 -0700 Subject: [PATCH 100/146] Fix typing --- src/components/PurposeForUsingExpensifyModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index d566ef9cafd1..0a080371372b 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -97,7 +97,7 @@ function PurposeForUsingExpensifyModal() { setIsModalOpen(false); }, []); - const completeModalAndClose = (message, choice) => { + const completeModalAndClose = (message: string, choice: string) => { Report.completeEngagementModal(message, choice); setIsModalOpen(false); Report.navigateToConciergeChat(); @@ -175,7 +175,7 @@ function PurposeForUsingExpensifyModal() { > {translate('purposeForExpensify.welcomeMessage')}
- {translate('purposeForExpensify.welcomeSubtitle')} + {translate('purposeForExpensify.welcomeSubtitle')}
Date: Thu, 11 Jan 2024 16:57:41 -0700 Subject: [PATCH 101/146] Add undefined link to fix types --- src/components/MenuItemList.tsx | 2 +- src/components/PurposeForUsingExpensifyModal.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index 7d7ecd9d41ac..f83f173a644f 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -15,7 +15,7 @@ type MenuItemWithLink = MenuItemProps & { type MenuItemListProps = { /** An array of props that are pass to individual MenuItem components */ - menuItems: MenuItemWithLink[] | MenuItemProps[]; + menuItems: MenuItemWithLink[]; /** Whether or not to use the single execution hook */ shouldUseSingleExecution?: boolean; diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 0a080371372b..4ed963c1665e 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -112,6 +112,7 @@ function PurposeForUsingExpensifyModal() { onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), shouldShowRightIcon: true, numberOfLinesTitle: 2, + link: undefined, }, { key: 'purposeForExpensify.submit', @@ -121,6 +122,7 @@ function PurposeForUsingExpensifyModal() { onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), shouldShowRightIcon: true, numberOfLinesTitle: 2, + link: undefined, }, { key: 'purposeForExpensify.manageTeam', @@ -130,6 +132,7 @@ function PurposeForUsingExpensifyModal() { onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), shouldShowRightIcon: true, numberOfLinesTitle: 2, + link: undefined, }, { key: 'purposeForExpensify.splitChat', @@ -139,6 +142,7 @@ function PurposeForUsingExpensifyModal() { onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), shouldShowRightIcon: true, numberOfLinesTitle: 2, + link: undefined, }, ]; From f216232d57ee28d28c4d6639d50bfc69ae993573 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 17:06:39 -0700 Subject: [PATCH 102/146] More types --- src/components/MenuItemList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index f83f173a644f..4660d1172c94 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -6,7 +6,7 @@ import CONST from '@src/CONST'; import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; -type MenuItemLink = string | (() => Promise); +type MenuItemLink = string | (() => Promise) | undefined; type MenuItemWithLink = MenuItemProps & { /** The link to open when the menu item is clicked */ From 6f8ccf01de2b80ab0a5612b759520dd6655d7320 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 17:07:13 -0700 Subject: [PATCH 103/146] Style --- src/components/PurposeForUsingExpensifyModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 4ed963c1665e..14ff86c3c865 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,3 +1,4 @@ +import {useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, View} from 'react-native'; @@ -6,7 +7,6 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import {useNavigation} from "@react-navigation/native"; import * as Report from '@userActions/Report'; import * as Welcome from '@userActions/Welcome'; import CONST from '@src/CONST'; From bca21afcd348a6f74ee05e98d1016980c7344fc9 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 11 Jan 2024 17:22:40 -0700 Subject: [PATCH 104/146] Style --- src/components/PurposeForUsingExpensifyModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 14ff86c3c865..fbc9c71185d8 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -82,7 +82,7 @@ function PurposeForUsingExpensifyModal() { useEffect(() => { const navigationState = navigation.getState(); - const routes = lodashGet(navigationState, 'routes', []); + const routes = navigationState.routes; const currentRoute = routes[navigationState.index]; if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { return; From 7c6b96a5b1d45494be317ca1eb205621d7c06295 Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Jan 2024 12:56:02 +0700 Subject: [PATCH 105/146] add comment --- .../subscribeToReportCommentPushNotifications.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index 1b9cb00e8afe..4b610ebba187 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -27,6 +27,7 @@ export default function subscribeToReportCommentPushNotifications() { Navigation.isNavigationReady() .then(Navigation.waitForProtectedRoutes) .then(() => { + // Close attachment modal before navigating to notification's report Modal.close(() => { try { // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back From 749795ff7e6b2cb16d9aabdc8e1315647d1a430f Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 12 Jan 2024 12:57:33 +0700 Subject: [PATCH 106/146] modify comment --- .../subscribeToReportCommentPushNotifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts index 4b610ebba187..9767210b3479 100644 --- a/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts +++ b/src/libs/Notification/PushNotification/subscribeToReportCommentPushNotifications.ts @@ -27,7 +27,7 @@ export default function subscribeToReportCommentPushNotifications() { Navigation.isNavigationReady() .then(Navigation.waitForProtectedRoutes) .then(() => { - // Close attachment modal before navigating to notification's report + // The attachment modal remains open when navigating to the report so we need to close it Modal.close(() => { try { // If a chat is visible other than the one we are trying to navigate to, then we need to navigate back From 8c59d64b956857dd51b403bdca6d79037e04ed39 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 Jan 2024 17:11:34 -0700 Subject: [PATCH 107/146] Fix types --- src/components/PurposeForUsingExpensifyModal.tsx | 5 +++-- src/libs/actions/Welcome.ts | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index fbc9c71185d8..fe984823803d 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,5 +1,4 @@ import {useNavigation} from '@react-navigation/native'; -import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -83,8 +82,10 @@ function PurposeForUsingExpensifyModal() { useEffect(() => { const navigationState = navigation.getState(); const routes = navigationState.routes; + debugger; const currentRoute = routes[navigationState.index]; - if (currentRoute && ![NAVIGATORS.CENTRAL_PANE_NAVIGATOR, SCREENS.HOME].includes(currentRoute.name)) { + const currentRouteName: string = currentRoute.name; + if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRouteName && currentRouteName !== SCREENS.HOME) { return; } diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 3f17ced0ea0e..019f6ccda9d5 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -10,6 +10,7 @@ import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; +import type {ParamListBase, RouteProp} from '@react-navigation/native'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; let isReadyPromise = new Promise((resolve) => { @@ -22,9 +23,8 @@ let hasSelectedChoice: boolean | undefined; let isLoadingReportData = true; let currentUserAccountID: number | undefined; -type Route = { - name: string; - params?: {path: string; exitTo?: string; openOnAdminRoom?: boolean}; +type Route = ParamListBase & { + params: {path?: string; exitTo?: string; openOnAdminRoom?: boolean}; }; type ShowParams = { @@ -147,7 +147,7 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. const topRoute = routes.length > 0 ? routes[routes.length - 1] : undefined; - const isWorkspaceRoute = topRoute !== undefined && topRoute.name === SCREENS.RIGHT_MODAL.SETTINGS && topRoute.params?.path.includes('workspace'); + const isWorkspaceRoute = topRoute !== undefined && topRoute.name === SCREENS.RIGHT_MODAL.SETTINGS && topRoute.params?.path?.includes('workspace'); const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; const openOnAdminRoom = topRoute?.params?.openOnAdminRoom ?? false; From 6ca99b068b5b9b1415a0976fe39f6f1baa400097 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 12 Jan 2024 17:49:40 -0700 Subject: [PATCH 108/146] Remove bad routing logic --- .../PurposeForUsingExpensifyModal.tsx | 14 +--- src/libs/actions/Welcome.ts | 70 ++----------------- 2 files changed, 5 insertions(+), 79 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index fe984823803d..c00fa392f4f1 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,4 +1,3 @@ -import {useNavigation} from '@react-navigation/native'; import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -9,7 +8,6 @@ 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'; @@ -75,21 +73,11 @@ function PurposeForUsingExpensifyModal() { 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; - debugger; - const currentRoute = routes[navigationState.index]; - const currentRouteName: string = currentRoute.name; - if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRouteName && currentRouteName !== SCREENS.HOME) { - return; - } - - Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); + Welcome.show({showEngagementModal: () => setIsModalOpen(true)}); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 019f6ccda9d5..875eb61d625f 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -1,16 +1,10 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as Policy from './Policy'; -import type {ParamListBase, RouteProp} from '@react-navigation/native'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; let isReadyPromise = new Promise((resolve) => { @@ -21,16 +15,9 @@ let isFirstTimeNewExpensifyUser: boolean | undefined; let hasDismissedModal: boolean | undefined; let hasSelectedChoice: boolean | undefined; let isLoadingReportData = true; -let currentUserAccountID: number | undefined; - -type Route = ParamListBase & { - params: {path?: string; exitTo?: string; openOnAdminRoom?: boolean}; -}; type ShowParams = { - routes: Route[]; - showEngagementModal?: () => void; - showPopoverMenu?: () => boolean; + showEngagementModal: () => void; }; /** @@ -124,68 +111,19 @@ Onyx.connect({ }, }); -Onyx.connect({ - key: ONYXKEYS.SESSION, - callback: (val, key) => { - if (!val || !key) { - return; - } - - currentUserAccountID = val.accountID; - }, -}); - /** * Shows a welcome action on first login */ -function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => false}: ShowParams) { +function show({showEngagementModal}: ShowParams) { isReadyPromise.then(() => { if (!isFirstTimeNewExpensifyUser) { return; } - // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global - // create menu right now. We should also stay on the workspace page if that is our destination. - const topRoute = routes.length > 0 ? routes[routes.length - 1] : undefined; - const isWorkspaceRoute = topRoute !== undefined && topRoute.name === SCREENS.RIGHT_MODAL.SETTINGS && topRoute.params?.path?.includes('workspace'); - const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); - const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; - const openOnAdminRoom = topRoute?.params?.openOnAdminRoom ?? false; - const isDisplayingWorkspaceRoute = isWorkspaceRoute ?? exitingToWorkspaceRoute; - - // If we already opened the workspace settings or want the admin room to stay open, do not - // navigate away to the workspace chat report - const shouldNavigateToWorkspaceChat = !isDisplayingWorkspaceRoute && !openOnAdminRoom; - - const workspaceChatReport = Object.values(allReports ?? {}).find((report) => { - if (report) { - return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED; - } - return false; - }); - - if (workspaceChatReport ?? openOnAdminRoom) { - // This key is only updated when we call ReconnectApp, setting it to false now allows the user to navigate normally instead of always redirecting to the workspace chat - Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); - } - - if (shouldNavigateToWorkspaceChat && workspaceChatReport) { - if (workspaceChatReport.reportID !== null) { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReport.reportID)); - } - - // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser - // so the Welcome logic doesn't run again - if (showPopoverMenu?.()) { - isFirstTimeNewExpensifyUser = false; - } - - return; - } - // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the engagement modal. - if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal && Object.keys(allPolicies ?? {}).length === 1) { + if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !hasSelectedChoice && !hasDismissedModal && Object.keys(allPolicies ?? {}).length === 1) { + Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); showEngagementModal(); } From 8c3c4e24e55f52409a74c1b6bc9a51e60f0c67e9 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sun, 14 Jan 2024 19:30:57 +0500 Subject: [PATCH 109/146] fix: encapsulate unread report logic into getUnreadReportsForUnreadIndicator function --- src/libs/UnreadIndicatorUpdater/index.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 832a00fb8f37..7ada51c7f381 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -9,16 +9,20 @@ import updateUnread from './updateUnread'; let allReports: OnyxCollection = {}; -const triggerUnreadUpdate = () => { - const currentReportID = navigationRef.isReady() ? Navigation.getTopmostReportId() : ''; - - // We want to keep notification count consistent with what can be accessed from the LHN list - const unreadReports = Object.values(allReports ?? {}).filter( +export default function getUnreadReportsForUnreadIndicator(currentReportID: string) { + return Object.values(allReports ?? {}).filter( (report) => ReportUtils.isUnread(report) && ReportUtils.shouldReportBeInOptionList(report, currentReportID ?? '', false, [], {}) && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); +} + +const triggerUnreadUpdate = () => { + const currentReportID = navigationRef.isReady() ? Navigation.getTopmostReportId() ?? '' : ''; + + // We want to keep notification count consistent with what can be accessed from the LHN list + const unreadReports = getUnreadReportsForUnreadIndicator(currentReportID); updateUnread(unreadReports.length); }; From 286adbd09e52f30de84275a37a22c07a9f5685d7 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:36:48 +0100 Subject: [PATCH 110/146] fix: resolve comment --- src/components/LHNOptionsList/OptionRowLHNData.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 741b66d59607..e324f37cc688 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -22,7 +22,7 @@ function OptionRowLHNData({ personalDetails = {}, preferredLocale = CONST.LOCALES.DEFAULT, comment, - policy = null, + policy, receiptTransactions, parentReportAction = null, transaction = null, @@ -40,7 +40,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale ?? CONST.LOCALES.DEFAULT, policy ?? null, parentReportAction); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } From 950c62f4c77f12150893524f2924e254541d314d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 15 Jan 2024 13:39:11 +0100 Subject: [PATCH 111/146] fix: removed memo --- src/components/LHNOptionsList/LHNOptionsList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index 12ad3a59592d..ae98fd776f34 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -1,6 +1,6 @@ import {FlashList} from '@shopify/flash-list'; import type {ReactElement} from 'react'; -import React, {memo, useCallback} from 'react'; +import React, {useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; @@ -113,7 +113,7 @@ export default withCurrentReportID( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - })(memo(LHNOptionsList)), + })(LHNOptionsList), ); export type {LHNOptionsListProps}; From 58a4e52194db7c6b6e98d4374c55b751d011f52d Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 15 Jan 2024 14:25:28 -0700 Subject: [PATCH 112/146] Fix typing and consolidate logic --- .../PurposeForUsingExpensifyModal.tsx | 3 +- src/libs/actions/Welcome.ts | 39 +++++++------------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index fe984823803d..a9f2a96d8dc9 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -82,14 +82,13 @@ function PurposeForUsingExpensifyModal() { useEffect(() => { const navigationState = navigation.getState(); const routes = navigationState.routes; - debugger; const currentRoute = routes[navigationState.index]; const currentRouteName: string = currentRoute.name; if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRouteName && currentRouteName !== SCREENS.HOME) { return; } - Welcome.show({routes, showEngagementModal: () => setIsModalOpen(true)}); + Welcome.show(routes, () => setIsModalOpen(true)); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 019f6ccda9d5..9881ee04be51 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -9,8 +9,9 @@ import SCREENS from '@src/SCREENS'; import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; +import type {RootStackParamList} from '@navigation/types'; +import type {NavigationState} from '@react-navigation/native'; import * as Policy from './Policy'; -import type {ParamListBase, RouteProp} from '@react-navigation/native'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; let isReadyPromise = new Promise((resolve) => { @@ -23,16 +24,6 @@ let hasSelectedChoice: boolean | undefined; let isLoadingReportData = true; let currentUserAccountID: number | undefined; -type Route = ParamListBase & { - params: {path?: string; exitTo?: string; openOnAdminRoom?: boolean}; -}; - -type ShowParams = { - routes: Route[]; - showEngagementModal?: () => void; - showPopoverMenu?: () => boolean; -}; - /** * Check that a few requests have completed so that the welcome action can proceed: * @@ -138,7 +129,7 @@ Onyx.connect({ /** * Shows a welcome action on first login */ -function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => false}: ShowParams) { +function show(routes: NavigationState['routes'], showEngagementModal = () => {}) { isReadyPromise.then(() => { if (!isFirstTimeNewExpensifyUser) { return; @@ -146,16 +137,14 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. - const topRoute = routes.length > 0 ? routes[routes.length - 1] : undefined; - const isWorkspaceRoute = topRoute !== undefined && topRoute.name === SCREENS.RIGHT_MODAL.SETTINGS && topRoute.params?.path?.includes('workspace'); - const transitionRoute = routes.find((route) => route.name === SCREENS.TRANSITION_BETWEEN_APPS); - const exitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; - const openOnAdminRoom = topRoute?.params?.openOnAdminRoom ?? false; - const isDisplayingWorkspaceRoute = isWorkspaceRoute ?? exitingToWorkspaceRoute; + const transitionRoute = routes.find( + (route): route is NavigationState>['routes'][number] => route.name === SCREENS.TRANSITION_BETWEEN_APPS, + ); + const isExitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; // If we already opened the workspace settings or want the admin room to stay open, do not // navigate away to the workspace chat report - const shouldNavigateToWorkspaceChat = !isDisplayingWorkspaceRoute && !openOnAdminRoom; + const shouldNavigateToWorkspaceChat = !isExitingToWorkspaceRoute; const workspaceChatReport = Object.values(allReports ?? {}).find((report) => { if (report) { @@ -164,7 +153,7 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f return false; }); - if (workspaceChatReport ?? openOnAdminRoom) { + if (workspaceChatReport) { // This key is only updated when we call ReconnectApp, setting it to false now allows the user to navigate normally instead of always redirecting to the workspace chat Onyx.set(ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER, false); } @@ -174,18 +163,16 @@ function show({routes, showEngagementModal = () => {}, showPopoverMenu = () => f Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(workspaceChatReport.reportID)); } - // If showPopoverMenu exists and returns true then it opened the Popover Menu successfully, and we can update isFirstTimeNewExpensifyUser - // so the Welcome logic doesn't run again - if (showPopoverMenu?.()) { - isFirstTimeNewExpensifyUser = false; - } + // New user has been redirected to their workspace chat, and we won't show them the engagement modal. + // So we update isFirstTimeNewExpensifyUser to prevent the Welcome logic from running again + isFirstTimeNewExpensifyUser = false; return; } // If user is not already an admin of a free policy and we are not navigating them to their workspace or creating a new workspace via workspace/new then // we will show the engagement modal. - if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isDisplayingWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal && Object.keys(allPolicies ?? {}).length === 1) { + if (!Policy.isAdminOfFreePolicy(allPolicies ?? undefined) && !isExitingToWorkspaceRoute && !hasSelectedChoice && !hasDismissedModal && Object.keys(allPolicies ?? {}).length === 1) { showEngagementModal(); } From fe2625e48d86c95ede67f95a97aa88fa5226263e Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 15 Jan 2024 14:32:51 -0700 Subject: [PATCH 113/146] Fix merge --- src/components/PurposeForUsingExpensifyModal.tsx | 3 +++ src/libs/actions/Welcome.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 9cb02c643c39..a9f2a96d8dc9 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,3 +1,4 @@ +import {useNavigation} from '@react-navigation/native'; import React, {useCallback, useEffect, useState} from 'react'; import {ScrollView, View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; @@ -8,6 +9,7 @@ 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'; @@ -73,6 +75,7 @@ function PurposeForUsingExpensifyModal() { const StyleUtils = useStyleUtils(); const styles = useThemeStyles(); const {isSmallScreenWidth, windowHeight} = useWindowDimensions(); + const navigation = useNavigation(); const [isModalOpen, setIsModalOpen] = useState(false); const theme = useTheme(); diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 055ce4b06932..3e55c10c920d 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -1,6 +1,11 @@ import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; +import Navigation from '@libs/Navigation/Navigation'; +import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; @@ -110,6 +115,17 @@ Onyx.connect({ }, }); +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (val, key) => { + if (!val || !key) { + return; + } + + currentUserAccountID = val.accountID; + }, +}); + /** * Shows a welcome action on first login */ From 189bae91d0a1e28c56637499d6aee8980f549568 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 15 Jan 2024 14:37:43 -0700 Subject: [PATCH 114/146] Comments --- src/ONYXKEYS.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 4d9fd999fd49..3213b1051805 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -110,8 +110,10 @@ 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 dismissed the engagement modal */ 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? */ From 9e86e8e9a63d90a534b1837dcaccbe8c79c70d12 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 15 Jan 2024 14:50:43 -0700 Subject: [PATCH 115/146] Fix copy pasta --- src/libs/actions/Report.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 7b94c0f151ca..46fe15ed16f3 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2491,11 +2491,10 @@ function getReportPrivateNote(reportID: string) { } /** - * Add up to two report actions to a report. This method can be called for the following situations: + * Completes the engagement modal that new NewDot users see when they first sign up/log in by doing the following: * - * - Adding one comment - * - Adding one attachment - * - Add both a comment and attachment simultaneously + * - Sets the introSelected NVP to the choice the user made + * - Creates an optimistic report comment from concierge */ function completeEngagementModal(text: string, choice: string) { const commandName = 'CompleteEngagementModal'; From 7edb569e935874469e1dd14a2e1d6670661e8263 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Mon, 15 Jan 2024 14:51:38 -0700 Subject: [PATCH 116/146] Style --- src/libs/actions/Welcome.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 3e55c10c920d..720298403e62 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -1,7 +1,9 @@ +import type {NavigationState} from '@react-navigation/native'; import type {OnyxCollection} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import type {RootStackParamList} from '@navigation/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -9,8 +11,6 @@ import SCREENS from '@src/SCREENS'; import type OnyxPolicy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; -import type {RootStackParamList} from '@navigation/types'; -import type {NavigationState} from '@react-navigation/native'; import * as Policy from './Policy'; let resolveIsReadyPromise: (value?: Promise) => void | undefined; From ef2e146115577c1188bd57e8def35920b4b33b92 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 16 Jan 2024 11:01:05 +0700 Subject: [PATCH 117/146] fix: In offline, tapping 3 dots on top and selecting "Join", user unable to join the thread --- src/libs/actions/Report.ts | 10 +++++----- src/pages/home/HeaderView.js | 6 +++--- .../settings/Report/NotificationPreferencePage.js | 4 +--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 9067e5592937..289ddee28732 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -1670,7 +1670,7 @@ function addPolicyReport(policyReport: ReportUtils.OptimisticChatReport) { /** Deletes a report, along with its reportActions, any linked reports, and any linked IOU report. */ function deleteReport(reportID: string) { - const report = allReports?.[reportID]; + const report = currentReportData?.[reportID]; const onyxData: Record = { [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: null, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: null, @@ -1821,7 +1821,7 @@ function shouldShowReportActionNotification(reportID: string, action: ReportActi return false; } - const report = allReports?.[reportID]; + const report = currentReportData?.[reportID]; if (!report || (report && report.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)) { Log.info(`${tag} No notification because the report does not exist or is pending deleted`, false); return false; @@ -2072,7 +2072,7 @@ function getCurrentUserAccountID(): number { /** Leave a report by setting the state to submitted and closed */ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = false) { - const report = allReports?.[reportID]; + const report = currentReportData?.[reportID]; if (!report) { return; @@ -2164,7 +2164,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal /** Invites people to a room */ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record) { - const report = allReports?.[reportID]; + const report = currentReportData?.[reportID]; if (!report) { return; @@ -2229,7 +2229,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record !targetAccountIDs.includes(id)); const visibleChatMemberAccountIDsAfterRemoval = report?.visibleChatMemberAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id)); diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 9b2765718250..63910fa57931 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -135,14 +135,14 @@ function HeaderView(props) { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: translate('common.delete'), - onSelected: Session.checkIfActionIsAllowed(() => Task.deleteTask(props.report.reportID, props.report.reportName, props.report.stateNum, props.report.statusNum)), + onSelected: Session.checkIfActionIsAllowed(() => Task.deleteTask(props.reportID, props.report.reportName, props.report.stateNum, props.report.statusNum)), }); } } const join = Session.checkIfActionIsAllowed(() => Report.updateNotificationPreference( - props.report.reportID, + props.reportID, props.report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, @@ -165,7 +165,7 @@ function HeaderView(props) { threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, text: translate('common.leave'), - onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.report.reportID, isWorkspaceMemberLeavingWorkspaceRoom)), + onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)), }); } diff --git a/src/pages/settings/Report/NotificationPreferencePage.js b/src/pages/settings/Report/NotificationPreferencePage.js index c6044bd81efe..488afa7c5a71 100644 --- a/src/pages/settings/Report/NotificationPreferencePage.js +++ b/src/pages/settings/Report/NotificationPreferencePage.js @@ -43,9 +43,7 @@ function NotificationPreferencePage(props) { /> - Report.updateNotificationPreference(props.report.reportID, props.report.notificationPreference, option.value, true, undefined, undefined, props.report) - } + onSelectRow={(option) => Report.updateNotificationPreference(props.reportID, props.report.notificationPreference, option.value, true, undefined, undefined, props.report)} initiallyFocusedOptionKey={_.find(notificationPreferenceOptions, (locale) => locale.isSelected).keyForList} /> From b6829a47ddb7c442524367bb7268d6002a3e6885 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 16 Jan 2024 11:31:17 +0700 Subject: [PATCH 118/146] lint fix --- src/pages/home/HeaderView.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index 63910fa57931..6cbf83ab32ec 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -73,6 +73,9 @@ const propTypes = { /** The URL for the policy avatar */ avatar: PropTypes.string, }), + + /** The reportID of the request */ + reportID: PropTypes.string.isRequired, }; const defaultProps = { From d85cc5f2965a54154c4826627d48806e4e1638cb Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Wed, 17 Jan 2024 00:41:20 +0500 Subject: [PATCH 119/146] feat: add unit test for unread indicator --- src/CONST.ts | 1 + src/libs/UnreadIndicatorUpdater/index.ts | 6 ++-- tests/unit/UnreadIndicatorUpdaterTest.ts | 41 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 tests/unit/UnreadIndicatorUpdaterTest.ts diff --git a/src/CONST.ts b/src/CONST.ts index b1a6b6895de7..2c7e067bcc4e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -652,6 +652,7 @@ const CONST = { MUTE: 'mute', DAILY: 'daily', ALWAYS: 'always', + // 'hidden' is a special report level preference that appears when you look at a thread without commenting on it HIDDEN: 'hidden', }, // Options for which room members can post diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 7ada51c7f381..2efead35f503 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -9,8 +9,8 @@ import updateUnread from './updateUnread'; let allReports: OnyxCollection = {}; -export default function getUnreadReportsForUnreadIndicator(currentReportID: string) { - return Object.values(allReports ?? {}).filter( +export default function getUnreadReportsForUnreadIndicator(reports: OnyxCollection, currentReportID: string) { + return Object.values(reports ?? {}).filter( (report) => ReportUtils.isUnread(report) && ReportUtils.shouldReportBeInOptionList(report, currentReportID ?? '', false, [], {}) && @@ -22,7 +22,7 @@ const triggerUnreadUpdate = () => { const currentReportID = navigationRef.isReady() ? Navigation.getTopmostReportId() ?? '' : ''; // We want to keep notification count consistent with what can be accessed from the LHN list - const unreadReports = getUnreadReportsForUnreadIndicator(currentReportID); + const unreadReports = getUnreadReportsForUnreadIndicator(allReports, currentReportID); updateUnread(unreadReports.length); }; diff --git a/tests/unit/UnreadIndicatorUpdaterTest.ts b/tests/unit/UnreadIndicatorUpdaterTest.ts new file mode 100644 index 000000000000..48aed6ba53b5 --- /dev/null +++ b/tests/unit/UnreadIndicatorUpdaterTest.ts @@ -0,0 +1,41 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import CONST from '../../src/CONST'; +import getUnreadReportsForUnreadIndicator from '../../src/libs/UnreadIndicatorUpdater'; + +describe('UnreadIndicatorUpdaterTest', () => { + describe('should return correct number of unread reports', () => { + it('given last read time < last visible action created', () => { + const reportsToBeUsed = { + 1: {reportID: '1', reportName: 'test', type: CONST.REPORT.TYPE.EXPENSE, lastReadTime: '2023-07-08 07:15:44.030', lastVisibleActionCreated: '2023-08-08 07:15:44.030'}, + 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, + 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, + }; + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(2); + }); + + it('given some reports are incomplete', () => { + const reportsToBeUsed = { + 1: {reportID: '1', type: CONST.REPORT.TYPE.EXPENSE, lastReadTime: '2023-07-08 07:15:44.030', lastVisibleActionCreated: '2023-08-08 07:15:44.030'}, + 2: {reportID: '2', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, + 3: {reportID: '3', type: CONST.REPORT.TYPE.TASK}, + }; + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(0); + }); + + it('given notification preference of some reports is hidden', () => { + const reportsToBeUsed = { + 1: { + reportID: '1', + reportName: 'test', + type: CONST.REPORT.TYPE.EXPENSE, + notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, + lastReadTime: '2023-07-08 07:15:44.030', + lastVisibleActionCreated: '2023-08-08 07:15:44.030', + }, + 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, + 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, + }; + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(1); + }); + }); +}); From 26bafc8daf8790daed170f34f133502746212bd1 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Wed, 17 Jan 2024 10:33:13 +0200 Subject: [PATCH 120/146] fix linter --- src/libs/ReportUtils.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3666dd723c44..d54657ccf81d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,7 +16,14 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, PolicyReportField, Report, ReportAction, ReportMetadata, Session, Transaction} from '@src/types/onyx'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated, OriginalMessageReimbursementDequeued, ReimbursementDeQueuedMessage} from '@src/types/onyx/OriginalMessage'; +import type { + ChangeLog, + IOUMessage, + OriginalMessageActionName, + OriginalMessageCreated, + OriginalMessageReimbursementDequeued, + ReimbursementDeQueuedMessage, +} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference} from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; From 305178951527aa99896a61f61cb2ca5c2f801dda Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 18 Jan 2024 04:16:15 +0500 Subject: [PATCH 121/146] handle more pr comments --- src/CONST.ts | 1 - tests/unit/UnreadIndicatorUpdaterTest.ts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 60acf9360ec2..d6f3d3cdcef6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -656,7 +656,6 @@ const CONST = { MUTE: 'mute', DAILY: 'daily', ALWAYS: 'always', - // 'hidden' is a special report level preference that appears when you look at a thread without commenting on it HIDDEN: 'hidden', }, // Options for which room members can post diff --git a/tests/unit/UnreadIndicatorUpdaterTest.ts b/tests/unit/UnreadIndicatorUpdaterTest.ts index 48aed6ba53b5..a5f58b57793a 100644 --- a/tests/unit/UnreadIndicatorUpdaterTest.ts +++ b/tests/unit/UnreadIndicatorUpdaterTest.ts @@ -10,7 +10,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, }; - expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(2); + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(2); }); it('given some reports are incomplete', () => { @@ -19,7 +19,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 2: {reportID: '2', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, 3: {reportID: '3', type: CONST.REPORT.TYPE.TASK}, }; - expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(0); + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(0); }); it('given notification preference of some reports is hidden', () => { @@ -35,7 +35,7 @@ describe('UnreadIndicatorUpdaterTest', () => { 2: {reportID: '2', reportName: 'test', type: CONST.REPORT.TYPE.TASK, lastReadTime: '2023-02-05 09:12:05.000', lastVisibleActionCreated: '2023-02-06 07:15:44.030'}, 3: {reportID: '3', reportName: 'test', type: CONST.REPORT.TYPE.TASK}, }; - expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '4').length).toBe(1); + expect(getUnreadReportsForUnreadIndicator(reportsToBeUsed, '3').length).toBe(1); }); }); }); From 76a26c4c5d833c458ca17deecea6dbef4d1dc21d Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 18 Jan 2024 15:11:54 +0700 Subject: [PATCH 122/146] fix Time input for custom time is not auto focused --- src/components/TimePicker/TimePicker.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/TimePicker/TimePicker.js b/src/components/TimePicker/TimePicker.js index a9b4566a390c..4664251ca765 100644 --- a/src/components/TimePicker/TimePicker.js +++ b/src/components/TimePicker/TimePicker.js @@ -8,6 +8,7 @@ import Button from '@components/Button'; import FormHelpMessage from '@components/FormHelpMessage'; import refPropTypes from '@components/refPropTypes'; import Text from '@components/Text'; +import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -127,6 +128,8 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) { const hourInputRef = useRef(null); const minuteInputRef = useRef(null); + const {inputCallbackRef} = useAutoFocusInput(); + const focusMinuteInputOnFirstCharacter = useCallback(() => setCursorPosition(0, minuteInputRef, setSelectionMinute), []); const focusHourInputOnLastCharacter = useCallback(() => setCursorPosition(2, hourInputRef, setSelectionHour), []); @@ -492,6 +495,7 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) { minuteInputRef.current = {hourRef: hourInputRef.current, minuteInputRef: ref}; } minuteInputRef.current = ref; + inputCallbackRef(ref); }} onSelectionChange={(e) => { setSelectionMinute(e.nativeEvent.selection); From 95f7ca2e7410ef26fb33c863a94bcd32fd2f8b59 Mon Sep 17 00:00:00 2001 From: Github Date: Thu, 18 Jan 2024 09:32:01 +0100 Subject: [PATCH 123/146] Update the Readme file for performance tests --- tests/perf-test/README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/perf-test/README.md b/tests/perf-test/README.md index e7391612f071..a9b1643d191d 100644 --- a/tests/perf-test/README.md +++ b/tests/perf-test/README.md @@ -32,7 +32,6 @@ We use Reassure for monitoring performance regression. It helps us check if our - **Render count**: If the number of renders increases by one compared to the baseline, it will be considered a performance regression, leading to a failed test. This metric helps detect unexpected changes in component rendering behavior. *NOTE: sometimes regressions are intentional. For instance, if a new functionality is added to the tested component, causing an additional re-render, this regression is expected.* - **Render duration**: A performance regression will occur if the measured rendering time is 20% higher than the baseline, resulting in a failed test. This threshold allows for reasonable fluctuations and accounts for changes that may lead to longer rendering times. - ## Tips for Performance Testing with Reassure - Before you start using Reassure, take a bit of time to learn what it does [docs](https://callstack.github.io/reassure/). @@ -40,6 +39,27 @@ We use Reassure for monitoring performance regression. It helps us check if our - Mocking is a crucial part of performance testing. To achieve more accurate and meaningful results, mock and use as much data as possible. - Inside each test, there is a defined scenario function that represents the specific user interaction you want to measure (HINT: there is no need to add assertions in performance tests). - More runs generally lead to better and more reliable results by averaging out variations. Additionally, consider adjusting the number of runs per series for each specific test to achieve more granular insights. +- There's no need to mock Onyx before every test that uses `measureFunction()` because it doesn't need to be reset between each test case and we can just configure it once before running the tests. + +## Why reassure test may fail: + +- **Wrong mocking**: + + - Double-check and ensure that the mocks are accurate and aligned with the expected behavior. + - Review the test cases and adjust the mocking accordingly. +- **Timeouts**: + + - The performance test takes much longer than regular tests. This is because we run each test scenario multiple times (10 by default) and repeat this for two branches of code. + - This may lead to timeouts, especially if the Onyx mockup has extensive data. + - Be mindful of the number of test runs. While repetition is essential, find the optimal balance to avoid unnecessarily extended test durations. +- **Render count error**: + + - If the number of renders increases, the test on CI will fail with the following error: + + ```Render count difference exceeded the allowed deviation of 0. Current difference: 1``` + + - Investigate the code changes that might be causing this and address them to maintain a stable render count. More info [here](https://github.com/Expensify/App/blob/fe9e9e3e31bae27c2398678aa632e808af2690b5/tests/perf-test/README.md?plain=1#L32). + - It is important to run Reassure tests locally and see if our changes caused a regression. ## What can be tested (scenarios) From 05272b846ead1e957ff0ffb7993e42b5830f8843 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Thu, 18 Jan 2024 17:09:41 +0200 Subject: [PATCH 124/146] Fix undefined in report admin simple thread --- src/libs/ReportUtils.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 5a78bae25eb1..cdb10bd8e0bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2216,12 +2216,20 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry) { +function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Record, parentReportActionMessage: string) { if (!parentReportAction || !parentReportAction.originalMessage) { return ''; } const originalMessage = parentReportAction.originalMessage as ChangeLog; + + const participantAccountIDs = originalMessage.targetAccountIDs ?? []; + const participants = participantAccountIDs.map((id) => getDisplayNameForParticipant(id)); + const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; + if (!users) { + return parentReportActionMessage; + } const actionType = parentReportAction.actionName; const isInviteAction = actionType === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || actionType === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; @@ -2231,9 +2239,6 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco const verb = Localize.translateLocal(verbKey); const preposition = Localize.translateLocal(prepositionKey); - const participantAccountIDs = originalMessage.targetAccountIDs ?? []; - const participants = participantAccountIDs.map((id) => getDisplayNameForParticipant(id)); - const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; const roomName = originalMessage.roomName ?? ''; return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; @@ -2262,7 +2267,7 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu return Localize.translateLocal('parentReportAction.hiddenMessage'); } if (isAdminRoom(report) || isUserCreatedPolicyRoom(report)) { - return getAdminRoomInvitedParticipants(parentReportAction); + return getAdminRoomInvitedParticipants(parentReportAction, parentReportActionMessage); } return parentReportActionMessage || Localize.translateLocal('parentReportAction.deletedMessage'); } From 0b6bf56729c45485483d67e8e4cba5f49a69263c Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 16:54:09 -0700 Subject: [PATCH 125/146] Call the name directly after checking for currentRoute so that we don't accidentally call an undefined object --- src/components/PurposeForUsingExpensifyModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index a9f2a96d8dc9..69a46ae92e56 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -83,8 +83,7 @@ function PurposeForUsingExpensifyModal() { const navigationState = navigation.getState(); const routes = navigationState.routes; const currentRoute = routes[navigationState.index]; - const currentRouteName: string = currentRoute.name; - if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRouteName && currentRouteName !== SCREENS.HOME) { + if (currentRoute && NAVIGATORS.CENTRAL_PANE_NAVIGATOR !== currentRoute.name && currentRoute.name !== SCREENS.HOME) { return; } From 47c32f4f54f79627a43d99d83bcc29818b84d3e0 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 17:57:58 -0700 Subject: [PATCH 126/146] Fix types --- src/libs/actions/Report.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 0649ad817262..88d760f2168a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2509,7 +2509,7 @@ function completeEngagementModal(text: string, choice: string) { const commandName = 'CompleteEngagementModal'; const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE])[0]; const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, undefined, conciergeAccountID); - const reportCommentAction: Partial = reportComment.reportAction; + const reportCommentAction: OptimisticAddCommentReportAction = reportComment.reportAction; const lastComment = reportCommentAction?.message?.[0]; const lastCommentText = ReportUtils.formatReportLastMessageText(lastComment?.text ?? ''); const reportCommentText = reportComment.commentText; @@ -2529,12 +2529,12 @@ function completeEngagementModal(text: string, choice: string) { const report = ReportUtils.getReport(conciergeChatReportID); - if (isNotEmptyObject(report) && ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { + if (!isEmptyObject(report) && ReportUtils.getReportNotificationPreference(report) === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN) { optimisticReport.notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS; } // Optimistically add the new actions to the store before waiting to save them to the server - const optimisticReportActions: OnyxCollection> = {}; + const optimisticReportActions: OnyxCollection = {}; if (reportCommentAction?.reportActionID) { optimisticReportActions[reportCommentAction.reportActionID] = reportCommentAction; } @@ -2564,7 +2564,7 @@ function completeEngagementModal(text: string, choice: string) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${conciergeChatReportID}`, - value: optimisticReportActions, + value: optimisticReportActions as ReportActions, }, { onyxMethod: Onyx.METHOD.MERGE, From 82b55525b47473cc8905a91a5f6bcc37bdc13775 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 18:15:32 -0700 Subject: [PATCH 127/146] Remove unnecessary type change and fix copy --- src/ONYXKEYS.ts | 2 +- src/components/MenuItemList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index e165d943579b..8a2ce5a4b63d 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -110,7 +110,7 @@ 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 dismissed the engagement modal */ + /** 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 */ diff --git a/src/components/MenuItemList.tsx b/src/components/MenuItemList.tsx index e4333cbf6f19..4ba9260e23ff 100644 --- a/src/components/MenuItemList.tsx +++ b/src/components/MenuItemList.tsx @@ -6,7 +6,7 @@ import CONST from '@src/CONST'; import type {MenuItemProps} from './MenuItem'; import MenuItem from './MenuItem'; -type MenuItemLink = string | (() => Promise) | undefined; +type MenuItemLink = string | (() => Promise); type MenuItemWithLink = MenuItemProps & { /** The link to open when the menu item is clicked */ From a526027d90963c0ea701c36acfc4cf9b73f02e54 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 18:15:45 -0700 Subject: [PATCH 128/146] Memoize and callback --- .../PurposeForUsingExpensifyModal.tsx | 97 ++++++++++--------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 69a46ae92e56..3301fa22775e 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,5 +1,5 @@ import {useNavigation} from '@react-navigation/native'; -import React, {useCallback, useEffect, useState} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {ScrollView, View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -15,6 +15,7 @@ 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'; @@ -70,6 +71,12 @@ const messageCopy = { "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 menuItemBase: MenuItemProps = { + iconRight: Expensicons.ArrowRight, + shouldShowRightIcon: true, + numberOfLinesTitle: 2, +}; + function PurposeForUsingExpensifyModal() { const {translate} = useLocalize(); const StyleUtils = useStyleUtils(); @@ -96,54 +103,54 @@ function PurposeForUsingExpensifyModal() { setIsModalOpen(false); }, []); - const completeModalAndClose = (message: string, choice: string) => { + const completeModalAndClose = useCallback((message: string, choice: string) => { Report.completeEngagementModal(message, choice); setIsModalOpen(false); Report.navigateToConciergeChat(); - }; + }, []); - const menuItems = [ - { - key: 'purposeForExpensify.track', - title: translate('purposeForExpensify.track'), - icon: Expensicons.ReceiptSearch, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - link: undefined, - }, - { - key: 'purposeForExpensify.submit', - title: translate('purposeForExpensify.submit'), - icon: Expensicons.Scan, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - link: undefined, - }, - { - key: 'purposeForExpensify.manageTeam', - title: translate('purposeForExpensify.manageTeam'), - icon: Expensicons.MoneyBag, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - link: undefined, - }, - { - key: 'purposeForExpensify.splitChat', - title: translate('purposeForExpensify.splitChat'), - icon: Expensicons.Briefcase, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - link: undefined, - }, - ]; + const menuItems: MenuItemProps[] = useMemo( + () => + [ + { + key: 'purposeForExpensify.track', + title: translate('purposeForExpensify.track'), + icon: Expensicons.ReceiptSearch, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.submit', + title: translate('purposeForExpensify.submit'), + icon: Expensicons.Scan, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.manageTeam', + title: translate('purposeForExpensify.manageTeam'), + icon: Expensicons.MoneyBag, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + { + key: 'purposeForExpensify.splitChat', + title: translate('purposeForExpensify.splitChat'), + icon: Expensicons.Briefcase, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, + }, + ].map((item) => ({...menuItemBase, ...item})), + [completeModalAndClose, translate], + ); return ( Date: Thu, 18 Jan 2024 19:22:50 -0700 Subject: [PATCH 129/146] Use const and condense menuitem logic --- src/CONST.ts | 7 ++ .../PurposeForUsingExpensifyModal.tsx | 67 ++++++------------- src/languages/en.ts | 8 +-- src/languages/es.ts | 8 +-- src/types/onyx/IntroSelected.ts | 5 +- 5 files changed, 40 insertions(+), 55 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 0b10e5767328..c1f310519a71 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3131,6 +3131,13 @@ const CONST = { REPORT: 'REPORT', }, + INTRO_CHOICES: { + TRACK: 'newDotTrack', + SUBMIT: 'newDotSubmit', + MANAGE_TEAM: 'newDotManageTeam', + CHAT_SPLIT: 'newDotSplitChat', + }, + MINI_CONTEXT_MENU_MAX_ITEMS: 4, } as const; diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 3301fa22775e..d590e2063117 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -22,7 +22,7 @@ import Text from './Text'; // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { - track: + [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' + @@ -38,7 +38,7 @@ const messageCopy = { "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!", - submit: + [CONST.INTRO_CHOICES.SUBMIT]: 'Hi there, to submit expenses for reimbursement, please:\n' + '\n' + '1. Press the big green + button\n' + @@ -47,7 +47,7 @@ const messageCopy = { '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!", - business: + [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' + @@ -56,7 +56,7 @@ const messageCopy = { '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!', - chatSplit: + [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' + @@ -71,10 +71,11 @@ const messageCopy = { "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 menuItemBase: MenuItemProps = { - iconRight: Expensicons.ArrowRight, - shouldShowRightIcon: true, - numberOfLinesTitle: 2, +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() { @@ -111,44 +112,18 @@ function PurposeForUsingExpensifyModal() { const menuItems: MenuItemProps[] = useMemo( () => - [ - { - key: 'purposeForExpensify.track', - title: translate('purposeForExpensify.track'), - icon: Expensicons.ReceiptSearch, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.track, 'newDotTrack'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.submit', - title: translate('purposeForExpensify.submit'), - icon: Expensicons.Scan, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.submit, 'newDotSubmit'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.manageTeam', - title: translate('purposeForExpensify.manageTeam'), - icon: Expensicons.MoneyBag, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.business, 'newDotManageTeam'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - { - key: 'purposeForExpensify.splitChat', - title: translate('purposeForExpensify.splitChat'), - icon: Expensicons.Briefcase, - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy.chatSplit, 'newDotSplitChat'), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, - }, - ].map((item) => ({...menuItemBase, ...item})), + 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], ); diff --git a/src/languages/en.ts b/src/languages/en.ts index 7e4455f7ac9e..8ee92156bc60 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2074,10 +2074,10 @@ export default { copyReferralLink: 'Copy invite link', }, purposeForExpensify: { - track: 'Track business spend for taxes', - submit: 'Get paid back by my employer', - manageTeam: "Manage my team's expenses", - splitChat: 'Chat and split bills with friends', + [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?', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index 1844fc95a0a7..858fe29a8faf 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2562,10 +2562,10 @@ export default { copyReferralLink: 'Copiar enlace de invitación', }, purposeForExpensify: { - track: 'Seguimiento de los gastos de empresa para fines fiscales', - submit: 'Reclamar gastos a mi empleador', - manageTeam: 'Gestionar los gastos de mi equipo', - splitChat: 'Chatea y divide gastos con tus amigos', + [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?', }, diff --git a/src/types/onyx/IntroSelected.ts b/src/types/onyx/IntroSelected.ts index f7446cdd7a4c..f0047ac134ee 100644 --- a/src/types/onyx/IntroSelected.ts +++ b/src/types/onyx/IntroSelected.ts @@ -1,6 +1,9 @@ +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; + type IntroSelected = { /** The choice that the user selected in the engagement modal */ - choice: string; + choice: ValueOf; }; export default IntroSelected; From 25027781650db6426d9c68d7a7b0318475bfb7c5 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 19:23:42 -0700 Subject: [PATCH 130/146] Style --- src/components/PurposeForUsingExpensifyModal.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index d590e2063117..43fe45a5d4b1 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -116,16 +116,16 @@ function PurposeForUsingExpensifyModal() { const translationKey = `purposeForExpensify.${choice}` as const; return { key: translationKey, - title: translate(translationKey), + title: translate(translationKey), icon: menuIcons[choice], - iconRight: Expensicons.ArrowRight, - onPress: () => completeModalAndClose(messageCopy[choice], choice), - shouldShowRightIcon: true, - numberOfLinesTitle: 2, + iconRight: Expensicons.ArrowRight, + onPress: () => completeModalAndClose(messageCopy[choice], choice), + shouldShowRightIcon: true, + numberOfLinesTitle: 2, }; }), [completeModalAndClose, translate], - ); + ); return ( Date: Thu, 18 Jan 2024 19:29:39 -0700 Subject: [PATCH 131/146] Fix type --- src/libs/actions/Report.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 88d760f2168a..62c4e21d3efb 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2505,7 +2505,7 @@ function getReportPrivateNote(reportID: string) { * - Sets the introSelected NVP to the choice the user made * - Creates an optimistic report comment from concierge */ -function completeEngagementModal(text: string, choice: string) { +function completeEngagementModal(text: string, choice: ValueOf) { const commandName = 'CompleteEngagementModal'; const conciergeAccountID = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE])[0]; const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text, undefined, conciergeAccountID); From 9bd6e2ecd2ab19d2b43b65299e0941d293e9f68c Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 19:35:29 -0700 Subject: [PATCH 132/146] More types --- src/components/PurposeForUsingExpensifyModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 43fe45a5d4b1..548e664dba5d 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -19,6 +19,7 @@ import type {MenuItemProps} from './MenuItem'; import MenuItemList from './MenuItemList'; import Modal from './Modal'; import Text from './Text'; +import type {ValueOf} from "type-fest"; // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { @@ -104,7 +105,7 @@ function PurposeForUsingExpensifyModal() { setIsModalOpen(false); }, []); - const completeModalAndClose = useCallback((message: string, choice: string) => { + const completeModalAndClose = useCallback((message: string, choice: ValueOf) => { Report.completeEngagementModal(message, choice); setIsModalOpen(false); Report.navigateToConciergeChat(); From 5fa240398a3287f14ae52b835afa9a0a74a2acb5 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Thu, 18 Jan 2024 19:42:48 -0700 Subject: [PATCH 133/146] More style --- src/components/PurposeForUsingExpensifyModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/PurposeForUsingExpensifyModal.tsx b/src/components/PurposeForUsingExpensifyModal.tsx index 548e664dba5d..a8cab171ffca 100644 --- a/src/components/PurposeForUsingExpensifyModal.tsx +++ b/src/components/PurposeForUsingExpensifyModal.tsx @@ -1,6 +1,7 @@ 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'; @@ -19,7 +20,6 @@ import type {MenuItemProps} from './MenuItem'; import MenuItemList from './MenuItemList'; import Modal from './Modal'; import Text from './Text'; -import type {ValueOf} from "type-fest"; // This is not translated because it is a message coming from concierge, which only supports english const messageCopy = { From 666835e6ee64cc4c4dc5857cef1bad7ffadf4d7e Mon Sep 17 00:00:00 2001 From: tienifr Date: Fri, 19 Jan 2024 14:59:24 +0700 Subject: [PATCH 134/146] lint fix --- src/pages/home/HeaderView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index b8b624af48ec..2a98a50552be 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -73,7 +73,7 @@ const propTypes = { /** The URL for the policy avatar */ avatar: PropTypes.string, }), - + /** The reportID of the request */ reportID: PropTypes.string.isRequired, }; From ccbab1c305a53d3398ece3339c0922ac433cf936 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Fri, 19 Jan 2024 16:16:49 +0700 Subject: [PATCH 135/146] change event listener in popover provider --- src/components/PopoverProvider/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx index b1a6ebb0c5c0..a738d1f9798a 100644 --- a/src/components/PopoverProvider/index.tsx +++ b/src/components/PopoverProvider/index.tsx @@ -95,9 +95,9 @@ function PopoverContextProvider(props: PopoverContextProps) { closePopover(); }; - document.addEventListener('scroll', listener, true); + document.addEventListener('wheel', listener, true); return () => { - document.removeEventListener('scroll', listener, true); + document.removeEventListener('wheel', listener, true); }; }, [closePopover]); From ce64efa8915dab99369c76940829f2d9ddaafa93 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Fri, 19 Jan 2024 18:01:54 +0500 Subject: [PATCH 136/146] fix: lint errors --- src/libs/UnreadIndicatorUpdater/index.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 62055dc7e611..044b643f5322 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -14,13 +14,13 @@ export default function getUnreadReportsForUnreadIndicator(reports: OnyxCollecti (report) => ReportUtils.isUnread(report) && ReportUtils.shouldReportBeInOptionList({ - report, - currentReportId: currentReportID ?? '', - betas: [], - policies: {}, - doesReportHaveViolations: false, - isInGSDMode: false, - excludeEmptyChats: false, + report, + currentReportId: currentReportID ?? '', + betas: [], + policies: {}, + doesReportHaveViolations: false, + isInGSDMode: false, + excludeEmptyChats: false, }) && report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); From a313c40cc4e52d8e1f4fe5c9b655b1478a623afd Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 19 Jan 2024 10:44:27 -0700 Subject: [PATCH 137/146] Fix type --- src/libs/actions/Welcome.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 720298403e62..cfe281546b16 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -138,7 +138,7 @@ function show(routes: NavigationState['routes'], showEngagem // If we are rendering the SidebarScreen at the same time as a workspace route that means we've already created a workspace via workspace/new and should not open the global // create menu right now. We should also stay on the workspace page if that is our destination. const transitionRoute = routes.find( - (route): route is NavigationState>['routes'][number] => route.name === SCREENS.TRANSITION_BETWEEN_APPS, + (route): route is NavigationState>['routes'][number] => route.name === SCREENS.TRANSITION_BETWEEN_APPS, ); const isExitingToWorkspaceRoute = transitionRoute?.params?.exitTo === 'workspace/new'; From da884be0e7c842bc336cfcfe070c2771acf55b85 Mon Sep 17 00:00:00 2001 From: Brandon Stites Date: Fri, 19 Jan 2024 12:41:58 -0700 Subject: [PATCH 138/146] Remove bad comments and copy/pasted code --- src/libs/actions/Report.ts | 12 ------------ src/libs/actions/Welcome.ts | 4 ---- 2 files changed, 16 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 62c4e21d3efb..a3cd8f52a411 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2581,18 +2581,6 @@ function completeEngagementModal(text: string, choice: ValueOf { - // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic - // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 hasSelectedChoice = !!value; checkOnReady(); @@ -68,8 +66,6 @@ Onyx.connect({ key: ONYXKEYS.NVP_HAS_DISMISSED_IDLE_PANEL, initWithStoredValues: true, callback: (value) => { - // If isFirstTimeNewExpensifyUser was true do not update it to false. We update it to false inside the Welcome.show logic - // More context here https://github.com/Expensify/App/pull/16962#discussion_r1167351359 hasDismissedModal = value ?? false; checkOnReady(); From 2d4a8a1005f3e3f4a483ce7ec89a196bfe049822 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sat, 20 Jan 2024 01:39:25 +0500 Subject: [PATCH 139/146] re-add comment to better explain hidden notification condition --- src/libs/UnreadIndicatorUpdater/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 044b643f5322..5ba15f519ce9 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -22,6 +22,9 @@ export default function getUnreadReportsForUnreadIndicator(reports: OnyxCollecti isInGSDMode: false, excludeEmptyChats: false, }) && + // Chats with hidden preference remain invisible in the LHN and are not considered "unread." + // They are excluded from the LHN rendering, but not filtered from the "option list." + // This ensures they appear in Search, but not in the LHN or unread count. report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); } From 7673831850f1f0b40c2c00f3f0ee5e25affe8409 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sat, 20 Jan 2024 01:41:29 +0500 Subject: [PATCH 140/146] re-add comment to better explain hidden notification condition --- src/libs/UnreadIndicatorUpdater/index.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libs/UnreadIndicatorUpdater/index.ts b/src/libs/UnreadIndicatorUpdater/index.ts index 5ba15f519ce9..2a7019686308 100644 --- a/src/libs/UnreadIndicatorUpdater/index.ts +++ b/src/libs/UnreadIndicatorUpdater/index.ts @@ -22,9 +22,11 @@ export default function getUnreadReportsForUnreadIndicator(reports: OnyxCollecti isInGSDMode: false, excludeEmptyChats: false, }) && - // Chats with hidden preference remain invisible in the LHN and are not considered "unread." - // They are excluded from the LHN rendering, but not filtered from the "option list." - // This ensures they appear in Search, but not in the LHN or unread count. + /** + * Chats with hidden preference remain invisible in the LHN and are not considered "unread." + * They are excluded from the LHN rendering, but not filtered from the "option list." + * This ensures they appear in Search, but not in the LHN or unread count. + */ report?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, ); } From c82a4a738a222db6b47f98556d0e75ad3402a123 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 22 Jan 2024 11:05:06 +0100 Subject: [PATCH 141/146] fix: typecheck --- src/ONYXKEYS.ts | 2 +- .../LHNOptionsList/LHNOptionsList.tsx | 24 ++++++++++++++++- .../LHNOptionsList/OptionRowLHNData.tsx | 6 ++--- src/components/LHNOptionsList/types.ts | 11 +++++++- src/libs/ReportUtils.ts | 6 ++--- src/libs/SidebarUtils.ts | 27 ++++++++++++------- 6 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 40a43d8195de..a2cb944edc76 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -461,7 +461,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.TRANSACTION_DRAFT]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; - [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; + [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[]; [ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT]: string; [ONYXKEYS.COLLECTION.NEXT_STEP]: OnyxTypes.ReportNextStep; diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx index b012deb5a4b4..ecf320807b48 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.tsx +++ b/src/components/LHNOptionsList/LHNOptionsList.tsx @@ -4,6 +4,7 @@ import React, {useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import withCurrentReportID from '@components/withCurrentReportID'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -30,8 +31,10 @@ function LHNOptionsList({ transactions = {}, currentReportID = '', draftComments = {}, + transactionViolations = {}, }: LHNOptionsListProps) { const styles = useThemeStyles(); + const {canUseViolations} = usePermissions(); /** * Function which renders a row in the list */ @@ -64,10 +67,26 @@ function LHNOptionsList({ onSelectRow={onSelectRow} preferredLocale={preferredLocale} comment={itemComment} + transactionViolations={transactionViolations} + canUseViolations={canUseViolations} /> ); }, - [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [ + currentReportID, + draftComments, + onSelectRow, + optionMode, + personalDetails, + policy, + preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, + transactionViolations, + canUseViolations, + ], ); return ( @@ -113,6 +132,9 @@ export default withCurrentReportID( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, })(LHNOptionsList), ); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.tsx b/src/components/LHNOptionsList/OptionRowLHNData.tsx index 50cecf355395..dca74e880169 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.tsx +++ b/src/components/LHNOptionsList/OptionRowLHNData.tsx @@ -41,7 +41,7 @@ function OptionRowLHNData({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport?.reportID, receiptTransactions, reportActions]); - const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction); + const hasViolations = canUseViolations && ReportUtils.doesTransactionThreadHaveViolations(fullReport, transactionViolations, parentReportAction ?? null); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! @@ -49,10 +49,10 @@ function OptionRowLHNData({ report: fullReport, reportActions, personalDetails, - preferredLocale, + preferredLocale: preferredLocale ?? CONST.LOCALES.DEFAULT, policy, parentReportAction, - hasViolations, + hasViolations: !!hasViolations, }); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; diff --git a/src/components/LHNOptionsList/types.ts b/src/components/LHNOptionsList/types.ts index 11980c9a38b9..24cebb8e3da2 100644 --- a/src/components/LHNOptionsList/types.ts +++ b/src/components/LHNOptionsList/types.ts @@ -6,7 +6,7 @@ import type {ValueOf} from 'type-fest'; import type {CurrentReportIDContextValue} from '@components/withCurrentReportID'; import type CONST from '@src/CONST'; import type {OptionData} from '@src/libs/ReportUtils'; -import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction} from '@src/types/onyx'; +import type {Locale, PersonalDetailsList, Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx'; type OptionMode = ValueOf; @@ -31,6 +31,9 @@ type LHNOptionsListOnyxProps = { /** List of draft comments */ draftComments: OnyxCollection; + + /** The list of transaction violations */ + transactionViolations: OnyxCollection; }; type CustomLHNOptionsListProps = { @@ -88,6 +91,12 @@ type OptionRowLHNDataProps = { /** Array of report actions for this report */ reportActions: OnyxEntry; + + /** List of transaction violation */ + transactionViolations: OnyxCollection; + + /** Whether the user can use violations */ + canUseViolations: boolean | undefined; }; type OptionRowLHNProps = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index de41d6f1b03f..a384f9dad4bd 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3546,8 +3546,8 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b /** * Checks to see if a report's parentAction is a money request that contains a violation */ -function doesTransactionThreadHaveViolations(report: Report, transactionViolations: OnyxCollection, parentReportAction: ReportAction): boolean { - if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { +function doesTransactionThreadHaveViolations(report: OnyxEntry, transactionViolations: OnyxCollection, parentReportAction: OnyxEntry): boolean { + if (parentReportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return false; } const {IOUTransactionID, IOUReportID} = parentReportAction.originalMessage ?? {}; @@ -3557,7 +3557,7 @@ function doesTransactionThreadHaveViolations(report: Report, transactionViolatio if (!isCurrentUserSubmitter(IOUReportID)) { return false; } - if (report.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { + if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) { return false; } return TransactionUtils.hasViolation(IOUTransactionID, transactionViolations); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 1f46f8310345..4a2c4a2da22a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -11,6 +11,7 @@ import type Policy from '@src/types/onyx/Policy'; import type Report from '@src/types/onyx/Report'; import type {ReportActions} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; +import type DeepValueOf from '@src/types/utils/DeepValueOf'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; @@ -247,15 +248,23 @@ type ActorDetails = { /** * Gets all the data necessary for rendering an OptionRowLHN component */ -function getOptionData( - report: OnyxEntry, - reportActions: OnyxEntry, - personalDetails: OnyxEntry, - preferredLocale: ValueOf, - policy: OnyxEntry, - parentReportAction: OnyxEntry, - hasViolations: boolean, -): ReportUtils.OptionData | undefined { +function getOptionData({ + report, + reportActions, + personalDetails, + preferredLocale, + policy, + parentReportAction, + hasViolations, +}: { + report: OnyxEntry; + reportActions: OnyxEntry; + personalDetails: OnyxEntry; + preferredLocale: DeepValueOf; + policy: OnyxEntry | undefined; + parentReportAction: OnyxEntry | undefined; + hasViolations: boolean; +}): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do // a null check here and return early. From 00a21a9c282b7abcfb2d89ffdf0c2ffda10e9a49 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Mon, 22 Jan 2024 13:56:48 +0200 Subject: [PATCH 142/146] add suggested changes --- src/libs/ReportUtils.ts | 2 +- src/pages/home/ReportScreen.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cdb10bd8e0bd..481ca2557f01 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2219,7 +2219,7 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry, parentReportActionMessage: string) { - if (!parentReportAction || !parentReportAction.originalMessage) { + if (!parentReportAction?.originalMessage) { return ''; } const originalMessage = parentReportAction.originalMessage as ChangeLog; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 58fc3e072a9f..70a0f1a236bb 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -615,7 +615,7 @@ export default compose( key: ({report}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report ? report.parentReportID : 0}`, selector: (parentReportActions, props) => { const parentReportActionID = lodashGet(props, 'report.parentReportActionID'); - if (!parentReportActionID || !parentReportActions) { + if (!parentReportActionID) { return {}; } return parentReportActions[parentReportActionID]; From 4af50cefac301cc1940f327ea5ecb2a9ae0e93ed Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 23 Jan 2024 11:21:46 +0200 Subject: [PATCH 143/146] Add safe check for Ts to remove direct assign to variable --- src/libs/ReportUtils.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b9feb8aed83b..594585f75f72 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -29,7 +29,12 @@ import type { TransactionViolation, } from '@src/types/onyx'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; +import type { + ChangeLog, + IOUMessage, + OriginalMessageActionName, + OriginalMessageCreated +} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference} from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; @@ -1615,7 +1620,6 @@ function getDisplayNameForParticipant(accountID?: number, shouldUseShortForm = f // and prevent from falling back to 'Hidden', so a correct value is shown // when searching for a new user if (personalDetails.isOptimisticPersonalDetail === true && formattedLogin) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing return formattedLogin; } @@ -2316,6 +2320,17 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry getDisplayNameForParticipant(id)); const users = participants.length > 1 ? participants.join(` ${Localize.translateLocal('common.and')} `) : participants[0]; if (!users) { @@ -2342,7 +2357,7 @@ function getAdminRoomInvitedParticipants(parentReportAction: ReportAction | Reco const verb = Localize.translateLocal(verbKey); const preposition = Localize.translateLocal(prepositionKey); - const roomName = originalMessage.roomName ?? ''; + const roomName = originalMessage?.roomName ?? ''; return roomName ? `${verb} ${users} ${preposition} ${roomName}` : `${verb} ${users}`; } From 0272df75abd10a7d702faf61a72664291828c360 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 23 Jan 2024 11:25:50 +0200 Subject: [PATCH 144/146] prettier --- src/libs/ReportUtils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 594585f75f72..ee58cbf2016e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -29,12 +29,7 @@ import type { TransactionViolation, } from '@src/types/onyx'; import type {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import type { - ChangeLog, - IOUMessage, - OriginalMessageActionName, - OriginalMessageCreated -} from '@src/types/onyx/OriginalMessage'; +import type {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageCreated} from '@src/types/onyx/OriginalMessage'; import type {Status} from '@src/types/onyx/PersonalDetails'; import type {NotificationPreference} from '@src/types/onyx/Report'; import type {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; From 08be310fe90cf0812702f145a4d7f14509147e16 Mon Sep 17 00:00:00 2001 From: Tomasz Lesniakiewicz Date: Tue, 23 Jan 2024 16:35:53 +0100 Subject: [PATCH 145/146] fix: skip flaky reassure test, until issue resolved --- tests/perf-test/SearchPage.perf-test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/perf-test/SearchPage.perf-test.js b/tests/perf-test/SearchPage.perf-test.js index c1568bea5dcd..046d651469f1 100644 --- a/tests/perf-test/SearchPage.perf-test.js +++ b/tests/perf-test/SearchPage.perf-test.js @@ -146,7 +146,7 @@ test('[Search Page] should render options list', async () => { .then(() => measurePerformance(, {scenario, runs})); }); -test('[Search Page] should search in options list', async () => { +test.skip('[Search Page] should search in options list', async () => { const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock(); const scenario = async () => { @@ -177,7 +177,7 @@ test('[Search Page] should search in options list', async () => { .then(() => measurePerformance(, {scenario, runs})); }); -test('[Search Page] should click on list item', async () => { +test.skip('[Search Page] should click on list item', async () => { const {triggerTransitionEnd, addListener} = TestHelper.createAddListenerMock(); const scenario = async () => { From a17a9fa1a1857ac6ed99c807dc0c313be8f8f566 Mon Sep 17 00:00:00 2001 From: Nicolay Arefyeu Date: Tue, 23 Jan 2024 19:37:24 +0200 Subject: [PATCH 146/146] add safe check --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ee58cbf2016e..7871359c5516 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2320,7 +2320,7 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry