From 8eb68aaef5890a7055e09d8f3023cd18d4dc9940 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Fri, 25 Aug 2023 02:50:10 +0530 Subject: [PATCH 01/99] Fix LHN display issue for money request via scan --- .../LHNOptionsList/OptionRowLHNData.js | 20 +++++++- src/hooks/useDeepCompareMemo.js | 26 ++++++++++ src/libs/ReportUtils.js | 48 +++++++++++++++++++ .../home/report/ReportActionItemSingle.js | 2 +- 4 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 src/hooks/useDeepCompareMemo.js diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 21fade6eb942..acf5a5523b27 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -12,9 +12,12 @@ import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDe import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './OptionRowLHN'; import * as Report from '../../libs/actions/Report'; import * as UserUtils from '../../libs/UserUtils'; +import * as ReportUtils from '../../libs/ReportUtils'; + import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; +import useDeepCompareMemo from '../../hooks/useDeepCompareMemo'; const propTypes = { /** If true will disable ever setting the OptionRowLHN to focused */ @@ -75,6 +78,7 @@ function OptionRowLHNData({ preferredLocale, comment, policies, + receiptTransactions, parentReportActions, ...propsToForward }) { @@ -88,6 +92,15 @@ function OptionRowLHNData({ const parentReportAction = parentReportActions[fullReport.parentReportActionID]; const optionItemRef = useRef(); + + const lastTransaction = useMemo(() => { + const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); + const lastTransaction = _.first(transactionsWithReceipts); + return lastTransaction; + }, [fullReport.reportID, receiptTransactions]); + + let memoizedLastTransaction = useDeepCompareMemo(lastTransaction); + const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy); @@ -98,7 +111,7 @@ function OptionRowLHNData({ return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); + }, [fullReport, memoizedLastTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { @@ -186,6 +199,11 @@ export default React.memo( key: ({fullReport}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${fullReport.parentReportID}`, canEvict: false, }, + // Ideally, we aim to access only the last transaction for the current report by listening to changes in reportActions. + // In some scenarios, a transaction might be created after reportActions have been modified. + // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. + // However, performance overhead of this is minimized by using memos inside the component. + receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, }), )(OptionRowLHNData), ); diff --git a/src/hooks/useDeepCompareMemo.js b/src/hooks/useDeepCompareMemo.js new file mode 100644 index 000000000000..ebed9ffdeebe --- /dev/null +++ b/src/hooks/useDeepCompareMemo.js @@ -0,0 +1,26 @@ +import {useRef} from 'react'; +import _ from 'lodash'; + +/** + * Custom hook to memoize a value based on deep comparison. + * Returns the previous value if the current value is deeply equal to the previous one. + * + * @function + * @template T + * @param {T} value - The value to be memoized. + * @returns {T} - The memoized value. Returns the previous value if the current value is deeply equal to the previous one. + * @example + * + * const object = { a: 1, b: 2 }; + * const memoizedObject = useDeepCompareMemo(object); + */ +export default function useDeepCompareMemo(value) { + const ref = useRef(); // Holds the previous value + + // If the new value is not deeply equal to the old value, update the ref + if (!_.isEqual(value, ref.current)) { + ref.current = value; + } + + return ref.current; +} diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 681495350069..798f4b2eec36 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1213,6 +1213,26 @@ function getMoneyRequestReportName(report, policy = undefined) { amount: formattedAmount, }); + // get lastReportActionTransaction + const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); + const filteredReportActions = _.filter(sortedReportActions, (reportAction) => { + return reportAction.pendingAction !== 'delete'; + }); + const lastReportAction = _.first(filteredReportActions); + const lastReportActionTransaction = TransactionUtils.getLinkedTransaction(lastReportAction); + + // get lastTransaction + // eslint-disable-next-line no-use-before-define + const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); + const lastTransaction = _.first(transactionsWithReceipts); + + const transactionIsLastReportAction = _.isEqual(lastReportActionTransaction, lastTransaction); + + if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } + if (report.isWaitingOnBankAccount) { return `${payerPaidAmountMesssage} • ${Localize.translateLocal('iou.pending')}`; } @@ -1274,6 +1294,33 @@ function getTransactionsWithReceipts(iouReportID) { ); } +/** + * Gets all sorted transactions on an IOU report with a receipt and whose pending action is not delete + * + * @param {Object|null} iouReportID + * @returns {[Object]} + */ +function getSortedTransactionsWithReceipts(iouReportID) { + const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); + const filteredSortedReportActions = _.filter(sortedReportActions, (reportAction) => { + return reportAction.pendingAction !== 'delete'; + }); + return _.reduce( + filteredSortedReportActions, + (transactions, action) => { + if (ReportActionsUtils.isMoneyRequestAction(action)) { + const transaction = TransactionUtils.getLinkedTransaction(action); + if (TransactionUtils.hasReceipt(transaction)) { + transactions.push(transaction); + } + } + return transactions; + }, + [], + ); +} + /** * For report previews, we display a "Receipt scan in progress" indicator * instead of the report total only when we have no report total ready to show. This is the case when @@ -3445,4 +3492,5 @@ export { areAllRequestsBeingSmartScanned, getReportPreviewDisplayTransactions, getTransactionsWithReceipts, + getSortedTransactionsWithReceipts, }; diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 80f49f177b6f..768076e7730b 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -151,7 +151,7 @@ function ReportActionItemSingle(props) { } showUserDetails(props.action.delegateAccountID ? props.action.delegateAccountID : actorAccountID); } - }, [isWorkspaceActor, props.report.reportID, actorAccountID, props.action.delegateAccountID, props.iouReport, displayAllActors]); + }, [isWorkspaceActor, props.report && props.report.reportID, actorAccountID, props.action.delegateAccountID, props.iouReport && props.iouReport.reportID, displayAllActors]); const shouldDisableDetailPage = useMemo( () => !isWorkspaceActor && ReportUtils.isOptimisticPersonalDetail(props.action.delegateAccountID ? props.action.delegateAccountID : actorAccountID), From 958b1ca33ecd43507c4e0e8361cab351a4892298 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Fri, 25 Aug 2023 04:41:10 +0530 Subject: [PATCH 02/99] Linting fixes --- src/components/LHNOptionsList/OptionRowLHNData.js | 5 ++--- src/libs/ReportUtils.js | 9 +++------ src/pages/home/report/ReportActionItemSingle.js | 9 ++++++--- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index acf5a5523b27..bd7735be4a14 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -95,11 +95,10 @@ function OptionRowLHNData({ const lastTransaction = useMemo(() => { const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); - const lastTransaction = _.first(transactionsWithReceipts); - return lastTransaction; + return _.first(transactionsWithReceipts); }, [fullReport.reportID, receiptTransactions]); - let memoizedLastTransaction = useDeepCompareMemo(lastTransaction); + const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 798f4b2eec36..4022d03da0ad 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1216,9 +1216,8 @@ function getMoneyRequestReportName(report, policy = undefined) { // get lastReportActionTransaction const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const filteredReportActions = _.filter(sortedReportActions, (reportAction) => { - return reportAction.pendingAction !== 'delete'; - }); + const filteredReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== 'delete'); + const lastReportAction = _.first(filteredReportActions); const lastReportActionTransaction = TransactionUtils.getLinkedTransaction(lastReportAction); @@ -1303,9 +1302,7 @@ function getTransactionsWithReceipts(iouReportID) { function getSortedTransactionsWithReceipts(iouReportID) { const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const filteredSortedReportActions = _.filter(sortedReportActions, (reportAction) => { - return reportAction.pendingAction !== 'delete'; - }); + const filteredSortedReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== 'delete'); return _.reduce( filteredSortedReportActions, (transactions, action) => { diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index 768076e7730b..d9dcb4446d9b 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -140,18 +140,21 @@ function ReportActionItemSingle(props) { ] : props.action.person; + const reportID = props.report && props.report.reportID; + const iouReportID = props.report && props.report.reportID; + const showActorDetails = useCallback(() => { if (isWorkspaceActor) { - showWorkspaceDetails(props.report.reportID); + showWorkspaceDetails(reportID); } else { // Show participants page IOU report preview if (displayAllActors) { - Navigation.navigate(ROUTES.getReportParticipantsRoute(props.iouReport.reportID)); + Navigation.navigate(ROUTES.getReportParticipantsRoute(iouReportID)); return; } showUserDetails(props.action.delegateAccountID ? props.action.delegateAccountID : actorAccountID); } - }, [isWorkspaceActor, props.report && props.report.reportID, actorAccountID, props.action.delegateAccountID, props.iouReport && props.iouReport.reportID, displayAllActors]); + }, [isWorkspaceActor, reportID, actorAccountID, props.action.delegateAccountID, iouReportID, displayAllActors]); const shouldDisableDetailPage = useMemo( () => !isWorkspaceActor && ReportUtils.isOptimisticPersonalDetail(props.action.delegateAccountID ? props.action.delegateAccountID : actorAccountID), From c735418548076ff00583711c472e6b36ca59a0df Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Fri, 25 Aug 2023 05:07:26 +0530 Subject: [PATCH 03/99] Eslint fixes --- src/components/LHNOptionsList/OptionRowLHNData.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index bd7735be4a14..83d59a24d5e1 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -96,6 +96,7 @@ function OptionRowLHNData({ const lastTransaction = useMemo(() => { const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); return _.first(transactionsWithReceipts); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions]); const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); From d7c3568e0dd856b9f27a35b356f6befdcc31c02e Mon Sep 17 00:00:00 2001 From: kadiealexander <59587260+kadiealexander@users.noreply.github.com> Date: Tue, 29 Aug 2023 12:25:41 +1200 Subject: [PATCH 04/99] Add instructions for Distance and Scan expenses --- docs/articles/request-money/Request-and-Split-Bills.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/articles/request-money/Request-and-Split-Bills.md b/docs/articles/request-money/Request-and-Split-Bills.md index a2c63cf6f8f7..550b749f486c 100644 --- a/docs/articles/request-money/Request-and-Split-Bills.md +++ b/docs/articles/request-money/Request-and-Split-Bills.md @@ -16,7 +16,10 @@ These two features ensure you can live in the moment and settle up afterward. # How to Request Money - Select the Green **+** button and choose **Request Money** -- Enter the amount **$** they owe and click **Next** +- Select the relevant option: + - **Manual:** Input the amount and description manually + - **Scan:** Take a photo of the receipt to have the merchant/amount autofilled, and input description manually + - **Distance:** Input the address of your start/end points for your trip to calculate mileage - Search for the user or enter their email! - Enter a reason for the request (optional) - Click **Request!** From 1b09b2555faa691773189272c86c2fff6ba8b919 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Tue, 29 Aug 2023 10:40:00 +0200 Subject: [PATCH 05/99] [TS migration] Migrate 'NumberFormatUtils.js' lib to TypeScript --- src/libs/NumberFormatUtils.js | 9 --------- src/libs/NumberFormatUtils.ts | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 src/libs/NumberFormatUtils.js create mode 100644 src/libs/NumberFormatUtils.ts diff --git a/src/libs/NumberFormatUtils.js b/src/libs/NumberFormatUtils.js deleted file mode 100644 index 48e4d3dadbb6..000000000000 --- a/src/libs/NumberFormatUtils.js +++ /dev/null @@ -1,9 +0,0 @@ -function format(locale, number, options) { - return new Intl.NumberFormat(locale, options).format(number); -} - -function formatToParts(locale, number, options) { - return new Intl.NumberFormat(locale, options).formatToParts(number); -} - -export {format, formatToParts}; diff --git a/src/libs/NumberFormatUtils.ts b/src/libs/NumberFormatUtils.ts new file mode 100644 index 000000000000..8d6b1e587af5 --- /dev/null +++ b/src/libs/NumberFormatUtils.ts @@ -0,0 +1,9 @@ +function format(locale: string, number: number, options?: Intl.NumberFormatOptions) { + return new Intl.NumberFormat(locale, options).format(number); +} + +function formatToParts(locale: string, number: number, options?: Intl.NumberFormatOptions) { + return new Intl.NumberFormat(locale, options).formatToParts(number); +} + +export {format, formatToParts}; From da87d1bf51a038951b273ef116a6633d58bf9446 Mon Sep 17 00:00:00 2001 From: tienifr Date: Tue, 29 Aug 2023 23:25:01 +0700 Subject: [PATCH 06/99] fix: fail to request camera permission in scan receipt screen --- src/CONST.ts | 2 +- .../CameraPermission/index.android.js | 11 ++++++++++ .../CameraPermission/index.ios.js | 11 ++++++++++ .../ReceiptSelector/CameraPermission/index.js | 5 +++++ src/pages/iou/ReceiptSelector/index.native.js | 20 ++++++++++--------- 5 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 src/pages/iou/ReceiptSelector/CameraPermission/index.android.js create mode 100644 src/pages/iou/ReceiptSelector/CameraPermission/index.ios.js create mode 100644 src/pages/iou/ReceiptSelector/CameraPermission/index.js diff --git a/src/CONST.ts b/src/CONST.ts index 4cb4509e96a3..2bf787dcd0f6 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -454,7 +454,7 @@ const CONST = { }, RECEIPT: { ICON_SIZE: 164, - PERMISSION_AUTHORIZED: 'authorized', + PERMISSION_GRANTED: 'granted', HAND_ICON_HEIGHT: 152, HAND_ICON_WIDTH: 200, SHUTTER_SIZE: 90, diff --git a/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js new file mode 100644 index 000000000000..fb1d270fd592 --- /dev/null +++ b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js @@ -0,0 +1,11 @@ +import {PermissionsAndroid} from 'react-native'; + +function requestCameraPermission() { + return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA); +} + +function getCameraPermissionStatus() { + return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.CAMERA); +} + +export {requestCameraPermission, getCameraPermissionStatus}; diff --git a/src/pages/iou/ReceiptSelector/CameraPermission/index.ios.js b/src/pages/iou/ReceiptSelector/CameraPermission/index.ios.js new file mode 100644 index 000000000000..3c24bfa10d6f --- /dev/null +++ b/src/pages/iou/ReceiptSelector/CameraPermission/index.ios.js @@ -0,0 +1,11 @@ +import {check, PERMISSIONS, request} from 'react-native-permissions'; + +function requestCameraPermission() { + return request(PERMISSIONS.IOS.CAMERA); +} + +function getCameraPermissionStatus() { + return check(PERMISSIONS.IOS.CAMERA); +} + +export {requestCameraPermission, getCameraPermissionStatus}; diff --git a/src/pages/iou/ReceiptSelector/CameraPermission/index.js b/src/pages/iou/ReceiptSelector/CameraPermission/index.js new file mode 100644 index 000000000000..4357b592d7ef --- /dev/null +++ b/src/pages/iou/ReceiptSelector/CameraPermission/index.js @@ -0,0 +1,5 @@ +function requestCameraPermission() {} + +function getCameraPermissionStatus() {} + +export {requestCameraPermission, getCameraPermissionStatus}; diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 727cbb05d395..619d7c8945de 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -1,4 +1,4 @@ -import {ActivityIndicator, Alert, AppState, Linking, Text, View} from 'react-native'; +import {ActivityIndicator, Alert, AppState, Linking, PermissionsAndroid, Text, View} from 'react-native'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Camera, useCameraDevices} from 'react-native-vision-camera'; import lodashGet from 'lodash/get'; @@ -21,6 +21,8 @@ import useLocalize from '../../../hooks/useLocalize'; import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../../libs/Log'; import participantPropTypes from '../../../components/participantPropTypes'; +import * as CameraPermission from './CameraPermission'; +import {RESULTS} from 'react-native-permissions'; const propTypes = { /** React Navigation route */ @@ -113,7 +115,7 @@ function ReceiptSelector(props) { useEffect(() => { const subscription = AppState.addEventListener('change', (nextAppState) => { if (appState.current.match(/inactive|background/) && nextAppState === 'active') { - Camera.getCameraPermissionStatus().then((permissionStatus) => { + CameraPermission.getCameraPermissionStatus().then((permissionStatus) => { setPermissions(permissionStatus); }); } @@ -156,11 +158,11 @@ function ReceiptSelector(props) { }; const askForPermissions = () => { - if (permissions === 'not-determined') { - Camera.requestCameraPermission().then((permissionStatus) => { + if (permissions === 'denied') { + CameraPermission.requestCameraPermission().then((permissionStatus) => { setPermissions(permissionStatus); }); - } else { + } else if (['blocked', 'never_ask_again'].includes(permissions)) { Linking.openSettings(); } }; @@ -219,13 +221,13 @@ function ReceiptSelector(props) { }); }, [flash, iouType, props.iou, props.report, reportID, translate]); - Camera.getCameraPermissionStatus().then((permissionStatus) => { + CameraPermission.getCameraPermissionStatus().then((permissionStatus) => { setPermissions(permissionStatus); }); return ( - {permissions !== CONST.RECEIPT.PERMISSION_AUTHORIZED && ( + {permissions !== CONST.RECEIPT.PERMISSION_GRANTED && ( )} - {permissions === CONST.RECEIPT.PERMISSION_AUTHORIZED && device == null && ( + {permissions === CONST.RECEIPT.PERMISSION_GRANTED && device == null && ( )} - {permissions === CONST.RECEIPT.PERMISSION_AUTHORIZED && device != null && ( + {permissions === CONST.RECEIPT.PERMISSION_GRANTED && device != null && ( Date: Wed, 30 Aug 2023 00:23:05 +0700 Subject: [PATCH 07/99] fix request permission on android --- .../ReceiptSelector/CameraPermission/index.android.js | 7 ++++--- src/pages/iou/ReceiptSelector/index.native.js | 9 ++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js index fb1d270fd592..ef0d8e4bddd3 100644 --- a/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js +++ b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js @@ -1,11 +1,12 @@ -import {PermissionsAndroid} from 'react-native'; +import {check, PERMISSIONS, request} from "react-native-permissions"; function requestCameraPermission() { - return PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA); + return request(PERMISSIONS.ANDROID.CAMERA); } +// Android will never return blocked after a check, you have to request the permission to get the info. function getCameraPermissionStatus() { - return PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.CAMERA); + return check(PERMISSIONS.ANDROID.CAMERA); } export {requestCameraPermission, getCameraPermissionStatus}; diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 619d7c8945de..dcc1f24ae673 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -1,4 +1,4 @@ -import {ActivityIndicator, Alert, AppState, Linking, PermissionsAndroid, Text, View} from 'react-native'; +import {ActivityIndicator, Alert, AppState, Linking, Text, View} from 'react-native'; import React, {useCallback, useEffect, useRef, useState} from 'react'; import {Camera, useCameraDevices} from 'react-native-vision-camera'; import lodashGet from 'lodash/get'; @@ -22,7 +22,6 @@ import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../../libs/Log'; import participantPropTypes from '../../../components/participantPropTypes'; import * as CameraPermission from './CameraPermission'; -import {RESULTS} from 'react-native-permissions'; const propTypes = { /** React Navigation route */ @@ -161,8 +160,12 @@ function ReceiptSelector(props) { if (permissions === 'denied') { CameraPermission.requestCameraPermission().then((permissionStatus) => { setPermissions(permissionStatus); + // Android will never return blocked after a check, you have to request the permission to get the info. + if (permissionStatus === 'blocked') { + Linking.openSettings(); + } }); - } else if (['blocked', 'never_ask_again'].includes(permissions)) { + } else if (permissions === 'blocked') { Linking.openSettings(); } }; From d16d9792ac04cbe9efe31dc7ac68ab49aa7a4dfd Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 30 Aug 2023 01:42:00 +0700 Subject: [PATCH 08/99] use predefined constants --- .../CameraPermission/index.android.js | 2 +- src/pages/iou/ReceiptSelector/index.native.js | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js index ef0d8e4bddd3..3eb9ef4eea5a 100644 --- a/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js +++ b/src/pages/iou/ReceiptSelector/CameraPermission/index.android.js @@ -1,4 +1,4 @@ -import {check, PERMISSIONS, request} from "react-native-permissions"; +import {check, PERMISSIONS, request} from 'react-native-permissions'; function requestCameraPermission() { return request(PERMISSIONS.ANDROID.CAMERA); diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index dcc1f24ae673..e2378883a80e 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import {launchImageLibrary} from 'react-native-image-picker'; import {withOnyx} from 'react-native-onyx'; import {useIsFocused} from '@react-navigation/native'; +import {RESULTS} from 'react-native-permissions'; import PressableWithFeedback from '../../../components/Pressable/PressableWithFeedback'; import Icon from '../../../components/Icon'; import * as Expensicons from '../../../components/Icon/Expensicons'; @@ -100,7 +101,8 @@ function ReceiptSelector(props) { const camera = useRef(null); const [flash, setFlash] = useState(false); - const [permissions, setPermissions] = useState('authorized'); + const [permissions, setPermissions] = useState('granted'); + const isAndroidBlockedPermissionRef = useRef(false); const appState = useRef(AppState.currentState); const iouType = lodashGet(props.route, 'params.iouType', ''); @@ -157,16 +159,14 @@ function ReceiptSelector(props) { }; const askForPermissions = () => { - if (permissions === 'denied') { + // Android will never return blocked after a check, you have to request the permission to get the info. + if (permissions === RESULTS.BLOCKED || isAndroidBlockedPermissionRef.current) { + Linking.openSettings(); + } else if (permissions === RESULTS.DENIED) { CameraPermission.requestCameraPermission().then((permissionStatus) => { setPermissions(permissionStatus); - // Android will never return blocked after a check, you have to request the permission to get the info. - if (permissionStatus === 'blocked') { - Linking.openSettings(); - } + isAndroidBlockedPermissionRef.current = permissionStatus === RESULTS.BLOCKED; }); - } else if (permissions === 'blocked') { - Linking.openSettings(); } }; @@ -230,7 +230,7 @@ function ReceiptSelector(props) { return ( - {permissions !== CONST.RECEIPT.PERMISSION_GRANTED && ( + {permissions !== RESULTS.GRANTED && ( )} - {permissions === CONST.RECEIPT.PERMISSION_GRANTED && device == null && ( + {permissions === RESULTS.GRANTED && device == null && ( )} - {permissions === CONST.RECEIPT.PERMISSION_GRANTED && device != null && ( + {permissions === RESULTS.GRANTED && device != null && ( Date: Tue, 29 Aug 2023 21:20:41 +0200 Subject: [PATCH 09/99] [TS migration] Migrate 'setSelection' lib to TypeScript --- src/libs/setSelection/index.js | 7 ------- src/libs/setSelection/index.native.js | 7 ------- src/libs/setSelection/index.native.ts | 14 ++++++++++++++ src/libs/setSelection/index.ts | 13 +++++++++++++ src/libs/setSelection/types.ts | 5 +++++ src/types/modules/react-native.d.ts | 8 ++++++++ 6 files changed, 40 insertions(+), 14 deletions(-) delete mode 100644 src/libs/setSelection/index.js delete mode 100644 src/libs/setSelection/index.native.js create mode 100644 src/libs/setSelection/index.native.ts create mode 100644 src/libs/setSelection/index.ts create mode 100644 src/libs/setSelection/types.ts create mode 100644 src/types/modules/react-native.d.ts diff --git a/src/libs/setSelection/index.js b/src/libs/setSelection/index.js deleted file mode 100644 index c7f24ae4a199..000000000000 --- a/src/libs/setSelection/index.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function setSelection(textInput, start, end) { - if (!textInput) { - return; - } - - textInput.setSelectionRange(start, end); -} diff --git a/src/libs/setSelection/index.native.js b/src/libs/setSelection/index.native.js deleted file mode 100644 index 02d812d84cd4..000000000000 --- a/src/libs/setSelection/index.native.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function setSelection(textInput, start, end) { - if (!textInput) { - return; - } - - textInput.setSelection(start, end); -} diff --git a/src/libs/setSelection/index.native.ts b/src/libs/setSelection/index.native.ts new file mode 100644 index 000000000000..75089c6a737d --- /dev/null +++ b/src/libs/setSelection/index.native.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-console */ +import SetSelection from './types'; + +const setSelection: SetSelection = (textInput, start, end) => { + if (!textInput) { + return; + } + + if ('setSelection' in textInput) { + textInput.setSelection(start, end); + } +}; + +export default setSelection; diff --git a/src/libs/setSelection/index.ts b/src/libs/setSelection/index.ts new file mode 100644 index 000000000000..5eee88881924 --- /dev/null +++ b/src/libs/setSelection/index.ts @@ -0,0 +1,13 @@ +import SetSelection from './types'; + +const setSelection: SetSelection = (textInput, start, end) => { + if (!textInput) { + return; + } + + if ('setSelectionRange' in textInput) { + textInput.setSelectionRange(start, end); + } +}; + +export default setSelection; diff --git a/src/libs/setSelection/types.ts b/src/libs/setSelection/types.ts new file mode 100644 index 000000000000..f2717079725f --- /dev/null +++ b/src/libs/setSelection/types.ts @@ -0,0 +1,5 @@ +import {TextInput} from 'react-native'; + +type SetSelection = (textInput: TextInput | HTMLInputElement, start: number, end: number) => void; + +export default SetSelection; diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts new file mode 100644 index 000000000000..ab7e389c5397 --- /dev/null +++ b/src/types/modules/react-native.d.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ +import 'react-native'; + +declare module 'react-native' { + interface TextInput { + setSelection: (start: number, end: number) => void; + } +} From 0d8d9eea1403b83d0d12b2c92f0dd3f4ef968384 Mon Sep 17 00:00:00 2001 From: tienifr Date: Wed, 30 Aug 2023 04:36:37 +0700 Subject: [PATCH 10/99] add comment --- src/pages/iou/ReceiptSelector/index.native.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index e2378883a80e..5a486f907f37 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -159,7 +159,8 @@ function ReceiptSelector(props) { }; const askForPermissions = () => { - // Android will never return blocked after a check, you have to request the permission to get the info. + // There's no way we can check for the BLOCKED status without requesting the permission first + // https://github.com/zoontek/react-native-permissions/blob/a836e114ce3a180b2b23916292c79841a267d828/README.md?plain=1#L670 if (permissions === RESULTS.BLOCKED || isAndroidBlockedPermissionRef.current) { Linking.openSettings(); } else if (permissions === RESULTS.DENIED) { From cc6965e2c95e63781bb7adf89e7cb803c543fe0e Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 30 Aug 2023 14:09:49 +0530 Subject: [PATCH 11/99] Fix LHN subtitle display issue for money request via scan --- .../LHNOptionsList/OptionRowLHNData.js | 20 +++++++- src/libs/ReportActionsUtils.js | 16 ++++++ src/libs/ReportUtils.js | 51 +++++++++++++++++-- 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 83d59a24d5e1..778c918936f1 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -13,6 +13,7 @@ import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultPro import * as Report from '../../libs/actions/Report'; import * as UserUtils from '../../libs/UserUtils'; import * as ReportUtils from '../../libs/ReportUtils'; +import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; @@ -93,11 +94,26 @@ function OptionRowLHNData({ const optionItemRef = useRef(); + /** + * If it is an IOU report, you get the last transaction with a receipt. + * Otherwise, if the last message is a reportPreview in a chat, + * you get the last transaction of the IOU report associated with that preview message. + */ + const lastTransaction = useMemo(() => { - const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); + let reportIDLocal = fullReport.reportID; + + // Check if reportIDLocal needs to be updated with the IOU report of the last report action, in case this report is a chat. + const filteredReportActions = ReportActionUtils.getFilteredSortedReportActionsForDisplay(reportActions); + const lastReportAction = _.first(filteredReportActions); + if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { + reportIDLocal = ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction); + } + + const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(reportIDLocal); return _.first(transactionsWithReceipts); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions]); + }, [fullReport.reportID, receiptTransactions, reportActions]); const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index c90efb54785d..7e17fd80d879 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -432,6 +432,21 @@ function getSortedReportActionsForDisplay(reportActions) { return getSortedReportActions(filteredReportActions, true); } +/** + * Gets sorted and filtered report actions for display. + * + * First, it sorts the report actions using `getSortedReportActionsForDisplay`. + * Then, it filters out actions that are pending deletion. + * + * @param {Array} reportActions - The array of report actions to filter. + * @returns {Array} - The filtered and sorted array of report actions. + */ +function getFilteredSortedReportActionsForDisplay(reportActions) { + const sortedReportActions = getSortedReportActionsForDisplay(reportActions); + const filteredReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + return filteredReportActions; +} + /** * In some cases, there can be multiple closed report actions in a chat report. * This method returns the last closed report action so we can always show the correct archived report reason. @@ -643,4 +658,5 @@ export { isSplitBillAction, isTaskAction, getAllReportActions, + getFilteredSortedReportActionsForDisplay, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 93469c488635..851af6f55106 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1237,10 +1237,11 @@ function getMoneyRequestReportName(report, policy = undefined) { amount: formattedAmount, }); + /* === Start - Check for Report Scan === */ + // get lastReportActionTransaction const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const filteredReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== 'delete'); + const filteredReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); const lastReportAction = _.first(filteredReportActions); const lastReportActionTransaction = TransactionUtils.getLinkedTransaction(lastReportAction); @@ -1256,6 +1257,8 @@ function getMoneyRequestReportName(report, policy = undefined) { return Localize.translateLocal('iou.receiptScanning'); } + /* === End - Check for Report Scan === */ + if (report.isWaitingOnBankAccount) { return `${payerPaidAmountMesssage} • ${Localize.translateLocal('iou.pending')}`; } @@ -1377,8 +1380,8 @@ function getTransactionsWithReceipts(iouReportID) { */ function getSortedTransactionsWithReceipts(iouReportID) { const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - const filteredSortedReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== 'delete'); + const filteredSortedReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); + return _.reduce( filteredSortedReportActions, (transactions, action) => { @@ -1394,6 +1397,26 @@ function getSortedTransactionsWithReceipts(iouReportID) { ); } +/** + * Gets all sorted money request reportActions + * + * @param {Object|null} iouReportID + * @returns {[Object]} + */ +function getSortedMoneyRequestActions(iouReportID) { + const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); + const filteredSortedReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); + + return _.reduce( + filteredSortedReportActions, + (transactions, action) => { + if (ReportActionsUtils.isMoneyRequestAction(action)) transactions.push(action); + return transactions; + }, + [], + ); +} + /** * For report previews, we display a "Receipt scan in progress" indicator * instead of the report total only when we have no report total ready to show. This is the case when @@ -1462,6 +1485,25 @@ function getReportPreviewMessage(report, reportAction = {}) { return `approved ${formattedAmount}`; } + /* === Start - Check for Report Scan === */ + + const filteredIouReportActions = getSortedMoneyRequestActions(report.reportID); + const lastIouReportAction = _.first(filteredIouReportActions); + const lastIouReportActionTransaction = TransactionUtils.getLinkedTransaction(lastIouReportAction); + + // get lastTransaction + // eslint-disable-next-line no-use-before-define + const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); + const lastTransaction = _.first(transactionsWithReceipts); + + const transactionIsLastReportAction = _.isEqual(lastIouReportActionTransaction, lastTransaction); + + if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } + + /* === End - Check for Report Scan === */ + if (isSettled(report.reportID)) { // A settled report preview message can come in three formats "paid ... using Paypal.me", "paid ... elsewhere" or "paid ... using Expensify" let translatePhraseKey = 'iou.paidElsewhereWithAmount'; @@ -3635,4 +3677,5 @@ export { getReportPreviewDisplayTransactions, getTransactionsWithReceipts, getSortedTransactionsWithReceipts, + getSortedMoneyRequestActions, }; From 7fe7fe5c9e297e500d087637472713c491f222cd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Wed, 30 Aug 2023 11:05:40 +0200 Subject: [PATCH 12/99] Add comment --- src/types/modules/react-native.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/modules/react-native.d.ts b/src/types/modules/react-native.d.ts index ab7e389c5397..1b0b39e5f67d 100644 --- a/src/types/modules/react-native.d.ts +++ b/src/types/modules/react-native.d.ts @@ -3,6 +3,7 @@ import 'react-native'; declare module 'react-native' { interface TextInput { + // Typescript type declaration is missing in React Native for setting text selection. setSelection: (start: number, end: number) => void; } } From 6b7f19070be71f4b0180a696a113fa6291d0b5c8 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Wed, 30 Aug 2023 11:38:22 +0200 Subject: [PATCH 13/99] Convert requireParameters to pure JS --- src/libs/{requireParameters.js => requireParameters.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/libs/{requireParameters.js => requireParameters.ts} (100%) diff --git a/src/libs/requireParameters.js b/src/libs/requireParameters.ts similarity index 100% rename from src/libs/requireParameters.js rename to src/libs/requireParameters.ts From 1c6bb4d9e3efbc8960850c2357828d50f0cf48d8 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 30 Aug 2023 18:05:34 +0530 Subject: [PATCH 14/99] Add lastIOUReportActions dependency to OptionRowLHNData --- .../LHNOptionsList/OptionRowLHNData.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 778c918936f1..506ab44cdbf8 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -81,6 +81,7 @@ function OptionRowLHNData({ policies, receiptTransactions, parentReportActions, + lastIOUReportActions, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -113,7 +114,7 @@ function OptionRowLHNData({ const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(reportIDLocal); return _.first(transactionsWithReceipts); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions, reportActions]); + }, [fullReport.reportID, receiptTransactions, reportActions, lastIOUReportActions]); const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); @@ -220,6 +221,20 @@ export default React.memo( // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, + lastIOUReportActions: { + key: ({fullReport, reportActions}) => { + let reportIDLocal = fullReport.reportID; + + // Check if reportIDLocal needs to be updated with the IOU report of the last report action, in case this report is a chat. + const filteredReportActions = ReportActionUtils.getFilteredSortedReportActionsForDisplay(reportActions); + const lastReportAction = _.first(filteredReportActions); + if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { + reportIDLocal = ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction); + } + return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDLocal}`; + }, + canEvict: false, + }, }), )(OptionRowLHNData), ); From fd64c225e605f5d3cfedb08b53f066ed347624a0 Mon Sep 17 00:00:00 2001 From: tienifr Date: Thu, 31 Aug 2023 09:56:39 +0700 Subject: [PATCH 15/99] fix lint --- src/pages/iou/ReceiptSelector/index.native.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 3893795b97d4..4ccaad40cdb5 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -21,7 +21,6 @@ import Button from '../../../components/Button'; import useLocalize from '../../../hooks/useLocalize'; import ONYXKEYS from '../../../ONYXKEYS'; import Log from '../../../libs/Log'; -import participantPropTypes from '../../../components/participantPropTypes'; import * as CameraPermission from './CameraPermission'; import {iouPropTypes, iouDefaultProps} from '../propTypes'; From 69b1700a06fa5be6874542b90e45d173485b1475 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Thu, 31 Aug 2023 20:29:27 +0530 Subject: [PATCH 16/99] Add ability to show receipt being scanned LHN subtitle for report with iou parent --- src/libs/OptionsListUtils.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 90f12bdfbcce..6d9536e2ad4f 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -17,6 +17,7 @@ import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as UserUtils from './UserUtils'; import * as ReportActionUtils from './ReportActionsUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; +import * as TransactionUtils from './TransactionUtils'; /** * OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can @@ -390,6 +391,11 @@ function getLastMessageTextForReport(report) { lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction); } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getModifiedExpenseMessage(lastReportAction); + + // check if it is an iou thread and if it has any message to show the scanned status + // eslint-disable-next-line no-use-before-define + } else if (isReportTransactionBeingScanned(report)) { + lastMessageTextFromReport = Localize.translateLocal('iou.receiptScanning'); } else { lastMessageTextFromReport = report ? report.lastMessageText || '' : ''; @@ -408,6 +414,31 @@ function getLastMessageTextForReport(report) { return lastMessageTextFromReport; } +/** + * Checks if the given report satisfies the following conditions: + * 1. It is a chat thread. + * 2. Its parent report action is a transaction thread. + * 3. The transaction associated with the parent report action has a receipt. + * 4. The receipt is being scanned. + * 5. The last visible action of the report is either empty or created. + * + * @param {Object} report - The report to be checked. + * + * @returns {boolean} - True if all conditions are met, false otherwise. + */ +function isReportTransactionBeingScanned(report) { + const parentReportAction = ReportActionUtils.getParentReportAction(report); + const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); + + return ( + ReportUtils.isChatThread(report) && + ReportActionUtils.isTransactionThread(parentReportAction) && + TransactionUtils.hasReceipt(transaction) && + TransactionUtils.isReceiptBeingScanned(transaction) && + (_.isEmpty(ReportActionUtils.getLastVisibleAction(report.reportID)) || ReportActionUtils.isCreatedAction(ReportActionUtils.getLastVisibleAction(report.reportID))) + ); +} + /** * Creates a report list option * From 78eaaab0dfe846d2bbef16ace7e417fc572f9f2a Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 1 Sep 2023 10:40:54 +0200 Subject: [PATCH 17/99] Add return types --- src/libs/NumberFormatUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/NumberFormatUtils.ts b/src/libs/NumberFormatUtils.ts index 8d6b1e587af5..7c81e71f4db8 100644 --- a/src/libs/NumberFormatUtils.ts +++ b/src/libs/NumberFormatUtils.ts @@ -1,8 +1,8 @@ -function format(locale: string, number: number, options?: Intl.NumberFormatOptions) { +function format(locale: string, number: number, options?: Intl.NumberFormatOptions): string { return new Intl.NumberFormat(locale, options).format(number); } -function formatToParts(locale: string, number: number, options?: Intl.NumberFormatOptions) { +function formatToParts(locale: string, number: number, options?: Intl.NumberFormatOptions): Intl.NumberFormatPart[] { return new Intl.NumberFormat(locale, options).formatToParts(number); } From b59c4df941b658697791a5746ea1b94ff97ad987 Mon Sep 17 00:00:00 2001 From: Bartosz Grajdek Date: Fri, 1 Sep 2023 10:53:23 +0200 Subject: [PATCH 18/99] [TS migration] Migrate 'requireParameters.js' lib to TypeScript --- src/libs/requireParameters.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/libs/requireParameters.ts b/src/libs/requireParameters.ts index aa2d5e0dc8de..098a6d114430 100644 --- a/src/libs/requireParameters.ts +++ b/src/libs/requireParameters.ts @@ -1,24 +1,25 @@ -import _ from 'underscore'; - /** * @throws {Error} If the "parameters" object has a null or undefined value for any of the given parameterNames * - * @param {String[]} parameterNames Array of the required parameter names - * @param {Object} parameters A map from available parameter names to their values - * @param {String} commandName The name of the API command + * @param parameterNames Array of the required parameter names + * @param parameters A map from available parameter names to their values + * @param commandName The name of the API command */ -export default function requireParameters(parameterNames, parameters, commandName) { +export default function requireParameters(parameterNames: string[], parameters: Record, commandName: string): void { parameterNames.forEach((parameterName) => { - if (_(parameters).has(parameterName) && parameters[parameterName] !== null && parameters[parameterName] !== undefined) { + if (parameterName in parameters && parameters[parameterName] !== null && parameters[parameterName] !== undefined) { return; } const propertiesToRedact = ['authToken', 'password', 'partnerUserSecret', 'twoFactorAuthCode']; - const parametersCopy = _.chain(parameters) - .clone() - .mapObject((val, key) => (_.contains(propertiesToRedact, key) ? '' : val)) - .value(); - const keys = _(parametersCopy).keys().join(', ') || 'none'; + const parametersCopy = {...parameters}; + Object.keys(parametersCopy).forEach((key) => { + if (!propertiesToRedact.includes(key.toString())) return; + + parametersCopy[key] = ''; + }); + + const keys = Object.keys(parametersCopy).join(', ') || 'none'; let error = `Parameter ${parameterName} is required for "${commandName}". `; error += `Supplied parameters: ${keys}`; From c251101975cb7119b685a56422c6aaf9708078c8 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 3 Sep 2023 21:49:11 +0530 Subject: [PATCH 19/99] feat: basic skeletal view added --- src/components/IOUSkeletonView.js | 60 +++++++++++++++++++ .../ReportActionItem/MoneyRequestPreview.js | 6 ++ 2 files changed, 66 insertions(+) create mode 100644 src/components/IOUSkeletonView.js diff --git a/src/components/IOUSkeletonView.js b/src/components/IOUSkeletonView.js new file mode 100644 index 000000000000..6bbac14347d8 --- /dev/null +++ b/src/components/IOUSkeletonView.js @@ -0,0 +1,60 @@ +import React from 'react'; +import {View} from 'react-native'; +import {Rect} from 'react-native-svg'; +import SkeletonViewContentLoader from 'react-content-loader/native'; +import PropTypes from 'prop-types'; +import styles from '../styles/styles'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import variables from '../styles/variables'; +import themeColors from '../styles/themes/default'; +import compose from '../libs/compose'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; + +const propTypes = { + ...windowDimensionsPropTypes, + ...withLocalizePropTypes, + shouldAnimate: PropTypes.bool, +}; + +const defaultProps = { + shouldAnimate: true, +}; + +function IOUSkeletonView(props) { + return ( + + + + + + + + ); +} + +IOUSkeletonView.propTypes = propTypes; +IOUSkeletonView.defaultProps = defaultProps; +IOUSkeletonView.displayName = 'IOUSkeletonView'; +export default compose(withWindowDimensions, withLocalize)(IOUSkeletonView); diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 5c834a53a00e..862583cf44fc 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -3,6 +3,7 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import lodashIsEmpty from 'lodash/isEmpty'; import _ from 'underscore'; import compose from '../../libs/compose'; import styles from '../../styles/styles'; @@ -35,6 +36,7 @@ import * as ReceiptUtils from '../../libs/ReceiptUtils'; import ReportActionItemImages from './ReportActionItemImages'; import transactionPropTypes from '../transactionPropTypes'; import colors from '../../styles/colors'; +import IOUSkeletonView from '../IOUSkeletonView'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ @@ -227,6 +229,9 @@ function MoneyRequestPreview(props) { isHovered={isScanning} /> )} + { + lodashIsEmpty(props.transaction) + ? : @@ -302,6 +307,7 @@ function MoneyRequestPreview(props) { )} +} From b2912b24f0fe307a3ac6d99737bf3abdee364d16 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 3 Sep 2023 21:59:06 +0530 Subject: [PATCH 20/99] fix: update the lenght of the skeleton view --- src/components/IOUSkeletonView.js | 10 +++++----- src/styles/variables.ts | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/IOUSkeletonView.js b/src/components/IOUSkeletonView.js index 6bbac14347d8..e7e53053c862 100644 --- a/src/components/IOUSkeletonView.js +++ b/src/components/IOUSkeletonView.js @@ -26,8 +26,8 @@ function IOUSkeletonView(props) { style={[styles.flex1, styles.overflowHidden]}> @@ -38,7 +38,7 @@ function IOUSkeletonView(props) { height="8" /> diff --git a/src/styles/variables.ts b/src/styles/variables.ts index f584e657c693..3b6dbf47970e 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -152,4 +152,6 @@ export default { qrShareHorizontalPadding: 32, baseMenuItemHeight: 64, + + moneyRequestSkeletonHeight: 107, } as const; From 6c6e69dea9c87ea5c74627022d12921625712498 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 3 Sep 2023 22:01:46 +0530 Subject: [PATCH 21/99] style: lint fixes --- .../ReportActionItem/MoneyRequestPreview.js | 141 +++++++++--------- 1 file changed, 71 insertions(+), 70 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 862583cf44fc..2e3cee33054a 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -230,83 +230,84 @@ function MoneyRequestPreview(props) { /> )} { - lodashIsEmpty(props.transaction) - ? : - - - - {getPreviewHeaderText()} - {Boolean(getSettledMessage()) && ( - <> + lodashIsEmpty(props.transaction) ? ( + ) : ( + + + + {getPreviewHeaderText()} + {Boolean(getSettledMessage()) && ( + <> + + {getSettledMessage()} + + )} + + {hasFieldErrors && ( - {getSettledMessage()} - - )} - - {hasFieldErrors && ( - - )} - - - - - {getDisplayAmountText()} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( - - - - )} - - {props.isBillSplit && ( - - - )} - - {!props.isBillSplit && !_.isEmpty(requestMerchant) && ( - - {requestMerchant} - - )} - - - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( - {props.translate('iou.pendingConversionMessage')} + + + {getDisplayAmountText()} + {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + + + + )} + + {props.isBillSplit && ( + + + + )} + + {!props.isBillSplit && !_.isEmpty(requestMerchant) && ( + + {requestMerchant} + )} - {!_.isEmpty(description) && {description}} + + + {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {props.translate('iou.pendingConversionMessage')} + )} + {!_.isEmpty(description) && {description}} + + {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( + + {props.translate('iou.amountEach', { + amount: CurrencyUtils.convertToDisplayString( + IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), + requestCurrency, + ), + })} + + )} + - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( - - {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString( - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), - requestCurrency, - ), - })} - - )} - - + ) } From c237268ff5c4ec13bb975a62e42dd72f07efdad8 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Sun, 3 Sep 2023 23:54:23 +0530 Subject: [PATCH 22/99] refactor: rename the skeleton view file --- ...{IOUSkeletonView.js => MoneyRequestSkeletonView.js} | 10 +++++----- src/components/ReportActionItem/MoneyRequestPreview.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/components/{IOUSkeletonView.js => MoneyRequestSkeletonView.js} (85%) diff --git a/src/components/IOUSkeletonView.js b/src/components/MoneyRequestSkeletonView.js similarity index 85% rename from src/components/IOUSkeletonView.js rename to src/components/MoneyRequestSkeletonView.js index e7e53053c862..ceee8f984f82 100644 --- a/src/components/IOUSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -20,7 +20,7 @@ const defaultProps = { shouldAnimate: true, }; -function IOUSkeletonView(props) { +function MoneyRequestSkeletonView(props) { return ( @@ -54,7 +54,7 @@ function IOUSkeletonView(props) { ); } -IOUSkeletonView.propTypes = propTypes; -IOUSkeletonView.defaultProps = defaultProps; -IOUSkeletonView.displayName = 'IOUSkeletonView'; -export default compose(withWindowDimensions, withLocalize)(IOUSkeletonView); +MoneyRequestSkeletonView.propTypes = propTypes; +MoneyRequestSkeletonView.defaultProps = defaultProps; +MoneyRequestSkeletonView.displayName = 'IOUSkeletonView'; +export default compose(withWindowDimensions, withLocalize)(MoneyRequestSkeletonView); diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 2e3cee33054a..461d788c37ac 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -36,7 +36,7 @@ import * as ReceiptUtils from '../../libs/ReceiptUtils'; import ReportActionItemImages from './ReportActionItemImages'; import transactionPropTypes from '../transactionPropTypes'; import colors from '../../styles/colors'; -import IOUSkeletonView from '../IOUSkeletonView'; +import MoneyRequestSkeletonView from '../MoneyRequestSkeletonView'; const propTypes = { /** The active IOUReport, used for Onyx subscription */ @@ -230,7 +230,7 @@ function MoneyRequestPreview(props) { /> )} { - lodashIsEmpty(props.transaction) ? ( + lodashIsEmpty(props.transaction) ? ( ) : ( From 230b2cc7c1499d06f8df0719cae4cb7d9e6f53ed Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 00:11:43 +0530 Subject: [PATCH 23/99] style: lint fixes --- src/components/MoneyRequestSkeletonView.js | 41 +++--- .../ReportActionItem/MoneyRequestPreview.js | 133 +++++++++--------- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index ceee8f984f82..2a5013bf28e7 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -22,8 +22,7 @@ const defaultProps = { function MoneyRequestSkeletonView(props) { return ( - + - - - - + + + + ); } diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 461d788c37ac..efe415f8fb30 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -229,86 +229,85 @@ function MoneyRequestPreview(props) { isHovered={isScanning} /> )} - { - lodashIsEmpty(props.transaction) ? ( - ) : ( - - - - {getPreviewHeaderText()} - {Boolean(getSettledMessage()) && ( - <> - - {getSettledMessage()} - - )} - - {hasFieldErrors && ( - + {lodashIsEmpty(props.transaction) ? ( + + ) : ( + + + + {getPreviewHeaderText()} + {Boolean(getSettledMessage()) && ( + <> + + {getSettledMessage()} + )} + + {hasFieldErrors && ( - - - - {getDisplayAmountText()} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( - - - - )} - - {props.isBillSplit && ( - - + + + + {getDisplayAmountText()} + {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + + )} - {!props.isBillSplit && !_.isEmpty(requestMerchant) && ( - - {requestMerchant} + {props.isBillSplit && ( + + )} + + {!props.isBillSplit && !_.isEmpty(requestMerchant) && ( - - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( - {props.translate('iou.pendingConversionMessage')} - )} - {!_.isEmpty(description) && {description}} - - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( - - {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString( - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), - requestCurrency, - ), - })} - + {requestMerchant} + + )} + + + {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {props.translate('iou.pendingConversionMessage')} )} + {!_.isEmpty(description) && {description}} + {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( + + {props.translate('iou.amountEach', { + amount: CurrencyUtils.convertToDisplayString( + IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), + requestCurrency, + ), + })} + + )} - ) -} + + )} From ad392c37828d80da2a51c314bbf833f3121a9dbd Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 01:13:09 +0530 Subject: [PATCH 24/99] fix: pr comments --- src/components/MoneyRequestSkeletonView.js | 10 ++-------- src/components/ReportActionItem/MoneyRequestPreview.js | 3 +-- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index 2a5013bf28e7..e1f3ac813aa1 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -4,15 +4,10 @@ import {Rect} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; import PropTypes from 'prop-types'; import styles from '../styles/styles'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; import variables from '../styles/variables'; import themeColors from '../styles/themes/default'; -import compose from '../libs/compose'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { - ...windowDimensionsPropTypes, - ...withLocalizePropTypes, shouldAnimate: PropTypes.bool, }; @@ -25,7 +20,6 @@ function MoneyRequestSkeletonView(props) { )} - {lodashIsEmpty(props.transaction) ? ( + {_.isEmpty(props.transaction) ? ( ) : ( From 166c12dbc362f47c6abed4b5dc43cb0da5e53df3 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 01:14:47 +0530 Subject: [PATCH 25/99] fix: remove props --- src/components/MoneyRequestSkeletonView.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index e1f3ac813aa1..4471faa7edf2 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -2,24 +2,15 @@ import React from 'react'; import {View} from 'react-native'; import {Rect} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; -import PropTypes from 'prop-types'; import styles from '../styles/styles'; import variables from '../styles/variables'; import themeColors from '../styles/themes/default'; -const propTypes = { - shouldAnimate: PropTypes.bool, -}; - -const defaultProps = { - shouldAnimate: true, -}; - -function MoneyRequestSkeletonView(props) { +function MoneyRequestSkeletonView() { return ( Date: Mon, 4 Sep 2023 14:04:42 +1200 Subject: [PATCH 26/99] Update Request-and-Split-Bills.md --- docs/articles/request-money/Request-and-Split-Bills.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/articles/request-money/Request-and-Split-Bills.md b/docs/articles/request-money/Request-and-Split-Bills.md index 550b749f486c..bb27cd75c742 100644 --- a/docs/articles/request-money/Request-and-Split-Bills.md +++ b/docs/articles/request-money/Request-and-Split-Bills.md @@ -17,9 +17,9 @@ These two features ensure you can live in the moment and settle up afterward. # How to Request Money - Select the Green **+** button and choose **Request Money** - Select the relevant option: - - **Manual:** Input the amount and description manually - - **Scan:** Take a photo of the receipt to have the merchant/amount autofilled, and input description manually - - **Distance:** Input the address of your start/end points for your trip to calculate mileage + - **Manual:** Enter the merchant and amount manually. + - **Scan:** Take a photo of the receipt to have the merchant and amount auto-filled. + - **Distance:** Enter the details of your trip, plus any stops along the way, and the mileage and amount will be automatically calculated. - Search for the user or enter their email! - Enter a reason for the request (optional) - Click **Request!** From bcf73891b69c8bb771f8621f4e603bb509a19f99 Mon Sep 17 00:00:00 2001 From: DrLoopFall Date: Mon, 4 Sep 2023 11:15:48 +0530 Subject: [PATCH 27/99] Fix scrolling emoji picker with arrows --- .../EmojiPicker/EmojiPickerMenu/index.js | 50 ++++--------------- .../EmojiPicker/EmojiPickerMenuItem/index.js | 10 +++- 2 files changed, 17 insertions(+), 43 deletions(-) diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 6e2856a7e058..61f6981edbbe 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -65,7 +65,6 @@ class EmojiPickerMenu extends Component { this.filterEmojis = _.debounce(this.filterEmojis.bind(this), 300); this.highlightAdjacentEmoji = this.highlightAdjacentEmoji.bind(this); - this.scrollToHighlightedIndex = this.scrollToHighlightedIndex.bind(this); this.setupEventHandlers = this.setupEventHandlers.bind(this); this.cleanupEventHandlers = this.cleanupEventHandlers.bind(this); this.renderItem = this.renderItem.bind(this); @@ -76,7 +75,6 @@ class EmojiPickerMenu extends Component { this.getItemLayout = this.getItemLayout.bind(this); this.scrollToHeader = this.scrollToHeader.bind(this); - this.currentScrollOffset = 0; this.firstNonHeaderIndex = 0; const {filteredEmojis, headerEmojis, headerRowIndices} = this.getEmojisAndHeaderRowIndices(); @@ -299,9 +297,9 @@ class EmojiPickerMenu extends Component { return; } - // Blur the input and change the highlight type to keyboard + // Blur the input, change the highlight type to keyboard, and disable pointer events this.searchInput.blur(); - this.setState({isUsingKeyboardMovement: true}); + this.setState({isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); // We only want to hightlight the Emoji if none was highlighted already // If we already have a highlighted Emoji, lets just skip the first navigation @@ -311,10 +309,9 @@ class EmojiPickerMenu extends Component { } // If nothing is highlighted and an arrow key is pressed - // select the first emoji + // select the first emoji, apply keyboard movement styles, and disable pointer events if (this.state.highlightedIndex === -1) { - this.setState({highlightedIndex: this.firstNonHeaderIndex}); - this.scrollToHighlightedIndex(); + this.setState({highlightedIndex: this.firstNonHeaderIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); return; } @@ -368,10 +365,9 @@ class EmojiPickerMenu extends Component { break; } - // Actually highlight the new emoji, apply keyboard movement styles, and scroll to it if the index was changed + // Actually highlight the new emoji, apply keyboard movement styles, and disable pointer events if (newIndex !== this.state.highlightedIndex) { - this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true}); - this.scrollToHighlightedIndex(); + this.setState({highlightedIndex: newIndex, isUsingKeyboardMovement: true, arePointerEventsDisabled: true}); } } @@ -381,36 +377,6 @@ class EmojiPickerMenu extends Component { this.emojiList.scrollToOffset({offset: calculatedOffset, animated: true}); } - /** - * Calculates the required scroll offset (aka distance from top) and scrolls the FlatList to the highlighted emoji - * if any portion of it falls outside of the window. - * Doing this because scrollToIndex doesn't work as expected. - */ - scrollToHighlightedIndex() { - // Calculate the number of rows above the current row, then add 1 to include the current row - const numRows = Math.floor(this.state.highlightedIndex / CONST.EMOJI_NUM_PER_ROW) + 1; - - // The scroll offsets at the top and bottom of the highlighted emoji - const offsetAtEmojiBottom = numRows * CONST.EMOJI_PICKER_HEADER_HEIGHT; - const offsetAtEmojiTop = offsetAtEmojiBottom - CONST.EMOJI_PICKER_ITEM_HEIGHT; - - // Scroll to fit the entire highlighted emoji into the window if we need to - let targetOffset = this.currentScrollOffset; - if (offsetAtEmojiBottom - this.currentScrollOffset >= CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT) { - targetOffset = offsetAtEmojiBottom - CONST.NON_NATIVE_EMOJI_PICKER_LIST_HEIGHT; - } else if (offsetAtEmojiTop - CONST.EMOJI_PICKER_HEADER_HEIGHT <= this.currentScrollOffset) { - // There is always a sticky header on the top, subtract the EMOJI_PICKER_HEADER_HEIGHT from offsetAtEmojiTop to get the correct scroll position. - targetOffset = offsetAtEmojiTop - CONST.EMOJI_PICKER_HEADER_HEIGHT; - } - if (targetOffset !== this.currentScrollOffset) { - // Disable pointer events so that onHover doesn't get triggered when the items move while we're scrolling - if (!this.state.arePointerEventsDisabled) { - this.setState({arePointerEventsDisabled: true}); - } - this.emojiList.scrollToOffset({offset: targetOffset, animated: false}); - } - } - /** * Filter the entire list of emojis to only emojis that have the search term in their keywords * @@ -530,6 +496,7 @@ class EmojiPickerMenu extends Component { return ( @@ -566,10 +533,11 @@ class EmojiPickerMenu extends Component { {overscrollBehaviorY: 'contain'}, // Set overflow to hidden to prevent elastic scrolling when there are not enough contents to scroll in FlatList {overflowY: this.state.filteredEmojis.length > overflowLimit ? 'auto' : 'hidden'}, + // Set scrollPaddingTop to consider sticky headers while scrolling + {scrollPaddingTop: isFiltered ? 0 : CONST.EMOJI_PICKER_ITEM_HEIGHT}, ]} extraData={[this.state.filteredEmojis, this.state.highlightedIndex, this.props.preferredSkinTone]} stickyHeaderIndices={this.state.headerIndices} - onScroll={(e) => (this.currentScrollOffset = e.nativeEvent.contentOffset.y)} getItemLayout={this.getItemLayout} contentContainerStyle={styles.flexGrow1} ListEmptyComponent={{this.props.translate('common.noResultsFound')}} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index 37e90f01c707..728e56792ddb 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -42,13 +42,14 @@ class EmojiPickerMenuItem extends PureComponent { super(props); this.ref = null; + this.focusAndScroll = this.focusAndScroll.bind(this); } componentDidMount() { if (!this.props.isFocused) { return; } - this.ref.focus(); + this.focusAndScroll(); } componentDidUpdate(prevProps) { @@ -58,7 +59,12 @@ class EmojiPickerMenuItem extends PureComponent { if (!this.props.isFocused) { return; } - this.ref.focus(); + this.focusAndScroll(); + } + + focusAndScroll() { + this.ref.focus({preventScroll: true}); + this.ref.scrollIntoView({block: 'nearest'}); } render() { From a5df84ad3bd1f43f7f658335a964a36d2c49e75a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 4 Sep 2023 09:21:41 +0200 Subject: [PATCH 28/99] Remove eslint comment --- src/libs/setSelection/index.native.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/setSelection/index.native.ts b/src/libs/setSelection/index.native.ts index 75089c6a737d..e27cd4e58bd7 100644 --- a/src/libs/setSelection/index.native.ts +++ b/src/libs/setSelection/index.native.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import SetSelection from './types'; const setSelection: SetSelection = (textInput, start, end) => { From cc9f83ffbfa568308eaab159ee6907d21ab14a3b Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 14:51:31 +0530 Subject: [PATCH 29/99] fix: correct padding --- src/components/MoneyRequestSkeletonView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index 4471faa7edf2..3587e2d5fcd0 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -22,7 +22,7 @@ function MoneyRequestSkeletonView() { height="8" /> Date: Mon, 4 Sep 2023 16:54:17 +0530 Subject: [PATCH 30/99] reportAction with errors - not lastVisibleAction --- src/libs/ReportActionsUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 3ed10b865812..ad63a6b6d93a 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -358,7 +358,7 @@ function shouldReportActionBeVisible(reportAction, key) { * @returns {Boolean} */ function shouldReportActionBeVisibleAsLastAction(reportAction) { - if (!reportAction) { + if (!reportAction || !_.isEmpty(reportAction.errors)) { return false; } From 882e77e315ab7a9599f667a02dde73650758c40f Mon Sep 17 00:00:00 2001 From: c3024 Date: Mon, 4 Sep 2023 17:26:22 +0530 Subject: [PATCH 31/99] separate check for reportAction with errors --- src/libs/ReportActionsUtils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index ad63a6b6d93a..9cbc414bf582 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -358,7 +358,11 @@ function shouldReportActionBeVisible(reportAction, key) { * @returns {Boolean} */ function shouldReportActionBeVisibleAsLastAction(reportAction) { - if (!reportAction || !_.isEmpty(reportAction.errors)) { + if (!reportAction) { + return false; + } + + if (!_.isEmpty(reportAction.errors)) { return false; } From d37fcfb5b7b1ee5d5e04944720625274d595a1b6 Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 18:44:50 +0530 Subject: [PATCH 32/99] style: lint fixes --- .../ReportActionItem/MoneyRequestPreview.js | 120 +++++++++--------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index cda8b0d7263a..637302f7d4f0 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -238,77 +238,77 @@ function MoneyRequestPreview(props) { {_.isEmpty(props.transaction) ? ( ) : ( - - - - {getPreviewHeaderText()} - {Boolean(getSettledMessage()) && ( - <> - - {getSettledMessage()} - + + + + {getPreviewHeaderText()} + {Boolean(getSettledMessage()) && ( + <> + + {getSettledMessage()} + + )} + + {hasFieldErrors && ( + )} - {hasFieldErrors && ( - - )} - - - - {getDisplayAmountText()} - {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( - - + + {getDisplayAmountText()} + {ReportUtils.isSettled(props.iouReport.reportID) && !props.isBillSplit && ( + + + + )} + + {props.isBillSplit && ( + + )} - {props.isBillSplit && ( - - + {shouldShowMerchant && ( + + {requestMerchant} )} - - {shouldShowMerchant && ( - {requestMerchant} - - )} - - - {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( - {props.translate('iou.pendingConversionMessage')} + + {!isCurrentUserManager && props.shouldShowPendingConversionMessage && ( + {props.translate('iou.pendingConversionMessage')} + )} + {shouldShowDescription && {description}} + + {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( + + {props.translate('iou.amountEach', { + amount: CurrencyUtils.convertToDisplayString( + IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), + requestCurrency, + ), + })} + )} - {shouldShowDescription && {description}} - {props.isBillSplit && !_.isEmpty(participantAccountIDs) && ( - - {props.translate('iou.amountEach', { - amount: CurrencyUtils.convertToDisplayString( - IOUUtils.calculateAmount(isPolicyExpenseChat ? 1 : participantAccountIDs.length - 1, requestAmount, requestCurrency), - requestCurrency, - ), - })} - - )} - )} @@ -359,4 +359,4 @@ export default compose( key: ONYXKEYS.WALLET_TERMS, }, }), -)(MoneyRequestPreview); \ No newline at end of file +)(MoneyRequestPreview); From 2e8059f49f7e1d65353bf260941880b07e2d007c Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 18:52:44 +0530 Subject: [PATCH 33/99] Display "Receipt is scanning..." only if its the first reportAction --- src/libs/ReportUtils.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index f196fc0e54af..a1679a2b9bfe 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1250,21 +1250,13 @@ function getMoneyRequestReportName(report, policy = undefined) { /* === Start - Check for Report Scan === */ - // get lastReportActionTransaction - const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); - const filteredReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); - - const lastReportAction = _.first(filteredReportActions); - const lastReportActionTransaction = TransactionUtils.getLinkedTransaction(lastReportAction); - - // get lastTransaction // eslint-disable-next-line no-use-before-define - const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); - const lastTransaction = _.first(transactionsWithReceipts); + const moneyRequestreportActions = getSortedMoneyRequestActions(report.reportID); - const transactionIsLastReportAction = _.isEqual(lastReportActionTransaction, lastTransaction); + const firstReoprtAction = _.last(moneyRequestreportActions); + const firstReportActionTransaction = TransactionUtils.getLinkedTransaction(firstReoprtAction); - if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { + if (!_.isEmpty(firstReportActionTransaction) && TransactionUtils.isReceiptBeingScanned(firstReportActionTransaction)) { return Localize.translateLocal('iou.receiptScanning'); } From f587fd840c345221ce404649e554775c86e4819d Mon Sep 17 00:00:00 2001 From: situchan Date: Mon, 4 Sep 2023 19:25:37 +0600 Subject: [PATCH 34/99] remove distance flow in split bill --- src/pages/iou/MoneyRequestDatePage.js | 2 +- src/pages/iou/MoneyRequestDescriptionPage.js | 2 +- src/pages/iou/steps/MoneyRequestConfirmPage.js | 2 +- .../MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js index 0fe9e7460d86..19fc5ab081f6 100644 --- a/src/pages/iou/MoneyRequestDatePage.js +++ b/src/pages/iou/MoneyRequestDatePage.js @@ -51,7 +51,7 @@ function MoneyRequestDatePage({iou, route, selectedTab}) { const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index 382f29c3c8e4..e1e1e832f1e2 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -55,7 +55,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { const inputRef = useRef(null); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index ae36c60e717b..f827713da818 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -59,9 +59,9 @@ const defaultProps = { function MoneyRequestConfirmPage(props) { const {windowHeight} = useWindowDimensions(); - const isDistanceRequest = props.selectedTab === CONST.TAB.DISTANCE; const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); + const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && props.selectedTab === CONST.TAB.DISTANCE; const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 949e17d02ffe..6fdbee6e8d61 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -48,7 +48,7 @@ function MoneyRequestParticipantsPage(props) { const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); - const isDistanceRequest = props.selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && props.selectedTab === CONST.TAB.DISTANCE; const navigateToNextStep = () => { Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType.current, reportID.current)); diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index fbe8fd92b09c..a5501e55f749 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -64,7 +64,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { const reportID = lodashGet(route, 'params.reportID', ''); const isEditing = lodashGet(route, 'path', '').includes('amount'); const currentCurrency = lodashGet(route, 'params.currency', ''); - const isDistanceRequestTab = selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequestTab = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; const currency = currentCurrency || iou.currency; From 93df38ada3aca3cb1d6bc30b943a054354819f5f Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 19:00:16 +0530 Subject: [PATCH 35/99] fix: update the design skeleton --- src/components/MoneyRequestSkeletonView.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index 3587e2d5fcd0..005775d7f29c 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -1,16 +1,16 @@ import React from 'react'; -import {View} from 'react-native'; import {Rect} from 'react-native-svg'; import SkeletonViewContentLoader from 'react-content-loader/native'; -import styles from '../styles/styles'; import variables from '../styles/variables'; import themeColors from '../styles/themes/default'; +import styles from '../styles/styles'; function MoneyRequestSkeletonView() { return ( - + - + ); } From 1ae2a01636d5c0ff986c144503c6f6f72d431abf Mon Sep 17 00:00:00 2001 From: situchan Date: Mon, 4 Sep 2023 19:52:20 +0600 Subject: [PATCH 36/99] fix wrong condition --- src/pages/iou/MoneyRequestDatePage.js | 2 +- src/pages/iou/MoneyRequestDescriptionPage.js | 2 +- src/pages/iou/steps/NewRequestAmountPage.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js index 19fc5ab081f6..726809dc3732 100644 --- a/src/pages/iou/MoneyRequestDatePage.js +++ b/src/pages/iou/MoneyRequestDatePage.js @@ -51,7 +51,7 @@ function MoneyRequestDatePage({iou, route, selectedTab}) { const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index e1e1e832f1e2..7476812b4ca3 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -55,7 +55,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { const inputRef = useRef(null); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index a5501e55f749..cf29f8761d6e 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -64,7 +64,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { const reportID = lodashGet(route, 'params.reportID', ''); const isEditing = lodashGet(route, 'path', '').includes('amount'); const currentCurrency = lodashGet(route, 'params.currency', ''); - const isDistanceRequestTab = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequestTab = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; const currency = currentCurrency || iou.currency; From aae804d91b03f99861f91b276bcddda194103d6e Mon Sep 17 00:00:00 2001 From: Manan Jadhav Date: Mon, 4 Sep 2023 19:35:14 +0530 Subject: [PATCH 37/99] style: lint fixes --- src/components/MoneyRequestSkeletonView.js | 54 +++++++++++----------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/src/components/MoneyRequestSkeletonView.js b/src/components/MoneyRequestSkeletonView.js index 005775d7f29c..50a7b56b91e3 100644 --- a/src/components/MoneyRequestSkeletonView.js +++ b/src/components/MoneyRequestSkeletonView.js @@ -7,34 +7,32 @@ import styles from '../styles/styles'; function MoneyRequestSkeletonView() { return ( - - - - - - - + + + + + ); } From 4a4fbcd93a95486207ec3c0237a891d6e83b216b Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 19:38:01 +0530 Subject: [PATCH 38/99] Check if thereAreFollowingManualIOURequests before showing report name as "Receipt being scanned..." --- src/libs/ReportUtils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c559dec643cb..278ea39b2e3f 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1264,7 +1264,9 @@ function getMoneyRequestReportName(report, policy = undefined) { const firstReoprtAction = _.last(moneyRequestreportActions); const firstReportActionTransaction = TransactionUtils.getLinkedTransaction(firstReoprtAction); - if (!_.isEmpty(firstReportActionTransaction) && TransactionUtils.isReceiptBeingScanned(firstReportActionTransaction)) { + const thereAreFollowingManualIOURequests = getMoneyRequestTotal(report) > 0; + + if (!thereAreFollowingManualIOURequests && !_.isEmpty(firstReportActionTransaction) && TransactionUtils.isReceiptBeingScanned(firstReportActionTransaction)) { return Localize.translateLocal('iou.receiptScanning'); } From a772a351e4f81ea76b626c56c4c1403af1ba00c0 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 19:48:39 +0530 Subject: [PATCH 39/99] Fix pretteir diff --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 278ea39b2e3f..b1f06585d2f9 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1264,7 +1264,7 @@ function getMoneyRequestReportName(report, policy = undefined) { const firstReoprtAction = _.last(moneyRequestreportActions); const firstReportActionTransaction = TransactionUtils.getLinkedTransaction(firstReoprtAction); - const thereAreFollowingManualIOURequests = getMoneyRequestTotal(report) > 0; + const thereAreFollowingManualIOURequests = getMoneyRequestTotal(report) > 0; if (!thereAreFollowingManualIOURequests && !_.isEmpty(firstReportActionTransaction) && TransactionUtils.isReceiptBeingScanned(firstReportActionTransaction)) { return Localize.translateLocal('iou.receiptScanning'); From 860161522dec6cadcc2374089f2154f137dd9b3b Mon Sep 17 00:00:00 2001 From: Puneet Lath Date: Mon, 4 Sep 2023 10:41:37 -0400 Subject: [PATCH 40/99] Add regression tests to design doc issue template --- .github/ISSUE_TEMPLATE/DesignDoc.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/DesignDoc.md b/.github/ISSUE_TEMPLATE/DesignDoc.md index 424b549a0940..2fbdcf7a65d5 100644 --- a/.github/ISSUE_TEMPLATE/DesignDoc.md +++ b/.github/ISSUE_TEMPLATE/DesignDoc.md @@ -27,6 +27,7 @@ labels: Daily, NewFeature - [ ] Confirm that the doc has the minimum necessary number of reviews before proceeding - [ ] Email `strategy@expensify.com` one last time to let them know the Design Doc is moving into the implementation phase - [ ] Implement the changes +- [ ] Add regression tests so that QA can test your feature with every deploy ([instructions](https://stackoverflowteams.com/c/expensify/questions/363)) - [ ] Send out a follow up email to `strategy@expensify.com` once everything has been implemented and do a **Project Wrap-Up** retrospective that provides: - Summary of what we accomplished with this project - What went well? From 83a1c8fb2521db67317dc2f80f29d48d4878b138 Mon Sep 17 00:00:00 2001 From: situchan Date: Mon, 4 Sep 2023 21:01:58 +0600 Subject: [PATCH 41/99] util function to check if distance request --- src/libs/MoneyRequestUtils.js | 13 ++++++++++++- src/pages/iou/MoneyRequestDatePage.js | 3 ++- src/pages/iou/MoneyRequestDescriptionPage.js | 3 ++- src/pages/iou/steps/MoneyRequestConfirmPage.js | 3 ++- .../MoneyRequestParticipantsPage.js | 3 ++- src/pages/iou/steps/NewRequestAmountPage.js | 3 ++- 6 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libs/MoneyRequestUtils.js b/src/libs/MoneyRequestUtils.js index 706c34ad912d..e60eae0cdfe5 100644 --- a/src/libs/MoneyRequestUtils.js +++ b/src/libs/MoneyRequestUtils.js @@ -82,4 +82,15 @@ function replaceAllDigits(text, convertFn) { .value(); } -export {stripCommaFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits}; +/** + * Check if distance request or not + * + * @param {String} iouType - `send` | `split` | `request` + * @param {String} selectedTab - `manual` | `scan` | `distance` + * @returns {Boolean} + */ +function isDistanceRequest(iouType, selectedTab) { + return iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; +} + +export {stripCommaFromAmount, stripSpacesFromAmount, addLeadingZero, validateAmount, replaceAllDigits, isDistanceRequest}; diff --git a/src/pages/iou/MoneyRequestDatePage.js b/src/pages/iou/MoneyRequestDatePage.js index 726809dc3732..c6f2e0d40922 100644 --- a/src/pages/iou/MoneyRequestDatePage.js +++ b/src/pages/iou/MoneyRequestDatePage.js @@ -11,6 +11,7 @@ import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import * as IOU from '../../libs/actions/IOU'; +import * as MoneyRequestUtils from '../../libs/MoneyRequestUtils'; import NewDatePicker from '../../components/NewDatePicker'; import useLocalize from '../../hooks/useLocalize'; import CONST from '../../CONST'; @@ -51,7 +52,7 @@ function MoneyRequestDatePage({iou, route, selectedTab}) { const {translate} = useLocalize(); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/MoneyRequestDescriptionPage.js b/src/pages/iou/MoneyRequestDescriptionPage.js index 7476812b4ca3..72e1270f1fcb 100644 --- a/src/pages/iou/MoneyRequestDescriptionPage.js +++ b/src/pages/iou/MoneyRequestDescriptionPage.js @@ -14,6 +14,7 @@ import styles from '../../styles/styles'; import Navigation from '../../libs/Navigation/Navigation'; import ROUTES from '../../ROUTES'; import * as IOU from '../../libs/actions/IOU'; +import * as MoneyRequestUtils from '../../libs/MoneyRequestUtils'; import CONST from '../../CONST'; import useLocalize from '../../hooks/useLocalize'; import focusAndUpdateMultilineInputRange from '../../libs/focusAndUpdateMultilineInputRange'; @@ -55,7 +56,7 @@ function MoneyRequestDescriptionPage({iou, route, selectedTab}) { const inputRef = useRef(null); const iouType = lodashGet(route, 'params.iouType', ''); const reportID = lodashGet(route, 'params.reportID', ''); - const isDistanceRequest = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); useEffect(() => { const moneyRequestId = `${iouType}${reportID}`; diff --git a/src/pages/iou/steps/MoneyRequestConfirmPage.js b/src/pages/iou/steps/MoneyRequestConfirmPage.js index f827713da818..178179f31745 100644 --- a/src/pages/iou/steps/MoneyRequestConfirmPage.js +++ b/src/pages/iou/steps/MoneyRequestConfirmPage.js @@ -14,6 +14,7 @@ import * as IOU from '../../../libs/actions/IOU'; import compose from '../../../libs/compose'; import * as ReportUtils from '../../../libs/ReportUtils'; import * as OptionsListUtils from '../../../libs/OptionsListUtils'; +import * as MoneyRequestUtils from '../../../libs/MoneyRequestUtils'; import withLocalize from '../../../components/withLocalize'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; import ONYXKEYS from '../../../ONYXKEYS'; @@ -61,7 +62,7 @@ function MoneyRequestConfirmPage(props) { const {windowHeight} = useWindowDimensions(); const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); - const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && props.selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); const participants = useMemo( () => diff --git a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js index 6fdbee6e8d61..68b21189f546 100644 --- a/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js +++ b/src/pages/iou/steps/MoneyRequstParticipantsPage/MoneyRequestParticipantsPage.js @@ -16,6 +16,7 @@ import compose from '../../../../libs/compose'; import * as DeviceCapabilities from '../../../../libs/DeviceCapabilities'; import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; import * as IOU from '../../../../libs/actions/IOU'; +import * as MoneyRequestUtils from '../../../../libs/MoneyRequestUtils'; import {iouPropTypes, iouDefaultProps} from '../../propTypes'; const propTypes = { @@ -48,7 +49,7 @@ function MoneyRequestParticipantsPage(props) { const prevMoneyRequestId = useRef(props.iou.id); const iouType = useRef(lodashGet(props.route, 'params.iouType', '')); const reportID = useRef(lodashGet(props.route, 'params.reportID', '')); - const isDistanceRequest = iouType.current === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && props.selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequest = MoneyRequestUtils.isDistanceRequest(iouType.current, props.selectedTab); const navigateToNextStep = () => { Navigation.navigate(ROUTES.getMoneyRequestConfirmationRoute(iouType.current, reportID.current)); diff --git a/src/pages/iou/steps/NewRequestAmountPage.js b/src/pages/iou/steps/NewRequestAmountPage.js index cf29f8761d6e..32a9134cb101 100644 --- a/src/pages/iou/steps/NewRequestAmountPage.js +++ b/src/pages/iou/steps/NewRequestAmountPage.js @@ -15,6 +15,7 @@ import * as IOU from '../../../libs/actions/IOU'; import useLocalize from '../../../hooks/useLocalize'; import MoneyRequestAmountForm from './MoneyRequestAmountForm'; import * as IOUUtils from '../../../libs/IOUUtils'; +import * as MoneyRequestUtils from '../../../libs/MoneyRequestUtils'; import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView'; import styles from '../../../styles/styles'; import HeaderWithBackButton from '../../../components/HeaderWithBackButton'; @@ -64,7 +65,7 @@ function NewRequestAmountPage({route, iou, report, selectedTab}) { const reportID = lodashGet(route, 'params.reportID', ''); const isEditing = lodashGet(route, 'path', '').includes('amount'); const currentCurrency = lodashGet(route, 'params.currency', ''); - const isDistanceRequestTab = iouType === CONST.IOU.MONEY_REQUEST_TYPE.REQUEST && selectedTab === CONST.TAB.DISTANCE; + const isDistanceRequestTab = MoneyRequestUtils.isDistanceRequest(iouType, selectedTab); const currency = currentCurrency || iou.currency; From 08f3d3bf01b05ac0d47830c0c070fb57a22fd56e Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 4 Sep 2023 08:48:09 -0700 Subject: [PATCH 42/99] Show suggestions for places --- src/components/AddressSearch/index.js | 8 ++++++-- src/pages/iou/WaypointEditor.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index e761ebeed26b..1697dddba805 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -95,6 +95,9 @@ const propTypes = { /** Maximum number of characters allowed in search input */ maxInputLength: PropTypes.number, + /** The result types to return from the Google Places Autocomplete request */ + resultTypes: PropTypes.string, + /** Information about the network */ network: networkPropTypes.isRequired, @@ -123,6 +126,7 @@ const defaultProps = { }, maxInputLength: undefined, predefinedPlaces: [], + resultTypes: 'address', }; // Do not convert to class component! It's been tried before and presents more challenges than it's worth. @@ -134,10 +138,10 @@ function AddressSearch(props) { const query = useMemo( () => ({ language: props.preferredLocale, - types: 'address', + types: props.resultTypes, components: props.isLimitedToUSA ? 'country:us' : undefined, }), - [props.preferredLocale, props.isLimitedToUSA], + [props.preferredLocale, props.resultTypes, props.isLimitedToUSA], ); const saveLocationDetails = (autocompleteData, details) => { diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index 8c168a456210..6f7a8f5d08d5 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -222,6 +222,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI state: null, }} predefinedPlaces={recentWaypoints} + resultTypes='' /> From c805b6747bb29aea038f9f8ec1831a16f643f688 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Mon, 4 Sep 2023 16:07:43 +0000 Subject: [PATCH 43/99] Update version to 1.3.63-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index db6109d0f77d..11ce415ad0ae 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001036204 - versionName "1.3.62-4" + versionCode 1001036300 + versionName "1.3.63-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f684494e8563..b69066e7546a 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.62 + 1.3.63 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.62.4 + 1.3.63.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 0da2355a39fd..dcb0ddc8af49 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.62 + 1.3.63 CFBundleSignature ???? CFBundleVersion - 1.3.62.4 + 1.3.63.0 diff --git a/package-lock.json b/package-lock.json index 82b0af87b6dd..d595fc527a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.62-4", + "version": "1.3.63-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.62-4", + "version": "1.3.63-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 16991adc44f2..cf3fd570164b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.62-4", + "version": "1.3.63-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 89598d8793b5db05488a1358c1fd13d6da73fe1f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 4 Sep 2023 09:14:49 -0700 Subject: [PATCH 44/99] Fix lint --- src/pages/iou/WaypointEditor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/WaypointEditor.js b/src/pages/iou/WaypointEditor.js index 6f7a8f5d08d5..68e85a34746d 100644 --- a/src/pages/iou/WaypointEditor.js +++ b/src/pages/iou/WaypointEditor.js @@ -222,7 +222,7 @@ function WaypointEditor({transactionID, route: {params: {iouType = '', waypointI state: null, }} predefinedPlaces={recentWaypoints} - resultTypes='' + resultTypes="" /> From bca81161f1d41d683c2def7129c2f5d5ca54b515 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 21:57:16 +0530 Subject: [PATCH 45/99] Don't show "Receipt being scanned..." for Workspace chat view LHN subtitle --- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 9f59e60fbc66..2872243ffa32 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -398,7 +398,7 @@ function getLastMessageTextForReport(report) { lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction); + lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction, false); } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getModifiedExpenseMessage(lastReportAction); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index b1f06585d2f9..6a225d6af337 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1479,9 +1479,10 @@ function getTransactionReportName(reportAction) { * * @param {Object} report * @param {Object} [reportAction={}] + * @param {Boolean} [shouldConsiderReceiptBeingScanned=true] * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}) { +function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = true) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { @@ -1500,19 +1501,21 @@ function getReportPreviewMessage(report, reportAction = {}) { /* === Start - Check for Report Scan === */ - const filteredIouReportActions = getSortedMoneyRequestActions(report.reportID); - const lastIouReportAction = _.first(filteredIouReportActions); - const lastIouReportActionTransaction = TransactionUtils.getLinkedTransaction(lastIouReportAction); + if (shouldConsiderReceiptBeingScanned) { + const filteredIouReportActions = getSortedMoneyRequestActions(report.reportID); + const lastIouReportAction = _.first(filteredIouReportActions); + const lastIouReportActionTransaction = TransactionUtils.getLinkedTransaction(lastIouReportAction); - // get lastTransaction - // eslint-disable-next-line no-use-before-define - const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); - const lastTransaction = _.first(transactionsWithReceipts); + // get lastTransaction + // eslint-disable-next-line no-use-before-define + const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); + const lastTransaction = _.first(transactionsWithReceipts); - const transactionIsLastReportAction = _.isEqual(lastIouReportActionTransaction, lastTransaction); + const transactionIsLastReportAction = _.isEqual(lastIouReportActionTransaction, lastTransaction); - if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { - return Localize.translateLocal('iou.receiptScanning'); + if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { + return Localize.translateLocal('iou.receiptScanning'); + } } /* === End - Check for Report Scan === */ From f018b21a17a674cddccc3bfb5e5ecac9b4398309 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 22:01:54 +0530 Subject: [PATCH 46/99] Don't show "Report is being scanned..." for Exprense Report View's LHN title --- src/libs/ReportUtils.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 6a225d6af337..6f79e8fcefa1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1256,22 +1256,6 @@ function getMoneyRequestReportName(report, policy = undefined) { amount: formattedAmount, }); - /* === Start - Check for Report Scan === */ - - // eslint-disable-next-line no-use-before-define - const moneyRequestreportActions = getSortedMoneyRequestActions(report.reportID); - - const firstReoprtAction = _.last(moneyRequestreportActions); - const firstReportActionTransaction = TransactionUtils.getLinkedTransaction(firstReoprtAction); - - const thereAreFollowingManualIOURequests = getMoneyRequestTotal(report) > 0; - - if (!thereAreFollowingManualIOURequests && !_.isEmpty(firstReportActionTransaction) && TransactionUtils.isReceiptBeingScanned(firstReportActionTransaction)) { - return Localize.translateLocal('iou.receiptScanning'); - } - - /* === End - Check for Report Scan === */ - if (report.isWaitingOnBankAccount) { return `${payerPaidAmountMesssage} • ${Localize.translateLocal('iou.pending')}`; } From 6c0320f20367dd8e472fee7512d673ce1fead53a Mon Sep 17 00:00:00 2001 From: tienifr Date: Mon, 4 Sep 2023 23:34:16 +0700 Subject: [PATCH 47/99] fix lint --- src/pages/iou/ReceiptSelector/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/ReceiptSelector/index.native.js b/src/pages/iou/ReceiptSelector/index.native.js index 3b87fe448aef..4ff32d940c9f 100644 --- a/src/pages/iou/ReceiptSelector/index.native.js +++ b/src/pages/iou/ReceiptSelector/index.native.js @@ -1,6 +1,6 @@ import {ActivityIndicator, Alert, AppState, Linking, Text, View} from 'react-native'; import React, {useCallback, useEffect, useRef, useState} from 'react'; -import {Camera, useCameraDevices} from 'react-native-vision-camera'; +import {useCameraDevices} from 'react-native-vision-camera'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import {launchImageLibrary} from 'react-native-image-picker'; From 1392317ab510608e6009509dffdebb2e7ec4b11f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 22:05:08 +0530 Subject: [PATCH 48/99] Don't show "Receipt is being scanned..." in Request View LHN subtitle --- src/libs/OptionsListUtils.js | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 2872243ffa32..368bbc2694a6 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -17,7 +17,6 @@ import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as UserUtils from './UserUtils'; import * as ReportActionUtils from './ReportActionsUtils'; import * as PersonalDetailsUtils from './PersonalDetailsUtils'; -import * as TransactionUtils from './TransactionUtils'; import * as ErrorUtils from './ErrorUtils'; /** @@ -401,11 +400,6 @@ function getLastMessageTextForReport(report) { lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction, false); } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getModifiedExpenseMessage(lastReportAction); - - // check if it is an iou thread and if it has any message to show the scanned status - // eslint-disable-next-line no-use-before-define - } else if (isReportTransactionBeingScanned(report)) { - lastMessageTextFromReport = Localize.translateLocal('iou.receiptScanning'); } else { lastMessageTextFromReport = report ? report.lastMessageText || '' : ''; @@ -424,31 +418,6 @@ function getLastMessageTextForReport(report) { return lastMessageTextFromReport; } -/** - * Checks if the given report satisfies the following conditions: - * 1. It is a chat thread. - * 2. Its parent report action is a transaction thread. - * 3. The transaction associated with the parent report action has a receipt. - * 4. The receipt is being scanned. - * 5. The last visible action of the report is either empty or created. - * - * @param {Object} report - The report to be checked. - * - * @returns {boolean} - True if all conditions are met, false otherwise. - */ -function isReportTransactionBeingScanned(report) { - const parentReportAction = ReportActionUtils.getParentReportAction(report); - const transaction = TransactionUtils.getLinkedTransaction(parentReportAction); - - return ( - ReportUtils.isChatThread(report) && - ReportActionUtils.isTransactionThread(parentReportAction) && - TransactionUtils.hasReceipt(transaction) && - TransactionUtils.isReceiptBeingScanned(transaction) && - (_.isEmpty(ReportActionUtils.getLastVisibleAction(report.reportID)) || ReportActionUtils.isCreatedAction(ReportActionUtils.getLastVisibleAction(report.reportID))) - ); -} - /** * Creates a report list option * From 59630d60041991056e87692e6b513302a861eb65 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 22:13:27 +0530 Subject: [PATCH 49/99] OptionRowLHNData Dependency cleanup for bca81161f1d41d683c2def7129c2f5d5ca54b515 --- .../LHNOptionsList/OptionRowLHNData.js | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 506ab44cdbf8..feadeaa32390 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -81,7 +81,6 @@ function OptionRowLHNData({ policies, receiptTransactions, parentReportActions, - lastIOUReportActions, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -95,26 +94,13 @@ function OptionRowLHNData({ const optionItemRef = useRef(); - /** - * If it is an IOU report, you get the last transaction with a receipt. - * Otherwise, if the last message is a reportPreview in a chat, - * you get the last transaction of the IOU report associated with that preview message. - */ - const lastTransaction = useMemo(() => { let reportIDLocal = fullReport.reportID; - // Check if reportIDLocal needs to be updated with the IOU report of the last report action, in case this report is a chat. - const filteredReportActions = ReportActionUtils.getFilteredSortedReportActionsForDisplay(reportActions); - const lastReportAction = _.first(filteredReportActions); - if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { - reportIDLocal = ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction); - } - const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(reportIDLocal); return _.first(transactionsWithReceipts); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport.reportID, receiptTransactions, reportActions, lastIOUReportActions]); + }, [fullReport.reportID, receiptTransactions, reportActions]); const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); @@ -221,20 +207,6 @@ export default React.memo( // This can lead to situations where `lastTransaction` doesn't update and retains the previous value. // However, performance overhead of this is minimized by using memos inside the component. receiptTransactions: {key: ONYXKEYS.COLLECTION.TRANSACTION}, - lastIOUReportActions: { - key: ({fullReport, reportActions}) => { - let reportIDLocal = fullReport.reportID; - - // Check if reportIDLocal needs to be updated with the IOU report of the last report action, in case this report is a chat. - const filteredReportActions = ReportActionUtils.getFilteredSortedReportActionsForDisplay(reportActions); - const lastReportAction = _.first(filteredReportActions); - if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { - reportIDLocal = ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction); - } - return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportIDLocal}`; - }, - canEvict: false, - }, }), )(OptionRowLHNData), ); From a51601c073481f7c975ab24059a81fded98ddd99 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 12:51:25 -0400 Subject: [PATCH 50/99] simplify render code --- src/components/ReportActionItem/ReportPreview.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index e7d6cede36e9..9b1389efd117 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -120,6 +120,7 @@ function ReportPreview(props) { const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID); const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); + const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || '')); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest @@ -181,7 +182,7 @@ function ReportPreview(props) { {hasReceipts && ( ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || ''))} + images={lastThreeReceipts} size={3} total={transactionsWithReceipts.length} isHovered={props.isHovered || isScanning} From c157585c5f4724649242232667bd06e9aaae4676 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 12:51:32 -0400 Subject: [PATCH 51/99] use placeholder optimistic receipt --- src/libs/actions/IOU.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b4f04174c1ac..8d1de1dc4d60 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -21,6 +21,7 @@ import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; import * as NumberUtils from '../NumberUtils'; +import ReceiptGeneric from '../../../assets/images/receipt-generic.png'; let allReports; Onyx.connect({ @@ -394,7 +395,7 @@ function getMoneyRequestInformation( let filename; if (receipt && receipt.source) { receiptObject.source = receipt.source; - receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; + receiptObject.state = receipt.state || CONST.IOU.RECEIPT_STATE.SCANREADY; filename = receipt.name; } let optimisticTransaction = TransactionUtils.buildOptimisticTransaction( @@ -509,6 +510,10 @@ function getMoneyRequestInformation( * @param {String} merchant */ function createDistanceRequest(report, participant, comment, created, transactionID, amount, currency, merchant) { + const optimisticReceipt = { + source: ReceiptGeneric, + state: CONST.IOU.RECEIPT_STATE.OPEN, + }; const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( report, participant, @@ -519,7 +524,7 @@ function createDistanceRequest(report, participant, comment, created, transactio merchant, null, null, - null, + optimisticReceipt, transactionID, ); API.write( From 73bdde1a64631084fc1bc37d3d539e5f7e3a19ff Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 22:22:57 +0530 Subject: [PATCH 52/99] set getReportPreviewMessage's shouldConsiderReceiptBeingScanned param to undefined/ false by default --- src/libs/OptionsListUtils.js | 4 ++-- src/libs/ReportUtils.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 368bbc2694a6..d26ad48430b0 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -394,10 +394,10 @@ function getLastMessageTextForReport(report) { if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { lastMessageTextFromReport = `[${Localize.translateLocal(report.lastMessageTranslationKey || 'common.attachment')}]`; } else if (ReportActionUtils.isMoneyRequestAction(lastReportAction)) { - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction); + lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction, true); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction, false); + lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction); } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { lastMessageTextFromReport = ReportUtils.getModifiedExpenseMessage(lastReportAction); } else { diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 6f79e8fcefa1..1c3393793abe 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1463,10 +1463,10 @@ function getTransactionReportName(reportAction) { * * @param {Object} report * @param {Object} [reportAction={}] - * @param {Boolean} [shouldConsiderReceiptBeingScanned=true] + * @param {Boolean} [shouldConsiderReceiptBeingScanned=false] * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = true) { +function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { From 03e4b3febdacb207a542be90afc8c915f3818177 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Mon, 4 Sep 2023 22:23:51 +0530 Subject: [PATCH 53/99] Linting fixes --- src/components/LHNOptionsList/OptionRowLHNData.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index feadeaa32390..e60b5e05e6ba 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -13,7 +13,6 @@ import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultPro import * as Report from '../../libs/actions/Report'; import * as UserUtils from '../../libs/UserUtils'; import * as ReportUtils from '../../libs/ReportUtils'; -import * as ReportActionUtils from '../../libs/ReportActionsUtils'; import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; @@ -95,9 +94,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); const lastTransaction = useMemo(() => { - let reportIDLocal = fullReport.reportID; - - const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(reportIDLocal); + const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); return _.first(transactionsWithReceipts); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); From 168cf36a5bf58c55a075babe9c84d4d0e083dee1 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 13:19:34 -0400 Subject: [PATCH 54/99] do not default to the actor account as the manager --- 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 9b1389efd117..f75f35481aed 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -106,7 +106,7 @@ const defaultProps = { }; function ReportPreview(props) { - const managerID = props.iouReport.managerID || props.action.actorAccountID || 0; + const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const reportTotal = ReportUtils.getMoneyRequestTotal(props.iouReport); From f14cbcd25ed90fca3881f7be53d8452281082263 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 14:01:20 -0400 Subject: [PATCH 55/99] try both transaction.filename and transaction.receiptFilename --- src/components/ReportActionItem/MoneyRequestPreview.js | 4 +++- src/components/ReportActionItem/ReportPreview.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index fa14c3dee4f5..25bc4bebb450 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -163,6 +163,8 @@ function MoneyRequestPreview(props) { !_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant; + const receiptImages = [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || props.transaction.receiptFilename || '')]; + const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { case CONST.IOU.PAYMENT_TYPE.PAYPAL_ME: @@ -230,7 +232,7 @@ function MoneyRequestPreview(props) { {hasReceipt && ( )} diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index f75f35481aed..b95fdaad1025 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -120,7 +120,9 @@ function ReportPreview(props) { const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID); const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action); - const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || '')); + const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename, receiptFilename}) => + ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || receiptFilename || ''), + ); const hasOnlyOneReceiptRequest = numberOfRequests === 1 && hasReceipts; const previewSubtitle = hasOnlyOneReceiptRequest From 50d3a09342660894d385966dc2f85e54b1853470 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 4 Sep 2023 12:17:51 -0700 Subject: [PATCH 56/99] Remove distance requests beta --- src/CONST.ts | 1 - src/libs/Permissions.js | 9 --------- src/pages/iou/MoneyRequestSelectorPage.js | 3 +-- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 6be817b6296b..1c67535e54e4 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -235,7 +235,6 @@ const CONST = { TASKS: 'tasks', THREADS: 'threads', CUSTOM_STATUS: 'customStatus', - DISTANCE_REQUESTS: 'distanceRequests', }, BUTTON_STATES: { DEFAULT: 'default', diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index e1b51fb0f7c5..f37cd5bb5bf3 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -86,14 +86,6 @@ function canUseCustomStatus(betas) { return _.contains(betas, CONST.BETAS.CUSTOM_STATUS) || canUseAllBetas(betas); } -/** - * @param {Array} betas - * @returns {Boolean} - */ -function canUseDistanceRequests(betas) { - return _.contains(betas, CONST.BETAS.DISTANCE_REQUESTS) || canUseAllBetas(betas); -} - /** * Link previews are temporarily disabled. * @returns {Boolean} @@ -112,6 +104,5 @@ export default { canUsePolicyRooms, canUseTasks, canUseCustomStatus, - canUseDistanceRequests, canUseLinkPreviews, }; diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 3863b43aa073..3dfa84ce8e3e 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -52,7 +52,6 @@ function MoneyRequestSelectorPage(props) { const iouType = lodashGet(props.route, 'params.iouType', ''); const reportID = lodashGet(props.route, 'params.reportID', ''); const {translate} = useLocalize(); - const {canUseDistanceRequests} = usePermissions(); const title = { [CONST.IOU.MONEY_REQUEST_TYPE.REQUEST]: translate('iou.requestMoney'), @@ -61,7 +60,7 @@ function MoneyRequestSelectorPage(props) { }; const isFromGlobalCreate = !reportID; const isExpenseRequest = ReportUtils.isPolicyExpenseChat(props.report); - const shouldDisplayDistanceRequest = canUseDistanceRequests && (isExpenseRequest || isFromGlobalCreate); + const shouldDisplayDistanceRequest = isExpenseRequest || isFromGlobalCreate; const resetMoneyRequestInfo = () => { const moneyRequestID = `${iouType}${reportID}`; From 09e4e729de6e2c451eddf187ffc8dd1e917df6db Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 4 Sep 2023 13:03:32 -0700 Subject: [PATCH 57/99] Remove unused import to fix lint --- src/pages/iou/MoneyRequestSelectorPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/iou/MoneyRequestSelectorPage.js b/src/pages/iou/MoneyRequestSelectorPage.js index 3dfa84ce8e3e..2a2f3674cdfd 100644 --- a/src/pages/iou/MoneyRequestSelectorPage.js +++ b/src/pages/iou/MoneyRequestSelectorPage.js @@ -17,7 +17,6 @@ import ReceiptSelector from './ReceiptSelector'; import * as IOU from '../../libs/actions/IOU'; import DistanceRequestPage from './DistanceRequestPage'; import DragAndDropProvider from '../../components/DragAndDrop/Provider'; -import usePermissions from '../../hooks/usePermissions'; import OnyxTabNavigator, {TopTab} from '../../libs/Navigation/OnyxTabNavigator'; import NewRequestAmountPage from './steps/NewRequestAmountPage'; import reportPropTypes from '../reportPropTypes'; From 0f8f64bfaea78ffb865dd71f9364f7ceb3f25147 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 17:26:42 -0400 Subject: [PATCH 58/99] use pending route receipt image --- assets/images/receipt-route-pending.png | Bin 0 -> 52343 bytes src/libs/actions/IOU.js | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 assets/images/receipt-route-pending.png diff --git a/assets/images/receipt-route-pending.png b/assets/images/receipt-route-pending.png new file mode 100644 index 0000000000000000000000000000000000000000..628fb86c9a7b2cab1b29f68db75a8c77f93fb180 GIT binary patch literal 52343 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 literal 0 HcmV?d00001 diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 8d1de1dc4d60..c1089c788d60 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -21,7 +21,7 @@ import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; import * as NumberUtils from '../NumberUtils'; -import ReceiptGeneric from '../../../assets/images/receipt-generic.png'; +import ReceiptRoutePending from '../../../assets/images/receipt-route-pending.png'; let allReports; Onyx.connect({ @@ -511,7 +511,7 @@ function getMoneyRequestInformation( */ function createDistanceRequest(report, participant, comment, created, transactionID, amount, currency, merchant) { const optimisticReceipt = { - source: ReceiptGeneric, + source: ReceiptRoutePending, state: CONST.IOU.RECEIPT_STATE.OPEN, }; const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( From b174d91713650d9ebf1dbc5b34c4c39cd5689d65 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 17:41:42 -0400 Subject: [PATCH 59/99] make sure filename is also set --- src/libs/actions/IOU.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index c1089c788d60..baa4bb31c2a9 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -512,6 +512,7 @@ function getMoneyRequestInformation( function createDistanceRequest(report, participant, comment, created, transactionID, amount, currency, merchant) { const optimisticReceipt = { source: ReceiptRoutePending, + name: ReceiptRoutePending, state: CONST.IOU.RECEIPT_STATE.OPEN, }; const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( From c9f4d7fafa2442982dc17c0635d930f0f6cf4766 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 17:42:12 -0400 Subject: [PATCH 60/99] ignore thumbnail for local assets as well --- src/libs/ReceiptUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index f7a53227d8d7..b3b3a182ed2d 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -41,7 +41,7 @@ function getThumbnailAndImageURIs(path, filename) { const isReceiptImage = Str.isImage(filename); // For local files, we won't have a thumbnail yet - if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) { + if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:') || path.startsWith('/'))) { return {thumbnail: null, image: path}; } From db76cf27b78cc7fefa38f6587a9b82b095998a3a Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 5 Sep 2023 02:43:42 +0500 Subject: [PATCH 61/99] fix: wrap distance request inside scrollable view --- src/components/DistanceRequest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/DistanceRequest.js b/src/components/DistanceRequest.js index f3b1dcd94cf9..efaf42639567 100644 --- a/src/components/DistanceRequest.js +++ b/src/components/DistanceRequest.js @@ -163,7 +163,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) useEffect(updateGradientVisibility, [scrollContainerHeight, scrollContentHeight]); return ( - <> + setScrollContainerHeight(lodashGet(event, 'nativeEvent.layout.height', 0))} @@ -266,7 +266,7 @@ function DistanceRequest({iou, iouType, report, transaction, mapboxAccessToken}) isDisabled={_.size(validatedWaypoints) < 2} text={translate('common.next')} /> - + ); } From 5150c90a1e1f910c594bae2beef886ff43d2d04d Mon Sep 17 00:00:00 2001 From: Pujan Date: Tue, 5 Sep 2023 03:57:08 +0530 Subject: [PATCH 62/99] set initial state for confirmed route --- src/components/ConfirmedRoute.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index f57fac050c65..7afcfaeddc4d 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -93,6 +93,10 @@ function ConfirmedRoute({mapboxAccessToken, transaction}) { accessToken={mapboxAccessToken.token} mapPadding={CONST.MAP_PADDING} pitchEnabled={false} + initialState={{ + zoom: CONST.MAPBOX.DEFAULT_ZOOM, + location: waypointMarkers[0].coordinate, + }} directionCoordinates={coordinates} style={styles.mapView} waypoints={waypointMarkers} From 3ed0a45c01fac14f26f3ce6d561f41db5d63f5f7 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 20:01:52 -0400 Subject: [PATCH 63/99] Revert "ignore thumbnail for local assets as well" This reverts commit c9f4d7fafa2442982dc17c0635d930f0f6cf4766. --- src/libs/ReceiptUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReceiptUtils.js b/src/libs/ReceiptUtils.js index b3b3a182ed2d..f7a53227d8d7 100644 --- a/src/libs/ReceiptUtils.js +++ b/src/libs/ReceiptUtils.js @@ -41,7 +41,7 @@ function getThumbnailAndImageURIs(path, filename) { const isReceiptImage = Str.isImage(filename); // For local files, we won't have a thumbnail yet - if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:') || path.startsWith('/'))) { + if (isReceiptImage && (path.startsWith('blob:') || path.startsWith('file:'))) { return {thumbnail: null, image: path}; } From a202a8cb299ece6d7aad6f290f21f40a8bd60c96 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 20:02:04 -0400 Subject: [PATCH 64/99] Revert "make sure filename is also set" This reverts commit b174d91713650d9ebf1dbc5b34c4c39cd5689d65. --- src/libs/actions/IOU.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index baa4bb31c2a9..c1089c788d60 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -512,7 +512,6 @@ function getMoneyRequestInformation( function createDistanceRequest(report, participant, comment, created, transactionID, amount, currency, merchant) { const optimisticReceipt = { source: ReceiptRoutePending, - name: ReceiptRoutePending, state: CONST.IOU.RECEIPT_STATE.OPEN, }; const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( From 035b011ebbce8a3c6d3ee29de785fcb50244216d Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 20:02:16 -0400 Subject: [PATCH 65/99] Revert "use pending route receipt image" This reverts commit 0f8f64bfaea78ffb865dd71f9364f7ceb3f25147. --- assets/images/receipt-route-pending.png | Bin 52343 -> 0 bytes src/libs/actions/IOU.js | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 assets/images/receipt-route-pending.png diff --git a/assets/images/receipt-route-pending.png b/assets/images/receipt-route-pending.png deleted file mode 100644 index 628fb86c9a7b2cab1b29f68db75a8c77f93fb180..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52343 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 diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index c1089c788d60..8d1de1dc4d60 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -21,7 +21,7 @@ import * as ErrorUtils from '../ErrorUtils'; import * as UserUtils from '../UserUtils'; import * as Report from './Report'; import * as NumberUtils from '../NumberUtils'; -import ReceiptRoutePending from '../../../assets/images/receipt-route-pending.png'; +import ReceiptGeneric from '../../../assets/images/receipt-generic.png'; let allReports; Onyx.connect({ @@ -511,7 +511,7 @@ function getMoneyRequestInformation( */ function createDistanceRequest(report, participant, comment, created, transactionID, amount, currency, merchant) { const optimisticReceipt = { - source: ReceiptRoutePending, + source: ReceiptGeneric, state: CONST.IOU.RECEIPT_STATE.OPEN, }; const {iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( From 842e85f1c8b31f49af0ab4567a5da5962343596c Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 20:05:11 -0400 Subject: [PATCH 66/99] transparent border --- src/styles/styles.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/styles.js b/src/styles/styles.js index 9d7e14a51fc1..3e111fd1e458 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -3778,8 +3778,8 @@ const styles = { reportActionItemImages: { flexDirection: 'row', - borderWidth: 2, - borderColor: themeColors.cardBG, + borderWidth: 4, + borderColor: themeColors.transparent, borderTopLeftRadius: variables.componentBorderRadiusLarge, borderTopRightRadius: variables.componentBorderRadiusLarge, borderBottomLeftRadius: variables.componentBorderRadiusLarge, From a87da98d806dbf14e5e4f27fab90156b48cd2ce5 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Mon, 4 Sep 2023 20:44:25 -0400 Subject: [PATCH 67/99] use border only for multiple images --- src/components/ReportActionItem/ReportActionItemImages.js | 4 +++- src/styles/styles.js | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index e9fed1ec289c..1a585e3cc8b1 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -54,10 +54,12 @@ function ReportActionItemImages({images, size, total, isHovered}) { {_.map(shownImages, ({thumbnail, image}, index) => { const isLastImage = index === numberOfShownImages - 1; + const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; + const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : undefined; return ( Date: Tue, 5 Sep 2023 10:23:51 +0800 Subject: [PATCH 68/99] Force nav up to prevent back to demo page --- src/libs/Navigation/AppNavigator/AuthScreens.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index 26282cebc398..d50bd53a0a1c 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -164,9 +164,9 @@ class AuthScreens extends React.Component { // Check if we should be running any demos immediately after signing in. if (lodashGet(this.props.demoInfo, 'saastr.isBeginningDemo', false)) { - Navigation.navigate(ROUTES.SAASTR); + Navigation.navigate(ROUTES.SAASTR, CONST.NAVIGATION.TYPE.FORCED_UP); } else if (lodashGet(this.props.demoInfo, 'sbe.isBeginningDemo', false)) { - Navigation.navigate(ROUTES.SBE); + Navigation.navigate(ROUTES.SBE, CONST.NAVIGATION.TYPE.FORCED_UP); } if (this.props.lastOpenedPublicRoomID) { // Re-open the last opened public room if the user logged in from a public room link From cb962c98c1eaf79b60c243541dd278e6c66eb2d3 Mon Sep 17 00:00:00 2001 From: Pujan Date: Tue, 5 Sep 2023 09:10:48 +0530 Subject: [PATCH 69/99] lodashget for the initial location --- src/components/ConfirmedRoute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index 7afcfaeddc4d..ef4d49ecd47e 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -95,7 +95,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction}) { pitchEnabled={false} initialState={{ zoom: CONST.MAPBOX.DEFAULT_ZOOM, - location: waypointMarkers[0].coordinate, + location: lodashGet(waypointMarkers , [0, 'coordinate'], CONST.MAPBOX.DEFAULT_COORDINATE), }} directionCoordinates={coordinates} style={styles.mapView} From 85b5b5212d3fd680da06445939d4d17fc1ebc511 Mon Sep 17 00:00:00 2001 From: Pujan Date: Tue, 5 Sep 2023 09:11:41 +0530 Subject: [PATCH 70/99] lint fix --- src/components/ConfirmedRoute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index ef4d49ecd47e..aa7c38e0c535 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -95,7 +95,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction}) { pitchEnabled={false} initialState={{ zoom: CONST.MAPBOX.DEFAULT_ZOOM, - location: lodashGet(waypointMarkers , [0, 'coordinate'], CONST.MAPBOX.DEFAULT_COORDINATE), + location: lodashGet(waypointMarkers, [0, 'coordinate'], CONST.MAPBOX.DEFAULT_COORDINATE), }} directionCoordinates={coordinates} style={styles.mapView} From f95eb46dcae41c434f89a30adb36fcd8c23748be Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Wed, 30 Aug 2023 15:00:14 +0200 Subject: [PATCH 71/99] migrate languages files to ts --- src/languages/{en.js => en.ts} | 214 ++++++++++----- src/languages/{es-ES.js => es-ES.ts} | 0 src/languages/{es.js => es.ts} | 215 ++++++++++----- .../{translations.js => translations.ts} | 1 + src/languages/types.ts | 255 ++++++++++++++++++ 5 files changed, 550 insertions(+), 135 deletions(-) rename src/languages/{en.js => en.ts} (88%) rename src/languages/{es-ES.js => es-ES.ts} (100%) rename src/languages/{es.js => es.ts} (90%) rename src/languages/{translations.js => translations.ts} (65%) create mode 100644 src/languages/types.ts diff --git a/src/languages/en.js b/src/languages/en.ts similarity index 88% rename from src/languages/en.js rename to src/languages/en.ts index 364029a81ece..79bb1300010c 100755 --- a/src/languages/en.js +++ b/src/languages/en.ts @@ -1,5 +1,74 @@ import {CONST as COMMON_CONST} from 'expensify-common/lib/CONST'; import CONST from '../CONST'; +import type { + AddressLineParams, + CharacterLimitParams, + MaxParticipantsReachedParams, + ZipCodeExampleFormatParams, + LoggedInAsParams, + NewFaceEnterMagicCodeParams, + WelcomeEnterMagicCodeParams, + AlreadySignedInParams, + GoBackMessageParams, + LocalTimeParams, + EditActionParams, + DeleteActionParams, + DeleteConfirmationParams, + BeginningOfChatHistoryDomainRoomPartOneParams, + BeginningOfChatHistoryAdminRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartTwo, + WelcomeToRoomParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + ReportArchiveReasonsPolicyDeletedParams, + RequestCountParams, + SettleExpensifyCardParams, + SettlePaypalMeParams, + RequestAmountParams, + SplitAmountParams, + AmountEachParams, + PayerOwesAmountParams, + PayerOwesParams, + PayerPaidAmountParams, + PayerPaidParams, + PayerSettledParams, + WaitingOnBankAccountParams, + SettledAfterAddedBankAccountParams, + PaidElsewhereWithAmountParams, + PaidUsingPaypalWithAmountParams, + PaidUsingExpensifyWithAmountParams, + ThreadRequestReportNameParams, + ThreadSentMoneyReportNameParams, + SizeExceededParams, + ResolutionConstraintsParams, + NotAllowedExtensionParams, + EnterMagicCodeParams, + TransferParams, + InstantSummaryParams, + NotYouParams, + DateShouldBeBeforeParams, + DateShouldBeAfterParams, + IncorrectZipFormatParams, + WeSentYouMagicSignInLinkParams, + ToValidateLoginParams, + NoLongerHaveAccessParams, + OurEmailProviderParams, + ConfirmThatParams, + UntilTimeParams, + StepCounterParams, + UserIsAlreadyMemberOfWorkspaceParams, + GoToRoomParams, + WelcomeNoteParams, + RoomNameReservedErrorParams, + RenamedRoomActionParams, + RoomRenamedToParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + ParentNavigationSummaryParams, + ManagerApprovedParams, +} from './types'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; /* eslint-disable max-len */ @@ -73,7 +142,7 @@ export default { currentMonth: 'Current month', ssnLast4: 'Last 4 digits of SSN', ssnFull9: 'Full 9 digits of SSN', - addressLine: ({lineNumber}) => `Address line ${lineNumber}`, + addressLine: ({lineNumber}: AddressLineParams) => `Address line ${lineNumber}`, personalAddress: 'Personal address', companyAddress: 'Company address', noPO: 'PO boxes and mail drop addresses are not allowed', @@ -104,7 +173,7 @@ export default { acceptTerms: 'You must accept the Terms of Service to continue', phoneNumber: `Please enter a valid phone number, with the country code (e.g. ${CONST.EXAMPLE_PHONE_NUMBER})`, fieldRequired: 'This field is required.', - characterLimit: ({limit}) => `Exceeds the maximum length of ${limit} characters`, + characterLimit: ({limit}: CharacterLimitParams) => `Exceeds the maximum length of ${limit} characters`, dateInvalid: 'Please select a valid date', invalidCharacter: 'Invalid character', enterMerchant: 'Enter a merchant name', @@ -137,14 +206,14 @@ export default { youAfterPreposition: 'you', your: 'your', conciergeHelp: 'Please reach out to Concierge for help.', - maxParticipantsReached: ({count}) => `You've selected the maximum number (${count}) of participants.`, + maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `You've selected the maximum number (${count}) of participants.`, youAppearToBeOffline: 'You appear to be offline.', thisFeatureRequiresInternet: 'This feature requires an active internet connection to be used.', areYouSure: 'Are you sure?', verify: 'Verify', yesContinue: 'Yes, continue', websiteExample: 'e.g. https://www.expensify.com', - zipCodeExampleFormat: ({zipSampleFormat}) => (zipSampleFormat ? `e.g. ${zipSampleFormat}` : ''), + zipCodeExampleFormat: ({zipSampleFormat}: ZipCodeExampleFormatParams) => (zipSampleFormat ? `e.g. ${zipSampleFormat}` : ''), description: 'Description', with: 'with', shareCode: 'Share code', @@ -210,7 +279,7 @@ export default { redirectedToDesktopApp: "We've redirected you to the desktop app.", youCanAlso: 'You can also', openLinkInBrowser: 'open this link in your browser', - loggedInAs: ({email}) => `You're logged in as ${email}. Click "Open link" in the prompt to log into the desktop app with this account.`, + loggedInAs: ({email}: LoggedInAsParams) => `You're logged in as ${email}. Click "Open link" in the prompt to log into the desktop app with this account.`, doNotSeePrompt: "Can't see the prompt?", tryAgain: 'Try again', or: ', or', @@ -256,8 +325,9 @@ export default { phrase2: "Money talks. And now that chat and payments are in one place, it's also easy.", phrase3: 'Your payments get to you as fast as you can get your point across.', enterPassword: 'Please enter your password', - newFaceEnterMagicCode: ({login}) => `It's always great to see a new face around here! Please enter the magic code sent to ${login}. It should arrive within a minute or two.`, - welcomeEnterMagicCode: ({login}) => `Please enter the magic code sent to ${login}. It should arrive within a minute or two.`, + newFaceEnterMagicCode: ({login}: NewFaceEnterMagicCodeParams) => + `It's always great to see a new face around here! Please enter the magic code sent to ${login}. It should arrive within a minute or two.`, + welcomeEnterMagicCode: ({login}: WelcomeEnterMagicCodeParams) => `Please enter the magic code sent to ${login}. It should arrive within a minute or two.`, }, DownloadAppModal: { downloadTheApp: 'Download the app', @@ -271,8 +341,8 @@ export default { }, }, thirdPartySignIn: { - alreadySignedIn: ({email}) => `You are already signed in as ${email}.`, - goBackMessage: ({provider}) => `Don't want to sign in with ${provider}?`, + alreadySignedIn: ({email}: AlreadySignedInParams) => `You are already signed in as ${email}.`, + goBackMessage: ({provider}: GoBackMessageParams) => `Don't want to sign in with ${provider}?`, continueWithMyCurrentSession: 'Continue with my current session', redirectToDesktopMessage: "We'll redirect you to the desktop app once you finish signing in.", signInAgreementMessage: 'By logging in, you agree to the', @@ -297,7 +367,7 @@ export default { ], blockedFromConcierge: 'Communication is barred', fileUploadFailed: 'Upload failed. File is not supported.', - localTime: ({user, time}) => `It's ${time} for ${user}`, + localTime: ({user, time}: LocalTimeParams) => `It's ${time} for ${user}`, edited: '(edited)', emoji: 'Emoji', collapse: 'Collapse', @@ -311,9 +381,9 @@ export default { copyEmailToClipboard: 'Copy email to clipboard', markAsUnread: 'Mark as unread', markAsRead: 'Mark as read', - editAction: ({action}) => `Edit ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, - deleteAction: ({action}) => `Delete ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, - deleteConfirmation: ({action}) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, + editAction: ({action}: EditActionParams) => `Edit ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, + deleteAction: ({action}: DeleteActionParams) => `Delete ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}`, + deleteConfirmation: ({action}: DeleteConfirmationParams) => `Are you sure you want to delete this ${ReportActionsUtils.isMoneyRequestAction(action) ? 'request' : 'comment'}?`, onlyVisible: 'Only visible to', replyInThread: 'Reply in thread', flagAsOffensive: 'Flag as offensive', @@ -325,13 +395,14 @@ export default { reportActionsView: { beginningOfArchivedRoomPartOne: 'You missed the party in ', beginningOfArchivedRoomPartTwo: ", there's nothing to see here.", - beginningOfChatHistoryDomainRoomPartOne: ({domainRoom}) => `Collaboration with everyone at ${domainRoom} starts here! 🎉\nUse `, + beginningOfChatHistoryDomainRoomPartOne: ({domainRoom}: BeginningOfChatHistoryDomainRoomPartOneParams) => `Collaboration with everyone at ${domainRoom} starts here! 🎉\nUse `, beginningOfChatHistoryDomainRoomPartTwo: ' to chat with colleagues, share tips, and ask questions.', - beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}) => `Collaboration among ${workspaceName} admins starts here! 🎉\nUse `, + beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAdminRoomPartOneParams) => `Collaboration among ${workspaceName} admins starts here! 🎉\nUse `, beginningOfChatHistoryAdminRoomPartTwo: ' to chat about topics such as workspace configurations and more.', beginningOfChatHistoryAdminOnlyPostingRoom: 'Only admins can send messages in this room.', - beginningOfChatHistoryAnnounceRoomPartOne: ({workspaceName}) => `Collaboration between all ${workspaceName} members starts here! 🎉\nUse `, - beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}) => ` to chat about anything ${workspaceName} related.`, + beginningOfChatHistoryAnnounceRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartOneParams) => + `Collaboration between all ${workspaceName} members starts here! 🎉\nUse `, + beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` to chat about anything ${workspaceName} related.`, beginningOfChatHistoryUserRoomPartOne: 'Collaboration starts here! 🎉\nUse this space to chat about anything ', beginningOfChatHistoryUserRoomPartTwo: ' related.', beginningOfChatHistory: 'This is the beginning of your chat with ', @@ -340,7 +411,7 @@ export default { beginningOfChatHistoryPolicyExpenseChatPartThree: ' starts here! 🎉 This is the place to chat, request money and settle up.', chatWithAccountManager: 'Chat with your account manager here', sayHello: 'Say hello!', - welcomeToRoom: ({roomName}) => `Welcome to ${roomName}!`, + welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `Welcome to ${roomName}!`, usePlusButton: '\n\nYou can also use the + button below to request money or assign a task!', }, reportAction: { @@ -357,12 +428,14 @@ export default { }, reportArchiveReasons: { [CONST.REPORT.ARCHIVE_REASON.DEFAULT]: 'This chat room has been archived.', - [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED]: ({displayName}) => `This workspace chat is no longer active because ${displayName} closed their account.`, - [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED]: ({displayName, oldDisplayName}) => + [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED]: ({displayName}: ReportArchiveReasonsClosedParams) => + `This workspace chat is no longer active because ${displayName} closed their account.`, + [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED]: ({displayName, oldDisplayName}: ReportArchiveReasonsMergedParams) => `This workspace chat is no longer active because ${oldDisplayName} has merged their account with ${displayName}.`, - [CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY]: ({displayName, policyName}) => + [CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY]: ({displayName, policyName}: ReportArchiveReasonsRemovedFromPolicyParams) => `This workspace chat is no longer active because ${displayName} is no longer a member of the ${policyName} workspace.`, - [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}) => `This workspace chat is no longer active because ${policyName} is no longer an active workspace.`, + [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsPolicyDeletedParams) => + `This workspace chat is no longer active because ${policyName} is no longer an active workspace.`, }, writeCapabilityPage: { label: 'Who can post', @@ -424,33 +497,34 @@ export default { receiptScanning: 'Receipt scan in progress…', receiptStatusTitle: 'Scanning…', receiptStatusText: "Only you can see this receipt when it's scanning. Check back later or enter the details now.", - requestCount: ({count, scanningReceipts = 0}) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} requests${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}`, deleteRequest: 'Delete request', deleteConfirmation: 'Are you sure that you want to delete this request?', settledExpensify: 'Paid', settledElsewhere: 'Paid elsewhere', settledPaypalMe: 'Paid using Paypal.me', - settleExpensify: ({formattedAmount}) => `Pay ${formattedAmount} with Expensify`, + settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pay ${formattedAmount} with Expensify`, payElsewhere: 'Pay elsewhere', - settlePaypalMe: ({formattedAmount}) => `Pay ${formattedAmount} with PayPal.me`, - requestAmount: ({amount}) => `request ${amount}`, - splitAmount: ({amount}) => `split ${amount}`, - amountEach: ({amount}) => `${amount} each`, - payerOwesAmount: ({payer, amount}) => `${payer} owes ${amount}`, - payerOwes: ({payer}) => `${payer} owes: `, - payerPaidAmount: ({payer, amount}) => `${payer} paid ${amount}`, - payerPaid: ({payer}) => `${payer} paid: `, - managerApproved: ({manager}) => `${manager} approved:`, - payerSettled: ({amount}) => `paid ${amount}`, - waitingOnBankAccount: ({submitterDisplayName}) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, - settledAfterAddedBankAccount: ({submitterDisplayName, amount}) => `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, - paidElsewhereWithAmount: ({amount}) => `paid ${amount} elsewhere`, - paidUsingPaypalWithAmount: ({amount}) => `paid ${amount} using Paypal.me`, - paidUsingExpensifyWithAmount: ({amount}) => `paid ${amount} using Expensify`, + settlePaypalMe: ({formattedAmount}: SettlePaypalMeParams) => `Pay ${formattedAmount} with PayPal.me`, + requestAmount: ({amount}: RequestAmountParams) => `request ${amount}`, + splitAmount: ({amount}: SplitAmountParams) => `split ${amount}`, + amountEach: ({amount}: AmountEachParams) => `${amount} each`, + payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} owes ${amount}`, + payerOwes: ({payer}: PayerOwesParams) => `${payer} owes: `, + payerPaidAmount: ({payer, amount}: PayerPaidAmountParams): string => `${payer} paid ${amount}`, + payerPaid: ({payer}: PayerPaidParams) => `${payer} paid: `, + managerApproved: ({manager}: ManagerApprovedParams) => `${manager} approved:`, + payerSettled: ({amount}: PayerSettledParams) => `paid ${amount}`, + waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `started settling up, payment is held until ${submitterDisplayName} adds a bank account`, + settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => + `${submitterDisplayName} added a bank account. The ${amount} payment has been made.`, + paidElsewhereWithAmount: ({amount}: PaidElsewhereWithAmountParams) => `paid ${amount} elsewhere`, + paidUsingPaypalWithAmount: ({amount}: PaidUsingPaypalWithAmountParams) => `paid ${amount} using Paypal.me`, + paidUsingExpensifyWithAmount: ({amount}: PaidUsingExpensifyWithAmountParams) => `paid ${amount} using Expensify`, noReimbursableExpenses: 'This report has an invalid amount', pendingConversionMessage: "Total will update when you're back online", - threadRequestReportName: ({formattedAmount, comment}) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, - threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, + threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `${formattedAmount} request${comment ? ` for ${comment}` : ''}`, + threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} sent${comment ? ` for ${comment}` : ''}`, error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', @@ -478,10 +552,10 @@ export default { removePhoto: 'Remove photo', editImage: 'Edit photo', deleteWorkspaceError: 'Sorry, there was an unexpected problem deleting your workspace avatar.', - sizeExceeded: ({maxUploadSizeInMB}) => `The selected image exceeds the maximum upload size of ${maxUploadSizeInMB}MB.`, - resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}) => + sizeExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `The selected image exceeds the maximum upload size of ${maxUploadSizeInMB}MB.`, + resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}: ResolutionConstraintsParams) => `Please upload an image larger than ${minHeightInPx}x${minWidthInPx} pixels and smaller than ${maxHeightInPx}x${maxWidthInPx} pixels.`, - notAllowedExtension: ({allowedExtensions}) => `Profile picture must be one of the following types: ${allowedExtensions.join(', ')}.`, + notAllowedExtension: ({allowedExtensions}: NotAllowedExtensionParams) => `Profile picture must be one of the following types: ${allowedExtensions.join(', ')}.`, }, profilePage: { profile: 'Profile', @@ -517,7 +591,7 @@ export default { helpTextAfterEmail: ' from multiple email addresses.', pleaseVerify: 'Please verify this contact method', getInTouch: "Whenever we need to get in touch with you, we'll use this contact method.", - enterMagicCode: ({contactMethod}) => `Please enter the magic code sent to ${contactMethod}`, + enterMagicCode: ({contactMethod}: EnterMagicCodeParams) => `Please enter the magic code sent to ${contactMethod}`, setAsDefault: 'Set as default', yourDefaultContactMethod: 'This is your current default contact method. You will not be able to delete this contact method until you set an alternative default by selecting another contact method and pressing “Set as default”.', @@ -708,9 +782,9 @@ export default { addBankAccountFailure: 'An unexpected error occurred while trying to add your bank account. Please try again.', }, transferAmountPage: { - transfer: ({amount}) => `Transfer${amount ? ` ${amount}` : ''}`, + transfer: ({amount}: TransferParams) => `Transfer${amount ? ` ${amount}` : ''}`, instant: 'Instant (Debit card)', - instantSummary: ({rate, minAmount}) => `${rate}% fee (${minAmount} minimum)`, + instantSummary: ({rate, minAmount}: InstantSummaryParams) => `${rate}% fee (${minAmount} minimum)`, ach: '1-3 Business days (Bank account)', achSummary: 'No fee', whichAccount: 'Which account?', @@ -841,7 +915,7 @@ export default { }, cannotGetAccountDetails: "Couldn't retrieve account details, please try to sign in again.", loginForm: 'Login form', - notYou: ({user}) => `Not ${user}?`, + notYou: ({user}: NotYouParams) => `Not ${user}?`, }, personalDetails: { error: { @@ -857,27 +931,29 @@ export default { legalLastName: 'Legal last name', homeAddress: 'Home address', error: { - dateShouldBeBefore: ({dateString}) => `Date should be before ${dateString}.`, - dateShouldBeAfter: ({dateString}) => `Date should be after ${dateString}.`, + dateShouldBeBefore: ({dateString}: DateShouldBeBeforeParams) => `Date should be before ${dateString}.`, + dateShouldBeAfter: ({dateString}: DateShouldBeAfterParams) => `Date should be after ${dateString}.`, hasInvalidCharacter: 'Name can only include letters.', - incorrectZipFormat: ({zipFormat}) => `Incorrect zip code format.${zipFormat ? ` Acceptable format: ${zipFormat}` : ''}`, + incorrectZipFormat: ({zipFormat}: IncorrectZipFormatParams) => `Incorrect zip code format.${zipFormat ? ` Acceptable format: ${zipFormat}` : ''}`, }, }, resendValidationForm: { linkHasBeenResent: 'Link has been re-sent', - weSentYouMagicSignInLink: ({login, loginType}) => `I've sent a magic sign-in link to ${login}. Please check your ${loginType} to sign in.`, + weSentYouMagicSignInLink: ({login, loginType}: WeSentYouMagicSignInLinkParams) => `I've sent a magic sign-in link to ${login}. Please check your ${loginType} to sign in.`, resendLink: 'Resend link', }, unlinkLoginForm: { - toValidateLogin: ({primaryLogin, secondaryLogin}) => `To validate ${secondaryLogin}, please resend the magic code from the Account Settings of ${primaryLogin}.`, - noLongerHaveAccess: ({primaryLogin}) => `If you no longer have access to ${primaryLogin}, please unlink your accounts.`, + toValidateLogin: ({primaryLogin, secondaryLogin}: ToValidateLoginParams) => + `To validate ${secondaryLogin}, please resend the magic code from the Account Settings of ${primaryLogin}.`, + noLongerHaveAccess: ({primaryLogin}: NoLongerHaveAccessParams) => `If you no longer have access to ${primaryLogin}, please unlink your accounts.`, unlink: 'Unlink', linkSent: 'Link sent!', succesfullyUnlinkedLogin: 'Secondary login successfully unlinked!', }, emailDeliveryFailurePage: { - ourEmailProvider: ({login}) => `Our email provider has temporarily suspended emails to ${login} due to delivery issues. To unblock your login, please follow these steps:`, - confirmThat: ({login}) => `Confirm that ${login} is spelled correctly and is a real, deliverable email address. `, + ourEmailProvider: (user: OurEmailProviderParams) => + `Our email provider has temporarily suspended emails to ${user.login} due to delivery issues. To unblock your login, please follow these steps:`, + confirmThat: ({login}: ConfirmThatParams) => `Confirm that ${login} is spelled correctly and is a real, deliverable email address. `, emailAliases: 'Email aliases such as "expenses@domain.com" must have access to their own email inbox for it to be a valid Expensify login.', ensureYourEmailClient: 'Ensure your email client allows expensify.com emails. ', youCanFindDirections: 'You can find directions on how to complete this step ', @@ -922,9 +998,9 @@ export default { save: 'Save', message: 'Message', untilTomorrow: 'Until tomorrow', - untilTime: ({time}) => `Until ${time}`, + untilTime: ({time}: UntilTimeParams) => `Until ${time}`, }, - stepCounter: ({step, total, text}) => { + stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Step ${step}`; if (total) { @@ -1007,7 +1083,7 @@ export default { messages: { errorMessageInvalidPhone: `Please enter a valid phone number without brackets or dashes. If you're outside the US please include your country code (e.g. ${CONST.EXAMPLE_PHONE_NUMBER}).`, errorMessageInvalidEmail: 'Invalid email', - userIsAlreadyMemberOfWorkspace: ({login, workspace}) => `${login} is already a member of ${workspace}`, + userIsAlreadyMemberOfWorkspace: ({login, workspace}: UserIsAlreadyMemberOfWorkspaceParams) => `${login} is already a member of ${workspace}`, }, onfidoStep: { acceptTerms: 'By continuing with the request to activate your Expensify wallet, you confirm that you have read, understand and accept ', @@ -1212,7 +1288,7 @@ export default { unavailable: 'Unavailable workspace', memberNotFound: 'Member not found. To invite a new member to the workspace, please use the Invite button above.', notAuthorized: `You do not have access to this page. Are you trying to join the workspace? Please reach out to the owner of this workspace so they can add you as a member! Something else? Reach out to ${CONST.EMAIL.CONCIERGE}`, - goToRoom: ({roomName}) => `Go to ${roomName} room`, + goToRoom: ({roomName}: GoToRoomParams) => `Go to ${roomName} room`, }, emptyWorkspace: { title: 'Create a new workspace', @@ -1308,7 +1384,7 @@ export default { personalMessagePrompt: 'Message', genericFailureMessage: 'An error occurred inviting the user to the workspace, please try again.', inviteNoMembersError: 'Please select at least one member to invite', - welcomeNote: ({workspaceName}) => + welcomeNote: ({workspaceName}: WelcomeNoteParams) => `You have been invited to ${workspaceName || 'a workspace'}! Download the Expensify mobile app at use.expensify.com/download to start tracking your expenses.`, }, editor: { @@ -1381,15 +1457,16 @@ export default { restrictedDescription: 'People in your workspace can find this room', privateDescription: 'People invited to this room can find it', publicDescription: 'Anyone can find this room', + // eslint-disable-next-line @typescript-eslint/naming-convention public_announceDescription: 'Anyone can find this room', createRoom: 'Create room', roomAlreadyExistsError: 'A room with this name already exists', - roomNameReservedError: ({reservedName}) => `${reservedName} is a default room on all workspaces. Please choose another name.`, + roomNameReservedError: ({reservedName}: RoomNameReservedErrorParams) => `${reservedName} is a default room on all workspaces. Please choose another name.`, roomNameInvalidError: 'Room names can only include lowercase letters, numbers and hyphens', pleaseEnterRoomName: 'Please enter a room name', pleaseSelectWorkspace: 'Please select a workspace', - renamedRoomAction: ({oldName, newName}) => ` renamed this room from ${oldName} to ${newName}`, - roomRenamedTo: ({newName}) => `Room renamed to ${newName}`, + renamedRoomAction: ({oldName, newName}: RenamedRoomActionParams) => ` renamed this room from ${oldName} to ${newName}`, + roomRenamedTo: ({newName}: RoomRenamedToParams) => `Room renamed to ${newName}`, social: 'social', selectAWorkspace: 'Select a workspace', growlMessageOnRenameError: 'Unable to rename policy room, please check your connection and try again.', @@ -1397,6 +1474,7 @@ export default { restricted: 'Restricted', private: 'Private', public: 'Public', + // eslint-disable-next-line @typescript-eslint/naming-convention public_announce: 'Public Announce', }, }, @@ -1540,8 +1618,8 @@ export default { noActivityYet: 'No activity yet', }, chronos: { - oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} for ${dayCount} ${dayCount === 1 ? 'day' : 'days'} until ${date}`, - oooEventSummaryPartialDay: ({summary, timePeriod, date}) => `${summary} from ${timePeriod} on ${date}`, + oooEventSummaryFullDay: ({summary, dayCount, date}: OOOEventSummaryFullDayParams) => `${summary} for ${dayCount} ${dayCount === 1 ? 'day' : 'days'} until ${date}`, + oooEventSummaryPartialDay: ({summary, timePeriod, date}: OOOEventSummaryPartialDayParams) => `${summary} from ${timePeriod} on ${date}`, }, footer: { features: 'Features', @@ -1597,7 +1675,7 @@ export default { reply: 'Reply', from: 'From', in: 'In', - parentNavigationSummary: ({rootReportName, workspaceName}) => `From ${rootReportName}${workspaceName ? ` in ${workspaceName}` : ''}`, + parentNavigationSummary: ({rootReportName, workspaceName}: ParentNavigationSummaryParams) => `From ${rootReportName}${workspaceName ? ` in ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copy URL to clipboard', @@ -1677,4 +1755,4 @@ export default { heroBody: 'Use New Expensify for event updates, networking, social chatter, and to get paid back for your ride to or from the show!', }, }, -}; +} as const; diff --git a/src/languages/es-ES.js b/src/languages/es-ES.ts similarity index 100% rename from src/languages/es-ES.js rename to src/languages/es-ES.ts diff --git a/src/languages/es.js b/src/languages/es.ts similarity index 90% rename from src/languages/es.js rename to src/languages/es.ts index 2e7ae7dd09eb..0252fce3a421 100644 --- a/src/languages/es.js +++ b/src/languages/es.ts @@ -1,5 +1,74 @@ import CONST from '../CONST'; import * as ReportActionsUtils from '../libs/ReportActionsUtils'; +import type { + AddressLineParams, + CharacterLimitParams, + MaxParticipantsReachedParams, + ZipCodeExampleFormatParams, + LoggedInAsParams, + NewFaceEnterMagicCodeParams, + WelcomeEnterMagicCodeParams, + AlreadySignedInParams, + GoBackMessageParams, + LocalTimeParams, + EditActionParams, + DeleteActionParams, + DeleteConfirmationParams, + BeginningOfChatHistoryDomainRoomPartOneParams, + BeginningOfChatHistoryAdminRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartTwo, + WelcomeToRoomParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + ReportArchiveReasonsPolicyDeletedParams, + RequestCountParams, + SettleExpensifyCardParams, + SettlePaypalMeParams, + RequestAmountParams, + SplitAmountParams, + AmountEachParams, + PayerOwesAmountParams, + PayerOwesParams, + PayerPaidAmountParams, + PayerPaidParams, + PayerSettledParams, + WaitingOnBankAccountParams, + SettledAfterAddedBankAccountParams, + PaidElsewhereWithAmountParams, + PaidUsingPaypalWithAmountParams, + PaidUsingExpensifyWithAmountParams, + ThreadRequestReportNameParams, + ThreadSentMoneyReportNameParams, + SizeExceededParams, + ResolutionConstraintsParams, + NotAllowedExtensionParams, + EnterMagicCodeParams, + TransferParams, + InstantSummaryParams, + NotYouParams, + DateShouldBeBeforeParams, + DateShouldBeAfterParams, + IncorrectZipFormatParams, + WeSentYouMagicSignInLinkParams, + ToValidateLoginParams, + NoLongerHaveAccessParams, + OurEmailProviderParams, + ConfirmThatParams, + UntilTimeParams, + StepCounterParams, + UserIsAlreadyMemberOfWorkspaceParams, + GoToRoomParams, + WelcomeNoteParams, + RoomNameReservedErrorParams, + RenamedRoomActionParams, + RoomRenamedToParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + ParentNavigationSummaryParams, + ManagerApprovedParams, +} from './types'; /* eslint-disable max-len */ export default { @@ -72,7 +141,7 @@ export default { currentMonth: 'Mes actual', ssnLast4: 'Últimos 4 dígitos de su SSN', ssnFull9: 'Los 9 dígitos del SSN', - addressLine: ({lineNumber}) => `Dirección línea ${lineNumber}`, + addressLine: ({lineNumber}: AddressLineParams) => `Dirección línea ${lineNumber}`, personalAddress: 'Dirección física personal', companyAddress: 'Dirección física de la empresa', noPO: 'No se aceptan apartados ni direcciones postales', @@ -103,7 +172,7 @@ export default { acceptTerms: 'Debes aceptar los Términos de Servicio para continuar', phoneNumber: `Introduce un teléfono válido, incluyendo el código del país (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER})`, fieldRequired: 'Este campo es obligatorio.', - characterLimit: ({limit}) => `Supera el límite de ${limit} caracteres`, + characterLimit: ({limit}: CharacterLimitParams) => `Supera el límite de ${limit} caracteres`, dateInvalid: 'Por favor, selecciona una fecha válida', invalidCharacter: 'Carácter invalido', enterMerchant: 'Introduce un comerciante', @@ -136,14 +205,14 @@ export default { youAfterPreposition: 'ti', your: 'tu', conciergeHelp: 'Por favor, contacta con Concierge para obtener ayuda.', - maxParticipantsReached: ({count}) => `Has seleccionado el número máximo (${count}) de participantes.`, + maxParticipantsReached: ({count}: MaxParticipantsReachedParams) => `Has seleccionado el número máximo (${count}) de participantes.`, youAppearToBeOffline: 'Parece que estás desconectado.', thisFeatureRequiresInternet: 'Esta función requiere una conexión a Internet activa para ser utilizada.', areYouSure: '¿Estás seguro?', verify: 'Verifique', yesContinue: 'Sí, continuar', websiteExample: 'p. ej. https://www.expensify.com', - zipCodeExampleFormat: ({zipSampleFormat}) => (zipSampleFormat ? `p. ej. ${zipSampleFormat}` : ''), + zipCodeExampleFormat: ({zipSampleFormat}: ZipCodeExampleFormatParams) => (zipSampleFormat ? `p. ej. ${zipSampleFormat}` : ''), description: 'Descripción', with: 'con', shareCode: 'Compartir código', @@ -209,7 +278,8 @@ export default { redirectedToDesktopApp: 'Te hemos redirigido a la aplicación de escritorio.', youCanAlso: 'También puedes', openLinkInBrowser: 'abrir este enlace en tu navegador', - loggedInAs: ({email}) => `Has iniciado sesión como ${email}. Haga clic en "Abrir enlace" en el aviso para iniciar sesión en la aplicación de escritorio con esta cuenta.`, + loggedInAs: ({email}: LoggedInAsParams) => + `Has iniciado sesión como ${email}. Haga clic en "Abrir enlace" en el aviso para iniciar sesión en la aplicación de escritorio con esta cuenta.`, doNotSeePrompt: '¿No ves el aviso?', tryAgain: 'Inténtalo de nuevo', or: ', o', @@ -255,8 +325,9 @@ export default { phrase2: 'El dinero habla. Y ahora que chat y pagos están en un mismo lugar, es también fácil.', phrase3: 'Tus pagos llegan tan rápido como tus mensajes.', enterPassword: 'Por favor, introduce tu contraseña', - newFaceEnterMagicCode: ({login}) => `¡Siempre es genial ver una cara nueva por aquí! Por favor ingresa el código mágico enviado a ${login}. Debería llegar en un par de minutos.`, - welcomeEnterMagicCode: ({login}) => `Por favor, introduce el código mágico enviado a ${login}. Debería llegar en un par de minutos.`, + newFaceEnterMagicCode: ({login}: NewFaceEnterMagicCodeParams) => + `¡Siempre es genial ver una cara nueva por aquí! Por favor ingresa el código mágico enviado a ${login}. Debería llegar en un par de minutos.`, + welcomeEnterMagicCode: ({login}: WelcomeEnterMagicCodeParams) => `Por favor, introduce el código mágico enviado a ${login}. Debería llegar en un par de minutos.`, }, DownloadAppModal: { downloadTheApp: 'Descarga la aplicación', @@ -270,8 +341,8 @@ export default { }, }, thirdPartySignIn: { - alreadySignedIn: ({email}) => `Ya has iniciado sesión con ${email}.`, - goBackMessage: ({provider}) => `No quieres iniciar sesión con ${provider}?`, + alreadySignedIn: ({email}: AlreadySignedInParams) => `Ya has iniciado sesión con ${email}.`, + goBackMessage: ({provider}: GoBackMessageParams) => `No quieres iniciar sesión con ${provider}?`, continueWithMyCurrentSession: 'Continuar con mi sesión actual', redirectToDesktopMessage: 'Lo redirigiremos a la aplicación de escritorio una vez que termine de iniciar sesión.', signInAgreementMessage: 'Al iniciar sesión, aceptas las', @@ -296,7 +367,7 @@ export default { ], blockedFromConcierge: 'Comunicación no permitida', fileUploadFailed: 'Subida fallida. El archivo no es compatible.', - localTime: ({user, time}) => `Son las ${time} para ${user}`, + localTime: ({user, time}: LocalTimeParams) => `Son las ${time} para ${user}`, edited: '(editado)', emoji: 'Emoji', collapse: 'Colapsar', @@ -310,9 +381,9 @@ export default { copyEmailToClipboard: 'Copiar email al portapapeles', markAsUnread: 'Marcar como no leído', markAsRead: 'Marcar como leído', - editAction: ({action}) => `Edit ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, - deleteAction: ({action}) => `Eliminar ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, - deleteConfirmation: ({action}) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + editAction: ({action}: EditActionParams) => `Edit ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + deleteAction: ({action}: DeleteActionParams) => `Eliminar ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, + deleteConfirmation: ({action}: DeleteConfirmationParams) => `¿Estás seguro de que quieres eliminar este ${ReportActionsUtils.isMoneyRequestAction(action) ? 'pedido' : 'comentario'}`, onlyVisible: 'Visible sólo para', replyInThread: 'Responder en el hilo', flagAsOffensive: 'Marcar como ofensivo', @@ -324,13 +395,15 @@ export default { reportActionsView: { beginningOfArchivedRoomPartOne: 'Te perdiste la fiesta en ', beginningOfArchivedRoomPartTwo: ', no hay nada que ver aquí.', - beginningOfChatHistoryDomainRoomPartOne: ({domainRoom}) => `Colabora aquí con todos los participantes de ${domainRoom}! 🎉\nUtiliza `, + beginningOfChatHistoryDomainRoomPartOne: ({domainRoom}: BeginningOfChatHistoryDomainRoomPartOneParams) => `Colabora aquí con todos los participantes de ${domainRoom}! 🎉\nUtiliza `, beginningOfChatHistoryDomainRoomPartTwo: ' para chatear con compañeros, compartir consejos o hacer una pregunta.', - beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}) => `Este es el lugar para que los administradores de ${workspaceName} colaboren! 🎉\nUsa `, + beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAdminRoomPartOneParams) => + `Este es el lugar para que los administradores de ${workspaceName} colaboren! 🎉\nUsa `, beginningOfChatHistoryAdminRoomPartTwo: ' para chatear sobre temas como la configuración del espacio de trabajo y mas.', beginningOfChatHistoryAdminOnlyPostingRoom: 'Solo los administradores pueden enviar mensajes en esta sala.', - beginningOfChatHistoryAnnounceRoomPartOne: ({workspaceName}) => `Este es el lugar para que todos los miembros de ${workspaceName} colaboren! 🎉\nUsa `, - beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`, + beginningOfChatHistoryAnnounceRoomPartOne: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartOneParams) => + `Este es el lugar para que todos los miembros de ${workspaceName} colaboren! 🎉\nUsa `, + beginningOfChatHistoryAnnounceRoomPartTwo: ({workspaceName}: BeginningOfChatHistoryAnnounceRoomPartTwo) => ` para chatear sobre cualquier cosa relacionada con ${workspaceName}.`, beginningOfChatHistoryUserRoomPartOne: 'Este es el lugar para colaborar! 🎉\nUsa este espacio para chatear sobre cualquier cosa relacionada con ', beginningOfChatHistoryUserRoomPartTwo: '.', beginningOfChatHistory: 'Aquí comienzan tus conversaciones con ', @@ -339,7 +412,7 @@ export default { beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquí! 🎉 Este es el lugar donde chatear, pedir dinero y pagar.', chatWithAccountManager: 'Chatea con tu gestor de cuenta aquí', sayHello: '¡Saluda!', - welcomeToRoom: ({roomName}) => `¡Bienvenido a ${roomName}!`, + welcomeToRoom: ({roomName}: WelcomeToRoomParams) => `¡Bienvenido a ${roomName}!`, usePlusButton: '\n\n¡También puedes usar el botón + de abajo para pedir dinero o asignar una tarea!', }, reportAction: { @@ -356,12 +429,14 @@ export default { }, reportArchiveReasons: { [CONST.REPORT.ARCHIVE_REASON.DEFAULT]: 'Esta sala de chat ha sido eliminada.', - [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED]: ({displayName}) => `Este chat de espacio de trabajo esta desactivado porque ${displayName} ha cerrado su cuenta.`, - [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED]: ({displayName, oldDisplayName}) => + [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_CLOSED]: ({displayName}: ReportArchiveReasonsClosedParams) => + `Este chat de espacio de trabajo esta desactivado porque ${displayName} ha cerrado su cuenta.`, + [CONST.REPORT.ARCHIVE_REASON.ACCOUNT_MERGED]: ({displayName, oldDisplayName}: ReportArchiveReasonsMergedParams) => `Este chat de espacio de trabajo esta desactivado porque ${oldDisplayName} ha combinado su cuenta con ${displayName}.`, - [CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY]: ({displayName, policyName}) => + [CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY]: ({displayName, policyName}: ReportArchiveReasonsRemovedFromPolicyParams) => `Este chat de espacio de trabajo esta desactivado porque ${displayName} ha dejado de ser miembro del espacio de trabajo ${policyName}.`, - [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}) => `Este chat de espacio de trabajo esta desactivado porque el espacio de trabajo ${policyName} se ha eliminado.`, + [CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED]: ({policyName}: ReportArchiveReasonsPolicyDeletedParams) => + `Este chat de espacio de trabajo esta desactivado porque el espacio de trabajo ${policyName} se ha eliminado.`, }, writeCapabilityPage: { label: 'Quién puede postear', @@ -423,33 +498,34 @@ export default { receiptScanning: 'Escaneo de recibo en curso…', receiptStatusTitle: 'Escaneando…', receiptStatusText: 'Solo tú puedes ver este recibo cuando se está escaneando. Vuelve más tarde o introduce los detalles ahora.', - requestCount: ({count, scanningReceipts = 0}) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, + requestCount: ({count, scanningReceipts = 0}: RequestCountParams) => `${count} solicitudes${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}`, deleteRequest: 'Eliminar pedido', deleteConfirmation: '¿Estás seguro de que quieres eliminar este pedido?', settledExpensify: 'Pagado', settledElsewhere: 'Pagado de otra forma', settledPaypalMe: 'Pagado con PayPal.me', - settleExpensify: ({formattedAmount}) => `Pagar ${formattedAmount} con Expensify`, + settleExpensify: ({formattedAmount}: SettleExpensifyCardParams) => `Pagar ${formattedAmount} con Expensify`, payElsewhere: 'Pagar de otra forma', - settlePaypalMe: ({formattedAmount}) => `Pagar ${formattedAmount} con PayPal.me`, - requestAmount: ({amount}) => `solicitar ${amount}`, - splitAmount: ({amount}) => `dividir ${amount}`, - amountEach: ({amount}) => `${amount} cada uno`, - payerOwesAmount: ({payer, amount}) => `${payer} debe ${amount}`, - payerOwes: ({payer}) => `${payer} debe: `, - payerPaidAmount: ({payer, amount}) => `${payer} pagó ${amount}`, - payerPaid: ({payer}) => `${payer} pagó: `, - managerApproved: ({manager}) => `${manager} aprobó:`, - payerSettled: ({amount}) => `pagó ${amount}`, - waitingOnBankAccount: ({submitterDisplayName}) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, - settledAfterAddedBankAccount: ({submitterDisplayName, amount}) => `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, - paidElsewhereWithAmount: ({amount}) => `pagó ${amount} de otra forma`, - paidUsingPaypalWithAmount: ({amount}) => `pagó ${amount} con PayPal.me`, - paidUsingExpensifyWithAmount: ({amount}) => `pagó ${amount} con Expensify`, + settlePaypalMe: ({formattedAmount}: SettlePaypalMeParams) => `Pagar ${formattedAmount} con PayPal.me`, + requestAmount: ({amount}: RequestAmountParams) => `solicitar ${amount}`, + splitAmount: ({amount}: SplitAmountParams) => `dividir ${amount}`, + amountEach: ({amount}: AmountEachParams) => `${amount} cada uno`, + payerOwesAmount: ({payer, amount}: PayerOwesAmountParams) => `${payer} debe ${amount}`, + payerOwes: ({payer}: PayerOwesParams) => `${payer} debe: `, + payerPaidAmount: ({payer, amount}: PayerPaidAmountParams) => `${payer} pagó ${amount}`, + payerPaid: ({payer}: PayerPaidParams) => `${payer} pagó: `, + managerApproved: ({manager}: ManagerApprovedParams) => `${manager} aprobó:`, + payerSettled: ({amount}: PayerSettledParams) => `pagó ${amount}`, + waitingOnBankAccount: ({submitterDisplayName}: WaitingOnBankAccountParams) => `inicio el pago, pero no se procesará hasta que ${submitterDisplayName} añada una cuenta bancaria`, + settledAfterAddedBankAccount: ({submitterDisplayName, amount}: SettledAfterAddedBankAccountParams) => + `${submitterDisplayName} añadió una cuenta bancaria. El pago de ${amount} se ha realizado.`, + paidElsewhereWithAmount: ({amount}: PaidElsewhereWithAmountParams) => `pagó ${amount} de otra forma`, + paidUsingPaypalWithAmount: ({amount}: PaidUsingPaypalWithAmountParams) => `pagó ${amount} con PayPal.me`, + paidUsingExpensifyWithAmount: ({amount}: PaidUsingExpensifyWithAmountParams) => `pagó ${amount} con Expensify`, noReimbursableExpenses: 'El importe de este informe no es válido', pendingConversionMessage: 'El total se actualizará cuando estés online', - threadRequestReportName: ({formattedAmount, comment}) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, - threadSentMoneyReportName: ({formattedAmount, comment}) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, + threadRequestReportName: ({formattedAmount, comment}: ThreadRequestReportNameParams) => `Solicitud de ${formattedAmount}${comment ? ` para ${comment}` : ''}`, + threadSentMoneyReportName: ({formattedAmount, comment}: ThreadSentMoneyReportNameParams) => `${formattedAmount} enviado${comment ? ` para ${comment}` : ''}`, error: { invalidSplit: 'La suma de las partes no equivale al monto total', other: 'Error inesperado, por favor inténtalo más tarde', @@ -477,10 +553,10 @@ export default { removePhoto: 'Eliminar foto', editImage: 'Editar foto', deleteWorkspaceError: 'Lo sentimos, hubo un problema eliminando el avatar de su espacio de trabajo.', - sizeExceeded: ({maxUploadSizeInMB}) => `La imagen supera el tamaño máximo de ${maxUploadSizeInMB}MB.`, - resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}) => + sizeExceeded: ({maxUploadSizeInMB}: SizeExceededParams) => `La imagen supera el tamaño máximo de ${maxUploadSizeInMB}MB.`, + resolutionConstraints: ({minHeightInPx, minWidthInPx, maxHeightInPx, maxWidthInPx}: ResolutionConstraintsParams) => `Por favor, elige una imagen más grande que ${minHeightInPx}x${minWidthInPx} píxeles y más pequeña que ${maxHeightInPx}x${maxWidthInPx} píxeles.`, - notAllowedExtension: ({allowedExtensions}) => `La foto de perfil debe ser de uno de los siguientes tipos: ${allowedExtensions.join(', ')}.`, + notAllowedExtension: ({allowedExtensions}: NotAllowedExtensionParams) => `La foto de perfil debe ser de uno de los siguientes tipos: ${allowedExtensions.join(', ')}.`, }, profilePage: { profile: 'Perfil', @@ -517,7 +593,7 @@ export default { helpTextAfterEmail: ' desde varias direcciones de correo electrónico.', pleaseVerify: 'Por favor, verifica este método de contacto', getInTouch: 'Utilizaremos este método de contacto cuando necesitemos contactarte.', - enterMagicCode: ({contactMethod}) => `Por favor, introduce el código mágico enviado a ${contactMethod}`, + enterMagicCode: ({contactMethod}: EnterMagicCodeParams) => `Por favor, introduce el código mágico enviado a ${contactMethod}`, setAsDefault: 'Establecer como predeterminado', yourDefaultContactMethod: 'Este es tu método de contacto predeterminado. No podrás eliminarlo hasta que añadas otro método de contacto y lo marques como predeterminado pulsando "Establecer como predeterminado".', @@ -709,9 +785,9 @@ export default { addBankAccountFailure: 'Ocurrió un error inesperado al intentar añadir la cuenta bancaria. Inténtalo de nuevo.', }, transferAmountPage: { - transfer: ({amount}) => `Transferir${amount ? ` ${amount}` : ''}`, + transfer: ({amount}: TransferParams) => `Transferir${amount ? ` ${amount}` : ''}`, instant: 'Instante', - instantSummary: ({rate, minAmount}) => `Tarifa del ${rate}% (${minAmount} mínimo)`, + instantSummary: ({rate, minAmount}: InstantSummaryParams) => `Tarifa del ${rate}% (${minAmount} mínimo)`, ach: '1-3 días laborales', achSummary: 'Sin cargo', whichAccount: '¿Qué cuenta?', @@ -843,7 +919,7 @@ export default { }, cannotGetAccountDetails: 'No se pudieron cargar los detalles de tu cuenta. Por favor, intenta iniciar sesión de nuevo.', loginForm: 'Formulario de inicio de sesión', - notYou: ({user}) => `¿No eres ${user}?`, + notYou: ({user}: NotYouParams) => `¿No eres ${user}?`, }, personalDetails: { error: { @@ -859,28 +935,30 @@ export default { legalLastName: 'Apellidos legales', homeAddress: 'Domicilio', error: { - dateShouldBeBefore: ({dateString}) => `La fecha debe ser anterior a ${dateString}.`, - dateShouldBeAfter: ({dateString}) => `La fecha debe ser posterior a ${dateString}.`, - incorrectZipFormat: ({zipFormat}) => `Formato de código postal incorrecto.${zipFormat ? ` Formato aceptable: ${zipFormat}` : ''}`, + dateShouldBeBefore: ({dateString}: DateShouldBeBeforeParams) => `La fecha debe ser anterior a ${dateString}.`, + dateShouldBeAfter: ({dateString}: DateShouldBeAfterParams) => `La fecha debe ser posterior a ${dateString}.`, + incorrectZipFormat: ({zipFormat}: IncorrectZipFormatParams) => `Formato de código postal incorrecto.${zipFormat ? ` Formato aceptable: ${zipFormat}` : ''}`, hasInvalidCharacter: 'El nombre sólo puede incluir letras.', }, }, resendValidationForm: { linkHasBeenResent: 'El enlace se ha reenviado', - weSentYouMagicSignInLink: ({login, loginType}) => `Te he enviado un hiperenlace mágico para iniciar sesión a ${login}. Por favor, revisa tu ${loginType}`, + weSentYouMagicSignInLink: ({login, loginType}: WeSentYouMagicSignInLinkParams) => + `Te he enviado un hiperenlace mágico para iniciar sesión a ${login}. Por favor, revisa tu ${loginType}`, resendLink: 'Reenviar enlace', }, unlinkLoginForm: { - toValidateLogin: ({primaryLogin, secondaryLogin}) => `Para validar ${secondaryLogin}, reenvía el código mágico desde la Configuración de la cuenta de ${primaryLogin}.`, - noLongerHaveAccess: ({primaryLogin}) => `Si ya no tienes acceso a ${primaryLogin} por favor, desvincula las cuentas.`, + toValidateLogin: ({primaryLogin, secondaryLogin}: ToValidateLoginParams) => + `Para validar ${secondaryLogin}, reenvía el código mágico desde la Configuración de la cuenta de ${primaryLogin}.`, + noLongerHaveAccess: ({primaryLogin}: NoLongerHaveAccessParams) => `Si ya no tienes acceso a ${primaryLogin} por favor, desvincula las cuentas.`, unlink: 'Desvincular', linkSent: '¡Enlace enviado!', succesfullyUnlinkedLogin: '¡Nombre de usuario secundario desvinculado correctamente!', }, emailDeliveryFailurePage: { - ourEmailProvider: ({login}) => + ourEmailProvider: ({login}: OurEmailProviderParams) => `Nuestro proveedor de correo electrónico ha suspendido temporalmente los correos electrónicos a ${login} debido a problemas de entrega. Para desbloquear el inicio de sesión, sigue estos pasos:`, - confirmThat: ({login}) => `Confirma que ${login} está escrito correctamente y que es una dirección de correo electrónico real que puede recibir correos. `, + confirmThat: ({login}: ConfirmThatParams) => `Confirma que ${login} está escrito correctamente y que es una dirección de correo electrónico real que puede recibir correos. `, emailAliases: 'Los alias de correo electrónico como "expenses@domain.com" deben tener acceso a su propia bandeja de entrada de correo electrónico para que sea un inicio de sesión válido de Expensify.', ensureYourEmailClient: 'Asegúrese de que su cliente de correo electrónico permita correos electrónicos de expensify.com. ', @@ -926,7 +1004,7 @@ export default { save: 'Guardar', message: 'Mensaje', untilTomorrow: 'Hasta mañana', - untilTime: ({time}) => { + untilTime: ({time}: UntilTimeParams) => { // Check for HH:MM AM/PM format and starts with '01:' if (CONST.REGEX.TIME_STARTS_01.test(time)) { return `Hasta la ${time}`; @@ -943,7 +1021,7 @@ export default { return `Hasta ${time}`; }, }, - stepCounter: ({step, total, text}) => { + stepCounter: ({step, total, text}: StepCounterParams) => { let result = `Paso ${step}`; if (total) { @@ -1029,7 +1107,7 @@ export default { messages: { errorMessageInvalidPhone: `Por favor, introduce un número de teléfono válido sin paréntesis o guiones. Si reside fuera de Estados Unidos, por favor incluye el prefijo internacional (p. ej. ${CONST.EXAMPLE_PHONE_NUMBER}).`, errorMessageInvalidEmail: 'Email inválido', - userIsAlreadyMemberOfWorkspace: ({login, workspace}) => `${login} ya es miembro de ${workspace}`, + userIsAlreadyMemberOfWorkspace: ({login, workspace}: UserIsAlreadyMemberOfWorkspaceParams) => `${login} ya es miembro de ${workspace}`, }, onfidoStep: { acceptTerms: 'Al continuar con la solicitud para activar su billetera Expensify, confirma que ha leído, comprende y acepta ', @@ -1237,7 +1315,7 @@ export default { unavailable: 'Espacio de trabajo no disponible', memberNotFound: 'Miembro no encontrado. Para invitar a un nuevo miembro al espacio de trabajo, por favor, utiliza el botón Invitar que está arriba.', notAuthorized: `No tienes acceso a esta página. ¿Estás tratando de unirte al espacio de trabajo? Comunícate con el propietario de este espacio de trabajo para que pueda añadirte como miembro. ¿Necesitas algo más? Comunícate con ${CONST.EMAIL.CONCIERGE}`, - goToRoom: ({roomName}) => `Ir a la sala ${roomName}`, + goToRoom: ({roomName}: GoToRoomParams) => `Ir a la sala ${roomName}`, }, emptyWorkspace: { title: 'Crear un nuevo espacio de trabajo', @@ -1334,7 +1412,7 @@ export default { personalMessagePrompt: 'Mensaje', inviteNoMembersError: 'Por favor, selecciona al menos un miembro a invitar', genericFailureMessage: 'Se produjo un error al invitar al usuario al espacio de trabajo. Vuelva a intentarlo..', - welcomeNote: ({workspaceName}) => + welcomeNote: ({workspaceName}: WelcomeNoteParams) => `¡Has sido invitado a ${workspaceName}! Descargue la aplicación móvil Expensify en use.expensify.com/download para comenzar a rastrear sus gastos.`, }, editor: { @@ -1408,15 +1486,17 @@ export default { restrictedDescription: 'Sólo las personas en tu espacio de trabajo pueden encontrar esta sala', privateDescription: 'Sólo las personas que están invitadas a esta sala pueden encontrarla', publicDescription: 'Cualquier persona puede unirse a esta sala', + // eslint-disable-next-line @typescript-eslint/naming-convention public_announceDescription: 'Cualquier persona puede unirse a esta sala', createRoom: 'Crea una sala de chat', roomAlreadyExistsError: 'Ya existe una sala con este nombre', - roomNameReservedError: ({reservedName}) => `${reservedName} es el nombre una sala por defecto de todos los espacios de trabajo. Por favor, elige otro nombre.`, + roomNameReservedError: ({reservedName}: RoomNameReservedErrorParams) => + `${reservedName} es el nombre una sala por defecto de todos los espacios de trabajo. Por favor, elige otro nombre.`, roomNameInvalidError: 'Los nombres de las salas solo pueden contener minúsculas, números y guiones', pleaseEnterRoomName: 'Por favor, escribe el nombre de una sala', pleaseSelectWorkspace: 'Por favor, selecciona un espacio de trabajo', - renamedRoomAction: ({oldName, newName}) => ` cambió el nombre de la sala de ${oldName} a ${newName}`, - roomRenamedTo: ({newName}) => `Sala renombrada a ${newName}`, + renamedRoomAction: ({oldName, newName}: RenamedRoomActionParams) => ` cambió el nombre de la sala de ${oldName} a ${newName}`, + roomRenamedTo: ({newName}: RoomRenamedToParams) => `Sala renombrada a ${newName}`, social: 'social', selectAWorkspace: 'Seleccionar un espacio de trabajo', growlMessageOnRenameError: 'No se ha podido cambiar el nombre del espacio de trabajo, por favor, comprueba tu conexión e inténtalo de nuevo.', @@ -1424,6 +1504,7 @@ export default { restricted: 'Restringida', private: 'Privada', public: 'Público', + // eslint-disable-next-line @typescript-eslint/naming-convention public_announce: 'Anuncio Público', }, }, @@ -1567,8 +1648,8 @@ export default { noActivityYet: 'Sin actividad todavía', }, chronos: { - oooEventSummaryFullDay: ({summary, dayCount, date}) => `${summary} por ${dayCount} ${dayCount === 1 ? 'día' : 'días'} hasta el ${date}`, - oooEventSummaryPartialDay: ({summary, timePeriod, date}) => `${summary} de ${timePeriod} del ${date}`, + oooEventSummaryFullDay: ({summary, dayCount, date}: OOOEventSummaryFullDayParams) => `${summary} por ${dayCount} ${dayCount === 1 ? 'día' : 'días'} hasta el ${date}`, + oooEventSummaryPartialDay: ({summary, timePeriod, date}: OOOEventSummaryPartialDayParams) => `${summary} de ${timePeriod} del ${date}`, }, footer: { features: 'Características', @@ -2084,7 +2165,7 @@ export default { reply: 'Respuesta', from: 'De', in: 'en', - parentNavigationSummary: ({rootReportName, workspaceName}) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`, + parentNavigationSummary: ({rootReportName, workspaceName}: ParentNavigationSummaryParams) => `De ${rootReportName}${workspaceName ? ` en ${workspaceName}` : ''}`, }, qrCodes: { copyUrlToClipboard: 'Copiar URL al portapapeles', diff --git a/src/languages/translations.js b/src/languages/translations.ts similarity index 65% rename from src/languages/translations.js rename to src/languages/translations.ts index c8dd8c8ab0e0..a2d27baa26c9 100644 --- a/src/languages/translations.js +++ b/src/languages/translations.ts @@ -5,5 +5,6 @@ import esES from './es-ES'; export default { en, es, + // eslint-disable-next-line @typescript-eslint/naming-convention 'es-ES': esES, }; diff --git a/src/languages/types.ts b/src/languages/types.ts new file mode 100644 index 000000000000..50290fb5776c --- /dev/null +++ b/src/languages/types.ts @@ -0,0 +1,255 @@ +type AddressLineParams = { + lineNumber: number; +}; + +type CharacterLimitParams = { + limit: number; +}; + +type MaxParticipantsReachedParams = { + count: number; +}; + +type ZipCodeExampleFormatParams = { + zipSampleFormat: string; +}; + +type LoggedInAsParams = { + email: string; +}; + +type NewFaceEnterMagicCodeParams = { + login: string; +}; + +type WelcomeEnterMagicCodeParams = { + login: string; +}; + +type AlreadySignedInParams = { + email: string; +}; + +type GoBackMessageParams = { + provider: string; +}; + +type LocalTimeParams = { + user: string; + time: string; +}; + +type EditActionParams = { + action: NonNullable; +}; + +type DeleteActionParams = { + action: NonNullable; +}; + +type DeleteConfirmationParams = { + action: NonNullable; +}; + +type BeginningOfChatHistoryDomainRoomPartOneParams = { + domainRoom: string; +}; + +type BeginningOfChatHistoryAdminRoomPartOneParams = { + workspaceName: string; +}; + +type BeginningOfChatHistoryAnnounceRoomPartOneParams = { + workspaceName: string; +}; + +type BeginningOfChatHistoryAnnounceRoomPartTwo = { + workspaceName: string; +}; + +type WelcomeToRoomParams = { + roomName: string; +}; + +type ReportArchiveReasonsClosedParams = { + displayName: string; +}; + +type ReportArchiveReasonsMergedParams = { + displayName: string; + oldDisplayName: string; +}; + +type ReportArchiveReasonsRemovedFromPolicyParams = { + displayName: string; + policyName: string; +}; + +type ReportArchiveReasonsPolicyDeletedParams = { + policyName: string; +}; + +type RequestCountParams = { + count: number; + scanningReceipts: number; +}; + +type SettleExpensifyCardParams = { + formattedAmount: string; +}; + +type SettlePaypalMeParams = {formattedAmount: string}; + +type RequestAmountParams = {amount: number}; + +type SplitAmountParams = {amount: number}; + +type AmountEachParams = {amount: number}; + +type PayerOwesAmountParams = {payer: string; amount: number}; + +type PayerOwesParams = {payer: string}; + +type PayerPaidAmountParams = {payer: string; amount: number}; + +type ManagerApprovedParams = {manager: string}; + +type PayerPaidParams = {payer: string}; + +type PayerSettledParams = {amount: number}; + +type WaitingOnBankAccountParams = {submitterDisplayName: string}; + +type SettledAfterAddedBankAccountParams = {submitterDisplayName: string; amount: string}; + +type PaidElsewhereWithAmountParams = {amount: string}; + +type PaidUsingPaypalWithAmountParams = {amount: string}; + +type PaidUsingExpensifyWithAmountParams = {amount: string}; + +type ThreadRequestReportNameParams = {formattedAmount: string; comment: string}; + +type ThreadSentMoneyReportNameParams = {formattedAmount: string; comment: string}; + +type SizeExceededParams = {maxUploadSizeInMB: number}; + +type ResolutionConstraintsParams = {minHeightInPx: number; minWidthInPx: number; maxHeightInPx: number; maxWidthInPx: number}; + +type NotAllowedExtensionParams = {allowedExtensions: string[]}; + +type EnterMagicCodeParams = {contactMethod: string}; + +type TransferParams = {amount: string}; + +type InstantSummaryParams = {rate: number; minAmount: number}; + +type NotYouParams = {user: string}; + +type DateShouldBeBeforeParams = {dateString: string}; + +type DateShouldBeAfterParams = {dateString: string}; + +type IncorrectZipFormatParams = {zipFormat?: string}; + +type WeSentYouMagicSignInLinkParams = {login: string; loginType: string}; + +type ToValidateLoginParams = {primaryLogin: string; secondaryLogin: string}; + +type NoLongerHaveAccessParams = {primaryLogin: string}; + +type OurEmailProviderParams = {login: string}; + +type ConfirmThatParams = {login: string}; + +type UntilTimeParams = {time: string}; + +type StepCounterParams = {step: number; total?: number; text?: string}; + +type UserIsAlreadyMemberOfWorkspaceParams = {login: string; workspace: string}; + +type GoToRoomParams = {roomName: string}; + +type WelcomeNoteParams = {workspaceName: string}; + +type RoomNameReservedErrorParams = {reservedName: string}; + +type RenamedRoomActionParams = {oldName: string; newName: string}; + +type RoomRenamedToParams = {newName: string}; + +type OOOEventSummaryFullDayParams = {summary: string; dayCount: number; date: string}; + +type OOOEventSummaryPartialDayParams = {summary: string; timePeriod: string; date: string}; + +type ParentNavigationSummaryParams = {rootReportName: string; workspaceName: string}; + +export type { + AddressLineParams, + CharacterLimitParams, + MaxParticipantsReachedParams, + ZipCodeExampleFormatParams, + LoggedInAsParams, + NewFaceEnterMagicCodeParams, + WelcomeEnterMagicCodeParams, + AlreadySignedInParams, + GoBackMessageParams, + LocalTimeParams, + EditActionParams, + DeleteActionParams, + DeleteConfirmationParams, + BeginningOfChatHistoryDomainRoomPartOneParams, + BeginningOfChatHistoryAdminRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartOneParams, + BeginningOfChatHistoryAnnounceRoomPartTwo, + WelcomeToRoomParams, + ReportArchiveReasonsClosedParams, + ReportArchiveReasonsMergedParams, + ReportArchiveReasonsRemovedFromPolicyParams, + ReportArchiveReasonsPolicyDeletedParams, + RequestCountParams, + SettleExpensifyCardParams, + SettlePaypalMeParams, + RequestAmountParams, + SplitAmountParams, + AmountEachParams, + PayerOwesAmountParams, + PayerOwesParams, + PayerPaidAmountParams, + PayerPaidParams, + ManagerApprovedParams, + PayerSettledParams, + WaitingOnBankAccountParams, + SettledAfterAddedBankAccountParams, + PaidElsewhereWithAmountParams, + PaidUsingPaypalWithAmountParams, + PaidUsingExpensifyWithAmountParams, + ThreadRequestReportNameParams, + ThreadSentMoneyReportNameParams, + SizeExceededParams, + ResolutionConstraintsParams, + NotAllowedExtensionParams, + EnterMagicCodeParams, + TransferParams, + InstantSummaryParams, + NotYouParams, + DateShouldBeBeforeParams, + DateShouldBeAfterParams, + IncorrectZipFormatParams, + WeSentYouMagicSignInLinkParams, + ToValidateLoginParams, + NoLongerHaveAccessParams, + OurEmailProviderParams, + ConfirmThatParams, + UntilTimeParams, + StepCounterParams, + UserIsAlreadyMemberOfWorkspaceParams, + GoToRoomParams, + WelcomeNoteParams, + RoomNameReservedErrorParams, + RenamedRoomActionParams, + RoomRenamedToParams, + OOOEventSummaryFullDayParams, + OOOEventSummaryPartialDayParams, + ParentNavigationSummaryParams, +}; From 18259d70235fd894ab911b8e6c5d41b5e3160d31 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 5 Sep 2023 15:53:35 +0500 Subject: [PATCH 72/99] fix: download app modal hide permanently when clicked on download --- src/components/DownloadAppModal.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/DownloadAppModal.js b/src/components/DownloadAppModal.js index ffa933708e4c..831f0b5554e8 100644 --- a/src/components/DownloadAppModal.js +++ b/src/components/DownloadAppModal.js @@ -44,6 +44,8 @@ function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) { } const handleOpenAppStore = () => { + setShowDownloadAppModal(false); + setshouldShowBanner(false); Link.openExternalLink(link, true); }; From a42191a7d0b6748546e0c088934be3843eb4f17d Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 5 Sep 2023 13:17:44 +0200 Subject: [PATCH 73/99] move demo pages to AuthScreens --- .../Navigation/AppNavigator/AuthScreens.js | 11 +++++++++++ .../Navigators/CentralPaneNavigator.js | 18 ------------------ src/libs/Navigation/linkingConfig.js | 6 ++++-- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.js b/src/libs/Navigation/AppNavigator/AuthScreens.js index d50bd53a0a1c..e10e51b307e4 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.js +++ b/src/libs/Navigation/AppNavigator/AuthScreens.js @@ -35,6 +35,7 @@ import styles from '../../../styles/styles'; import * as SessionUtils from '../../SessionUtils'; import NotFoundPage from '../../../pages/ErrorPage/NotFoundPage'; import getRootNavigatorScreenOptions from './getRootNavigatorScreenOptions'; +import DemoSetupPage from '../../../pages/DemoSetupPage'; let timezone; let currentAccountID; @@ -282,6 +283,16 @@ class AuthScreens extends React.Component { return ConciergePage; }} /> + + - - ); diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 8390aa7d700b..ee3054e02f96 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -18,6 +18,10 @@ export default { [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: ROUTES.DESKTOP_SIGN_IN_REDIRECT, [SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS, + // Demo routes + [CONST.DEMO_PAGES.SAASTR]: ROUTES.SAASTR, + [CONST.DEMO_PAGES.SBE]: ROUTES.SBE, + // Sidebar [SCREENS.HOME]: { path: ROUTES.HOME, @@ -26,8 +30,6 @@ export default { [NAVIGATORS.CENTRAL_PANE_NAVIGATOR]: { screens: { [SCREENS.REPORT]: ROUTES.REPORT_WITH_ID, - [CONST.DEMO_PAGES.SAASTR]: ROUTES.SAASTR, - [CONST.DEMO_PAGES.SBE]: ROUTES.SBE, }, }, [SCREENS.NOT_FOUND]: '*', From c698c08ebdbe7932a82eb8a3822e4d03cf406883 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 5 Sep 2023 16:23:23 +0500 Subject: [PATCH 74/99] fix: set state typo in the download app modal component --- src/components/DownloadAppModal.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/DownloadAppModal.js b/src/components/DownloadAppModal.js index 831f0b5554e8..c96c6b3d28c0 100644 --- a/src/components/DownloadAppModal.js +++ b/src/components/DownloadAppModal.js @@ -26,13 +26,13 @@ const defaultProps = { }; function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) { - const [shouldShowBanner, setshouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner); + const [shouldShowBanner, setShouldShowBanner] = useState(Browser.isMobile() && isAuthenticated && showDownloadAppBanner); const {translate} = useLocalize(); const handleCloseBanner = () => { setShowDownloadAppModal(false); - setshouldShowBanner(false); + setShouldShowBanner(false); }; let link = ''; @@ -45,7 +45,7 @@ function DownloadAppModal({isAuthenticated, showDownloadAppBanner}) { const handleOpenAppStore = () => { setShowDownloadAppModal(false); - setshouldShowBanner(false); + setShouldShowBanner(false); Link.openExternalLink(link, true); }; From 2b99c3cec7fcd92965b564efdb0cb076876577b2 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 17:16:33 +0530 Subject: [PATCH 75/99] Refactoring - LHN subtitle report scan --- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 18 ++++-------------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index d26ad48430b0..a9c319865bbb 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -394,7 +394,7 @@ function getLastMessageTextForReport(report) { if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { lastMessageTextFromReport = `[${Localize.translateLocal(report.lastMessageTranslationKey || 'common.attachment')}]`; } else if (ReportActionUtils.isMoneyRequestAction(lastReportAction)) { - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction, true); + lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1c3393793abe..8af1ca910811 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1463,10 +1463,9 @@ function getTransactionReportName(reportAction) { * * @param {Object} report * @param {Object} [reportAction={}] - * @param {Boolean} [shouldConsiderReceiptBeingScanned=false] * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned) { +function getReportPreviewMessage(report, reportAction = {}) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { @@ -1485,19 +1484,10 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip /* === Start - Check for Report Scan === */ - if (shouldConsiderReceiptBeingScanned) { - const filteredIouReportActions = getSortedMoneyRequestActions(report.reportID); - const lastIouReportAction = _.first(filteredIouReportActions); - const lastIouReportActionTransaction = TransactionUtils.getLinkedTransaction(lastIouReportAction); + if (isIOUReport(report) && ReportActionsUtils.isMoneyRequestAction(reportAction)) { + const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); - // get lastTransaction - // eslint-disable-next-line no-use-before-define - const transactionsWithReceipts = getSortedTransactionsWithReceipts(report.reportID); - const lastTransaction = _.first(transactionsWithReceipts); - - const transactionIsLastReportAction = _.isEqual(lastIouReportActionTransaction, lastTransaction); - - if (lastTransaction && transactionIsLastReportAction && TransactionUtils.isReceiptBeingScanned(lastTransaction)) { + if (!_.isEmpty(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { return Localize.translateLocal('iou.receiptScanning'); } } From b3f15ba969183d84c5ecefa73f1a9c3928d1fb47 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 17:28:36 +0530 Subject: [PATCH 76/99] Refactoring --- src/libs/ReportActionsUtils.js | 16 ---------------- src/libs/ReportUtils.js | 25 ++----------------------- 2 files changed, 2 insertions(+), 39 deletions(-) diff --git a/src/libs/ReportActionsUtils.js b/src/libs/ReportActionsUtils.js index 1f063a221e15..3ed10b865812 100644 --- a/src/libs/ReportActionsUtils.js +++ b/src/libs/ReportActionsUtils.js @@ -434,21 +434,6 @@ function getSortedReportActionsForDisplay(reportActions) { return getSortedReportActions(filteredReportActions, true); } -/** - * Gets sorted and filtered report actions for display. - * - * First, it sorts the report actions using `getSortedReportActionsForDisplay`. - * Then, it filters out actions that are pending deletion. - * - * @param {Array} reportActions - The array of report actions to filter. - * @returns {Array} - The filtered and sorted array of report actions. - */ -function getFilteredSortedReportActionsForDisplay(reportActions) { - const sortedReportActions = getSortedReportActionsForDisplay(reportActions); - const filteredReportActions = _.filter(sortedReportActions, (reportAction) => reportAction.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - return filteredReportActions; -} - /** * In some cases, there can be multiple closed report actions in a chat report. * This method returns the last closed report action so we can always show the correct archived report reason. @@ -660,5 +645,4 @@ export { isSplitBillAction, isTaskAction, getAllReportActions, - getFilteredSortedReportActionsForDisplay, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 8af1ca910811..c998a479b0f1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1366,10 +1366,10 @@ function getTransactionsWithReceipts(iouReportID) { */ function getSortedTransactionsWithReceipts(iouReportID) { const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); - const filteredSortedReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); return _.reduce( - filteredSortedReportActions, + sortedReportActions, (transactions, action) => { if (ReportActionsUtils.isMoneyRequestAction(action)) { const transaction = TransactionUtils.getLinkedTransaction(action); @@ -1383,26 +1383,6 @@ function getSortedTransactionsWithReceipts(iouReportID) { ); } -/** - * Gets all sorted money request reportActions - * - * @param {Object|null} iouReportID - * @returns {[Object]} - */ -function getSortedMoneyRequestActions(iouReportID) { - const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); - const filteredSortedReportActions = ReportActionsUtils.getFilteredSortedReportActionsForDisplay(reportActions); - - return _.reduce( - filteredSortedReportActions, - (transactions, action) => { - if (ReportActionsUtils.isMoneyRequestAction(action)) transactions.push(action); - return transactions; - }, - [], - ); -} - /** * For report previews, we display a "Receipt scan in progress" indicator * instead of the report total only when we have no report total ready to show. This is the case when @@ -3670,6 +3650,5 @@ export { getReportPreviewDisplayTransactions, getTransactionsWithReceipts, getSortedTransactionsWithReceipts, - getSortedMoneyRequestActions, hasMissingSmartscanFields, }; From 41e35b63652fad7bea317f5b28ff5b52749212de Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 17:31:18 +0530 Subject: [PATCH 77/99] Bugfix - properly access iouReportID instead of reportID --- src/pages/home/report/ReportActionItemSingle.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionItemSingle.js b/src/pages/home/report/ReportActionItemSingle.js index ba44efa5f274..bfbce8aed336 100644 --- a/src/pages/home/report/ReportActionItemSingle.js +++ b/src/pages/home/report/ReportActionItemSingle.js @@ -141,7 +141,7 @@ function ReportActionItemSingle(props) { : props.action.person; const reportID = props.report && props.report.reportID; - const iouReportID = props.report && props.report.reportID; + const iouReportID = props.iouReport && props.iouReport.reportID; const showActorDetails = useCallback(() => { if (isWorkspaceActor) { From d9e57a7f12d3a0e6be80599ef83330eb609c4cc7 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 17:46:19 +0530 Subject: [PATCH 78/99] Revert LHN subtitle "Report scan in progress..." to use previous method via function argument for display logic. --- src/libs/OptionsListUtils.js | 2 +- src/libs/ReportUtils.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index a9c319865bbb..d26ad48430b0 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -394,7 +394,7 @@ function getLastMessageTextForReport(report) { if (ReportUtils.isReportMessageAttachment({text: report.lastMessageText, html: report.lastMessageHtml, translationKey: report.lastMessageTranslationKey})) { lastMessageTextFromReport = `[${Localize.translateLocal(report.lastMessageTranslationKey || 'common.attachment')}]`; } else if (ReportActionUtils.isMoneyRequestAction(lastReportAction)) { - lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction); + lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(report, lastReportAction, true); } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) { const iouReport = ReportUtils.getReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction)); lastMessageTextFromReport = ReportUtils.getReportPreviewMessage(iouReport, lastReportAction); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index c998a479b0f1..ac558cf33be1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1443,9 +1443,10 @@ function getTransactionReportName(reportAction) { * * @param {Object} report * @param {Object} [reportAction={}] + * @param {Boolean} [shouldConsiderReceiptBeingScanned=false] * @returns {String} */ -function getReportPreviewMessage(report, reportAction = {}) { +function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceiptBeingScanned = false) { const reportActionMessage = lodashGet(reportAction, 'message[0].html', ''); if (_.isEmpty(report) || !report.reportID) { @@ -1464,7 +1465,7 @@ function getReportPreviewMessage(report, reportAction = {}) { /* === Start - Check for Report Scan === */ - if (isIOUReport(report) && ReportActionsUtils.isMoneyRequestAction(reportAction)) { + if (shouldConsiderReceiptBeingScanned && ReportActionsUtils.isMoneyRequestAction(reportAction)) { const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); if (!_.isEmpty(linkedTransaction) && TransactionUtils.hasReceipt(linkedTransaction) && TransactionUtils.isReceiptBeingScanned(linkedTransaction)) { From eeefd9ffd55b09547a59208ffa45306136ee97a4 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 5 Sep 2023 15:32:31 +0200 Subject: [PATCH 79/99] wait for isNavigationReady in DemoSetupPage --- src/pages/DemoSetupPage.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/pages/DemoSetupPage.js b/src/pages/DemoSetupPage.js index 53739820142b..0f7578760c16 100644 --- a/src/pages/DemoSetupPage.js +++ b/src/pages/DemoSetupPage.js @@ -22,14 +22,16 @@ const propTypes = { */ function DemoSetupPage(props) { useFocusEffect(() => { - // Depending on the route that the user hit to get here, run a specific demo flow - if (props.route.name === CONST.DEMO_PAGES.SAASTR) { - DemoActions.runSaastrDemo(); - } else if (props.route.name === CONST.DEMO_PAGES.SBE) { - DemoActions.runSbeDemo(); - } else { - Navigation.goBack(); - } + Navigation.isNavigationReady().then(() => { + // Depending on the route that the user hit to get here, run a specific demo flow + if (props.route.name === CONST.DEMO_PAGES.SAASTR) { + DemoActions.runSaastrDemo(); + } else if (props.route.name === CONST.DEMO_PAGES.SBE) { + DemoActions.runSbeDemo(); + } else { + Navigation.goBack(); + } + }); }); return ; From 228d59ec4235f9b15deab9bf56066d0552f2ad38 Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 5 Sep 2023 15:34:26 +0200 Subject: [PATCH 80/99] dont run Welcome.show for demo users --- .../FloatingActionButtonAndPopover.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 0e8553b00dd0..4db103e4482c 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -67,6 +67,16 @@ const propTypes = { /** Forwarded ref to FloatingActionButtonAndPopover */ innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + + /** Information about any currently running demos */ + demoInfo: PropTypes.shape({ + saastr: PropTypes.shape({ + isBeginningDemo: PropTypes.bool, + }), + sbe: PropTypes.shape({ + isBeginningDemo: PropTypes.bool, + }), + }), }; const defaultProps = { onHideCreateMenu: () => {}, @@ -76,6 +86,7 @@ const defaultProps = { isLoading: false, innerRef: null, shouldShowDownloadAppBanner: true, + demoInfo: {}, }; /** @@ -162,6 +173,9 @@ function FloatingActionButtonAndPopover(props) { if (props.shouldShowDownloadAppBanner && Browser.isMobile()) { return; } + if (lodashGet(props.demoInfo, 'saastr.isBeginningDemo', false) || lodashGet(props.demoInfo, 'sbe.isBeginningDemo', false)) { + return; + } Welcome.show({routes, showCreateMenu}); }, [props.shouldShowDownloadAppBanner, props.navigation, showCreateMenu]); @@ -299,6 +313,9 @@ export default compose( shouldShowDownloadAppBanner: { key: ONYXKEYS.SHOW_DOWNLOAD_APP_BANNER, }, + demoInfo: { + key: ONYXKEYS.DEMO_INFO, + }, }), )( forwardRef((props, ref) => ( @@ -308,4 +325,4 @@ export default compose( innerRef={ref} /> )), -); +); \ No newline at end of file From cb294a2f2825608dd748a5d4e43f133a30baeaac Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 19:28:59 +0530 Subject: [PATCH 81/99] Refactoring LHN subtitle 'Receipt scan in progress...' --- .../LHNOptionsList/OptionRowLHNData.js | 8 +++--- src/libs/ReportUtils.js | 26 ------------------- 2 files changed, 5 insertions(+), 29 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index e60b5e05e6ba..dbb779670eda 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -12,7 +12,8 @@ import withCurrentReportID, {withCurrentReportIDPropTypes, withCurrentReportIDDe import OptionRowLHN, {propTypes as basePropTypes, defaultProps as baseDefaultProps} from './OptionRowLHN'; import * as Report from '../../libs/actions/Report'; import * as UserUtils from '../../libs/UserUtils'; -import * as ReportUtils from '../../libs/ReportUtils'; +import * as ReportActionsUtils from '../../libs/ReportActionsUtils'; +import * as TransactionUtils from '../../libs/TransactionUtils'; import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; @@ -94,8 +95,9 @@ function OptionRowLHNData({ const optionItemRef = useRef(); const lastTransaction = useMemo(() => { - const transactionsWithReceipts = ReportUtils.getSortedTransactionsWithReceipts(fullReport.reportID); - return _.first(transactionsWithReceipts); + const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); + const lastReportAction = _.first(sortedReportActions); + return TransactionUtils.getLinkedTransaction(lastReportAction); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index ac558cf33be1..1b58a4d73ce8 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1358,31 +1358,6 @@ function getTransactionsWithReceipts(iouReportID) { return _.filter(allTransactions, (transaction) => TransactionUtils.hasReceipt(transaction)); } -/** - * Gets all sorted transactions on an IOU report with a receipt and whose pending action is not delete - * - * @param {Object|null} iouReportID - * @returns {[Object]} - */ -function getSortedTransactionsWithReceipts(iouReportID) { - const reportActions = ReportActionsUtils.getAllReportActions(iouReportID); - const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); - - return _.reduce( - sortedReportActions, - (transactions, action) => { - if (ReportActionsUtils.isMoneyRequestAction(action)) { - const transaction = TransactionUtils.getLinkedTransaction(action); - if (TransactionUtils.hasReceipt(transaction)) { - transactions.push(transaction); - } - } - return transactions; - }, - [], - ); -} - /** * For report previews, we display a "Receipt scan in progress" indicator * instead of the report total only when we have no report total ready to show. This is the case when @@ -3650,6 +3625,5 @@ export { areAllRequestsBeingSmartScanned, getReportPreviewDisplayTransactions, getTransactionsWithReceipts, - getSortedTransactionsWithReceipts, hasMissingSmartscanFields, }; From b9c48328903b583ebfeb1f02d1e2110d25c42a2e Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 5 Sep 2023 14:02:05 +0000 Subject: [PATCH 82/99] Update version to 1.3.63-1 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 11ce415ad0ae..65bf90eb72c7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001036300 - versionName "1.3.63-0" + versionCode 1001036301 + versionName "1.3.63-1" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index b69066e7546a..feaffd09a957 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.63.0 + 1.3.63.1 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index dcb0ddc8af49..cf459dc75e9a 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.63.0 + 1.3.63.1 diff --git a/package-lock.json b/package-lock.json index d595fc527a77..c0277e347167 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.63-0", + "version": "1.3.63-1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.63-0", + "version": "1.3.63-1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index cf3fd570164b..3b9b0d844492 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.63-0", + "version": "1.3.63-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From d727a072773c9de017c31922d3e829a13310556b Mon Sep 17 00:00:00 2001 From: Adam Grzybowski Date: Tue, 5 Sep 2023 16:12:15 +0200 Subject: [PATCH 83/99] add props.demoInfo dependency --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 4db103e4482c..0dd6beeeb1a0 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -177,7 +177,7 @@ function FloatingActionButtonAndPopover(props) { return; } Welcome.show({routes, showCreateMenu}); - }, [props.shouldShowDownloadAppBanner, props.navigation, showCreateMenu]); + }, [props.shouldShowDownloadAppBanner, props.navigation, showCreateMenu, props.demoInfo]); useEffect(() => { if (!didScreenBecomeInactive()) { From 0eea032895c8f797d3beeac65506625180870b8f Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 5 Sep 2023 10:22:02 -0400 Subject: [PATCH 84/99] use empty object instead Co-authored-by: 0xmiroslav <97473779+0xmiroslav@users.noreply.github.com> --- src/components/ReportActionItem/ReportActionItemImages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 1a585e3cc8b1..119ea0918aab 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -55,7 +55,7 @@ function ReportActionItemImages({images, size, total, isHovered}) { {_.map(shownImages, ({thumbnail, image}, index) => { const isLastImage = index === numberOfShownImages - 1; const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; - const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : undefined; + const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; return ( Date: Tue, 5 Sep 2023 16:26:33 +0200 Subject: [PATCH 85/99] prettier fix --- .../sidebar/SidebarScreen/FloatingActionButtonAndPopover.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js index 0dd6beeeb1a0..1a3f63ede6e6 100644 --- a/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js +++ b/src/pages/home/sidebar/SidebarScreen/FloatingActionButtonAndPopover.js @@ -325,4 +325,4 @@ export default compose( innerRef={ref} /> )), -); \ No newline at end of file +); From 55688505e8c8d0c663d9c789d63e55b8fda5e59f Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 20:02:49 +0530 Subject: [PATCH 86/99] Refactoring --- .../LHNOptionsList/OptionRowLHNData.js | 5 +--- src/hooks/useDeepCompareMemo.js | 26 ------------------- 2 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 src/hooks/useDeepCompareMemo.js diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index dbb779670eda..2d6ff6fcfb73 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -18,7 +18,6 @@ import * as TransactionUtils from '../../libs/TransactionUtils'; import participantPropTypes from '../participantPropTypes'; import CONST from '../../CONST'; import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes'; -import useDeepCompareMemo from '../../hooks/useDeepCompareMemo'; const propTypes = { /** If true will disable ever setting the OptionRowLHN to focused */ @@ -101,8 +100,6 @@ function OptionRowLHNData({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [fullReport.reportID, receiptTransactions, reportActions]); - const memoizedLastTransaction = useDeepCompareMemo(lastTransaction); - const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy); @@ -113,7 +110,7 @@ function OptionRowLHNData({ return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, memoizedLastTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); + }, [fullReport, lastTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { diff --git a/src/hooks/useDeepCompareMemo.js b/src/hooks/useDeepCompareMemo.js deleted file mode 100644 index ebed9ffdeebe..000000000000 --- a/src/hooks/useDeepCompareMemo.js +++ /dev/null @@ -1,26 +0,0 @@ -import {useRef} from 'react'; -import _ from 'lodash'; - -/** - * Custom hook to memoize a value based on deep comparison. - * Returns the previous value if the current value is deeply equal to the previous one. - * - * @function - * @template T - * @param {T} value - The value to be memoized. - * @returns {T} - The memoized value. Returns the previous value if the current value is deeply equal to the previous one. - * @example - * - * const object = { a: 1, b: 2 }; - * const memoizedObject = useDeepCompareMemo(object); - */ -export default function useDeepCompareMemo(value) { - const ref = useRef(); // Holds the previous value - - // If the new value is not deeply equal to the old value, update the ref - if (!_.isEqual(value, ref.current)) { - ref.current = value; - } - - return ref.current; -} From edab657d7c03b1e43ab0765b2fb9a5011cfa7232 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 5 Sep 2023 15:03:52 +0000 Subject: [PATCH 87/99] Update version to 1.3.63-2 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 2 +- ios/NewExpensifyTests/Info.plist | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 65bf90eb72c7..1f337e6fada0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001036301 - versionName "1.3.63-1" + versionCode 1001036302 + versionName "1.3.63-2" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index feaffd09a957..8a635f1797bc 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.63.1 + 1.3.63.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index cf459dc75e9a..028aca87d0da 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 1.3.63.1 + 1.3.63.2 diff --git a/package-lock.json b/package-lock.json index c0277e347167..0f814d11df2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.63-1", + "version": "1.3.63-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.63-1", + "version": "1.3.63-2", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 3b9b0d844492..99c389e1b460 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.63-1", + "version": "1.3.63-2", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 554608198a2f1f5d037484e275f26bfa92870d43 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:29:51 +0530 Subject: [PATCH 88/99] Remove unnecessary comment Co-authored-by: Carlos Martins --- src/libs/ReportUtils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 1b58a4d73ce8..33721d89d503 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1448,8 +1448,6 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip } } - /* === End - Check for Report Scan === */ - if (isSettled(report.reportID)) { // A settled report preview message can come in three formats "paid ... using Paypal.me", "paid ... elsewhere" or "paid ... using Expensify" let translatePhraseKey = 'iou.paidElsewhereWithAmount'; From ebad276423857eda589db81ec0226236067471d4 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt <65986357+ygshbht@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:30:18 +0530 Subject: [PATCH 89/99] Remove unnecessary comment Co-authored-by: Carlos Martins --- src/libs/ReportUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 33721d89d503..cefc9674bcfc 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1438,7 +1438,6 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip return `approved ${formattedAmount}`; } - /* === Start - Check for Report Scan === */ if (shouldConsiderReceiptBeingScanned && ReportActionsUtils.isMoneyRequestAction(reportAction)) { const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); From 91e0ae42c2a973ff76d79cdfe4c7fd0b777bc776 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Sep 2023 11:06:34 -0600 Subject: [PATCH 90/99] update logic to render receipt --- .../ReportActionItem/ReportActionItemImage.js | 69 +++++++++---------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 5f8444af0b21..c25fef778c7b 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -23,7 +23,7 @@ const propTypes = { }; const defaultProps = { - thumbnail: null, + thumbnail: '', enablePreviewModal: false, }; @@ -35,47 +35,44 @@ const defaultProps = { function ReportActionItemImage({thumbnail, image, enablePreviewModal}) { const {translate} = useLocalize(); + const imageSource = tryResolveUrlFromApiRoot(image); + const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail); - if (thumbnail) { - const imageSource = tryResolveUrlFromApiRoot(image); - const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail); - const thumbnailComponent = ( - - ); - - if (enablePreviewModal) { - return ( - - {({report}) => ( - { - const route = ROUTES.getReportAttachmentRoute(report.reportID, imageSource); - Navigation.navigate(route); - }} - accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - > - {thumbnailComponent} - - )} - - ); - } - return thumbnailComponent; - } - - return ( + const receiptImageComponent = thumbnail ? ( + + ) : ( ); + + if (enablePreviewModal) { + return ( + + {({report}) => ( + { + const route = ROUTES.getReportAttachmentRoute(report.reportID, imageSource); + Navigation.navigate(route); + }} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + > + {receiptImageComponent} + + )} + + ); + } + + return receiptImageComponent; } ReportActionItemImage.propTypes = propTypes; From f652ef558dd1fea6227c2d33d3e05c63e55d0ae2 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Tue, 5 Sep 2023 22:39:04 +0530 Subject: [PATCH 91/99] Prettier linting --- src/libs/ReportUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index cefc9674bcfc..7390bac47dd1 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1438,7 +1438,6 @@ function getReportPreviewMessage(report, reportAction = {}, shouldConsiderReceip return `approved ${formattedAmount}`; } - if (shouldConsiderReceiptBeingScanned && ReportActionsUtils.isMoneyRequestAction(reportAction)) { const linkedTransaction = TransactionUtils.getLinkedTransaction(reportAction); From 5b2c016390096a9c27c929daaf1c7921b11c6742 Mon Sep 17 00:00:00 2001 From: Carlos Martins Date: Tue, 5 Sep 2023 11:20:49 -0600 Subject: [PATCH 92/99] fix defaults --- src/components/ReportActionItem/ReportActionItemImage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index c25fef778c7b..070f534f4924 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -23,7 +23,7 @@ const propTypes = { }; const defaultProps = { - thumbnail: '', + thumbnail: null, enablePreviewModal: false, }; @@ -35,8 +35,8 @@ const defaultProps = { function ReportActionItemImage({thumbnail, image, enablePreviewModal}) { const {translate} = useLocalize(); - const imageSource = tryResolveUrlFromApiRoot(image); - const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail); + const imageSource = tryResolveUrlFromApiRoot(image || ''); + const thumbnailSource = tryResolveUrlFromApiRoot(thumbnail || ''); const receiptImageComponent = thumbnail ? ( Date: Tue, 5 Sep 2023 13:55:47 -0400 Subject: [PATCH 93/99] check for receipt first --- src/components/ReportActionItem/MoneyRequestPreview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index 25bc4bebb450..65984f9d1ecd 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -163,7 +163,7 @@ function MoneyRequestPreview(props) { !_.isEmpty(requestMerchant) && !props.isBillSplit && requestMerchant !== CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT && requestMerchant !== CONST.TRANSACTION.DEFAULT_MERCHANT; const shouldShowDescription = !_.isEmpty(description) && !shouldShowMerchant; - const receiptImages = [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || props.transaction.receiptFilename || '')]; + const receiptImages = hasReceipt ? [ReceiptUtils.getThumbnailAndImageURIs(props.transaction.receipt.source, props.transaction.filename || props.transaction.receiptFilename || '')] : []; const getSettledMessage = () => { switch (lodashGet(props.action, 'originalMessage.paymentType', '')) { From fcd8fd898dd61d9e153683293239e67768526095 Mon Sep 17 00:00:00 2001 From: Andrew Rosiclair Date: Tue, 5 Sep 2023 13:59:08 -0400 Subject: [PATCH 94/99] clarify thumbnail border logic --- src/components/ReportActionItem/ReportActionItemImages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/ReportActionItem/ReportActionItemImages.js b/src/components/ReportActionItem/ReportActionItemImages.js index 119ea0918aab..82082b18ce1c 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.js +++ b/src/components/ReportActionItem/ReportActionItemImages.js @@ -54,6 +54,8 @@ function ReportActionItemImages({images, size, total, isHovered}) { {_.map(shownImages, ({thumbnail, image}, index) => { const isLastImage = index === numberOfShownImages - 1; + + // Show a border to separate multiple images. Shown to the right for each except the last. const shouldShowBorder = shownImages.length > 1 && index < shownImages.length - 1; const borderStyle = shouldShowBorder ? styles.reportActionItemImageBorder : {}; return ( From a33640cbba821217066dec8b69a35f545a890d37 Mon Sep 17 00:00:00 2001 From: OSBotify Date: Tue, 5 Sep 2023 18:30:18 +0000 Subject: [PATCH 95/99] Update version to 1.3.64-0 --- android/app/build.gradle | 4 ++-- ios/NewExpensify/Info.plist | 4 ++-- ios/NewExpensifyTests/Info.plist | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 1f337e6fada0..a9e7a0d48b73 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001036302 - versionName "1.3.63-2" + versionCode 1001036400 + versionName "1.3.64-0" } flavorDimensions "default" diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index 8a635f1797bc..81d81db8616d 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.3.63 + 1.3.64 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.3.63.2 + 1.3.64.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index 028aca87d0da..377e23436ec7 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.3.63 + 1.3.64 CFBundleSignature ???? CFBundleVersion - 1.3.63.2 + 1.3.64.0 diff --git a/package-lock.json b/package-lock.json index 0f814d11df2a..ea138c99b8a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.3.63-2", + "version": "1.3.64-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.3.63-2", + "version": "1.3.64-0", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 99c389e1b460..1fc9d4022ee2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.3.63-2", + "version": "1.3.64-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", From 4811977a3acb278ca1e210601eb521584ba8db95 Mon Sep 17 00:00:00 2001 From: Yogesh Bhatt Date: Wed, 6 Sep 2023 00:02:01 +0530 Subject: [PATCH 96/99] rename lastTransaction to linkedTransaction in OptionRowLHNData --- src/components/LHNOptionsList/OptionRowLHNData.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 2d6ff6fcfb73..4c7bd54efa18 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -93,7 +93,7 @@ function OptionRowLHNData({ const optionItemRef = useRef(); - const lastTransaction = useMemo(() => { + const linkedTransaction = useMemo(() => { const sortedReportActions = ReportActionsUtils.getSortedReportActionsForDisplay(reportActions); const lastReportAction = _.first(sortedReportActions); return TransactionUtils.getLinkedTransaction(lastReportAction); @@ -110,7 +110,7 @@ function OptionRowLHNData({ return item; // Listen parentReportAction to update title of thread report when parentReportAction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, lastTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { From d254777f72bbe325f18064d46d9b0ce7768c55f4 Mon Sep 17 00:00:00 2001 From: makiour Date: Thu, 24 Aug 2023 04:16:20 +0100 Subject: [PATCH 97/99] Fix adding invalid contact method through URL --- .../Contacts/ContactMethodDetailsPage.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index 7b2cf85ef141..d0e88a501b15 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -22,10 +22,10 @@ import * as User from '../../../../libs/actions/User'; import CONST from '../../../../CONST'; import * as ErrorUtils from '../../../../libs/ErrorUtils'; import themeColors from '../../../../styles/themes/default'; -import NotFoundPage from '../../../ErrorPage/NotFoundPage'; import ValidateCodeForm from './ValidateCodeForm'; import ROUTES from '../../../../ROUTES'; import FullscreenLoadingIndicator from '../../../../components/FullscreenLoadingIndicator'; +import FullPageNotFoundView from '../../../../components/BlockingViews/FullPageNotFoundView'; const propTypes = { /* Onyx Props */ @@ -108,7 +108,11 @@ class ContactMethodDetailsPage extends Component { } componentDidMount() { - User.resetContactMethodValidateCodeSentState(this.getContactMethod()); + const contactMethod = this.getContactMethod(); + const loginData = this.props.loginList[contactMethod]; + if (loginData) { + User.resetContactMethodValidateCodeSentState(contactMethod); + } } componentDidUpdate(prevProps) { @@ -211,7 +215,14 @@ class ContactMethodDetailsPage extends Component { const loginData = this.props.loginList[contactMethod]; if (!contactMethod || !loginData) { - return ; + return ( + Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} + onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} + /> + ); } const isDefaultContactMethod = this.props.session.email === loginData.partnerUserID; From d7090bd18b0840d0035ce14c8c24f047bf19a933 Mon Sep 17 00:00:00 2001 From: makiour Date: Thu, 24 Aug 2023 04:48:27 +0100 Subject: [PATCH 98/99] Fix lint issue; removing unnecessary curly braces --- src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index d0e88a501b15..fe3054d1dc01 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -218,7 +218,7 @@ class ContactMethodDetailsPage extends Component { return ( Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} /> From 6c382b41f2a770b4bc9a2509e8a686411893f02a Mon Sep 17 00:00:00 2001 From: makiour Date: Mon, 28 Aug 2023 12:01:38 +0100 Subject: [PATCH 99/99] Fix comment, add screenwrapper, and goBack linkKey --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../Profile/Contacts/ContactMethodDetailsPage.js | 14 ++++++++------ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 79bb1300010c..af7957e1a560 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -610,6 +610,7 @@ export default { invalidContactMethod: 'Invalid contact method', }, newContactMethod: 'New contact method', + goBackContactMethods: 'Go back to contact methods', }, pronouns: { coCos: 'Co / Cos', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0252fce3a421..f950733b005c 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -612,6 +612,7 @@ export default { invalidContactMethod: 'Método de contacto no válido', }, newContactMethod: 'Nuevo método de contacto', + goBackContactMethods: 'Volver a métodos de contacto', }, pronouns: { coCos: 'Co / Cos', diff --git a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js index fe3054d1dc01..b7a4118bc272 100644 --- a/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js +++ b/src/pages/settings/Profile/Contacts/ContactMethodDetailsPage.js @@ -216,12 +216,14 @@ class ContactMethodDetailsPage extends Component { const loginData = this.props.loginList[contactMethod]; if (!contactMethod || !loginData) { return ( - Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} - onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} - /> + + Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} + onLinkPress={() => Navigation.goBack(ROUTES.SETTINGS_CONTACT_METHODS)} + /> + ); }