From ae0648c0b4a0565aecb7e8958859d4e1231f87e5 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 13 Dec 2023 18:37:06 +0000 Subject: [PATCH 001/113] refactor(typescript): migrate conciergepage --- .../{ConciergePage.js => ConciergePage.tsx} | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) rename src/pages/{ConciergePage.js => ConciergePage.tsx} (67%) diff --git a/src/pages/ConciergePage.js b/src/pages/ConciergePage.tsx similarity index 67% rename from src/pages/ConciergePage.js rename to src/pages/ConciergePage.tsx index 841ce524b2cb..ffb82f689620 100644 --- a/src/pages/ConciergePage.js +++ b/src/pages/ConciergePage.tsx @@ -1,36 +1,28 @@ import {useFocusEffect} from '@react-navigation/native'; -import PropTypes from 'prop-types'; import React from 'react'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Navigation from '@libs/Navigation/Navigation'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import {Session} from '@src/types/onyx'; -const propTypes = { +type ConciergePageOnyxProps = { /** Session info for the currently logged in user. */ - session: PropTypes.shape({ - /** Currently logged in user authToken */ - authToken: PropTypes.string, - }), + session: OnyxEntry; }; -const defaultProps = { - session: { - authToken: null, - }, -}; +type ConciergePageProps = ConciergePageOnyxProps; /* * This is a "utility page", that does this: * - If the user is authenticated, find their concierge chat and re-route to it * - Else re-route to the login page */ -function ConciergePage(props) { +function ConciergePage({session}: ConciergePageProps) { useFocusEffect(() => { - if (_.has(props.session, 'authToken')) { + if (session?.authToken) { // Pop the concierge loading page before opening the concierge report. Navigation.isNavigationReady().then(() => { Navigation.goBack(ROUTES.HOME); @@ -43,12 +35,9 @@ function ConciergePage(props) { return ; } - -ConciergePage.propTypes = propTypes; -ConciergePage.defaultProps = defaultProps; ConciergePage.displayName = 'ConciergePage'; -export default withOnyx({ +export default withOnyx({ session: { key: ONYXKEYS.SESSION, }, From d24c8f1b6c82de2cf89cc3657cc3aef9a290f7c7 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 14 Dec 2023 13:16:33 +0000 Subject: [PATCH 002/113] refactor(typescript): migrate loginwithshortlivedauthtoken --- src/libs/Navigation/types.ts | 5 +- ...s => LogInWithShortLivedAuthTokenPage.tsx} | 61 ++++++------------- 2 files changed, 23 insertions(+), 43 deletions(-) rename src/pages/{LogInWithShortLivedAuthTokenPage.js => LogInWithShortLivedAuthTokenPage.tsx} (64%) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 94a07ddc6b73..1a7f017cf4d8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -3,6 +3,7 @@ import {CommonActions, NavigationContainerRefWithCurrent, NavigationHelpers, Nav import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; +import type {Route as Routes} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; type NavigationRef = NavigationContainerRefWithCurrent; @@ -366,8 +367,10 @@ type PublicScreensParamList = { [SCREENS.TRANSITION_BETWEEN_APPS]: { shouldForceLogin: string; email: string; + error: string; shortLivedAuthToken: string; - exitTo: string; + shortLivedToken: string; + exitTo: Routes; }; [SCREENS.VALIDATE_LOGIN]: { accountID: string; diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.js b/src/pages/LogInWithShortLivedAuthTokenPage.tsx similarity index 64% rename from src/pages/LogInWithShortLivedAuthTokenPage.js rename to src/pages/LogInWithShortLivedAuthTokenPage.tsx index 16d0c3909d62..c262b4d14658 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.js +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -1,8 +1,7 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect} from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; +import {OnyxEntry, withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -11,64 +10,44 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; +import {PublicScreensParamList} from '@libs/Navigation/types'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; +import SCREENS from '@src/SCREENS'; +import {Account} from '@src/types/onyx'; -const propTypes = { - /** The parameters needed to authenticate with a short-lived token are in the URL */ - route: PropTypes.shape({ - /** Each parameter passed via the URL */ - params: PropTypes.shape({ - /** Short-lived authToken to sign in a user */ - shortLivedAuthToken: PropTypes.string, - - /** Short-lived authToken to sign in as a user, if they are coming from the old mobile app */ - shortLivedToken: PropTypes.string, - - /** The email of the transitioning user */ - email: PropTypes.string, - }), - }).isRequired, - +type LogInWithShortLivedAuthTokenPageOnyxProps = { /** The details about the account that the user is signing in with */ - account: PropTypes.shape({ - /** Whether a sign is loading */ - isLoading: PropTypes.bool, - }), + account: OnyxEntry; }; -const defaultProps = { - account: { - isLoading: false, - }, -}; +type LogInWithShortLivedAuthTokenPageProps = LogInWithShortLivedAuthTokenPageOnyxProps & StackScreenProps; -function LogInWithShortLivedAuthTokenPage(props) { +function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedAuthTokenPageProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); + const { + params: {email, shortLivedAuthToken, shortLivedToken, exitTo, error}, + } = route; useEffect(() => { - const email = lodashGet(props, 'route.params.email', ''); - // We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated. - const shortLivedAuthToken = lodashGet(props, 'route.params.shortLivedAuthToken', '') || lodashGet(props, 'route.params.shortLivedToken', ''); + const token = shortLivedAuthToken || shortLivedToken; // Try to authenticate using the shortLivedToken if we're not already trying to load the accounts - if (shortLivedAuthToken && !props.account.isLoading) { - Session.signInWithShortLivedAuthToken(email, shortLivedAuthToken); + if (token && !account?.isLoading) { + Session.signInWithShortLivedAuthToken(email, token); return; } // If an error is returned as part of the route, ensure we set it in the onyxData for the account - const error = lodashGet(props, 'route.params.error', ''); if (error) { Session.setAccountError(error); } - const exitTo = lodashGet(props, 'route.params.exitTo', ''); if (exitTo) { Navigation.isNavigationReady().then(() => { Navigation.navigate(exitTo); @@ -76,9 +55,9 @@ function LogInWithShortLivedAuthTokenPage(props) { } // The only dependencies of the effect are based on props.route // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.route]); + }, [route]); - if (props.account.isLoading) { + if (account?.isLoading) { return ; } @@ -94,7 +73,7 @@ function LogInWithShortLivedAuthTokenPage(props) { {translate('deeplinkWrapper.launching')} - + {translate('deeplinkWrapper.expired')}{' '} { @@ -119,10 +98,8 @@ function LogInWithShortLivedAuthTokenPage(props) { ); } -LogInWithShortLivedAuthTokenPage.propTypes = propTypes; -LogInWithShortLivedAuthTokenPage.defaultProps = defaultProps; LogInWithShortLivedAuthTokenPage.displayName = 'LogInWithShortLivedAuthTokenPage'; -export default withOnyx({ +export default withOnyx({ account: {key: ONYXKEYS.ACCOUNT}, })(LogInWithShortLivedAuthTokenPage); From 7052b2212499ff4c689207fd482c0c09fac044f3 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 14 Dec 2023 15:00:00 +0000 Subject: [PATCH 003/113] refactor(typescript): apply pull request feedback --- src/libs/Navigation/types.ts | 11 +++++------ src/pages/LogInWithShortLivedAuthTokenPage.tsx | 10 +++++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 1a7f017cf4d8..84e838cad4ed 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -365,12 +365,11 @@ type RightModalNavigatorParamList = { type PublicScreensParamList = { [SCREENS.HOME]: undefined; [SCREENS.TRANSITION_BETWEEN_APPS]: { - shouldForceLogin: string; - email: string; - error: string; - shortLivedAuthToken: string; - shortLivedToken: string; - exitTo: Routes; + email?: string; + error?: string; + shortLivedAuthToken?: string; + shortLivedToken?: string; + exitTo?: Routes; }; [SCREENS.VALIDATE_LOGIN]: { accountID: string; diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index c262b4d14658..bee93f9db915 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -10,13 +10,13 @@ import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@libs/Navigation/Navigation'; -import {PublicScreensParamList} from '@libs/Navigation/types'; +import type {PublicScreensParamList} from '@libs/Navigation/types'; import useTheme from '@styles/themes/useTheme'; import useThemeStyles from '@styles/useThemeStyles'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; import SCREENS from '@src/SCREENS'; -import {Account} from '@src/types/onyx'; +import type {Account} from '@src/types/onyx'; type LogInWithShortLivedAuthTokenPageOnyxProps = { /** The details about the account that the user is signing in with */ @@ -35,10 +35,10 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA useEffect(() => { // We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated. - const token = shortLivedAuthToken || shortLivedToken; + const token = shortLivedAuthToken ?? shortLivedToken; // Try to authenticate using the shortLivedToken if we're not already trying to load the accounts - if (token && !account?.isLoading) { + if (email && token && !account?.isLoading) { Session.signInWithShortLivedAuthToken(email, token); return; } @@ -73,7 +73,7 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA {translate('deeplinkWrapper.launching')} - + {translate('deeplinkWrapper.expired')}{' '} { From 356a876806cebd308ede65c69cc00ffcdf6f763e Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Thu, 14 Dec 2023 15:11:02 +0000 Subject: [PATCH 004/113] refactor(typescript): add missing screen params types --- src/pages/ConciergePage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index ffb82f689620..d440e6a83705 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -1,11 +1,14 @@ import {useFocusEffect} from '@react-navigation/native'; +import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Navigation from '@libs/Navigation/Navigation'; +import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; import {Session} from '@src/types/onyx'; type ConciergePageOnyxProps = { @@ -13,7 +16,7 @@ type ConciergePageOnyxProps = { session: OnyxEntry; }; -type ConciergePageProps = ConciergePageOnyxProps; +type ConciergePageProps = ConciergePageOnyxProps & StackScreenProps; /* * This is a "utility page", that does this: From fb473d0f2c7628a0dcc5b6fbeca8a5be59bbf856 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 18 Dec 2023 17:31:09 +0000 Subject: [PATCH 005/113] refactor: apply pull request feedback --- src/pages/ConciergePage.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index d440e6a83705..5f4b5ad375f7 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -9,7 +9,7 @@ import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; -import {Session} from '@src/types/onyx'; +import type {Session} from '@src/types/onyx'; type ConciergePageOnyxProps = { /** Session info for the currently logged in user. */ @@ -38,6 +38,7 @@ function ConciergePage({session}: ConciergePageProps) { return ; } + ConciergePage.displayName = 'ConciergePage'; export default withOnyx({ From 7a8e43e3f1bf0be2e2a1b4827b573d42e02b26ce Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Mon, 18 Dec 2023 18:18:48 +0000 Subject: [PATCH 006/113] refactor: apply pull request feedback --- src/pages/LogInWithShortLivedAuthTokenPage.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 2bf59bd76af7..5e95f31d1d6c 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -29,16 +29,14 @@ function LogInWithShortLivedAuthTokenPage({route, account}: LogInWithShortLivedA const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const { - params: {email, shortLivedAuthToken, shortLivedToken, exitTo, error}, - } = route; + const {email = '', shortLivedAuthToken = '', shortLivedToken = '', exitTo, error} = route?.params ?? {}; useEffect(() => { // We have to check for both shortLivedAuthToken and shortLivedToken, as the old mobile app uses shortLivedToken, and is not being actively updated. - const token = shortLivedAuthToken ?? shortLivedToken; + const token = shortLivedAuthToken || shortLivedToken; // Try to authenticate using the shortLivedToken if we're not already trying to load the accounts - if (email && token && !account?.isLoading) { + if (token && !account?.isLoading) { Session.signInWithShortLivedAuthToken(email, token); return; } From e30aef2cef323c939996368409d8a6db404e5c56 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 20 Dec 2023 17:30:52 +0700 Subject: [PATCH 007/113] fix: plaid iframe transparent background --- web/index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/index.html b/web/index.html index 967873fe586c..7c02614d17b2 100644 --- a/web/index.html +++ b/web/index.html @@ -96,6 +96,11 @@ caret-color: #ffffff; } + /* Customize Plaid iframe */ + [id^="plaid-link-iframe"] { + color-scheme: dark !important; + } + /* Prevent autofill from overlapping with the input label in Chrome */ div:has(input:-webkit-autofill, input[chrome-autofilled]) > label { transform: translateY(var(--active-label-translate-y)) scale(var(--active-label-scale)) !important; From 8810934caddf3cb2ba995a6bb5307dd9741585b7 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 21 Dec 2023 09:45:53 +0500 Subject: [PATCH 008/113] feat: add report field menu items --- src/components/OfflineWithFeedback.tsx | 2 +- .../ReportActionItem/MoneyReportView.tsx | 37 +++++++++++++++++-- src/libs/ReportUtils.ts | 18 ++++++++- src/pages/home/report/ReportActionItem.js | 9 ++++- src/pages/reportPropTypes.js | 3 ++ 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/components/OfflineWithFeedback.tsx b/src/components/OfflineWithFeedback.tsx index 5fcf1fe7442b..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; + pendingAction?: OnyxCommon.PendingAction; /** Determine whether to hide the component's children if deletion is pending */ shouldHideOnDelete?: boolean; diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 36daa037fd78..2ffbda4d347b 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -1,8 +1,10 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import {StyleProp, TextStyle, View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import SpacerView from '@components/SpacerView'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -13,22 +15,27 @@ import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as ReportUtils from '@libs/ReportUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; -import type {Report} from '@src/types/onyx'; +import type {PolicyReportField, Report} from '@src/types/onyx'; +import usePermissions from '@hooks/usePermissions'; type MoneyReportViewProps = { /** The report currently being looked at */ report: Report; + /** Policy report fields */ + policyReportFields: PolicyReportField[]; + /** Whether we should display the horizontal rule below the component */ shouldShowHorizontalRule: boolean; }; -function MoneyReportView({report, shouldShowHorizontalRule}: MoneyReportViewProps) { +function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: MoneyReportViewProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const {isSmallScreenWidth} = useWindowDimensions(); + const {canUseReportFields} = usePermissions(); const isSettled = ReportUtils.isSettled(report.reportID); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); @@ -45,10 +52,34 @@ function MoneyReportView({report, shouldShowHorizontalRule}: MoneyReportViewProp StyleUtils.getColorStyle(theme.textSupporting), ]; + const sortedPolicyReportFields = useMemo(() => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), [policyReportFields]); + return ( + {canUseReportFields && sortedPolicyReportFields.map((reportField) => { + const title = ReportUtils.getReportFieldTitle(report, reportField); + return ( + + {}} + shouldShowRightIcon + disabled={false} + wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} + shouldGreyOutWhenDisabled={false} + numberOfLinesTitle={0} + interactive + shouldStackHorizontally={false} + onSecondaryInteraction={() => {}} + hoverAndPressStyle={false} + titleWithTooltips={[]} + /> + + ); + })} , reportField: PolicyReportField) { + const value = report?.reportFields?.[reportField.fieldID] ?? reportField.defaultValue; + + if (reportField.type !== 'formula') { + return value; + } + + return value.replaceAll(/{report:([a-zA-Z]+)}/g, (match, property) => { + if (report && property in report) { + return report[property as keyof Report]?.toString() ?? match; + } + return match; + }); +} + export { getReportParticipantsTitle, isReportMessageAttachment, @@ -4343,6 +4358,7 @@ export { canEditWriteCapability, hasSmartscanError, shouldAutoFocusOnKeyPress, + getReportFieldTitle, }; export type {ExpenseOriginalMessage, OptionData, OptimisticChatReport}; diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c81e47016dcc..84e573145b9a 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -620,6 +620,7 @@ function ReportActionItem(props) { @@ -765,6 +766,10 @@ export default compose( }, initialValue: {}, }, + policyReportFields: { + key: ({report}) => report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined, + initialValue: [], + }, emojiReactions: { key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${action.reportActionID}`, initialValue: {}, @@ -800,6 +805,8 @@ export default compose( prevProps.shouldHideThreadDividerLine === nextProps.shouldHideThreadDividerLine && lodashGet(prevProps.report, 'total', 0) === lodashGet(nextProps.report, 'total', 0) && lodashGet(prevProps.report, 'nonReimbursableTotal', 0) === lodashGet(nextProps.report, 'nonReimbursableTotal', 0) && - prevProps.linkedReportActionID === nextProps.linkedReportActionID, + prevProps.linkedReportActionID === nextProps.linkedReportActionID && + _.isEqual(prevProps.policyReportFields, nextProps.policyReportFields) && + _.isEqual(prevProps.report.reportFields, nextProps.report.reportFields), ), ); diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index c89ea761f582..507965266ecc 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -67,4 +67,7 @@ export default PropTypes.shape({ /** Field-specific pending states for offline UI status */ pendingFields: PropTypes.objectOf(PropTypes.string), + + /** Custom fields attached to the report */ + reportFields: PropTypes.objectOf(PropTypes.string), }); From 9a9f8e2a8bf2dcff03cb169f8ef791e8d1b81f58 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 28 Dec 2023 16:31:14 +0700 Subject: [PATCH 009/113] show personal bank account option in wallet page --- src/pages/settings/Wallet/WalletPage/WalletPage.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/settings/Wallet/WalletPage/WalletPage.js b/src/pages/settings/Wallet/WalletPage/WalletPage.js index e0577930b73d..87c802ba98ea 100644 --- a/src/pages/settings/Wallet/WalletPage/WalletPage.js +++ b/src/pages/settings/Wallet/WalletPage/WalletPage.js @@ -542,6 +542,7 @@ function WalletPage({bankAccountList, cardList, fundList, isLoadingPaymentMethod }} onItemSelected={(method) => addPaymentMethodTypePressed(method)} anchorRef={addPaymentMethodAnchorRef} + shouldShowPersonalBankAccountOption /> ); From cd8c36caa0a48cde013436e83552295e632f0474 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 28 Dec 2023 11:23:24 +0100 Subject: [PATCH 010/113] unrevert --- src/CONST.ts | 3 + src/components/MoneyReportHeader.js | 28 +++++++- src/languages/en.ts | 5 ++ src/languages/es.ts | 5 ++ src/languages/types.ts | 3 + src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.ts | 48 ++++++++++++- src/libs/actions/IOU.js | 103 ++++++++++++++++++++++++++++ 8 files changed, 192 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 34720adf8a21..9ed1b91a5903 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -595,6 +595,9 @@ const CONST = { }, }, }, + CANCEL_PAYMENT_REASONS: { + ADMIN: 'CANCEL_REASON_ADMIN', + }, ARCHIVE_REASON: { DEFAULT: 'default', ACCOUNT_CLOSED: 'accountClosed', diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index a559e876af18..535c1194229e 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; @@ -24,7 +24,9 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import Button from './Button'; +import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; +import * as Expensicons from './Icon/Expensicons'; import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar'; import participantPropTypes from './participantPropTypes'; import SettlementButton from './SettlementButton'; @@ -89,6 +91,13 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt isPolicyAdmin && (isApproved || isManager) : isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager); const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport); + const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false); + + const cancelPayment = useCallback(() => { + IOU.cancelPayment(moneyRequestReport, chatReport); + setIsConfirmModalVisible(false); + }, [moneyRequestReport, chatReport]); + const shouldShowPayButton = useMemo( () => isPayer && !isDraft && !isSettled && !moneyRequestReport.isWaitingOnBankAccount && reimbursableTotal !== 0 && !ReportUtils.isArchivedRoom(chatReport), [isPayer, isDraft, isSettled, moneyRequestReport, reimbursableTotal, chatReport], @@ -109,6 +118,13 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; + if (isPayer && isSettled) { + threeDotsMenuItems.push({ + icon: Expensicons.Trashcan, + text: 'Cancel payment', + onSelected: () => setIsConfirmModalVisible(true), + }); + } if (!ReportUtils.isArchivedRoom(chatReport)) { threeDotsMenuItems.push({ icon: ZoomIcon, @@ -206,6 +222,16 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt )} + setIsConfirmModalVisible(false)} + prompt={translate('iou.cancelPaymentConfirmation')} + confirmText={translate('iou.cancelPayment')} + cancelText={translate('common.dismiss')} + danger + /> ); } diff --git a/src/languages/en.ts b/src/languages/en.ts index 3cdb7cf2bf98..2fd453e4ba48 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2,6 +2,7 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import CONST from '@src/CONST'; import type { AddressLineParams, + AdminCanceledRequestParams, AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, @@ -96,6 +97,7 @@ type AllCountries = Record; export default { common: { cancel: 'Cancel', + dismiss: 'Dismiss', yes: 'Yes', no: 'No', ok: 'OK', @@ -549,6 +551,8 @@ export default { requestMoney: 'Request money', sendMoney: 'Send money', pay: 'Pay', + cancelPayment: 'Cancel payment', + cancelPaymentConfirmation: 'Are you sure that you want to cancel this payment?', viewDetails: 'View details', pending: 'Pending', canceled: 'Canceled', @@ -585,6 +589,7 @@ export default { payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, approvedAmount: ({amount}: ApprovedAmountParams) => `approved ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, + adminCanceledRequest: ({amount}: AdminCanceledRequestParams) => `The ${amount} payment has been cancelled by the admin.`, canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceled the ${amount} payment, because ${submitterDisplayName} did not enable their Expensify Wallet within 30 days`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => diff --git a/src/languages/es.ts b/src/languages/es.ts index d11211ca9325..8c21e9ccf6f5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,6 +1,7 @@ import CONST from '@src/CONST'; import type { AddressLineParams, + AdminCanceledRequestParams, AlreadySignedInParams, AmountEachParams, ApprovedAmountParams, @@ -86,6 +87,7 @@ import type { export default { common: { cancel: 'Cancelar', + dismiss: 'Descartar', yes: 'Sí', no: 'No', ok: 'OK', @@ -542,6 +544,8 @@ export default { requestMoney: 'Pedir dinero', sendMoney: 'Enviar dinero', pay: 'Pagar', + cancelPayment: 'Cancelar el pago', + cancelPaymentConfirmation: '¿Estás seguro de que quieres cancelar este pago?', viewDetails: 'Ver detalles', pending: 'Pendiente', canceled: 'Canceló', @@ -578,6 +582,7 @@ export default { payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, approvedAmount: ({amount}: ApprovedAmountParams) => `aprobó ${amount}`, waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, + adminCanceledRequest: ({amount}: AdminCanceledRequestParams) => `El pago de ${amount} ha sido cancelado por el administrador.`, canceledRequest: ({amount, submitterDisplayName}: CanceledRequestParams) => `Canceló el pago ${amount}, porque ${submitterDisplayName} no habilitó su billetera Expensify en un plazo de 30 días.`, settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => diff --git a/src/languages/types.ts b/src/languages/types.ts index 8e72c700a9cc..1a4b833b4122 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -131,6 +131,8 @@ type WaitingOnBankAccountParams = {submitterDisplayName: string}; type CanceledRequestParams = {amount: string; submitterDisplayName: string}; +type AdminCanceledRequestParams = {amount: string}; + type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; type PaidElsewhereWithAmountParams = {payer: string; amount: string}; @@ -293,6 +295,7 @@ export type { PayerSettledParams, WaitingOnBankAccountParams, CanceledRequestParams, + AdminCanceledRequestParams, SettledAfterAddedBankAccountParams, PaidElsewhereWithAmountParams, PaidWithExpensifyWithAmountParams, diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 6e84ef4dca27..1c2899f14c94 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -402,7 +402,7 @@ function getLastMessageTextForReport(report) { } else if (ReportActionUtils.isReimbursementQueuedAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getReimbursementQueuedActionMessage(lastReportAction, report); } else if (ReportActionUtils.isReimbursementDeQueuedAction(lastReportAction)) { - lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(report); + lastMessageTextFromReport = ReportUtils.getReimbursementDeQueuedActionMessage(lastReportAction, report); } else if (ReportActionUtils.isDeletedParentAction(lastReportAction) && ReportUtils.isChatReport(report)) { lastMessageTextFromReport = ReportUtils.getDeletedParentActionMessageForChatReport(lastReportAction); } else if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 81bbf1df6273..331fc03990bb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -15,7 +15,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import {Beta, Login, PersonalDetails, PersonalDetailsList, Policy, 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 {IOUMessage, OriginalMessageActionName, OriginalMessageCreated, ReimbursementDeQueuedMessage} from '@src/types/onyx/OriginalMessage'; import {NotificationPreference} from '@src/types/onyx/Report'; import {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; @@ -176,6 +176,11 @@ type OptimisticSubmittedReportAction = Pick< 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'isAttachment' | 'originalMessage' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' >; +type OptimisticCancelPaymentReportAction = Pick< + ReportAction, + 'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' + >; + type OptimisticEditedTaskReportAction = Pick< ReportAction, 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person' @@ -1535,9 +1540,13 @@ function getReimbursementQueuedActionMessage(reportAction: OnyxEntry): string { +function getReimbursementDeQueuedActionMessage(reportAction: OnyxEntry, report: OnyxEntry): string { + const amount = CurrencyUtils.convertToDisplayString(Math.abs(report?.total ?? 0), report?.currency); + const originalMessage = reportAction?.originalMessage as ReimbursementDeQueuedMessage | undefined; + if (originalMessage?.cancellationReason === CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN) { + return Localize.translateLocal('iou.adminCanceledRequest', {amount}); + } const submitterDisplayName = getDisplayNameForParticipant(report?.ownerAccountID, true) ?? ''; - const amount = CurrencyUtils.convertToDisplayString(report?.total ?? 0, report?.currency); return Localize.translateLocal('iou.canceledRequest', {submitterDisplayName, amount}); } @@ -2787,6 +2796,38 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string, }; } +/** + * Builds an optimistic REIMBURSEMENTDEQUEUED report action with a randomly generated reportActionID. + * + */ +function buildOptimisticCancelPaymentReportAction(): OptimisticCancelPaymentReportAction { + return { + actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED, + actorAccountID: currentUserAccountID, + message: [ + { + cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN, + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: '', + }, + ], + originalMessage: { + cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN, + }, + person: [ + { + style: 'strong', + text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + type: 'TEXT', + }, + ], + reportActionID: NumberUtils.rand64(), + shouldShow: true, + created: DateUtils.getDBTime(), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }; +} + /** * Builds an optimistic report preview action with a randomly generated reportActionID. * @@ -4271,6 +4312,7 @@ export { buildOptimisticIOUReportAction, buildOptimisticReportPreview, buildOptimisticModifiedExpenseReportAction, + buildOptimisticCancelPaymentReportAction, updateReportPreview, buildOptimisticTaskReportAction, buildOptimisticAddCommentReportAction, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index d43fefca20bc..add8bded5e25 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3114,6 +3114,108 @@ function submitReport(expenseReport) { ); } +/** + * @param {Object} expenseReport + * @param {Object} chatReport + */ +function cancelPayment(expenseReport, chatReport) { + const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(); + const policy = ReportUtils.getPolicy(chatReport.policyID); + const isFree = policy && policy.type === CONST.POLICY.TYPE.FREE; + const optimisticData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticReportAction.reportActionID]: { + ...optimisticReportAction, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + value: { + ...expenseReport, + lastMessageText: lodashGet(optimisticReportAction, 'message.0.text', ''), + lastMessageHtml: lodashGet(optimisticReportAction, 'message.0.html', ''), + state: isFree ? CONST.REPORT.STATE.SUBMITTED : CONST.REPORT.STATE.OPEN, + stateNum: isFree ? CONST.REPORT.STATE_NUM.PROCESSING : CONST.REPORT.STATE.OPEN, + statusNum: isFree ? CONST.REPORT.STATUS.SUBMITTED : CONST.REPORT.STATE.OPEN, + }, + }, + ...(chatReport.reportID + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + hasOutstandingIOU: true, + hasOutstandingChildRequest: true, + iouReportID: expenseReport.reportID, + }, + }, + ] + : []), + ]; + + const successData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [optimisticReportAction.reportActionID]: { + pendingAction: null, + }, + }, + }, + ]; + + const failureData = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport.reportID}`, + value: { + [expenseReport.reportActionID]: { + errors: ErrorUtils.getMicroSecondOnyxError('iou.error.other'), + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, + value: { + statusNum: CONST.REPORT.STATUS.REIMBURSED, + }, + }, + ...(chatReport.reportID + ? [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingIOU: false, + hasOutstandingChildRequest: false, + iouReportID: 0, + }, + }, + ] + : []), + ]; + + API.write( + 'CancelPayment', + { + reportID: expenseReport.reportID, + managerAccountID: expenseReport.managerID, + reportActionID: optimisticReportAction.reportActionID, + }, + {optimisticData, successData, failureData}, + ); +} + /** * @param {String} paymentType * @param {Object} chatReport @@ -3431,4 +3533,5 @@ export { detachReceipt, getIOUReportID, editMoneyRequest, + cancelPayment, }; From 05f0e3db60f3a7851eefbbd6131d27f8dc9a5f2b Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 28 Dec 2023 11:28:42 +0100 Subject: [PATCH 011/113] a couple extra files --- src/pages/home/report/ReportActionItem.js | 5 +---- src/types/onyx/OriginalMessage.ts | 6 ++++++ src/types/onyx/ReportAction.ts | 6 +++++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 57627d819197..763c6610fb14 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -422,10 +422,7 @@ function ReportActionItem(props) { ); } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED) { - const submitterDisplayName = PersonalDetailsUtils.getDisplayNameOrDefault(lodashGet(personalDetails, [props.report.ownerAccountID, 'displayName'])); - const amount = CurrencyUtils.convertToDisplayString(props.report.total, props.report.currency); - - children = ; + children = ; } else if (props.action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE) { children = ; } else { diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 767f724dd571..5ed696a9019b 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -43,10 +43,15 @@ type IOUMessage = { participantAccountIDs?: number[]; type: ValueOf; paymentType?: DeepValueOf; + cancellationReason?: string; /** Only exists when we are sending money */ IOUDetails?: IOUDetails; }; +type ReimbursementDeQueuedMessage = { + cancellationReason: string; +}; + type OriginalMessageIOU = { actionName: typeof CONST.REPORT.ACTIONS.TYPE.IOU; originalMessage: IOUMessage; @@ -260,6 +265,7 @@ export type { Reaction, ActionName, IOUMessage, + ReimbursementDeQueuedMessage, Closed, OriginalMessageActionName, ChangeLog, diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index a881b63fbb95..02adc8ae0b95 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -51,9 +51,13 @@ type Message = { translationKey?: string; /** ID of a task report */ - taskReportID?: string; + taskReportID?: string + + /** Reason pf payment cancellation */ + cancellationReason?: string; }; + type ImageMetadata = { /** The height of the image. */ height?: number; From 47426208214aff0536fc61aa49824a8b188fbc50 Mon Sep 17 00:00:00 2001 From: Alberto Date: Thu, 28 Dec 2023 11:58:40 +0100 Subject: [PATCH 012/113] only allow in expense reports --- src/components/MoneyReportHeader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyReportHeader.js b/src/components/MoneyReportHeader.js index 535c1194229e..ce749a027704 100644 --- a/src/components/MoneyReportHeader.js +++ b/src/components/MoneyReportHeader.js @@ -118,7 +118,7 @@ function MoneyReportHeader({session, personalDetails, policy, chatReport, nextSt const isMoreContentShown = shouldShowNextStep || (shouldShowAnyButton && isSmallScreenWidth); const threeDotsMenuItems = [HeaderUtils.getPinMenuItem(moneyRequestReport)]; - if (isPayer && isSettled) { + if (isPayer && isSettled && ReportUtils.isExpenseReport(moneyRequestReport)) { threeDotsMenuItems.push({ icon: Expensicons.Trashcan, text: 'Cancel payment', From 54e7f76d9f5ede5acb255813a67e1c3f3f857fde Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 31 Dec 2023 15:49:13 +0100 Subject: [PATCH 013/113] Clean up the states and statuses in the app --- src/CONST.ts | 9 +--- .../ReportActionItem/TaskPreview.js | 4 +- src/libs/ReportUtils.ts | 31 ++++++------- src/libs/actions/IOU.js | 13 +++--- src/libs/actions/Policy.js | 10 ++--- src/libs/actions/Report.ts | 4 +- src/libs/actions/Task.js | 12 ++--- src/libs/actions/Welcome.ts | 2 +- src/pages/home/HeaderView.js | 2 +- src/pages/home/ReportScreen.js | 6 +-- tests/actions/IOUTest.js | 20 ++++----- tests/unit/ReportUtilsTest.js | 38 ++++++++-------- tests/unit/SidebarFilterTest.js | 28 ++++++------ tests/unit/SidebarOrderTest.js | 44 +++++++++---------- tests/unit/SidebarTest.js | 8 ++-- tests/utils/LHNTestUtils.js | 4 +- 16 files changed, 109 insertions(+), 126 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index abba27b0c33b..15cb361eb5db 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -626,18 +626,13 @@ const CONST = { ANNOUNCE: '#announce', ADMINS: '#admins', }, - STATE: { - OPEN: 'OPEN', - SUBMITTED: 'SUBMITTED', - PROCESSING: 'PROCESSING', - }, STATE_NUM: { OPEN: 0, PROCESSING: 1, - SUBMITTED: 2, + APPROVED: 2, BILLING: 3, }, - STATUS: { + STATUS_NUM: { OPEN: 0, SUBMITTED: 1, CLOSED: 2, diff --git a/src/components/ReportActionItem/TaskPreview.js b/src/components/ReportActionItem/TaskPreview.js index a7728045f407..578b41c713bc 100644 --- a/src/components/ReportActionItem/TaskPreview.js +++ b/src/components/ReportActionItem/TaskPreview.js @@ -90,8 +90,8 @@ function TaskPreview(props) { // Only the direct parent reportAction will contain details about the taskReport // Other linked reportActions will only contain the taskReportID and we will grab the details from there const isTaskCompleted = !_.isEmpty(props.taskReport) - ? props.taskReport.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.taskReport.statusNum === CONST.REPORT.STATUS.APPROVED - : props.action.childStateNum === CONST.REPORT.STATE_NUM.SUBMITTED && props.action.childStatusNum === CONST.REPORT.STATUS.APPROVED; + ? props.taskReport.stateNum === CONST.REPORT.STATE_NUM.APPROVED && props.taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED + : props.action.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && props.action.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = _.escape(TaskUtils.getTaskTitle(props.taskReportID, props.action.childReportName)); const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(props.taskReport) || props.action.childManagerAccountID; const assigneeLogin = lodashGet(personalDetails, [taskAssigneeAccountID, 'login'], ''); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 24d0050997f7..99a3ec6d796a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -529,14 +529,14 @@ function isCanceledTaskReport(report: OnyxEntry | EmptyObject = {}, pare * @param parentReportAction - The parent report action of the report (Used to check if the task has been canceled) */ function isOpenTaskReport(report: OnyxEntry, parentReportAction: OnyxEntry | EmptyObject = {}): boolean { - return isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; + return isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN; } /** * Checks if a report is a completed task report. */ function isCompletedTaskReport(report: OnyxEntry): boolean { - return isTaskReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED; + return isTaskReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED; } /** @@ -550,14 +550,14 @@ function isReportManager(report: OnyxEntry): boolean { * Checks if the supplied report has been approved */ function isReportApproved(report: OnyxEntry | EmptyObject): boolean { - return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS.APPROVED; + return report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && report?.statusNum === CONST.REPORT.STATUS_NUM.APPROVED; } /** * Checks if the supplied report is an expense report in Open state and status. */ function isDraftExpenseReport(report: OnyxEntry): boolean { - return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; + return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN; } /** @@ -588,11 +588,11 @@ function isSettled(reportID: string | undefined): boolean { // In case the payment is scheduled and we are waiting for the payee to set up their wallet, // consider the report as paid as well. - if (report.isWaitingOnBankAccount && report.statusNum === CONST.REPORT.STATUS.APPROVED) { + if (report.isWaitingOnBankAccount && report.statusNum === CONST.REPORT.STATUS_NUM.APPROVED) { return true; } - return report?.statusNum === CONST.REPORT.STATUS.REIMBURSED; + return report?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED; } /** @@ -767,7 +767,7 @@ function isConciergeChatReport(report: OnyxEntry): boolean { * Returns true if report is still being processed */ function isProcessingReport(report: OnyxEntry): boolean { - return report?.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && report?.statusNum === CONST.REPORT.STATUS.SUBMITTED; + return report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED && report?.statusNum === CONST.REPORT.STATUS_NUM.SUBMITTED; } /** @@ -873,7 +873,7 @@ function findLastAccessedReport( * Whether the provided report is an archived room */ function isArchivedRoom(report: OnyxEntry | EmptyObject): boolean { - return report?.statusNum === CONST.REPORT.STATUS.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.SUBMITTED; + return report?.statusNum === CONST.REPORT.STATUS_NUM.CLOSED && report?.stateNum === CONST.REPORT.STATE_NUM.APPROVED; } /** @@ -2469,7 +2469,7 @@ function buildOptimisticTaskCommentReportAction(taskReportID: string, taskTitle: reportAction.reportAction.childType = CONST.REPORT.TYPE.TASK; reportAction.reportAction.childReportName = taskTitle; reportAction.reportAction.childManagerAccountID = taskAssigneeAccountID; - reportAction.reportAction.childStatusNum = CONST.REPORT.STATUS.OPEN; + reportAction.reportAction.childStatusNum = CONST.REPORT.STATUS_NUM.OPEN; reportAction.reportAction.childStateNum = CONST.REPORT.STATE_NUM.OPEN; return reportAction; @@ -2499,9 +2499,8 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number ownerAccountID: payeeAccountID, participantAccountIDs: [payeeAccountID, payerAccountID], reportID: generateReportID(), - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: isSendingMoney ? CONST.REPORT.STATUS.REIMBURSED : CONST.REPORT.STATE_NUM.PROCESSING, + stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, total, // We don't translate reportName because the server response is always in English @@ -2534,9 +2533,8 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa const isFree = policy?.type === CONST.POLICY.TYPE.FREE; // Define the state and status of the report based on whether the policy is free or paid - const state = isFree ? CONST.REPORT.STATE.SUBMITTED : CONST.REPORT.STATE.OPEN; - const stateNum = isFree ? CONST.REPORT.STATE_NUM.PROCESSING : CONST.REPORT.STATE_NUM.OPEN; - const statusNum = isFree ? CONST.REPORT.STATUS.SUBMITTED : CONST.REPORT.STATUS.OPEN; + const stateNum = isFree ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.OPEN; + const statusNum = isFree ? CONST.REPORT.STATUS_NUM.SUBMITTED : CONST.REPORT.STATUS_NUM.OPEN; return { reportID: generateReportID(), @@ -2548,7 +2546,6 @@ function buildOptimisticExpenseReport(chatReportID: string, policyID: string, pa // We don't translate reportName because the server response is always in English reportName: `${policyName} owes ${formattedTotal}`, - state, stateNum, statusNum, total: storedTotal, @@ -3248,7 +3245,7 @@ function buildOptimisticTaskReport( parentReportID, policyID, stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, lastVisibleActionCreated: DateUtils.getDBTime(), }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index ecae885392b9..2c1d6486ca67 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -2827,7 +2827,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho lastMessageText: optimisticIOUReportAction.message[0].text, lastMessageHtml: optimisticIOUReportAction.message[0].html, hasOutstandingChildRequest: false, - statusNum: CONST.REPORT.STATUS.REIMBURSED, + statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }, }, { @@ -2965,8 +2965,8 @@ function approveMoneyRequest(expenseReport) { ...expenseReport, lastMessageText: optimisticApprovedReportAction.message[0].text, lastMessageHtml: optimisticApprovedReportAction.message[0].html, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.APPROVED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, }; const optimisticData = [optimisticIOUReportData, optimisticReportActionsData]; @@ -3038,9 +3038,8 @@ function submitReport(expenseReport) { ...expenseReport, lastMessageText: lodashGet(optimisticSubmittedReportAction, 'message.0.text', ''), lastMessageHtml: lodashGet(optimisticSubmittedReportAction, 'message.0.html', ''), - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }, }, ...(parentReport.reportID @@ -3084,7 +3083,7 @@ function submitReport(expenseReport) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${expenseReport.reportID}`, value: { - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, stateNum: CONST.REPORT.STATE_NUM.OPEN, }, }, diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index f33e6637e2de..2aa583016957 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -172,8 +172,8 @@ function deleteWorkspace(policyID, reports, policyName) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, hasDraft: false, oldPolicyName: allPolicies[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`].name, }, @@ -352,8 +352,8 @@ function removeMembers(accountIDs, policyID) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, oldPolicyName: policy.name, hasDraft: false, }, @@ -459,7 +459,7 @@ function createPolicyExpenseChats(policyID, invitedEmailsToAccountIDs, hasOutsta key: `${ONYXKEYS.COLLECTION.REPORT}${oldChat.reportID}`, value: { stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, }, }); return; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 06c0316a40b5..701a68ec060a 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2079,8 +2079,8 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal } : { reportID: null, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }, }, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 0fe6a528cda1..d6f1c920bbd0 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -238,8 +238,8 @@ function completeTask(taskReport) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, value: { - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.APPROVED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, }, @@ -267,7 +267,7 @@ function completeTask(taskReport) { key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, value: { stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, }, }, { @@ -306,7 +306,7 @@ function reopenTask(taskReport) { key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, value: { stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, lastVisibleActionCreated: reopenedTaskReportAction.created, lastMessageText: message, lastActorAccountID: reopenedTaskReportAction.actorAccountID, @@ -336,8 +336,8 @@ function reopenTask(taskReport) { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, value: { - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.APPROVED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }, }, { diff --git a/src/libs/actions/Welcome.ts b/src/libs/actions/Welcome.ts index 02109804efb9..fdf68b72fc3f 100644 --- a/src/libs/actions/Welcome.ts +++ b/src/libs/actions/Welcome.ts @@ -131,7 +131,7 @@ function show({routes, showCreateMenu = () => {}, showPopoverMenu = () => false} const workspaceChatReport = Object.values(allReports ?? {}).find((report) => { if (report) { - return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS.CLOSED; + return ReportUtils.isPolicyExpenseChat(report) && report.ownerAccountID === currentUserAccountID && report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED; } return false; }); diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js index d3ec573b5886..4d77fb90622f 100644 --- a/src/pages/home/HeaderView.js +++ b/src/pages/home/HeaderView.js @@ -132,7 +132,7 @@ function HeaderView(props) { } // Task is not closed - if (props.report.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED && props.report.statusNum !== CONST.REPORT.STATUS.CLOSED && canModifyTask) { + if (props.report.stateNum !== CONST.REPORT.STATE_NUM.APPROVED && props.report.statusNum !== CONST.REPORT.STATUS_NUM.CLOSED && canModifyTask) { threeDotMenuItems.push({ icon: Expensicons.Trashcan, text: translate('common.cancel'), diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 8e177e0c2e64..38ee1e81fc77 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -186,7 +186,7 @@ function ReportScreen({ // There are no reportActions at all to display and we are still in the process of loading the next set of actions. const isLoadingInitialReportActions = _.isEmpty(filteredReportActions) && reportMetadata.isLoadingInitialReportActions; - const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS.CLOSED; + const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS_NUM.CLOSED; const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas); @@ -358,8 +358,8 @@ function ReportScreen({ (prevOnyxReportID && prevOnyxReportID === routeReportID && !onyxReportID && - prevReport.statusNum === CONST.REPORT.STATUS.OPEN && - (report.statusNum === CONST.REPORT.STATUS.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || + prevReport.statusNum === CONST.REPORT.STATUS_NUM.OPEN && + (report.statusNum === CONST.REPORT.STATUS_NUM.CLOSED || (!report.statusNum && !prevReport.parentReportID && prevReport.chatType === CONST.REPORT.CHAT_TYPE.POLICY_ROOM))) || ((ReportUtils.isMoneyRequest(prevReport) || ReportUtils.isMoneyRequestReport(prevReport)) && _.isEmpty(report)) ) { Navigation.dismissModal(); diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index 4d9ce42a08ce..492726b1865a 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -1237,9 +1237,8 @@ describe('actions/IOU', () => { expect(chatReport.pendingFields).toBeFalsy(); expect(iouReport.pendingFields).toBeFalsy(); - // expect(iouReport.status).toBe(CONST.REPORT.STATUS.SUBMITTED); - // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED); - // expect(iouReport.state).toBe(CONST.REPORT.STATE.SUBMITTED); + // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.SUBMITTED); + // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED); resolve(); }, @@ -1306,9 +1305,8 @@ describe('actions/IOU', () => { expect(chatReport.iouReportID).toBeFalsy(); - // expect(iouReport.status).toBe(CONST.REPORT.STATUS.REIMBURSED); - // expect(iouReport.state).toBe(CONST.REPORT.STATE.MANUALREIMBURSED); - // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED); + // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED); + // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED); resolve(); }, @@ -1356,9 +1354,8 @@ describe('actions/IOU', () => { expect(chatReport.iouReportID).toBeFalsy(); - // expect(iouReport.status).toBe(CONST.REPORT.STATUS.REIMBURSED); - // expect(iouReport.state).toBe(CONST.REPORT.STATE.MANUALREIMBURSED); - // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.SUBMITTED); + // expect(iouReport.status).toBe(CONST.REPORT.STATUS_NUM.REIMBURSED); + // expect(iouReport.stateNum).toBe(CONST.REPORT.STATE_NUM.APPROVED); resolve(); }, @@ -1770,9 +1767,8 @@ describe('actions/IOU', () => { expect.objectContaining({ lastMessageHtml: `paid $${amount / 100}.00 with Expensify`, lastMessageText: `paid $${amount / 100}.00 with Expensify`, - state: CONST.REPORT.STATE.SUBMITTED, - statusNum: CONST.REPORT.STATUS.REIMBURSED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, + statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, }), ); expect(updatedChatReport).toEqual( diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index d700aa4724f1..c9e8053e3146 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -149,8 +149,8 @@ describe('ReportUtils', () => { test('Archived', () => { const archivedAdminsRoom = { ...baseAdminsRoom, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; expect(ReportUtils.getReportName(archivedAdminsRoom)).toBe('#admins (archived)'); @@ -172,8 +172,8 @@ describe('ReportUtils', () => { test('Archived', () => { const archivedPolicyRoom = { ...baseUserCreatedRoom, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; expect(ReportUtils.getReportName(archivedPolicyRoom)).toBe('#VikingsChat (archived)'); @@ -213,8 +213,8 @@ describe('ReportUtils', () => { ownerAccountID: 1, policyID: policy.policyID, oldPolicyName: policy.name, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; test('as member', () => { @@ -307,7 +307,7 @@ describe('ReportUtils', () => { managerID: currentUserAccountID, isUnreadWithMention: false, stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, }; expect(ReportUtils.requiresAttentionFromCurrentUser(report)).toBe(true); }); @@ -368,7 +368,7 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, - statusNum: CONST.REPORT.STATUS.REIMBURSED, + statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); @@ -378,8 +378,8 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: CONST.REPORT.STATUS.APPROVED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + statusNum: CONST.REPORT.STATUS_NUM.APPROVED, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); @@ -389,7 +389,7 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, - statusNum: CONST.REPORT.STATUS.REIMBURSED, + statusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID]); expect(moneyRequestOptions.length).toBe(0); @@ -419,8 +419,8 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, parentReportID: '101', }; const paidPolicy = { @@ -508,7 +508,7 @@ describe('ReportUtils', () => { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.EXPENSE, stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, parentReportID: '103', }; const paidPolicy = { @@ -523,9 +523,8 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); @@ -536,9 +535,8 @@ describe('ReportUtils', () => { const report = { ...LHNTestUtils.getFakeReport(), type: CONST.REPORT.TYPE.IOU, - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const moneyRequestOptions = ReportUtils.getMoneyRequestOptions(report, {}, [currentUserAccountID, participantsAccountIDs[0]]); expect(moneyRequestOptions.length).toBe(1); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index dd2985ea34a8..35d91e291666 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -471,20 +471,20 @@ describe('Sidebar', () => { // Given an archived chat report, an archived default policy room, and an archived user created policy room const archivedReport = { ...LHNTestUtils.getFakeReport([1, 2]), - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; const archivedPolicyRoomReport = { ...LHNTestUtils.getFakeReport([1, 2]), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; const archivedUserCreatedPolicyRoomReport = { ...LHNTestUtils.getFakeReport([1, 2]), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; LHNTestUtils.getDefaultRenderedSidebarLinks(); @@ -695,8 +695,8 @@ describe('Sidebar', () => { const report = { ...LHNTestUtils.getFakeReport(), lastVisibleActionCreated: '2022-11-22 03:48:27.267', - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; // Given the user is in all betas @@ -746,8 +746,8 @@ describe('Sidebar', () => { // Given an archived report that has all comments read const report = { ...LHNTestUtils.getFakeReport(), - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; // Given the user is in all betas @@ -795,8 +795,8 @@ describe('Sidebar', () => { const report = { ...LHNTestUtils.getFakeReport(), isPinned: false, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; // Given the user is in all betas @@ -840,8 +840,8 @@ describe('Sidebar', () => { // Given an archived report that is not the active report const report = { ...LHNTestUtils.getFakeReport(), - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; // Given the user is in all betas diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 44d6dd57de91..a6b0f4dba60d 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -255,7 +255,7 @@ describe('Sidebar', () => { reportName: taskReportName, managerID: 2, stateNum: CONST.REPORT.STATE_NUM.OPEN, - statusNum: CONST.REPORT.STATUS.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, }; // Each report has at least one ADDCOMMENT action so should be rendered in the LNH @@ -313,8 +313,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; report3.iouReportID = iouReport.reportID; @@ -374,9 +374,8 @@ describe('Sidebar', () => { policyName: 'Workspace', total: -10000, currency: 'USD', - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, chatReportID: report3.reportID, parentReportID: report3.reportID, }; @@ -575,9 +574,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - state: CONST.REPORT.STATE.SUBMITTED, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; report3.iouReportID = iouReport.reportID; const currentReportId = report2.reportID; @@ -740,8 +738,8 @@ describe('Sidebar', () => { const report1 = { ...LHNTestUtils.getFakeReport([1, 2]), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; const report2 = LHNTestUtils.getFakeReport([3, 4]); const report3 = LHNTestUtils.getFakeReport([5, 6]); @@ -837,8 +835,8 @@ describe('Sidebar', () => { const report1 = { ...LHNTestUtils.getFakeReport([1, 2], 3, true), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; const report2 = LHNTestUtils.getFakeReport([3, 4], 2, true); const report3 = LHNTestUtils.getFakeReport([5, 6], 1, true); @@ -914,8 +912,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const iouReport2 = { ...LHNTestUtils.getFakeReport([9, 10]), @@ -926,8 +924,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const iouReport3 = { ...LHNTestUtils.getFakeReport([11, 12]), @@ -938,8 +936,8 @@ describe('Sidebar', () => { total: 100000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const iouReport4 = { ...LHNTestUtils.getFakeReport([11, 12]), @@ -950,8 +948,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; const iouReport5 = { ...LHNTestUtils.getFakeReport([11, 12]), @@ -962,8 +960,8 @@ describe('Sidebar', () => { total: 10000, currency: 'USD', chatReportID: report3.reportID, - stateNum: CONST.REPORT.STATE_NUM.PROCESSING, - statusNum: CONST.REPORT.STATUS.SUBMITTED, + stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED, }; report1.iouReportID = iouReport1.reportID; diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 106b2c3b69a9..09d3905fe86a 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -51,8 +51,8 @@ describe('Sidebar', () => { const report = { ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true), chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; // Given the user is in all betas @@ -86,8 +86,8 @@ describe('Sidebar', () => { ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3, true), policyName: 'Vikings Policy', chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - statusNum: CONST.REPORT.STATUS.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, }; const action = { ...LHNTestUtils.getFakeReportAction('email1@test.com', 3, true), diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index a80feae730c6..0507a671358a 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -209,8 +209,8 @@ function getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorksp ...getFakeReport([1, 2], 0, isUnread), type: CONST.REPORT.TYPE.CHAT, chatType: isUserCreatedPolicyRoom ? CONST.REPORT.CHAT_TYPE.POLICY_ROOM : CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, - statusNum: isArchived ? CONST.REPORT.STATUS.CLOSED : 0, - stateNum: isArchived ? CONST.REPORT.STATE_NUM.SUBMITTED : 0, + statusNum: isArchived ? CONST.REPORT.STATUS_NUM.CLOSED : 0, + stateNum: isArchived ? CONST.REPORT.STATE_NUM.APPROVED : 0, errorFields: hasAddWorkspaceError ? {addWorkspaceRoom: 'blah'} : null, isPinned, hasDraft, From b93661414bdaa7f0bb24da494a75b0c4777c1b56 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 31 Dec 2023 15:57:34 +0100 Subject: [PATCH 014/113] remove the state from the Report type --- src/types/onyx/Report.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index b274025908f5..9db1de108885 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -83,9 +83,6 @@ type Report = { /** ID of the chat report */ chatReportID?: string; - /** The state of the report */ - state?: ValueOf; - /** The state that the report is currently in */ stateNum?: ValueOf; From 3877497f806050a569941170a219cc299ac79eb7 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 31 Dec 2023 16:09:25 +0100 Subject: [PATCH 015/113] Prettier --- src/libs/ReportUtils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 99a3ec6d796a..40101feb6d40 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -529,7 +529,9 @@ function isCanceledTaskReport(report: OnyxEntry | EmptyObject = {}, pare * @param parentReportAction - The parent report action of the report (Used to check if the task has been canceled) */ function isOpenTaskReport(report: OnyxEntry, parentReportAction: OnyxEntry | EmptyObject = {}): boolean { - return isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN; + return ( + isTaskReport(report) && !isCanceledTaskReport(report, parentReportAction) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS_NUM.OPEN + ); } /** From a5d07180966c77bdd0d2e5d3e358d8a6911161c0 Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 31 Dec 2023 16:20:52 +0100 Subject: [PATCH 016/113] Fix the statenum const --- src/CONST.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index 15cb361eb5db..62e79ec62400 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -628,7 +628,7 @@ const CONST = { }, STATE_NUM: { OPEN: 0, - PROCESSING: 1, + SUBMITTED: 1, APPROVED: 2, BILLING: 3, }, From 02d8deb0c84212f158c95194067b7bd23cc5f52b Mon Sep 17 00:00:00 2001 From: Vit Horacek Date: Sun, 31 Dec 2023 16:29:07 +0100 Subject: [PATCH 017/113] Fix type issues --- src/libs/E2E/apiMocks/openApp.ts | 4 ++-- src/libs/ReportUtils.ts | 4 +--- src/types/onyx/Report.ts | 2 +- src/types/onyx/ReportAction.ts | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/libs/E2E/apiMocks/openApp.ts b/src/libs/E2E/apiMocks/openApp.ts index 42d13716407d..4c12a95a1db4 100644 --- a/src/libs/E2E/apiMocks/openApp.ts +++ b/src/libs/E2E/apiMocks/openApp.ts @@ -2043,10 +2043,10 @@ const openApp = (): Response => ({ managerID: 16, currency: 'USD', chatReportID: '98817646', - state: 'SUBMITTED', cachedTotal: '($1,473.11)', total: 147311, stateNum: 1, + statusNum: 1, }, report_4249286573496381: { reportID: '4249286573496381', @@ -2054,10 +2054,10 @@ const openApp = (): Response => ({ managerID: 21, currency: 'USD', chatReportID: '4867098979334014', - state: 'SUBMITTED', cachedTotal: '($212.78)', total: 21278, stateNum: 1, + statusNum: 1, }, }, }, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 40101feb6d40..5c5fe45fc811 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -109,7 +109,6 @@ type OptimisticExpenseReport = Pick< | 'ownerAccountID' | 'currency' | 'reportName' - | 'state' | 'stateNum' | 'statusNum' | 'total' @@ -300,13 +299,12 @@ type OptimisticIOUReport = Pick< | 'ownerAccountID' | 'participantAccountIDs' | 'reportID' - | 'state' | 'stateNum' + | 'statusNum' | 'total' | 'reportName' | 'notificationPreference' | 'parentReportID' - | 'statusNum' | 'lastVisibleActionCreated' >; type DisplayNameWithTooltips = Array>; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 9db1de108885..607c303f1abb 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -87,7 +87,7 @@ type Report = { stateNum?: ValueOf; /** The status of the current report */ - statusNum?: ValueOf; + statusNum?: ValueOf; /** Which user role is capable of posting messages on the report */ writeCapability?: WriteCapability; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index a881b63fbb95..df4248b90d09 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -148,7 +148,7 @@ type ReportActionBase = { childManagerAccountID?: number; /** The status of the child report */ - childStatusNum?: ValueOf; + childStatusNum?: ValueOf; /** Report action child status name */ childStateNum?: ValueOf; From 0b7592d8efb3242816eb5aebba4120dc22e9a131 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 2 Jan 2024 11:55:17 +0500 Subject: [PATCH 018/113] fix: lint issues --- src/CONST.ts | 2 + .../ReportActionItem/MoneyReportView.tsx | 61 +++++++++++-------- src/libs/ReportUtils.ts | 2 +- src/pages/home/report/ReportActionItem.js | 2 +- 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index abba27b0c33b..e0479869987b 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1427,6 +1427,8 @@ const CONST = { INVISIBLE_CHARACTERS_GROUPS: /[\p{C}\p{Z}]/gu, OTHER_INVISIBLE_CHARACTERS: /[\u3164]/g, + + REPORT_FIELD_TITLE: /{report:([a-zA-Z]+)}/g, }, PRONOUNS: { diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 2ffbda4d347b..9ad9e73244f3 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -1,12 +1,13 @@ -import React, { useMemo } from 'react'; +import React, {useMemo} from 'react'; import {StyleProp, TextStyle, View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import SpacerView from '@components/SpacerView'; -import OfflineWithFeedback from '@components/OfflineWithFeedback'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; +import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import SpacerView from '@components/SpacerView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -16,7 +17,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; import type {PolicyReportField, Report} from '@src/types/onyx'; -import usePermissions from '@hooks/usePermissions'; type MoneyReportViewProps = { /** The report currently being looked at */ @@ -52,34 +52,41 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}: StyleUtils.getColorStyle(theme.textSupporting), ]; - const sortedPolicyReportFields = useMemo(() => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), [policyReportFields]); + const sortedPolicyReportFields = useMemo( + () => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight), + [policyReportFields], + ); return ( - {canUseReportFields && sortedPolicyReportFields.map((reportField) => { - const title = ReportUtils.getReportFieldTitle(report, reportField); - return ( - - {}} - shouldShowRightIcon - disabled={false} - wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} - shouldGreyOutWhenDisabled={false} - numberOfLinesTitle={0} - interactive - shouldStackHorizontally={false} - onSecondaryInteraction={() => {}} - hoverAndPressStyle={false} - titleWithTooltips={[]} - /> - - ); - })} + {canUseReportFields && + sortedPolicyReportFields.map((reportField) => { + const title = ReportUtils.getReportFieldTitle(report, reportField); + return ( + + {}} + shouldShowRightIcon + disabled={false} + wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} + shouldGreyOutWhenDisabled={false} + numberOfLinesTitle={0} + interactive + shouldStackHorizontally={false} + onSecondaryInteraction={() => {}} + hoverAndPressStyle={false} + titleWithTooltips={[]} + /> + + ); + })} , reportField: PolicyRepor return value; } - return value.replaceAll(/{report:([a-zA-Z]+)}/g, (match, property) => { + return value.replaceAll(CONST.REGEX.REPORT_FIELD_TITLE, (match, property) => { if (report && property in report) { return report[property as keyof Report]?.toString() ?? match; } diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 4e806faa1b47..a16ba6132ebc 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -761,7 +761,7 @@ export default compose( initialValue: {}, }, policyReportFields: { - key: ({report}) => report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined, + key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined), initialValue: [], }, emojiReactions: { From b7932d686605b53d460fe96ffc4527659db70adb Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 2 Jan 2024 11:26:38 +0100 Subject: [PATCH 019/113] add reportID to original message --- src/libs/ReportUtils.ts | 3 ++- src/libs/actions/IOU.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 331fc03990bb..3f735e130c2d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2800,13 +2800,14 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string, * Builds an optimistic REIMBURSEMENTDEQUEUED report action with a randomly generated reportActionID. * */ -function buildOptimisticCancelPaymentReportAction(): OptimisticCancelPaymentReportAction { +function buildOptimisticCancelPaymentReportAction(expenseReportID: string): OptimisticCancelPaymentReportAction { return { actionName: CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED, actorAccountID: currentUserAccountID, message: [ { cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN, + expenseReportID, type: CONST.REPORT.MESSAGE.TYPE.COMMENT, text: '', }, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index add8bded5e25..337c69764ecf 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3119,7 +3119,7 @@ function submitReport(expenseReport) { * @param {Object} chatReport */ function cancelPayment(expenseReport, chatReport) { - const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(); + const optimisticReportAction = ReportUtils.buildOptimisticCancelPaymentReportAction(expenseReport.reportID); const policy = ReportUtils.getPolicy(chatReport.policyID); const isFree = policy && policy.type === CONST.POLICY.TYPE.FREE; const optimisticData = [ From c7518ab4c8f6942c6b8cc88fa0214745dc3191b2 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 2 Jan 2024 11:40:42 +0100 Subject: [PATCH 020/113] copy correct message --- src/libs/ReportUtils.ts | 1 + src/pages/home/report/ContextMenu/ContextMenuActions.js | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3f735e130c2d..d3c4a7d0a01d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2814,6 +2814,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string): Opti ], originalMessage: { cancellationReason: CONST.REPORT.CANCEL_PAYMENT_REASONS.ADMIN, + expenseReportID, }, person: [ { diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index f1a46785a59a..dec687e06688 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -291,7 +291,13 @@ export default [ } else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportAction); Clipboard.setString(modifyExpenseMessage); - } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + }else if (ReportActionUtils.isReimbursementDeQueuedAction(reportAction)) { + const {expenseReportID} = reportAction.originalMessage; + const expenseReport = ReportUtils.getReport(expenseReportID); + console.log(reportAction); + const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReport); + Clipboard.setString(displayMessage); + }else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isCreatedTaskReportAction(reportAction)) { From 7a119c9155eca1d83ab5e4b1844f1e9b7a9eb910 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 2 Jan 2024 11:42:58 +0100 Subject: [PATCH 021/113] typo --- src/pages/home/report/ContextMenu/ContextMenuActions.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index dec687e06688..c465035b2a25 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -291,10 +291,9 @@ export default [ } else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportAction); Clipboard.setString(modifyExpenseMessage); - }else if (ReportActionUtils.isReimbursementDeQueuedAction(reportAction)) { + }else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { const {expenseReportID} = reportAction.originalMessage; const expenseReport = ReportUtils.getReport(expenseReportID); - console.log(reportAction); const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReport); Clipboard.setString(displayMessage); }else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { From 8aa11a4f95ba71c69b134c8e6529f75fd7eab294 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 2 Jan 2024 12:13:55 +0100 Subject: [PATCH 022/113] Correctly copy owes message --- src/libs/ReportUtils.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d3c4a7d0a01d..c956928c5ac7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4112,17 +4112,22 @@ function getIOUReportActionDisplayMessage(reportAction: OnyxEntry) const formattedAmount = CurrencyUtils.convertToDisplayString(amount, currency) ?? ''; const payerName = isExpenseReport(iouReport) ? getPolicyName(iouReport) : getDisplayNameForParticipant(iouReport?.managerID, true); - switch (originalMessage.paymentType) { - case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: - translationKey = 'iou.paidElsewhereWithAmount'; - break; - case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: - case CONST.IOU.PAYMENT_TYPE.VBBA: - translationKey = 'iou.paidWithExpensifyWithAmount'; - break; - default: - translationKey = 'iou.payerPaidAmount'; - break; + // If the payment was cancelled, show the "Owes" message + if (!isSettled(IOUReportID)) { + translationKey = 'iou.payerOwesAmount'; + } else { + switch (originalMessage.paymentType) { + case CONST.IOU.PAYMENT_TYPE.ELSEWHERE: + translationKey = 'iou.paidElsewhereWithAmount'; + break; + case CONST.IOU.PAYMENT_TYPE.EXPENSIFY: + case CONST.IOU.PAYMENT_TYPE.VBBA: + translationKey = 'iou.paidWithExpensifyWithAmount'; + break; + default: + translationKey = 'iou.payerPaidAmount'; + break; + } } return Localize.translateLocal(translationKey, {amount: formattedAmount, payer: payerName ?? ''}); } From 274a58acde359ba362385d785ad358d6f2231d78 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 2 Jan 2024 12:49:52 +0100 Subject: [PATCH 023/113] pass chat report --- src/libs/actions/IOU.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 337c69764ecf..9188fe56920f 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3208,7 +3208,8 @@ function cancelPayment(expenseReport, chatReport) { API.write( 'CancelPayment', { - reportID: expenseReport.reportID, + iouReportID: expenseReport.reportID, + chatReportID: chatReport.reportID, managerAccountID: expenseReport.managerID, reportActionID: optimisticReportAction.reportActionID, }, From a2508343aa10ec039299fbccf7aeab2c62014122 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 2 Jan 2024 17:41:12 +0100 Subject: [PATCH 024/113] ref: migrate ReportAttachments page to Typescript --- ...rtAttachments.js => ReportAttachments.tsx} | 41 ++++++++++--------- 1 file changed, 21 insertions(+), 20 deletions(-) rename src/pages/home/report/{ReportAttachments.js => ReportAttachments.tsx} (55%) diff --git a/src/pages/home/report/ReportAttachments.js b/src/pages/home/report/ReportAttachments.tsx similarity index 55% rename from src/pages/home/report/ReportAttachments.js rename to src/pages/home/report/ReportAttachments.tsx index 8ecbb036a756..1558cc8a13c4 100644 --- a/src/pages/home/report/ReportAttachments.js +++ b/src/pages/home/report/ReportAttachments.tsx @@ -1,43 +1,45 @@ -import PropTypes from 'prop-types'; +import {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback} from 'react'; -import _ from 'underscore'; import AttachmentModal from '@components/AttachmentModal'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; +import {AuthScreensParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import ROUTES from '@src/ROUTES'; +import SCREENS from '@src/SCREENS'; -const propTypes = { - /** Navigation route context info provided by react navigation */ - route: PropTypes.shape({ - /** Route specific parameters used on this screen */ - params: PropTypes.shape({ - /** The report ID which the attachment is associated with */ - reportID: PropTypes.string.isRequired, - /** The uri encoded source of the attachment */ - source: PropTypes.string.isRequired, - }).isRequired, - }).isRequired, +type Attachment = { + file: { + name: string; + }; + hasBeenFlagged: boolean; + isAuthTokenRequired: boolean; + isReceipt: boolean; + reportActionID: string; + source: string; }; -function ReportAttachments(props) { - const reportID = _.get(props, ['route', 'params', 'reportID']); +type ReportAttachmentsProps = StackScreenProps; + +function ReportAttachments({route}: ReportAttachmentsProps) { + const reportID = route.params?.reportID; const report = ReportUtils.getReport(reportID); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource - const decodedSource = decodeURI(_.get(props, ['route', 'params', 'source'])); + const decodedSource = decodeURI(route.params.source); const source = Number(decodedSource) || decodedSource; const onCarouselAttachmentChange = useCallback( - (attachment) => { - const route = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source); - Navigation.navigate(route); + (attachment: Attachment) => { + const routeToNavigate = ROUTES.REPORT_ATTACHMENTS.getRoute(reportID, attachment.source); + Navigation.navigate(routeToNavigate); }, [reportID], ); return ( Date: Wed, 3 Jan 2024 10:50:17 +0100 Subject: [PATCH 025/113] fix: seperate type --- src/pages/home/report/ReportAttachments.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index 1558cc8a13c4..cd8f809378cc 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -8,10 +8,12 @@ import * as ReportUtils from '@libs/ReportUtils'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; +type File = { + name: string; +}; + type Attachment = { - file: { - name: string; - }; + file: File; hasBeenFlagged: boolean; isAuthTokenRequired: boolean; isReceipt: boolean; From 134f074ae8d23d32fa5bc342e017a9e7fee87787 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 3 Jan 2024 13:22:45 +0100 Subject: [PATCH 026/113] lint --- src/pages/home/report/ReportActionItem.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index e177d309adb2..fe84cc812f48 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -37,7 +37,6 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import focusTextInputAfterAnimation from '@libs/focusTextInputAfterAnimation'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; From 09e7e8ad24a84c361f6966bafa9d69e388b908c7 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 3 Jan 2024 13:24:25 +0100 Subject: [PATCH 027/113] typescript --- src/types/onyx/OriginalMessage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 5ed696a9019b..e5803774632e 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -36,6 +36,7 @@ type IOUMessage = { /** The ID of the iou transaction */ IOUTransactionID?: string; IOUReportID?: string; + expenseReportID?: string; amount: number; comment?: string; currency: string; From de3678b3814a8475f4b860eafac02a7d06dae27d Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 3 Jan 2024 13:30:25 +0100 Subject: [PATCH 028/113] prettier --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/IOU.js | 42 +++++++++---------- .../report/ContextMenu/ContextMenuActions.js | 4 +- src/types/onyx/ReportAction.ts | 3 +- 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index feb68c5169a2..30f64cd8d134 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -181,7 +181,7 @@ type OptimisticSubmittedReportAction = Pick< type OptimisticCancelPaymentReportAction = Pick< ReportAction, 'actionName' | 'actorAccountID' | 'message' | 'originalMessage' | 'person' | 'reportActionID' | 'shouldShow' | 'created' | 'pendingAction' - >; +>; type OptimisticEditedTaskReportAction = Pick< ReportAction, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 43eef2b4e46c..549719a6906d 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -3191,17 +3191,17 @@ function cancelPayment(expenseReport, chatReport) { }, ...(chatReport.reportID ? [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - hasOutstandingIOU: true, - hasOutstandingChildRequest: true, - iouReportID: expenseReport.reportID, - }, - }, - ] + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + hasOutstandingIOU: true, + hasOutstandingChildRequest: true, + iouReportID: expenseReport.reportID, + }, + }, + ] : []), ]; @@ -3236,16 +3236,16 @@ function cancelPayment(expenseReport, chatReport) { }, ...(chatReport.reportID ? [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - hasOutstandingIOU: false, - hasOutstandingChildRequest: false, - iouReportID: 0, - }, - }, - ] + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingIOU: false, + hasOutstandingChildRequest: false, + iouReportID: 0, + }, + }, + ] : []), ]; diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index f6fe30817d35..66502ad1d4ab 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -281,12 +281,12 @@ export default [ } else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportAction); Clipboard.setString(modifyExpenseMessage); - }else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { + } else if (ReportActionsUtils.isReimbursementDeQueuedAction(reportAction)) { const {expenseReportID} = reportAction.originalMessage; const expenseReport = ReportUtils.getReport(expenseReportID); const displayMessage = ReportUtils.getReimbursementDeQueuedActionMessage(reportAction, expenseReport); Clipboard.setString(displayMessage); - }else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { + } else if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { const displayMessage = ReportUtils.getIOUReportActionDisplayMessage(reportAction); Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isCreatedTaskReportAction(reportAction)) { diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 02adc8ae0b95..cea5a5a570b5 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -51,13 +51,12 @@ type Message = { translationKey?: string; /** ID of a task report */ - taskReportID?: string + taskReportID?: string; /** Reason pf payment cancellation */ cancellationReason?: string; }; - type ImageMetadata = { /** The height of the image. */ height?: number; From cb2daae3cc629a84c1783985a7b916547efa8466 Mon Sep 17 00:00:00 2001 From: Alberto Date: Wed, 3 Jan 2024 13:34:09 +0100 Subject: [PATCH 029/113] more typescript --- src/types/onyx/ReportAction.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index cea5a5a570b5..95f0a97bdbeb 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -53,8 +53,11 @@ type Message = { /** ID of a task report */ taskReportID?: string; - /** Reason pf payment cancellation */ + /** Reason of payment cancellation */ cancellationReason?: string; + + /** ID of an expense report */ + expenseReportID?: string; }; type ImageMetadata = { From de27a34057e7b717cf1fc2d207de099cc4ee97a0 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 3 Jan 2024 14:05:58 +0100 Subject: [PATCH 030/113] migrate ConfirmModal.js to TypeScript --- src/components/ConfirmModal.js | 130 -------------------------------- src/components/ConfirmModal.tsx | 128 +++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 130 deletions(-) delete mode 100755 src/components/ConfirmModal.js create mode 100755 src/components/ConfirmModal.tsx diff --git a/src/components/ConfirmModal.js b/src/components/ConfirmModal.js deleted file mode 100755 index 82c4b27be7f1..000000000000 --- a/src/components/ConfirmModal.js +++ /dev/null @@ -1,130 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import CONST from '@src/CONST'; -import ConfirmContent from './ConfirmContent'; -import sourcePropTypes from './Image/sourcePropTypes'; -import Modal from './Modal'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; - -const propTypes = { - /** Title of the modal */ - title: PropTypes.string, - - /** A callback to call when the form has been submitted */ - onConfirm: PropTypes.func.isRequired, - - /** A callback to call when the form has been closed */ - onCancel: PropTypes.func, - - /** Modal visibility */ - isVisible: PropTypes.bool.isRequired, - - /** Confirm button text */ - confirmText: PropTypes.string, - - /** Cancel button text */ - cancelText: PropTypes.string, - - /** Modal content text/element */ - prompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - - /** Whether we should use the success button color */ - success: PropTypes.bool, - - /** Is the action destructive */ - danger: PropTypes.bool, - - /** Whether we should disable the confirm button when offline */ - shouldDisableConfirmButtonWhenOffline: PropTypes.bool, - - /** Whether we should show the cancel button */ - shouldShowCancelButton: PropTypes.bool, - - /** Callback method fired when the modal is hidden */ - onModalHide: PropTypes.func, - - /** Should we announce the Modal visibility changes? */ - shouldSetModalVisibility: PropTypes.bool, - - /** Icon to display above the title */ - iconSource: PropTypes.oneOfType([PropTypes.string, sourcePropTypes]), - - /** Styles for title */ - // eslint-disable-next-line react/forbid-prop-types - titleStyles: PropTypes.arrayOf(PropTypes.object), - - /** Styles for prompt */ - // eslint-disable-next-line react/forbid-prop-types - promptStyles: PropTypes.arrayOf(PropTypes.object), - - /** Styles for icon */ - // eslint-disable-next-line react/forbid-prop-types - iconAdditionalStyles: PropTypes.arrayOf(PropTypes.object), - - /** Whether to center the icon / text content */ - shouldCenterContent: PropTypes.bool, - - /** Whether to stack the buttons */ - shouldStackButtons: PropTypes.bool, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - confirmText: '', - cancelText: '', - prompt: '', - success: true, - danger: false, - onCancel: () => {}, - shouldDisableConfirmButtonWhenOffline: false, - shouldShowCancelButton: true, - shouldSetModalVisibility: true, - title: '', - iconSource: null, - onModalHide: () => {}, - titleStyles: [], - iconAdditionalStyles: [], - promptStyles: [], - shouldCenterContent: false, - shouldStackButtons: true, -}; - -function ConfirmModal(props) { - return ( - - (props.isVisible ? props.onConfirm() : null)} - onCancel={props.onCancel} - confirmText={props.confirmText} - cancelText={props.cancelText} - prompt={props.prompt} - success={props.success} - danger={props.danger} - shouldDisableConfirmButtonWhenOffline={props.shouldDisableConfirmButtonWhenOffline} - shouldShowCancelButton={props.shouldShowCancelButton} - shouldCenterContent={props.shouldCenterContent} - iconSource={props.iconSource} - iconAdditionalStyles={props.iconAdditionalStyles} - titleStyles={props.titleStyles} - promptStyles={props.promptStyles} - shouldStackButtons={props.shouldStackButtons} - /> - - ); -} - -ConfirmModal.propTypes = propTypes; -ConfirmModal.defaultProps = defaultProps; -ConfirmModal.displayName = 'ConfirmModal'; -export default withWindowDimensions(ConfirmModal); diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx new file mode 100755 index 000000000000..c3b97a9bf7d4 --- /dev/null +++ b/src/components/ConfirmModal.tsx @@ -0,0 +1,128 @@ +import React, {ReactNode} from 'react'; +import {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import CONST from '@src/CONST'; +import IconAsset from '@src/types/utils/IconAsset'; +import ConfirmContent from './ConfirmContent'; +import Modal from './Modal'; + +type ConfirmModalProps = { + /** Title of the modal */ + title?: string; + + /** A callback to call when the form has been submitted */ + onConfirm: () => void; + + /** A callback to call when the form has been closed */ + onCancel?: (ref?: React.RefObject | undefined) => void; + + /** Modal visibility */ + isVisible: boolean; + + /** Confirm button text */ + confirmText?: string; + + /** Cancel button text */ + cancelText?: string; + + /** Modal content text/element */ + prompt?: string | ReactNode; + + /** Whether we should use the success button color */ + success?: boolean; + + /** Is the action destructive */ + danger?: boolean; + + /** Whether we should disable the confirm button when offline */ + shouldDisableConfirmButtonWhenOffline?: boolean; + + /** Whether we should show the cancel button */ + shouldShowCancelButton?: boolean; + + /** Callback method fired when the modal is hidden */ + onModalHide?: () => void; + + /** Should we announce the Modal visibility changes? */ + shouldSetModalVisibility?: boolean; + + /** Icon to display above the title */ + iconSource?: IconAsset; + + /** Styles for title */ + // eslint-disable-next-line react/forbid-prop-types + titleStyles?: StyleProp; + + /** Styles for prompt */ + // eslint-disable-next-line react/forbid-prop-types + promptStyles?: StyleProp; + + /** Styles for icon */ + // eslint-disable-next-line react/forbid-prop-types + iconAdditionalStyles?: StyleProp; + + /** Whether to center the icon / text content */ + shouldCenterContent?: boolean; + + /** Whether to stack the buttons */ + shouldStackButtons?: boolean; +}; + +function ConfirmModal({ + confirmText = '', + cancelText = '', + prompt = '', + success = true, + danger = false, + onCancel = () => {}, + shouldDisableConfirmButtonWhenOffline = false, + shouldShowCancelButton = true, + shouldSetModalVisibility = true, + title = '', + iconSource, + onModalHide = () => {}, + titleStyles = [], + iconAdditionalStyles = [], + promptStyles = [], + shouldCenterContent = false, + shouldStackButtons = true, + isVisible, + onConfirm, +}: ConfirmModalProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + return ( + + (isVisible ? onConfirm() : null)} + onCancel={onCancel} + confirmText={confirmText} + cancelText={cancelText} + prompt={prompt} + success={success} + danger={danger} + shouldDisableConfirmButtonWhenOffline={shouldDisableConfirmButtonWhenOffline} + shouldShowCancelButton={shouldShowCancelButton} + shouldCenterContent={shouldCenterContent} + iconSource={iconSource} + iconAdditionalStyles={iconAdditionalStyles} + titleStyles={titleStyles} + promptStyles={promptStyles} + shouldStackButtons={shouldStackButtons} + /> + + ); +} + +ConfirmModal.displayName = 'ConfirmModal'; + +export default ConfirmModal; From 362121d09a09fa47de8994a2d40952acba1f3ff9 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Wed, 3 Jan 2024 16:26:08 +0000 Subject: [PATCH 031/113] chore: apply pull request feedback --- src/pages/ConciergePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index 5f4b5ad375f7..0ec0efa96ce5 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -25,7 +25,7 @@ type ConciergePageProps = ConciergePageOnyxProps & StackScreenProps { - if (session?.authToken) { + if (Object.hasOwn(session ?? {}, 'authToken')) { // Pop the concierge loading page before opening the concierge report. Navigation.isNavigationReady().then(() => { Navigation.goBack(ROUTES.HOME); From 9bd8f5d7fde9b899b6007597d1864fe26ab37b42 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Thu, 4 Jan 2024 16:29:47 -0500 Subject: [PATCH 032/113] add updatemoneyrequestdescription --- src/libs/actions/IOU.js | 16 ++++++++++++++++ src/pages/EditRequestPage.js | 19 +++++++++++-------- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 21997023fbc8..12ad2d007067 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1069,6 +1069,21 @@ function updateMoneyRequestTag(transactionID, transactionThreadReportID, tag) { API.write('UpdateMoneyRequestTag', params, onyxData); } +/** + * Updates the description of a money request + * + * @param {String} transactionID + * @param {Number} transactionThreadReportID + * @param {String} comment + */ +function updateMoneyRequestDescription(transactionID, transactionThreadReportID, comment) { + const transactionChanges = { + comment, + }; + const {params, onyxData} = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, true); + API.write('UpdateMoneyRequestDescription', params, onyxData); +} + /** * Edits an existing distance request * @@ -3510,6 +3525,7 @@ export { updateMoneyRequestDate, updateMoneyRequestTag, updateMoneyRequestAmountAndCurrency, + updateMoneyRequestDescription, replaceReceipt, detachReceipt, getIOUReportID, diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 54c5202fb205..1d8b175ae23a 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -162,18 +162,21 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT [transactionTag, transaction.transactionID, report.reportID], ); + const saveComment = useCallback( + ({comment: newComment}) => { + // Update comment only if it has changed + if (newComment.trim() !== transactionDescription) { + IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim()); + } + Navigation.dismissModal(); + } + ) + if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { return ( { - // In case the comment hasn't been changed, do not make the API request. - if (transactionChanges.comment.trim() === transactionDescription) { - Navigation.dismissModal(); - return; - } - editMoneyRequest({comment: transactionChanges.comment.trim()}); - }} + onSubmit={saveComment} /> ); } From e5594e91c22840105889e065f27ca74f49106115 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Thu, 4 Jan 2024 18:27:32 -0500 Subject: [PATCH 033/113] update comment --- src/libs/actions/IOU.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 12ad2d007067..d4ca15929675 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1055,7 +1055,7 @@ function updateMoneyRequestDate(transactionID, transactionThreadReportID, val) { } /** - * Updates the created date of a money request + * Updates the tag of a money request * * @param {String} transactionID * @param {Number} transactionThreadReportID From fc886b72359680e01ae26e4836e0a6fb1af9bbc9 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Thu, 4 Jan 2024 20:46:09 -0500 Subject: [PATCH 034/113] minor style and clarifications --- src/pages/EditRequestPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 1d8b175ae23a..c351efcf1bdc 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -164,13 +164,13 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT const saveComment = useCallback( ({comment: newComment}) => { - // Update comment only if it has changed + // Only update comment if it has changed if (newComment.trim() !== transactionDescription) { IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim()); } Navigation.dismissModal(); } - ) + ); if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { return ( From cd9e8ee5295417223ffe3b92c0910095e2a6e345 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 5 Jan 2024 14:25:53 +0100 Subject: [PATCH 035/113] lint --- src/components/HoldMenuSectionList.tsx | 7 ++++--- src/components/ProcessMoneyRequestHoldMenu.tsx | 4 ++-- src/components/TextPill.tsx | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx index 9a9857f037f2..24c5bb663cf9 100644 --- a/src/components/HoldMenuSectionList.tsx +++ b/src/components/HoldMenuSectionList.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import {ImageSourcePropType, View} from 'react-native'; -import {SvgProps} from 'react-native-svg'; +import type {ImageSourcePropType} from 'react-native'; +import { View} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import {TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import Icon from './Icon'; import * as Illustrations from './Icon/Illustrations'; import Text from './Text'; diff --git a/src/components/ProcessMoneyRequestHoldMenu.tsx b/src/components/ProcessMoneyRequestHoldMenu.tsx index be72fdb98a8b..1b711633ed3b 100644 --- a/src/components/ProcessMoneyRequestHoldMenu.tsx +++ b/src/components/ProcessMoneyRequestHoldMenu.tsx @@ -4,9 +4,9 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Button from './Button'; import HoldMenuSectionList from './HoldMenuSectionList'; -import {PopoverAnchorPosition} from './Modal/types'; +import type {PopoverAnchorPosition} from './Modal/types'; import Popover from './Popover'; -import {AnchorAlignment} from './Popover/types'; +import type {AnchorAlignment} from './Popover/types'; import Text from './Text'; import TextPill from './TextPill'; diff --git a/src/components/TextPill.tsx b/src/components/TextPill.tsx index 035ae1dd42d8..6d473b189534 100644 --- a/src/components/TextPill.tsx +++ b/src/components/TextPill.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; // eslint-disable-next-line no-restricted-imports import useThemeStyles from '@hooks/useThemeStyles'; import colors from '@styles/theme/colors'; From 39ecbe26f62c980a2a92290acf696885d0fded69 Mon Sep 17 00:00:00 2001 From: Alberto Date: Fri, 5 Jan 2024 14:30:23 +0100 Subject: [PATCH 036/113] prettier --- src/components/HoldMenuSectionList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/HoldMenuSectionList.tsx b/src/components/HoldMenuSectionList.tsx index 24c5bb663cf9..cbaad537647b 100644 --- a/src/components/HoldMenuSectionList.tsx +++ b/src/components/HoldMenuSectionList.tsx @@ -1,6 +1,6 @@ import React from 'react'; import type {ImageSourcePropType} from 'react-native'; -import { View} from 'react-native'; +import {View} from 'react-native'; import type {SvgProps} from 'react-native-svg'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; From 92f51e2c735c18d79890718799e3ec7d178fede5 Mon Sep 17 00:00:00 2001 From: BhuvaneshPatil Date: Fri, 5 Jan 2024 19:56:06 +0530 Subject: [PATCH 037/113] Select members from ONYS on page open, remove them on unmounting --- src/pages/workspace/WorkspaceInvitePage.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/pages/workspace/WorkspaceInvitePage.js b/src/pages/workspace/WorkspaceInvitePage.js index 589c4971506b..a8aaefeeb812 100644 --- a/src/pages/workspace/WorkspaceInvitePage.js +++ b/src/pages/workspace/WorkspaceInvitePage.js @@ -54,6 +54,7 @@ const propTypes = { }).isRequired, isLoadingReportData: PropTypes.bool, + invitedEmailsToAccountIDsDraft: PropTypes.objectOf(PropTypes.number), ...policyPropTypes, }; @@ -61,6 +62,7 @@ const defaultProps = { personalDetails: {}, betas: [], isLoadingReportData: true, + invitedEmailsToAccountIDsDraft: {}, ...policyDefaultProps, }; @@ -78,7 +80,10 @@ function WorkspaceInvitePage(props) { useEffect(() => { setSearchTerm(SearchInputManager.searchInput); - }, []); + return () => { + Policy.setWorkspaceInviteMembersDraft(props.route.params.policyID, {}); + }; + }, [props.route.params.policyID]); useEffect(() => { Policy.clearErrors(props.route.params.policyID); @@ -102,6 +107,12 @@ function WorkspaceInvitePage(props) { _.each(inviteOptions.personalDetails, (detail) => (detailsMap[detail.login] = OptionsListUtils.formatMemberForList(detail))); const newSelectedOptions = []; + _.each(_.keys(props.invitedEmailsToAccountIDsDraft), (login) => { + if (!_.has(detailsMap, login)) { + return; + } + newSelectedOptions.push({...detailsMap[login], isSelected: true}); + }); _.each(selectedOptions, (option) => { newSelectedOptions.push(_.has(detailsMap, option.login) ? {...detailsMap[option.login], isSelected: true} : option); }); @@ -318,5 +329,8 @@ export default compose( isLoadingReportData: { key: ONYXKEYS.IS_LOADING_REPORT_DATA, }, + invitedEmailsToAccountIDsDraft: { + key: ({route}) => `${ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT}${route.params.policyID.toString()}`, + }, }), )(WorkspaceInvitePage); From 01b9f03ab67c38979f2c02b07bb91189675f2332 Mon Sep 17 00:00:00 2001 From: Nikki Wines Date: Fri, 5 Jan 2024 13:29:45 -0500 Subject: [PATCH 038/113] include dependencies for callback --- src/pages/EditRequestPage.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/EditRequestPage.js b/src/pages/EditRequestPage.js index 1d8b175ae23a..4efbdeb7e841 100644 --- a/src/pages/EditRequestPage.js +++ b/src/pages/EditRequestPage.js @@ -169,8 +169,9 @@ function EditRequestPage({report, route, parentReport, policyCategories, policyT IOU.updateMoneyRequestDescription(transaction.transactionID, report.reportID, newComment.trim()); } Navigation.dismissModal(); - } - ) + }, + [transactionDescription, transaction.transactionID, report.reportID], + ); if (fieldToEdit === CONST.EDIT_REQUEST_FIELD.DESCRIPTION) { return ( From 26b8460a214b339d82fa099c21c9f10743f0be72 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Sat, 6 Jan 2024 02:27:43 +0500 Subject: [PATCH 039/113] fix: use policy report fields array --- src/pages/home/report/ReportActionItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 44c7e0f60ea6..93835416eeed 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -615,7 +615,7 @@ function ReportActionItem(props) { From 627f8243ede0b671efb6af7ce93d13626e0904ee Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Mon, 8 Jan 2024 08:49:33 +0100 Subject: [PATCH 040/113] apply suggested changes --- src/components/ConfirmModal.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index c3b97a9bf7d4..b8578de23be7 100755 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -14,7 +14,7 @@ type ConfirmModalProps = { onConfirm: () => void; /** A callback to call when the form has been closed */ - onCancel?: (ref?: React.RefObject | undefined) => void; + onCancel?: (ref?: React.RefObject) => void; /** Modal visibility */ isVisible: boolean; @@ -50,15 +50,12 @@ type ConfirmModalProps = { iconSource?: IconAsset; /** Styles for title */ - // eslint-disable-next-line react/forbid-prop-types titleStyles?: StyleProp; /** Styles for prompt */ - // eslint-disable-next-line react/forbid-prop-types promptStyles?: StyleProp; /** Styles for icon */ - // eslint-disable-next-line react/forbid-prop-types iconAdditionalStyles?: StyleProp; /** Whether to center the icon / text content */ @@ -81,15 +78,16 @@ function ConfirmModal({ title = '', iconSource, onModalHide = () => {}, - titleStyles = [], - iconAdditionalStyles = [], - promptStyles = [], + titleStyles, + iconAdditionalStyles, + promptStyles, shouldCenterContent = false, shouldStackButtons = true, isVisible, onConfirm, }: ConfirmModalProps) { const {isSmallScreenWidth} = useWindowDimensions(); + return ( Date: Mon, 8 Jan 2024 14:56:59 +0100 Subject: [PATCH 041/113] apply same styles as workspaces page --- src/pages/ReferralDetailsPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index 209b8f5fadc3..b369ea86eefa 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -71,8 +71,8 @@ function ReferralDetailsPage({route, account}) { headerContainerStyles={[styles.staticHeaderImage, styles.justifyContentEnd]} backgroundColor={theme.PAGE_THEMES[SCREENS.RIGHT_MODAL.REFERRAL].backgroundColor} > - {contentHeader} - {contentBody} + {contentHeader} + {contentBody} {shouldShowClipboard && ( Date: Mon, 8 Jan 2024 15:21:40 +0100 Subject: [PATCH 042/113] increase horizontal spacing --- src/pages/ReferralDetailsPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index b369ea86eefa..ab01758864c1 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -71,8 +71,8 @@ function ReferralDetailsPage({route, account}) { headerContainerStyles={[styles.staticHeaderImage, styles.justifyContentEnd]} backgroundColor={theme.PAGE_THEMES[SCREENS.RIGHT_MODAL.REFERRAL].backgroundColor} > - {contentHeader} - {contentBody} + {contentHeader} + {contentBody} {shouldShowClipboard && ( Date: Tue, 9 Jan 2024 14:58:16 +0700 Subject: [PATCH 043/113] fix duplicated in LHN when deleted members was invited --- 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 0d7658adf180..ddb03bcc334c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3551,7 +3551,7 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID: if (!report?.participantAccountIDs) { return false; } - const sortedParticipanctsAccountIDs = report.parentReportActionIDs?.sort(); + const sortedParticipanctsAccountIDs = report.participantAccountIDs?.sort(); // Only return the room if it has all the participants and is not a policy room return report.policyID === policyID && lodashIsEqual(newParticipantList, sortedParticipanctsAccountIDs); }) ?? null From 6a370652a87572c9565c4781b2c15b6ec19d1396 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Tue, 9 Jan 2024 09:32:13 +0100 Subject: [PATCH 044/113] fix linting --- src/components/ConfirmModal.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ConfirmModal.tsx b/src/components/ConfirmModal.tsx index b8578de23be7..a345ec72ad11 100755 --- a/src/components/ConfirmModal.tsx +++ b/src/components/ConfirmModal.tsx @@ -1,8 +1,9 @@ -import React, {ReactNode} from 'react'; -import {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import useWindowDimensions from '@hooks/useWindowDimensions'; import CONST from '@src/CONST'; -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; import ConfirmContent from './ConfirmContent'; import Modal from './Modal'; From 9f107a7849cbe6f39de996dba01e2f27ad3d060f Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 9 Jan 2024 12:34:31 +0100 Subject: [PATCH 045/113] replace icon --- .../product-illustrations/payment-hands.svg | 255 +++++++++++++++++- 1 file changed, 254 insertions(+), 1 deletion(-) diff --git a/assets/images/product-illustrations/payment-hands.svg b/assets/images/product-illustrations/payment-hands.svg index bf76b528ee76..bd317fdfabe8 100644 --- a/assets/images/product-illustrations/payment-hands.svg +++ b/assets/images/product-illustrations/payment-hands.svg @@ -1 +1,254 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 36f3abd147b32092f175bfee5ffdbbe2af6c9187 Mon Sep 17 00:00:00 2001 From: Mykhailo Kravchenko Date: Tue, 9 Jan 2024 12:34:53 +0100 Subject: [PATCH 046/113] set icon width by platform --- src/pages/ReferralDetailsPage.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index ab01758864c1..adde91af49b7 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -13,7 +13,9 @@ import useLocalize from '@hooks/useLocalize'; import useSingleExecution from '@hooks/useSingleExecution'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import Clipboard from '@libs/Clipboard'; +import getPlatform from '@libs/getPlatform'; import * as Link from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -43,9 +45,12 @@ const defaultProps = { function ReferralDetailsPage({route, account}) { const theme = useTheme(); const styles = useThemeStyles(); + const {windowWidth} = useWindowDimensions(); const {translate} = useLocalize(); const popoverAnchor = useRef(null); const {isExecuting, singleExecution} = useSingleExecution(); + const platform = getPlatform(); + const isNative = platform === CONST.PLATFORM.IOS || platform === CONST.PLATFORM.ANDROID; let {contentType} = route.params; if (!_.includes(_.values(CONST.REFERRAL_PROGRAM.CONTENT_TYPES), contentType)) { @@ -64,7 +69,7 @@ function ReferralDetailsPage({route, account}) { headerContent={ } From 35c5d0fdf26f259504c7917a403948ed3e3abb1d Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 9 Jan 2024 15:47:56 +0000 Subject: [PATCH 047/113] chore(typescript): add missing import type --- src/pages/ConciergePage.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/ConciergePage.tsx b/src/pages/ConciergePage.tsx index 0ec0efa96ce5..17e9aeafbd14 100644 --- a/src/pages/ConciergePage.tsx +++ b/src/pages/ConciergePage.tsx @@ -1,14 +1,15 @@ import {useFocusEffect} from '@react-navigation/native'; import type {StackScreenProps} from '@react-navigation/stack'; import React from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Navigation from '@libs/Navigation/Navigation'; import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as Report from '@userActions/Report'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; +import type SCREENS from '@src/SCREENS'; import type {Session} from '@src/types/onyx'; type ConciergePageOnyxProps = { From 3e2413ecfe28d49b9629102666817fe80a213067 Mon Sep 17 00:00:00 2001 From: Pedro Guerreiro Date: Tue, 9 Jan 2024 15:50:19 +0000 Subject: [PATCH 048/113] chore(typescript): add missing import type --- src/pages/LogInWithShortLivedAuthTokenPage.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/LogInWithShortLivedAuthTokenPage.tsx b/src/pages/LogInWithShortLivedAuthTokenPage.tsx index 5e95f31d1d6c..c5f8a9c20d5b 100644 --- a/src/pages/LogInWithShortLivedAuthTokenPage.tsx +++ b/src/pages/LogInWithShortLivedAuthTokenPage.tsx @@ -1,7 +1,8 @@ -import {StackScreenProps} from '@react-navigation/stack'; +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useEffect} from 'react'; import {View} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -15,7 +16,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PublicScreensParamList} from '@libs/Navigation/types'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; -import SCREENS from '@src/SCREENS'; +import type SCREENS from '@src/SCREENS'; import type {Account} from '@src/types/onyx'; type LogInWithShortLivedAuthTokenPageOnyxProps = { From 68d981572bf20fb6c62cd22e65b559e03ea0e0b7 Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 14:54:55 -0300 Subject: [PATCH 049/113] making requests not count scanning receipts --- src/components/ReportActionItem/ReportPreview.js | 2 +- src/languages/en.ts | 3 ++- src/languages/es.ts | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 27447a10a32b..4d41b522f148 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -165,7 +165,7 @@ function ReportPreview(props) { const previewSubtitle = formattedMerchant || props.translate('iou.requestCount', { - count: numberOfRequests, + count: numberOfRequests - numberOfScanningReceipts, scanningReceipts: numberOfScanningReceipts, }); diff --git a/src/languages/en.ts b/src/languages/en.ts index 1e84c8ea46be..55d680273e80 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1,4 +1,5 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; +import Str from 'expensify-common/lib/str'; import CONST from '@src/CONST'; import type { AddressLineParams, @@ -584,7 +585,7 @@ export default { receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.', transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.', - requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, deleteRequest: 'Delete request', deleteConfirmation: 'Are you sure that you want to delete this request?', settledExpensify: 'Paid', diff --git a/src/languages/es.ts b/src/languages/es.ts index eda992589f69..2802ec33b39b 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,4 +1,5 @@ import CONST from '@src/CONST'; +import Str from 'expensify-common/lib/str'; import type { AddressLineParams, AlreadySignedInParams, @@ -577,7 +578,7 @@ export default { receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', transactionPendingText: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', - requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, deleteRequest: 'Eliminar pedido', deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?', settledExpensify: 'Pagado', From 36f6ef8e21fe0faceb3f5f957d1f060fe18b29ab Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 18:10:20 -0300 Subject: [PATCH 050/113] making sure props.action gets update --- src/components/ReportActionItem/ReportPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 4d41b522f148..2a58369d0b63 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -241,7 +241,7 @@ function ReportPreview(props) { unsubscribeOnyxTransaction(); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [props.action]); const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(props.chatReport); const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(props.policy, 'role') === CONST.POLICY.ROLE.ADMIN; From 8cf72a7aebd0888fa28276d25e5ba6f81376dc80 Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 19:14:53 -0300 Subject: [PATCH 051/113] moving reportpreview updates from useEffect to useMemo --- .../ReportActionItem/ReportPreview.js | 39 ++++++------------- src/languages/en.ts | 3 +- src/languages/es.ts | 5 ++- 3 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 2a58369d0b63..da3dd0f2b078 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -13,6 +13,7 @@ import refPropTypes from '@components/refPropTypes'; import SettlementButton from '@components/SettlementButton'; import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; +import transactionPropTypes from '@components/transactionPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -23,7 +24,6 @@ import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; -import onyxSubscribe from '@libs/onyxSubscribe'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -107,6 +107,9 @@ const propTypes = { /** Whether a message is a whisper */ isWhisper: PropTypes.bool, + /** All the transactions, used to update ReportPreview label and status */ + transactions: PropTypes.objectOf(transactionPropTypes), + ...withLocalizePropTypes, }; @@ -123,6 +126,7 @@ const defaultProps = { policy: { isHarvestingEnabled: false, }, + transactions: {}, }; function ReportPreview(props) { @@ -131,10 +135,10 @@ function ReportPreview(props) { const {getLineHeightStyle} = useStyleUtils(); const {translate} = useLocalize(); - const [hasMissingSmartscanFields, sethasMissingSmartscanFields] = useState(false); - const [areAllRequestsBeingSmartScanned, setAreAllRequestsBeingSmartScanned] = useState(false); - const [hasOnlyDistanceRequests, setHasOnlyDistanceRequests] = useState(false); - const [hasNonReimbursableTransactions, setHasNonReimbursableTransactions] = useState(false); + const hasMissingSmartscanFields = useMemo(() => ReportUtils.hasMissingSmartscanFields(props.iouReportID), [props.transactions]); + const areAllRequestsBeingSmartScanned = useMemo(() => ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action), [props.transactions]); + const hasOnlyDistanceRequests = useMemo(() => ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID), [props.transactions]); + const hasNonReimbursableTransactions = useMemo(() => ReportUtils.hasNonReimbursableTransactions(props.iouReportID), [props.transactions]); const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); @@ -221,28 +225,6 @@ function ReportPreview(props) { const bankAccountRoute = ReportUtils.getBankAccountRoute(props.chatReport); - useEffect(() => { - const unsubscribeOnyxTransaction = onyxSubscribe({ - key: ONYXKEYS.COLLECTION.TRANSACTION, - waitForCollectionCallback: true, - callback: (allTransactions) => { - if (_.isEmpty(allTransactions)) { - return; - } - - sethasMissingSmartscanFields(ReportUtils.hasMissingSmartscanFields(props.iouReportID)); - setAreAllRequestsBeingSmartScanned(ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action)); - setHasOnlyDistanceRequests(ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID)); - setHasNonReimbursableTransactions(ReportUtils.hasNonReimbursableTransactions(props.iouReportID)); - }, - }); - - return () => { - unsubscribeOnyxTransaction(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.action]); - const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicyExpenseChat(props.chatReport); const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && lodashGet(props.policy, 'role') === CONST.POLICY.ROLE.ADMIN; const isPayer = isPaidGroupPolicy @@ -373,5 +355,8 @@ export default compose( session: { key: ONYXKEYS.SESSION, }, + transactions: { + key: ONYXKEYS.COLLECTION.TRANSACTION, + }, }), )(ReportPreview); diff --git a/src/languages/en.ts b/src/languages/en.ts index c1df0bddf358..7ff001a447e1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -585,7 +585,8 @@ export default { receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", receiptScanningFailed: 'Receipt scanning failed. Enter the details manually.', transactionPendingText: 'It takes a few days from the date the card was used for the transaction to post.', - requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => + `${count} ${Str.pluralize('request', 'requests', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, deleteRequest: 'Delete request', deleteConfirmation: 'Are you sure that you want to delete this request?', settledExpensify: 'Paid', diff --git a/src/languages/es.ts b/src/languages/es.ts index a32808a690a9..81b1df377499 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1,5 +1,5 @@ -import CONST from '@src/CONST'; import Str from 'expensify-common/lib/str'; +import CONST from '@src/CONST'; import type { AddressLineParams, AlreadySignedInParams, @@ -578,7 +578,8 @@ export default { receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', receiptScanningFailed: 'El escaneo de recibo ha fallado. Introduce los detalles manualmente.', transactionPendingText: 'La transacción tarda unos días en contabilizarse desde la fecha en que se utilizó la tarjeta.', - requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => + `${count} ${Str.pluralize('solicitude', 'solicitudes', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, deleteRequest: 'Eliminar pedido', deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?', settledExpensify: 'Pagado', From fcd96fcf7796ac196f27d2215dfd6915bb62757a Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 20:40:09 -0300 Subject: [PATCH 052/113] removing unused imports --- src/components/ReportActionItem/ReportPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index da3dd0f2b078..ab234c2e8ec4 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useEffect, useMemo, useState} from 'react'; +import React, {useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; From e3cab9926b1ef6cccf128d723287c082cee7dc2f Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 20:44:51 -0300 Subject: [PATCH 053/113] make use memo depend on every property needed --- src/components/ReportActionItem/ReportPreview.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index ab234c2e8ec4..cbdb1d38d496 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -135,10 +135,14 @@ function ReportPreview(props) { const {getLineHeightStyle} = useStyleUtils(); const {translate} = useLocalize(); - const hasMissingSmartscanFields = useMemo(() => ReportUtils.hasMissingSmartscanFields(props.iouReportID), [props.transactions]); - const areAllRequestsBeingSmartScanned = useMemo(() => ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action), [props.transactions]); - const hasOnlyDistanceRequests = useMemo(() => ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID), [props.transactions]); - const hasNonReimbursableTransactions = useMemo(() => ReportUtils.hasNonReimbursableTransactions(props.iouReportID), [props.transactions]); + const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyDistanceRequests, hasNonReimbursableTransactions} = useMemo(() => { + return { + hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(props.iouReportID), + areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action), + hasOnlyDistanceRequests: ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID), + hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(props.iouReportID), + }; + }, [props.transactions, props.iouReportID, props.action]); const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); From afcd7597f51fe1cd3cb94aab8b739d17e68a2368 Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 20:56:59 -0300 Subject: [PATCH 054/113] disabling exhaustive-deps lint --- src/components/ReportActionItem/ReportPreview.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index cbdb1d38d496..30e4cb78ccf1 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -142,6 +142,7 @@ function ReportPreview(props) { hasOnlyDistanceRequests: ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID), hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(props.iouReportID), }; + // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to run when transactions get updated }, [props.transactions, props.iouReportID, props.action]); const managerID = props.iouReport.managerID || 0; From 81d19652c6e653ec271b0ef6c647b7846002de3a Mon Sep 17 00:00:00 2001 From: Rodrigo Lino da Costa Date: Tue, 9 Jan 2024 21:17:46 -0300 Subject: [PATCH 055/113] lint/prettier --- src/components/ReportActionItem/ReportPreview.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 30e4cb78ccf1..9c2e9916730c 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -135,15 +135,16 @@ function ReportPreview(props) { const {getLineHeightStyle} = useStyleUtils(); const {translate} = useLocalize(); - const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyDistanceRequests, hasNonReimbursableTransactions} = useMemo(() => { - return { + const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyDistanceRequests, hasNonReimbursableTransactions} = useMemo( + () => ({ hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(props.iouReportID), areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action), hasOnlyDistanceRequests: ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID), hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(props.iouReportID), - }; + }), // eslint-disable-next-line react-hooks/exhaustive-deps -- we just want this to run when transactions get updated - }, [props.transactions, props.iouReportID, props.action]); + [props.transactions, props.iouReportID, props.action], + ); const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); From 20371372d2cc7abcab47153c93c23a3f5169c31f Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 10 Jan 2024 08:31:12 +0100 Subject: [PATCH 056/113] fix: imports and removed optional chaining where it was not necessary --- src/pages/home/report/ReportAttachments.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pages/home/report/ReportAttachments.tsx b/src/pages/home/report/ReportAttachments.tsx index cd8f809378cc..42ca51cabe0b 100644 --- a/src/pages/home/report/ReportAttachments.tsx +++ b/src/pages/home/report/ReportAttachments.tsx @@ -1,12 +1,12 @@ -import {StackScreenProps} from '@react-navigation/stack'; +import type {StackScreenProps} from '@react-navigation/stack'; import React, {useCallback} from 'react'; import AttachmentModal from '@components/AttachmentModal'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import Navigation from '@libs/Navigation/Navigation'; -import {AuthScreensParamList} from '@libs/Navigation/types'; +import type {AuthScreensParamList} from '@libs/Navigation/types'; import * as ReportUtils from '@libs/ReportUtils'; import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; +import type SCREENS from '@src/SCREENS'; type File = { name: string; @@ -24,7 +24,7 @@ type Attachment = { type ReportAttachmentsProps = StackScreenProps; function ReportAttachments({route}: ReportAttachmentsProps) { - const reportID = route.params?.reportID; + const reportID = route.params.reportID; const report = ReportUtils.getReport(reportID); // In native the imported images sources are of type number. Ref: https://reactnative.dev/docs/image#imagesource From 1ca9b76e86363c4f723a838a4b1c696f0920cca6 Mon Sep 17 00:00:00 2001 From: Github Date: Tue, 9 Jan 2024 12:14:39 +0100 Subject: [PATCH 057/113] refactor some Reassure tests --- tests/perf-test/OptionsSelector.perf-test.js | 2 +- .../perf-test/ReportActionsUtils.perf-test.ts | 205 ++++++------- tests/perf-test/ReportUtils.perf-test.ts | 287 ++++++++---------- tests/perf-test/SidebarLinks.perf-test.js | 155 +++++----- tests/perf-test/SidebarUtils.perf-test.ts | 99 +++--- 5 files changed, 339 insertions(+), 409 deletions(-) diff --git a/tests/perf-test/OptionsSelector.perf-test.js b/tests/perf-test/OptionsSelector.perf-test.js index 557a0baf1ba4..3236a468b216 100644 --- a/tests/perf-test/OptionsSelector.perf-test.js +++ b/tests/perf-test/OptionsSelector.perf-test.js @@ -84,7 +84,7 @@ test('[OptionsSelector] should render 1 section', () => { measurePerformance(, {runs}); }); -test('[OptionsSelector] should render mutliple sections', () => { +test('[OptionsSelector] should render multiple sections', () => { const sections = generateSections(mutlipleSectionsConfig); measurePerformance(, {runs}); }); diff --git a/tests/perf-test/ReportActionsUtils.perf-test.ts b/tests/perf-test/ReportActionsUtils.perf-test.ts index ea3b48bacf47..2a96a5959942 100644 --- a/tests/perf-test/ReportActionsUtils.perf-test.ts +++ b/tests/perf-test/ReportActionsUtils.perf-test.ts @@ -9,18 +9,6 @@ import createCollection from '../utils/collections/createCollection'; import createRandomReportAction from '../utils/collections/reportActions'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - }), -); - -// Clear out Onyx after each test so that each test starts with a clean slate -afterEach(() => { - Onyx.clear(); -}); - const getMockedReportActionsMap = (reportsLength = 10, actionsPerReportLength = 100) => { const mockReportActions = Array.from({length: actionsPerReportLength}, (v, i) => { const reportActionKey = i + 1; @@ -49,120 +37,113 @@ const reportId = '1'; const runs = CONST.PERFORMANCE_TESTS.RUNS; -/** - * This function will be executed 20 times and the average time will be used on the comparison. - * It will fail based on the CI configuration around Reassure: - * @see /.github/workflows/reassurePerformanceTests.yml - * - * Max deviation on the duration is set to 20% at the time of writing. - * - * More on the measureFunction API: - * @see https://callstack.github.io/reassure/docs/api#measurefunction-function - */ -test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => { - await Onyx.multiSet({ - ...mockedReportActionsMap, +describe('ReportActionsUtils', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + }); + + Onyx.multiSet({ + ...mockedReportActionsMap, + }); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs}); -}); + afterAll(() => { + Onyx.clear(); + }); -test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => { - const parentReportActionId = '1'; - const fakeParentAction = reportActions[parentReportActionId]; - const actionsToMerge = { - [parentReportActionId]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - previousMessage: fakeParentAction.message, - message: [ - { - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }, - ], - errors: null, - linkMetaData: [], - }, - } as unknown as ReportActions; - - await Onyx.multiSet({ - ...mockedReportActionsMap, + /** + * This function will be executed 20 times and the average time will be used on the comparison. + * It will fail based on the CI configuration around Reassure: + * @see /.github/workflows/reassurePerformanceTests.yml + * + * Max deviation on the duration is set to 20% at the time of writing. + * + * More on the measureFunction API: + * @see https://callstack.github.io/reassure/docs/api#measurefunction-function + */ + test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs}); -}); -test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { - const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getLastVisibleAction on 10k reportActions with actionsToMerge', async () => { + const parentReportActionId = '1'; + const fakeParentAction = reportActions[parentReportActionId]; + const actionsToMerge = { + [parentReportActionId]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + previousMessage: fakeParentAction.message, + message: [ + { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + ], + errors: null, + linkMetaData: [], + }, + } as unknown as ReportActions; + + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getLastVisibleAction(reportId, actionsToMerge), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs}); -}); -test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => { - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getMostRecentIOURequestActionID on 10k ReportActions', async () => { + const reportActionsArray = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getMostRecentIOURequestActionID(reportActionsArray), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs}); -}); -test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => { - const parentReportActionId = '1'; - const fakeParentAction = reportActions[parentReportActionId]; - const actionsToMerge = { - [parentReportActionId]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - previousMessage: fakeParentAction.message, - message: [ - { - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }, - ], - errors: null, - linkMetaData: [], - }, - } as unknown as ReportActions; - - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs}); -}); -test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getLastVisibleMessage on 10k ReportActions with actionsToMerge', async () => { + const parentReportActionId = '1'; + const fakeParentAction = reportActions[parentReportActionId]; + const actionsToMerge = { + [parentReportActionId]: { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + previousMessage: fakeParentAction.message, + message: [ + { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + ], + errors: null, + linkMetaData: [], + }, + } as unknown as ReportActions; + + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getLastVisibleMessage(reportId, actionsToMerge), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs}); -}); -test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getSortedReportActionsForDisplay on 10k ReportActions', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions), {runs}); + }); + + test('[ReportActionsUtils] getLastClosedReportAction on 10k ReportActions', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getLastClosedReportAction(reportActions), {runs}); -}); -test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => { - await Onyx.multiSet({ - ...mockedReportActionsMap, + test('[ReportActionsUtils] getMostRecentReportActionLastModified', async () => { + await waitForBatchedUpdates(); + await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportActionsUtils.getMostRecentReportActionLastModified(), {runs}); }); diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index 62b911fb0417..30f874c669ab 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -13,19 +13,6 @@ import createRandomTransaction from '../utils/collections/transaction'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const runs = CONST.PERFORMANCE_TESTS.RUNS; - -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - }), -); - -// Clear out Onyx after each test so that each test starts with a clean state -afterEach(() => { - Onyx.clear(); -}); - const getMockedReports = (length = 500) => createCollection( (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, @@ -50,195 +37,169 @@ const mockedReportsMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COL const mockedPoliciesMap = getMockedPolicies(5000) as Record<`${typeof ONYXKEYS.COLLECTION.POLICY}`, Policy>; const participantAccountIDs = Array.from({length: 1000}, (v, i) => i + 1); -test('[ReportUtils] findLastAccessedReport on 2k reports and policies', async () => { - const ignoreDomainRooms = true; - const isFirstTimeNewExpensifyUser = true; - const reports = getMockedReports(2000); - const policies = getMockedPolicies(2000); - const openOnAdminRoom = true; - - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs}); -}); +describe('ReportUtils', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + }); + + Onyx.multiSet({ + ...mockedPoliciesMap, + ...mockedReportsMap, + }); + }); -test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => { - const reportID = '1'; + afterAll(() => { + Onyx.clear(); + }); - const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction; + test('[ReportUtils] findLastAccessedReport on 2k reports and policies', async () => { + const ignoreDomainRooms = true; + const isFirstTimeNewExpensifyUser = true; + const reports = getMockedReports(2000); + const policies = getMockedPolicies(2000); + const openOnAdminRoom = true; - await Onyx.multiSet({ - ...mockedPoliciesMap, - ...mockedReportsMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.findLastAccessedReport(reports, ignoreDomainRooms, policies, isFirstTimeNewExpensifyUser, openOnAdminRoom), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs}); -}); + test('[ReportUtils] canDeleteReportAction on 5k reports and policies', async () => { + const reportID = '1'; + const reportAction = {...createRandomReportAction(1), actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT} as unknown as ReportAction; -test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => { - const report = {...createRandomReport(1), participantAccountIDs}; - const currentLoginAccountID = 1; - - await Onyx.multiSet({ - ...mockedReportsMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.canDeleteReportAction(reportAction, reportID), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs}); -}); - -test('[ReportUtils] getIconsForParticipants on 1k participants', async () => { - const participants = Array.from({length: 1000}, (v, i) => i + 1); + test('[ReportUtils] getReportRecipientAccountID on 1k participants', async () => { + const report = {...createRandomReport(1), participantAccountIDs}; + const currentLoginAccountID = 1; - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs}); -}); + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportRecipientAccountIDs(report, currentLoginAccountID), {runs}); + }); -test('[ReportUtils] getIcons on 1k participants', async () => { - const report = {...createRandomReport(1), parentReportID: '1', parentReportActionID: '1', type: CONST.REPORT.TYPE.CHAT}; - const policy = createRandomPolicy(1); - const defaultIcon = null; - const defaultName = ''; - const defaultIconId = -1; + test('[ReportUtils] getIconsForParticipants on 1k participants', async () => { + const participants = Array.from({length: 1000}, (v, i) => i + 1); - await Onyx.multiSet({ - [ONYXKEYS.PERSONAL_DETAILS_LIST]: personalDetails, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIconsForParticipants(participants, personalDetails), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs}); -}); - -test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => { - const isMultipleParticipantReport = true; - const shouldFallbackToHidden = true; + test('[ReportUtils] getIcons on 1k participants', async () => { + const report = {...createRandomReport(1), parentReportID: '1', parentReportActionID: '1', type: CONST.REPORT.TYPE.CHAT}; + const policy = createRandomPolicy(1); + const defaultIcon = null; + const defaultName = ''; + const defaultIconId = -1; - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs}); -}); + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIcons(report, personalDetails, defaultIcon, defaultName, defaultIconId, policy), {runs}); + }); -test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => { - const reportAction = createRandomReportAction(1); - const report = createRandomReport(1); - const policy = createRandomPolicy(1); - const shouldConsiderReceiptBeingScanned = true; - const isPreviewMessageForParentChatReport = true; + test('[ReportUtils] getDisplayNamesWithTooltips 1k participants', async () => { + const isMultipleParticipantReport = true; + const shouldFallbackToHidden = true; - await Onyx.multiSet({ - ...mockedPoliciesMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getDisplayNamesWithTooltips(personalDetails, isMultipleParticipantReport, shouldFallbackToHidden), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs}); -}); + test('[ReportUtils] getReportPreviewMessage on 5k policies', async () => { + const reportAction = createRandomReportAction(1); + const report = createRandomReport(1); + const policy = createRandomPolicy(1); + const shouldConsiderReceiptBeingScanned = true; + const isPreviewMessageForParentChatReport = true; -test('[ReportUtils] getReportName on 1k participants', async () => { - const report = {...createRandomReport(1), chatType: undefined, participantAccountIDs}; - const policy = createRandomPolicy(1); + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportPreviewMessage(report, reportAction, shouldConsiderReceiptBeingScanned, isPreviewMessageForParentChatReport, policy), {runs}); + }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getReportName(report, policy), {runs}); -}); + test('[ReportUtils] getReportName on 1k participants', async () => { + const report = {...createRandomReport(1), chatType: undefined, participantAccountIDs}; + const policy = createRandomPolicy(1); -test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => { - const report = {...createRandomReport(1), participantAccountIDs}; - const accountID = 1; - await Onyx.multiSet({ - ...mockedReportsMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getReportName(report, policy), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs}); -}); - -test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => { - const report = {...createRandomReport(1), participantAccountIDs, type: CONST.REPORT.TYPE.CHAT}; - const currentReportId = '2'; - const isInGSDMode = true; - const betas = [CONST.BETAS.DEFAULT_ROOMS]; - const policies = getMockedPolicies(); + test('[ReportUtils] canShowReportRecipientLocalTime on 1k participants', async () => { + const report = {...createRandomReport(1), participantAccountIDs}; + const accountID = 1; - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs}); -}); + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.canShowReportRecipientLocalTime(personalDetails, report, accountID), {runs}); + }); -test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { - const report = createRandomReport(1); - const policy = createRandomPolicy(1); + test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => { + const report = {...createRandomReport(1), participantAccountIDs, type: CONST.REPORT.TYPE.CHAT}; + const currentReportId = '2'; + const isInGSDMode = true; + const betas = [CONST.BETAS.DEFAULT_ROOMS]; + const policies = getMockedPolicies(); - await Onyx.multiSet({ - ...mockedPoliciesMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs}); -}); - -test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { - const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true}; - const policy = createRandomPolicy(1); - const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); + test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { + const report = createRandomReport(1); + const policy = createRandomPolicy(1); - await Onyx.multiSet({ - ...mockedPoliciesMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceIcon(report, policy), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs}); -}); - -test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => { - const report = createRandomReport(1); + test('[ReportUtils] getMoneyRequestOptions on 1k participants', async () => { + const report = {...createRandomReport(1), type: CONST.REPORT.TYPE.CHAT, chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true}; + const policy = createRandomPolicy(1); + const reportParticipants = Array.from({length: 1000}, (v, i) => i + 1); - await Onyx.multiSet({ - ...mockedPoliciesMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getMoneyRequestOptions(report, policy, reportParticipants), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs}); -}); -test('[ReportUtils] getWorkspaceChat on 5k policies', async () => { - const policyID = '1'; - const accountsID = Array.from({length: 20}, (v, i) => i + 1); + test('[ReportUtils] getWorkspaceAvatar on 5k policies', async () => { + const report = createRandomReport(1); - await Onyx.multiSet({ - ...mockedReportsMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceAvatar(report), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs}); -}); - -test('[ReportUtils] getTransactionDetails on 5k reports', async () => { - const transaction = createRandomTransaction(1); + test('[ReportUtils] getWorkspaceChat on 5k policies', async () => { + const policyID = '1'; + const accountsID = Array.from({length: 20}, (v, i) => i + 1); - await Onyx.multiSet({ - ...mockedReportsMap, + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getWorkspaceChats(policyID, accountsID), {runs}); }); - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs}); -}); + test('[ReportUtils] getTransactionDetails on 5k reports', async () => { + const transaction = createRandomTransaction(1); + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getTransactionDetails(transaction, 'yyyy-MM-dd'), {runs}); + }); -test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => { - const reportAction = { - ...createRandomReportAction(1), - actionName: CONST.REPORT.ACTIONS.TYPE.IOU, - originalMessage: { - IOUReportID: '1', - IOUTransactionID: '1', - amount: 100, - participantAccountID: 1, - currency: CONST.CURRENCY.USD, - type: CONST.IOU.REPORT_ACTION_TYPE.PAY, - paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - }, - }; - - await Onyx.multiSet({ - ...mockedPoliciesMap, - }); - - await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs}); + test('[ReportUtils] getIOUReportActionDisplayMessage on 5k policies', async () => { + const reportAction = { + ...createRandomReportAction(1), + actionName: CONST.REPORT.ACTIONS.TYPE.IOU, + originalMessage: { + IOUReportID: '1', + IOUTransactionID: '1', + amount: 100, + participantAccountID: 1, + currency: CONST.CURRENCY.USD, + type: CONST.IOU.REPORT_ACTION_TYPE.PAY, + paymentType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + }, + }; + + await waitForBatchedUpdates(); + await measureFunction(() => ReportUtils.getIOUReportActionDisplayMessage(reportAction), {runs}); + }); }); diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js index 1f529b08e6b3..1bc674045c23 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.js @@ -15,25 +15,6 @@ jest.mock('../../src/components/Icon/Expensicons'); jest.mock('@react-navigation/native'); -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - registerStorageEventListener: () => {}, - }), -); - -// Initialize the network key for OfflineWithFeedback -beforeEach(() => { - wrapOnyxWithWaitForBatchedUpdates(Onyx); - return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); -}); - -// Clear out Onyx after each test so that each test starts with a clean slate -afterEach(() => { - Onyx.clear(); -}); - const getMockedReportsMap = (length = 100) => { const mockReports = Array.from({length}, (__, i) => { const reportID = i + 1; @@ -51,71 +32,81 @@ const mockedResponseMap = getMockedReportsMap(500); const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('[SidebarLinks] should render Sidebar with 500 reports stored', () => { - const scenario = async () => { - // Query for the sidebar - await screen.findByTestId('lhn-options-list'); - /** - * Query for display names of participants [1, 2]. - * This will ensure that the sidebar renders a list of items. - */ - await screen.findAllByText('One, Two'); - }; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, - ...mockedResponseMap, - }), - ) - .then(() => measurePerformance(, {scenario, runs})); -}); +describe('SidebarLinks', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + registerStorageEventListener: () => {}, + }); + + Onyx.multiSet({ + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, + [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], + [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, + [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, + ...mockedResponseMap, + }); + }); -test('[SidebarLinks] should scroll and click some of the items', () => { - const scenario = async () => { - const eventData = { - nativeEvent: { - contentOffset: { - y: variables.optionRowHeight * 5, - }, - contentSize: { - // Dimensions of the scrollable content - height: variables.optionRowHeight * 10, - width: 100, - }, - layoutMeasurement: { - // Dimensions of the device - height: variables.optionRowHeight * 5, - width: 100, + // Initialize the network key for OfflineWithFeedback + beforeEach(() => { + wrapOnyxWithWaitForBatchedUpdates(Onyx); + return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); + }); + + afterAll(() => { + Onyx.clear(); + }); + + test('[SidebarLinks] should render Sidebar with 500 reports stored', async () => { + const scenario = async () => { + // Query for the sidebar + await screen.findByTestId('lhn-options-list'); + /** + * Query for display names of participants [1, 2]. + * This will ensure that the sidebar renders a list of items. + */ + await screen.findAllByText('One, Two'); + }; + + await waitForBatchedUpdates(); + await measurePerformance(, {scenario, runs}); + }); + + test('[SidebarLinks] should scroll and click some of the items', async () => { + const scenario = async () => { + const eventData = { + nativeEvent: { + contentOffset: { + y: variables.optionRowHeight * 5, + }, + contentSize: { + // Dimensions of the scrollable content + height: variables.optionRowHeight * 10, + width: 100, + }, + layoutMeasurement: { + // Dimensions of the device + height: variables.optionRowHeight * 5, + width: 100, + }, }, - }, + }; + + const lhnOptionsList = await screen.findByTestId('lhn-options-list'); + + fireEvent.scroll(lhnOptionsList, eventData); + // find elements that are currently visible in the viewport + const button1 = await screen.findByTestId('7'); + const button2 = await screen.findByTestId('8'); + fireEvent.press(button1); + fireEvent.press(button2); }; - const lhnOptionsList = await screen.findByTestId('lhn-options-list'); - - fireEvent.scroll(lhnOptionsList, eventData); - // find elements that are currently visible in the viewport - const button1 = await screen.findByTestId('7'); - const button2 = await screen.findByTestId('8'); - fireEvent.press(button1); - fireEvent.press(button2); - }; - - return waitForBatchedUpdates() - .then(() => - Onyx.multiSet({ - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.DEFAULT, - [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, - [ONYXKEYS.BETAS]: [CONST.BETAS.DEFAULT_ROOMS], - [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, - [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, - ...mockedResponseMap, - }), - ) - .then(() => measurePerformance(, {scenario, runs})); + await waitForBatchedUpdates(); + + await measurePerformance(, {scenario, runs}); + }); }); diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 6722cbf493a5..6ca81796d3ac 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -15,18 +15,6 @@ import createRandomReportAction from '../utils/collections/reportActions'; import createRandomReport from '../utils/collections/reports'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; -beforeAll(() => - Onyx.init({ - keys: ONYXKEYS, - safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], - }), -); - -// Clear out Onyx after each test so that each test starts with a clean slate -afterEach(() => { - Onyx.clear(); -}); - const getMockedReports = (length = 500) => createCollection( (item) => `${ONYXKEYS.COLLECTION.REPORT}${item.reportID}`, @@ -47,52 +35,61 @@ const personalDetails = createCollection( const mockedResponseMap = getMockedReports(5000) as Record<`${typeof ONYXKEYS.COLLECTION.REPORT}`, Report>; const runs = CONST.PERFORMANCE_TESTS.RUNS; -test('[SidebarUtils] getOptionData on 5k reports', async () => { - const report = createRandomReport(1); - const preferredLocale = 'en'; - const policy = createRandomPolicy(1); - const parentReportAction = createRandomReportAction(1); +describe('SidebarUtils', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS], + }); - Onyx.multiSet({ - ...mockedResponseMap, + Onyx.multiSet({ + ...mockedResponseMap, + }); }); - await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs}); -}); + afterAll(() => { + Onyx.clear(); + }); -test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => { - const currentReportId = '1'; - const allReports = getMockedReports(); - const betas = [CONST.BETAS.DEFAULT_ROOMS]; + test('[SidebarUtils] getOptionData on 5k reports', async () => { + const report = createRandomReport(1); + const preferredLocale = 'en'; + const policy = createRandomPolicy(1); + const parentReportAction = createRandomReportAction(1); - const policies = createCollection( - (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, - (index) => createRandomPolicy(index), - ); + await waitForBatchedUpdates(); + await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs}); + }); + + test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => { + const currentReportId = '1'; + const allReports = getMockedReports(); + const betas = [CONST.BETAS.DEFAULT_ROOMS]; - const allReportActions = Object.fromEntries( - Object.keys(reportActions).map((key) => [ - key, - [ - { - errors: reportActions[key].errors ?? [], - message: [ - { - moderationDecision: { - decision: reportActions[key].message?.[0]?.moderationDecision?.decision, + const policies = createCollection( + (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, + (index) => createRandomPolicy(index), + ); + + const allReportActions = Object.fromEntries( + Object.keys(reportActions).map((key) => [ + key, + [ + { + errors: reportActions[key].errors ?? [], + message: [ + { + moderationDecision: { + decision: reportActions[key].message?.[0]?.moderationDecision?.decision, + }, }, - }, - ], - }, - ], - ]), - ) as unknown as OnyxCollection; + ], + }, + ], + ]), + ) as unknown as OnyxCollection; - Onyx.multiSet({ - ...mockedResponseMap, + await waitForBatchedUpdates(); + await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions), {runs}); }); - - await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions), {runs}); }); From 11942e755df07c08267359e036cf40b63d57c3b7 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Jan 2024 16:13:05 +0700 Subject: [PATCH 058/113] migrate Error page to Typescript --- .../ErrorBodyText/{index.js => index.tsx} | 17 +++++++---------- .../{index.website.js => index.website.tsx} | 0 ...nericErrorPage.js => GenericErrorPage.tsx} | 17 +++++++---------- .../{NotFoundPage.js => NotFoundPage.tsx} | 19 +++++++------------ 4 files changed, 21 insertions(+), 32 deletions(-) rename src/pages/ErrorPage/ErrorBodyText/{index.js => index.tsx} (54%) rename src/pages/ErrorPage/ErrorBodyText/{index.website.js => index.website.tsx} (100%) rename src/pages/ErrorPage/{GenericErrorPage.js => GenericErrorPage.tsx} (91%) rename src/pages/ErrorPage/{NotFoundPage.js => NotFoundPage.tsx} (55%) diff --git a/src/pages/ErrorPage/ErrorBodyText/index.js b/src/pages/ErrorPage/ErrorBodyText/index.tsx similarity index 54% rename from src/pages/ErrorPage/ErrorBodyText/index.js rename to src/pages/ErrorPage/ErrorBodyText/index.tsx index 47b765f8f5e8..44c4f0ee2968 100644 --- a/src/pages/ErrorPage/ErrorBodyText/index.js +++ b/src/pages/ErrorPage/ErrorBodyText/index.tsx @@ -1,29 +1,26 @@ import React from 'react'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -const propTypes = { - ...withLocalizePropTypes, -}; - -function ErrorBodyText(props) { +function ErrorBodyText() { const styles = useThemeStyles(); + const {translate} = useLocalize(); return ( - {`${props.translate('genericErrorPage.body.helpTextMobile')} `} + {`${translate('genericErrorPage.body.helpTextMobile')} `} - {props.translate('genericErrorPage.body.helpTextWeb')} + {translate('genericErrorPage.body.helpTextWeb')} ); } ErrorBodyText.displayName = 'ErrorBodyText'; -ErrorBodyText.propTypes = propTypes; -export default withLocalize(ErrorBodyText); + +export default ErrorBodyText; diff --git a/src/pages/ErrorPage/ErrorBodyText/index.website.js b/src/pages/ErrorPage/ErrorBodyText/index.website.tsx similarity index 100% rename from src/pages/ErrorPage/ErrorBodyText/index.website.js rename to src/pages/ErrorPage/ErrorBodyText/index.website.tsx diff --git a/src/pages/ErrorPage/GenericErrorPage.js b/src/pages/ErrorPage/GenericErrorPage.tsx similarity index 91% rename from src/pages/ErrorPage/GenericErrorPage.js rename to src/pages/ErrorPage/GenericErrorPage.tsx index 56fb5b970084..a05ef7954ba9 100644 --- a/src/pages/ErrorPage/GenericErrorPage.js +++ b/src/pages/ErrorPage/GenericErrorPage.tsx @@ -9,7 +9,7 @@ import ImageSVG from '@components/ImageSVG'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -18,20 +18,18 @@ import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ErrorBodyText from './ErrorBodyText'; -const propTypes = { - ...withLocalizePropTypes, -}; - -function GenericErrorPage({translate}) { +function GenericErrorPage() { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); + const {resetBoundary} = useErrorBoundary(); return ( {({paddingBottom}) => ( - + @@ -78,7 +76,7 @@ function GenericErrorPage({translate}) { - + void; }; // eslint-disable-next-line rulesdir/no-negated-variables -function NotFoundPage(props) { +function NotFoundPage({onBackButtonPress = () => Navigation.goBack(ROUTES.HOME)}: NotFoundPageProps) { return ( Navigation.dismissModal()} /> ); } NotFoundPage.displayName = 'NotFoundPage'; -NotFoundPage.propTypes = propTypes; -NotFoundPage.defaultProps = defaultProps; export default NotFoundPage; From e35690c510c657e5fdd85a57b155171eb42c7134 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Jan 2024 16:34:59 +0700 Subject: [PATCH 059/113] change style to styles --- src/pages/ErrorPage/GenericErrorPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ErrorPage/GenericErrorPage.tsx b/src/pages/ErrorPage/GenericErrorPage.tsx index a05ef7954ba9..0d7fcca26954 100644 --- a/src/pages/ErrorPage/GenericErrorPage.tsx +++ b/src/pages/ErrorPage/GenericErrorPage.tsx @@ -76,7 +76,7 @@ function GenericErrorPage() { - + Date: Wed, 10 Jan 2024 10:59:24 +0100 Subject: [PATCH 060/113] update path --- src/pages/ReferralDetailsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReferralDetailsPage.js b/src/pages/ReferralDetailsPage.js index adde91af49b7..95d3960b6d9a 100644 --- a/src/pages/ReferralDetailsPage.js +++ b/src/pages/ReferralDetailsPage.js @@ -77,7 +77,7 @@ function ReferralDetailsPage({route, account}) { backgroundColor={theme.PAGE_THEMES[SCREENS.RIGHT_MODAL.REFERRAL].backgroundColor} > {contentHeader} - {contentBody} + {contentBody} {shouldShowClipboard && ( Date: Wed, 10 Jan 2024 17:06:21 +0700 Subject: [PATCH 061/113] remove unnecessary style --- src/pages/ErrorPage/GenericErrorPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ErrorPage/GenericErrorPage.tsx b/src/pages/ErrorPage/GenericErrorPage.tsx index 0d7fcca26954..f4f1d91418c7 100644 --- a/src/pages/ErrorPage/GenericErrorPage.tsx +++ b/src/pages/ErrorPage/GenericErrorPage.tsx @@ -76,7 +76,7 @@ function GenericErrorPage() { - + Date: Wed, 10 Jan 2024 17:56:04 +0700 Subject: [PATCH 062/113] make some props as optional --- src/components/BlockingViews/FullPageNotFoundView.tsx | 4 ++-- src/pages/ErrorPage/ErrorBodyText/index.tsx | 1 + src/pages/ErrorPage/NotFoundPage.tsx | 5 +---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index 5993e60861f5..807029addf5e 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -33,10 +33,10 @@ type FullPageNotFoundViewProps = { linkKey?: TranslationPaths; /** Method to trigger when pressing the back button of the header */ - onBackButtonPress: () => void; + onBackButtonPress?: () => void; /** Function to call when pressing the navigation link */ - onLinkPress: () => void; + onLinkPress?: () => void; }; // eslint-disable-next-line rulesdir/no-negated-variables diff --git a/src/pages/ErrorPage/ErrorBodyText/index.tsx b/src/pages/ErrorPage/ErrorBodyText/index.tsx index 44c4f0ee2968..d9ece1ccc35b 100644 --- a/src/pages/ErrorPage/ErrorBodyText/index.tsx +++ b/src/pages/ErrorPage/ErrorBodyText/index.tsx @@ -8,6 +8,7 @@ import CONST from '@src/CONST'; function ErrorBodyText() { const styles = useThemeStyles(); const {translate} = useLocalize(); + return ( {`${translate('genericErrorPage.body.helpTextMobile')} `} diff --git a/src/pages/ErrorPage/NotFoundPage.tsx b/src/pages/ErrorPage/NotFoundPage.tsx index 968b4865a7d8..a324b048119a 100644 --- a/src/pages/ErrorPage/NotFoundPage.tsx +++ b/src/pages/ErrorPage/NotFoundPage.tsx @@ -1,21 +1,18 @@ import React from 'react'; import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView'; import ScreenWrapper from '@components/ScreenWrapper'; -import Navigation from '@libs/Navigation/Navigation'; -import ROUTES from '@src/ROUTES'; type NotFoundPageProps = { onBackButtonPress?: () => void; }; // eslint-disable-next-line rulesdir/no-negated-variables -function NotFoundPage({onBackButtonPress = () => Navigation.goBack(ROUTES.HOME)}: NotFoundPageProps) { +function NotFoundPage({onBackButtonPress}: NotFoundPageProps) { return ( Navigation.dismissModal()} /> ); From 6950bbb66297ddb9964cbe259469fb2ff02f7d2d Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Jan 2024 17:57:46 +0700 Subject: [PATCH 063/113] fix lint --- src/pages/ErrorPage/ErrorBodyText/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ErrorPage/ErrorBodyText/index.tsx b/src/pages/ErrorPage/ErrorBodyText/index.tsx index d9ece1ccc35b..e675e0447361 100644 --- a/src/pages/ErrorPage/ErrorBodyText/index.tsx +++ b/src/pages/ErrorPage/ErrorBodyText/index.tsx @@ -8,7 +8,7 @@ import CONST from '@src/CONST'; function ErrorBodyText() { const styles = useThemeStyles(); const {translate} = useLocalize(); - + return ( {`${translate('genericErrorPage.body.helpTextMobile')} `} From 98dac168619fd9133300f0403ad57e1aab93ceb1 Mon Sep 17 00:00:00 2001 From: Julian Kobrynski Date: Wed, 10 Jan 2024 13:06:44 +0100 Subject: [PATCH 064/113] migrate ButtonWithDropdownMenu to TypeScript --- ...downMenu.js => ButtonWithDropdownMenu.tsx} | 148 +++++++++--------- src/components/PopoverMenu.tsx | 2 +- 2 files changed, 75 insertions(+), 75 deletions(-) rename src/components/{ButtonWithDropdownMenu.js => ButtonWithDropdownMenu.tsx} (54%) diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.tsx similarity index 54% rename from src/components/ButtonWithDropdownMenu.js rename to src/components/ButtonWithDropdownMenu.tsx index 4d3ec8796a31..8cd1086a9392 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.tsx @@ -1,89 +1,89 @@ -import PropTypes from 'prop-types'; +import type {RefObject} from 'react'; import React, {useEffect, useRef, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; import {View} from 'react-native'; -import _ from 'underscore'; +import type {SvgProps} from 'react-native-svg'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {AnchorPosition} from '@styles/index'; import CONST from '@src/CONST'; import Button from './Button'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import sourcePropTypes from './Image/sourcePropTypes'; +import type {AnchorAlignment} from './Popover/types'; import PopoverMenu from './PopoverMenu'; -const propTypes = { +type DropdownOption = { + value: string; + text: string; + icon: React.FC; + iconWidth?: number; + iconHeight?: number; + iconDescription?: string; +}; + +type ButtonWithDropdownMenuProps = { /** Text to display for the menu header */ - menuHeaderText: PropTypes.string, + menuHeaderText?: string; /** Callback to execute when the main button is pressed */ - onPress: PropTypes.func.isRequired, + onPress: (event: GestureResponderEvent | KeyboardEvent | undefined, value: string) => void; /** Call the onPress function on main button when Enter key is pressed */ - pressOnEnter: PropTypes.bool, + pressOnEnter?: boolean; /** Whether we should show a loading state for the main button */ - isLoading: PropTypes.bool, + isLoading?: boolean; /** The size of button size */ - buttonSize: PropTypes.oneOf(_.values(CONST.DROPDOWN_BUTTON_SIZE)), + buttonSize: ValueOf; /** Should the confirmation button be disabled? */ - isDisabled: PropTypes.bool, + isDisabled?: boolean; /** Additional styles to add to the component */ - style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + style?: StyleProp; /** Menu options to display */ /** e.g. [{text: 'Pay with Expensify', icon: Wallet}] */ - options: PropTypes.arrayOf( - PropTypes.shape({ - value: PropTypes.string.isRequired, - text: PropTypes.string.isRequired, - icon: sourcePropTypes, - iconWidth: PropTypes.number, - iconHeight: PropTypes.number, - iconDescription: PropTypes.string, - }), - ).isRequired, + options: DropdownOption[]; /** The anchor alignment of the popover menu */ - anchorAlignment: PropTypes.shape({ - horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), - vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), - }), + anchorAlignment?: AnchorAlignment; /* ref for the button */ - buttonRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + buttonRef: RefObject; }; -const defaultProps = { - isLoading: false, - isDisabled: false, - pressOnEnter: false, - menuHeaderText: '', - style: [], - buttonSize: CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, - anchorAlignment: { +function ButtonWithDropdownMenu({ + isLoading = false, + isDisabled = false, + pressOnEnter = false, + menuHeaderText = '', + style = [], + buttonSize = CONST.DROPDOWN_BUTTON_SIZE.MEDIUM, + anchorAlignment = { horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, // we assume that popover menu opens below the button, anchor is at TOP }, - buttonRef: () => {}, -}; - -function ButtonWithDropdownMenu(props) { + buttonRef, + onPress, + options, +}: ButtonWithDropdownMenuProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const [selectedItemIndex, setSelectedItemIndex] = useState(0); const [isMenuVisible, setIsMenuVisible] = useState(false); - const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); + const [popoverAnchorPosition, setPopoverAnchorPosition] = useState(null); const {windowWidth, windowHeight} = useWindowDimensions(); - const caretButton = useRef(null); - const selectedItem = props.options[selectedItemIndex] || _.first(props.options); - const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(props.buttonSize); - const isButtonSizeLarge = props.buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; + const caretButton = useRef(null); + const selectedItem = options[selectedItemIndex] || options[0]; + const innerStyleDropButton = StyleUtils.getDropDownButtonHeight(buttonSize); + const isButtonSizeLarge = buttonSize === CONST.DROPDOWN_BUTTON_SIZE.LARGE; useEffect(() => { if (!caretButton.current) { @@ -92,29 +92,31 @@ function ButtonWithDropdownMenu(props) { if (!isMenuVisible) { return; } - caretButton.current.measureInWindow((x, y, w, h) => { - setPopoverAnchorPosition({ - horizontal: x + w, - vertical: - props.anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP - ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding - : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + if ('measureInWindow' in caretButton.current) { + caretButton.current.measureInWindow((x, y, w, h) => { + setPopoverAnchorPosition({ + horizontal: x + w, + vertical: + anchorAlignment.vertical === CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP + ? y + h + CONST.MODAL.POPOVER_MENU_PADDING // if vertical anchorAlignment is TOP, menu will open below the button and we need to add the height of button and padding + : y - CONST.MODAL.POPOVER_MENU_PADDING, // if it is BOTTOM, menu will open above the button so NO need to add height but DO subtract padding + }); }); - }); - }, [windowWidth, windowHeight, isMenuVisible, props.anchorAlignment.vertical]); + } + }, [windowWidth, windowHeight, isMenuVisible, anchorAlignment.vertical]); return ( - {props.options.length > 1 ? ( - + {options.length > 1 ? ( +