From 087cbcddeb4ace851c591b571bb62da0b5492ee0 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Wed, 20 Mar 2024 15:09:31 +0300 Subject: [PATCH 001/861] implemented confirmed route for report preview --- .../ReportActionItemImages.tsx | 27 +++++++++++++------ .../ReportActionItem/ReportPreview.tsx | 2 +- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index ffc12957dcb4..dc9ed0d371fb 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -2,11 +2,13 @@ import React from 'react'; import {View} from 'react-native'; import {Polygon, Svg} from 'react-native-svg'; +import ConfirmedRoute from '@components/ConfirmedRoute'; import Text from '@components/Text'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import variables from '@styles/variables'; import ReportActionItemImage from './ReportActionItemImage'; @@ -70,19 +72,28 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report // 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 : {}; + const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); + const hasPendingWaypoints = transaction?.pendingFields?.waypoints; + const showMapAsImage = isDistanceRequest && hasPendingWaypoints; return ( - + {showMapAsImage ? ( + + + + ) : ( + + )} {isLastImage && remaining > 0 && ( diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f1aa1751dd84..57fa568eb487 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -135,7 +135,7 @@ function ReportPreview({ const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); - const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); + const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ({...ReceiptUtils.getThumbnailAndImageURIs(transaction), transaction})); let formattedMerchant = numberOfRequests === 1 && hasReceipts ? TransactionUtils.getMerchant(transactionsWithReceipts[0]) : null; if (TransactionUtils.isPartialMerchant(formattedMerchant ?? '')) { From eb4b183fcfbcce82e4277c84dc1aeb1a62a2e480 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Thu, 21 Mar 2024 11:29:12 +0700 Subject: [PATCH 002/861] feature: Add validation flow to bank account set up --- src/languages/en.ts | 1 + src/languages/es.ts | 1 + src/libs/actions/User.ts | 7 +++++++ src/pages/ReimbursementAccount/BankAccountStep.js | 15 +++++++++++++++ 4 files changed, 24 insertions(+) diff --git a/src/languages/en.ts b/src/languages/en.ts index 29618b083bd5..60c051a289b1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1413,6 +1413,7 @@ export default { validateAccountError: { phrase1: 'Hold up! We need you to validate your account first. To do so, ', phrase2: 'sign back in with a magic code', + phrase3: 'o verifique la cuenta aquí', }, hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.', hasBeenThrottledError: 'There was an error adding your bank account. Please wait a few minutes and try again.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 874921ee911a..811bfd2031d4 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1432,6 +1432,7 @@ export default { validateAccountError: { phrase1: '¡Un momento! Primero necesitas validar tu cuenta. Para hacerlo, ', phrase2: 'vuelve a iniciar sesión con un código mágico', + phrase3: '', }, hasPhoneLoginError: 'Para añadir una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes añadir tu número de teléfono como nombre de usuario secundario.', diff --git a/src/libs/actions/User.ts b/src/libs/actions/User.ts index 2d23edfba93f..e2bc4791d465 100644 --- a/src/libs/actions/User.ts +++ b/src/libs/actions/User.ts @@ -390,6 +390,13 @@ function validateSecondaryLogin(contactMethod: string, validateCode: string) { isLoading: true, }, }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.USER, + value: { + validated: true, + }, + }, ]; const successData: OnyxUpdate[] = [ { diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index e8aacadc6e1a..0ebcb1fe891e 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -20,6 +20,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import getPlaidDesktopMessage from '@libs/getPlaidDesktopMessage'; +import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; import * as BankAccounts from '@userActions/BankAccounts'; import * as Link from '@userActions/Link'; @@ -83,6 +84,7 @@ function BankAccountStep(props) { props.policyID, ROUTES.WORKSPACE_INITIAL.getRoute(props.policyID), )}`; + const loginNames = _.keys(props.loginList); const removeExistingBankAccountDetails = () => { const bankAccountData = { @@ -180,6 +182,16 @@ function BankAccountStep(props) { > {props.translate('bankAccount.validateAccountError.phrase2')} + { + const login = props.loginList[loginNames[0]]; + + Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login.partnerUserID || loginNames[0])); + }} + > + {props.translate('bankAccount.validateAccountError.phrase3')} + . @@ -221,5 +233,8 @@ export default compose( isPlaidDisabled: { key: ONYXKEYS.IS_PLAID_DISABLED, }, + loginList: { + key: ONYXKEYS.LOGIN_LIST, + }, }), )(BankAccountStep); From cd70586476cf318c6fc0b6da01d0ec7a12a28356 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 28 Mar 2024 22:50:35 +0300 Subject: [PATCH 003/861] minor fixes --- src/components/ReportActionItem/ReportActionItemImages.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 53560307b314..921bb2271c69 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -71,8 +71,8 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report // 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 : {}; - const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); - const hasPendingWaypoints = transaction?.pendingFields?.waypoints; + const isDistanceRequest = transaction && TransactionUtils.isDistanceRequest(transaction); + const hasPendingWaypoints = transaction && TransactionUtils.isFetchingWaypointsFromServer(transaction); const showMapAsImage = isDistanceRequest && hasPendingWaypoints; return ( {showMapAsImage ? ( - + ) : ( From 4828b6270f4cc3a13661b272111fc525730c6c37 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:01:55 +0300 Subject: [PATCH 004/861] Changed empty state route pending icon --- assets/images/emptystate__routepending.svg | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/assets/images/emptystate__routepending.svg b/assets/images/emptystate__routepending.svg index 90f3296d37d6..aba08554d02f 100644 --- a/assets/images/emptystate__routepending.svg +++ b/assets/images/emptystate__routepending.svg @@ -1 +1,18 @@ - \ No newline at end of file + + + + + + + + + From 77e3ca4cf5c1bdfbf8b60ac3fde93228a374ddf7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:14:46 +0300 Subject: [PATCH 005/861] add icon fill color --- src/components/DistanceMapView/index.android.tsx | 3 +++ src/components/MapView/PendingMapView.tsx | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/components/DistanceMapView/index.android.tsx b/src/components/DistanceMapView/index.android.tsx index 168a480c6100..629b05d7bccf 100644 --- a/src/components/DistanceMapView/index.android.tsx +++ b/src/components/DistanceMapView/index.android.tsx @@ -5,6 +5,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MapView from '@components/MapView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type DistanceMapViewProps from './types'; @@ -13,6 +14,7 @@ function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { const [isMapReady, setIsMapReady] = useState(false); const {isOffline} = useNetwork(); const {translate} = useLocalize(); + const theme = useTheme(); return ( <> @@ -33,6 +35,7 @@ function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { title={translate('distance.mapPending.title')} subtitle={isOffline ? translate('distance.mapPending.subtitle') : translate('distance.mapPending.onlineSubtitle')} shouldShowLink={false} + iconColor={theme.border} /> )} diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index 32bf42a14b10..e729d03ad477 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -4,6 +4,7 @@ import {View} from 'react-native'; import BlockingView from '@components/BlockingViews/BlockingView'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import type {PendingMapViewProps} from './MapViewTypes'; @@ -11,11 +12,13 @@ import type {PendingMapViewProps} from './MapViewTypes'; function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); const styles = useThemeStyles(); + const theme = useTheme(); return ( {hasTextContent ? ( )} From 420850de85d9ee701402a027b0692f42814432c7 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Mon, 8 Apr 2024 21:43:23 +0300 Subject: [PATCH 006/861] down sized icon for multiple previews --- src/components/ConfirmedRoute.tsx | 7 +++++-- src/components/MapView/MapViewTypes.ts | 3 +++ src/components/MapView/PendingMapView.tsx | 7 ++++--- src/components/ReportActionItem/ReportActionItemImages.tsx | 5 ++++- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 17c5097b8154..596fc74dbbf6 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -26,9 +26,12 @@ type ConfirmedRoutePropsOnyxProps = { type ConfirmedRouteProps = ConfirmedRoutePropsOnyxProps & { /** Transaction that stores the distance request data */ transaction: OnyxEntry; + + /** Whether the size of the route pending icon is small. */ + isSmallIcon?: boolean; }; -function ConfirmedRoute({mapboxAccessToken, transaction}: ConfirmedRouteProps) { +function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: ConfirmedRouteProps) { const {isOffline} = useNetwork(); const {route0: route} = transaction?.routes ?? {}; const waypoints = transaction?.comment?.waypoints ?? {}; @@ -102,7 +105,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction}: ConfirmedRouteProps) { styleURL={CONST.MAPBOX.STYLE_URL} /> ) : ( - + ); } diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index 3fa52c54339b..d5c903fdeccb 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -36,6 +36,9 @@ type PendingMapViewProps = { /** Style applied to PendingMapView */ style?: StyleProp; + + /** Whether the size of the route pending icon is small. */ + isSmallIcon?: boolean; }; // Initial state of the map diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index e729d03ad477..2c79a0446f5e 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -9,10 +9,11 @@ import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import type {PendingMapViewProps} from './MapViewTypes'; -function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { +function PendingMapView({title = '', subtitle = '', style, isSmallIcon = false}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); const styles = useThemeStyles(); const theme = useTheme(); + const iconSize = isSmallIcon ? variables.iconSizeSuperLarge : variables.iconSizeUltraLarge; return ( {hasTextContent ? ( @@ -27,8 +28,8 @@ function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) diff --git a/src/components/ReportActionItem/ReportActionItemImages.tsx b/src/components/ReportActionItem/ReportActionItemImages.tsx index 921bb2271c69..04f5e5e7fdbb 100644 --- a/src/components/ReportActionItem/ReportActionItemImages.tsx +++ b/src/components/ReportActionItem/ReportActionItemImages.tsx @@ -81,7 +81,10 @@ function ReportActionItemImages({images, size, total, isHovered = false}: Report > {showMapAsImage ? ( - + ) : ( Date: Tue, 9 Apr 2024 11:12:59 +0300 Subject: [PATCH 007/861] change ultra large icon size --- src/styles/variables.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 309c90fc631e..e70d2bf5c575 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -81,7 +81,7 @@ export default { iconSizeXLarge: 28, iconSizeExtraLarge: 40, iconSizeSuperLarge: 60, - iconSizeUltraLarge: 120, + iconSizeUltraLarge: 80, iconBottomBar: 24, sidebarAvatarSize: 28, iconHeader: 48, From 3a7f04cdbaa1232244d5c1efe4f1c587c81f7d64 Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Tue, 9 Apr 2024 13:02:58 +0300 Subject: [PATCH 008/861] changed background color --- src/styles/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index f165974119ff..d775fa04920a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4341,7 +4341,7 @@ const styles = (theme: ThemeColors) => }, mapPendingView: { - backgroundColor: theme.highlightBG, + backgroundColor: theme.hoverComponentBG, ...flex.flex1, borderRadius: variables.componentBorderRadiusLarge, }, From 69263a4349afadea85763a340f0d16af1825161c Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 04:07:01 +0700 Subject: [PATCH 009/861] fix Reddot pinned chat appears for approver for submitter's failed scanned --- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/OptionsListUtils.ts | 4 ++-- src/libs/ReportUtils.ts | 20 ++++++++++++++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index dd34d0ca2540..42c85a8ed9dd 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -210,7 +210,7 @@ function MoneyRequestView({ const hasReceipt = TransactionUtils.hasReceipt(transaction); let receiptURIs; - const hasErrors = canEdit && TransactionUtils.hasMissingSmartscanFields(transaction); + const hasErrors = TransactionUtils.hasMissingSmartscanFields(transaction); if (hasReceipt) { receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index e1a3e9207ad8..7e0de5611c8c 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -509,10 +509,10 @@ function getAllReportErrors(report: OnyxEntry, reportActions: OnyxEntry< reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } } else if ((ReportUtils.isIOUReport(report) || ReportUtils.isExpenseReport(report)) && report?.ownerAccountID === currentUserAccountID) { - if (ReportUtils.hasMissingSmartscanFields(report?.reportID ?? '') && !ReportUtils.isSettled(report?.reportID)) { + if (ReportUtils.hasMissingSmartscanFields(report?.reportID ?? '', true) && !ReportUtils.isSettled(report?.reportID)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } - } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}))) { + } else if (ReportUtils.hasSmartscanError(Object.values(reportActions ?? {}), true)) { reportActionErrors.smartscan = ErrorUtils.getMicroSecondOnyxError('report.genericSmartscanFailureMessage'); } // All error objects related to the report. Each object in the sources contains error messages keyed by microtime diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c9241054e74c..b2701569bd57 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2503,7 +2503,21 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio * Check if any of the transactions in the report has required missing fields * */ -function hasMissingSmartscanFields(iouReportID: string): boolean { +function hasMissingSmartscanFields(iouReportID: string, isLHNPreview = false): boolean { + if (isLHNPreview) { + const reportActions = Object.values(ReportActionsUtils.getAllReportActions(iouReportID)); + return reportActions.some((action) => { + if (!ReportActionsUtils.isMoneyRequestAction(action)) { + return false; + } + const transaction = getLinkedTransaction(action); + if (isEmptyObject(transaction)) { + return false; + } + console.log("22222222222", {transaction}) + return TransactionUtils.hasMissingSmartscanFields(transaction) && currentUserAccountID === action?.actorAccountID; + }); + } return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction)); } @@ -5401,13 +5415,13 @@ function canEditPolicyDescription(policy: OnyxEntry): boolean { /** * Checks if report action has error when smart scanning */ -function hasSmartscanError(reportActions: ReportAction[]) { +function hasSmartscanError(reportActions: ReportAction[], isLHNPreview = false) { return reportActions.some((action) => { if (!ReportActionsUtils.isSplitBillAction(action) && !ReportActionsUtils.isReportPreviewAction(action)) { return false; } const IOUReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); - const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID) && !isSettled(IOUReportID); + const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, true) && !isSettled(IOUReportID); const transactionID = (action.originalMessage as IOUMessage).IOUTransactionID ?? '0'; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; const isSplitBillError = ReportActionsUtils.isSplitBillAction(action) && TransactionUtils.hasMissingSmartscanFields(transaction as Transaction); From fe5f752c317f83725fdbf07ead86600c426bc8b9 Mon Sep 17 00:00:00 2001 From: dukenv0307 Date: Wed, 10 Apr 2024 04:15:23 +0700 Subject: [PATCH 010/861] fix lint --- src/libs/ReportUtils.ts | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b2701569bd57..e42b3add59e7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2499,6 +2499,22 @@ function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewActio return transactionsWithReceipts.every((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); } +/** + * Get the transactions related to a report preview with receipts + * Get the details linked to the IOU reportAction + * + * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. + */ +function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { + let transactionID = ''; + + if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { + transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; + } + + return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; +} + /** * Check if any of the transactions in the report has required missing fields * @@ -2514,29 +2530,12 @@ function hasMissingSmartscanFields(iouReportID: string, isLHNPreview = false): b if (isEmptyObject(transaction)) { return false; } - console.log("22222222222", {transaction}) return TransactionUtils.hasMissingSmartscanFields(transaction) && currentUserAccountID === action?.actorAccountID; }); } return TransactionUtils.getAllReportTransactions(iouReportID).some((transaction) => TransactionUtils.hasMissingSmartscanFields(transaction)); } -/** - * Get the transactions related to a report preview with receipts - * Get the details linked to the IOU reportAction - * - * NOTE: This method is only meant to be used inside this action file. Do not export and use it elsewhere. Use withOnyx or Onyx.connect() instead. - */ -function getLinkedTransaction(reportAction: OnyxEntry): Transaction | EmptyObject { - let transactionID = ''; - - if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.IOU) { - transactionID = (reportAction?.originalMessage as IOUMessage)?.IOUTransactionID ?? ''; - } - - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; -} - /** * Given a parent IOU report action get report name for the LHN. */ @@ -5421,7 +5420,7 @@ function hasSmartscanError(reportActions: ReportAction[], isLHNPreview = false) return false; } const IOUReportID = ReportActionsUtils.getIOUReportIDFromReportActionPreview(action); - const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, true) && !isSettled(IOUReportID); + const isReportPreviewError = ReportActionsUtils.isReportPreviewAction(action) && hasMissingSmartscanFields(IOUReportID, isLHNPreview) && !isSettled(IOUReportID); const transactionID = (action.originalMessage as IOUMessage).IOUTransactionID ?? '0'; const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? {}; const isSplitBillError = ReportActionsUtils.isSplitBillAction(action) && TransactionUtils.hasMissingSmartscanFields(transaction as Transaction); From 894c310bcd560b6135f4d10246bfbd472d225128 Mon Sep 17 00:00:00 2001 From: Abdelrahman Khattab Date: Thu, 11 Apr 2024 04:14:11 +0200 Subject: [PATCH 011/861] Preserve transactions amount in create IOU --- src/components/transactionPropTypes.js | 3 +++ src/libs/CurrencyUtils.ts | 20 +++++++++++++++--- src/libs/actions/IOU.ts | 6 +++--- src/pages/iou/request/IOURequestStartPage.js | 9 +++++++- .../iou/request/step/IOURequestStepAmount.js | 9 +++++++- .../iou/steps/MoneyRequestAmountForm.tsx | 21 +++++++++---------- src/types/onyx/Transaction.ts | 3 +++ tests/unit/CurrencyUtilsTest.ts | 20 +++++++++++++++--- 8 files changed, 69 insertions(+), 22 deletions(-) diff --git a/src/components/transactionPropTypes.js b/src/components/transactionPropTypes.js index 7eb1b776358c..46d246948460 100644 --- a/src/components/transactionPropTypes.js +++ b/src/components/transactionPropTypes.js @@ -93,4 +93,7 @@ export default PropTypes.shape({ /** Server side errors keyed by microtime */ errorFields: PropTypes.objectOf(PropTypes.objectOf(translatableTextPropTypes)), + + /** Whether the original input should be shown */ + shouldShowOriginalAmount: PropTypes.bool, }); diff --git a/src/libs/CurrencyUtils.ts b/src/libs/CurrencyUtils.ts index 95970d2a9582..4530dc105e03 100644 --- a/src/libs/CurrencyUtils.ts +++ b/src/libs/CurrencyUtils.ts @@ -87,9 +87,22 @@ function convertToBackendAmount(amountAsFloat: number): number { * * @note we do not support any currencies with more than two decimal places. */ -function convertToFrontendAmount(amountAsInt: number): number { +function convertToFrontendAmountAsInteger(amountAsInt: number): number { return Math.trunc(amountAsInt) / 100.0; } + +/** + * Takes an amount in "cents" as an integer and converts it to a string amount used in the frontend. + * + * @note we do not support any currencies with more than two decimal places. + */ +function convertToFrontendAmountAsString(amountAsInt: number | null | undefined): string { + if (amountAsInt === null || amountAsInt === undefined) { + return ''; + } + return convertToFrontendAmountAsInteger(amountAsInt).toFixed(2); +} + /** * Given an amount in the "cents", convert it to a string for display in the UI. * The backend always handle things in "cents" (subunit equal to 1/100) @@ -98,7 +111,7 @@ function convertToFrontendAmount(amountAsInt: number): number { * @param currency - IOU currency */ function convertToDisplayString(amountInCents = 0, currency: string = CONST.CURRENCY.USD): string { - const convertedAmount = convertToFrontendAmount(amountInCents); + const convertedAmount = convertToFrontendAmountAsInteger(amountInCents); return NumberFormatUtils.format(BaseLocaleListener.getPreferredLocale(), convertedAmount, { style: 'currency', currency, @@ -139,7 +152,8 @@ export { getCurrencySymbol, isCurrencySymbolLTR, convertToBackendAmount, - convertToFrontendAmount, + convertToFrontendAmountAsInteger, + convertToFrontendAmountAsString, convertToDisplayString, convertAmountToDisplayString, isValidCurrencyCode, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index dab0eed1653a..8e3ead229a61 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -357,12 +357,12 @@ function startMoneyRequest(iouType: ValueOf, reportID: st } // eslint-disable-next-line @typescript-eslint/naming-convention -function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false) { +function setMoneyRequestAmount_temporaryForRefactor(transactionID: string, amount: number, currency: string, removeOriginalCurrency = false, shouldShowOriginalAmount = false) { if (removeOriginalCurrency) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, originalCurrency: null, shouldShowOriginalAmount}); return; } - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency}); + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {amount, currency, shouldShowOriginalAmount}); } // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/pages/iou/request/IOURequestStartPage.js b/src/pages/iou/request/IOURequestStartPage.js index e9057fef9226..2a96e5b1e1eb 100644 --- a/src/pages/iou/request/IOURequestStartPage.js +++ b/src/pages/iou/request/IOURequestStartPage.js @@ -166,7 +166,14 @@ function IOURequestStartPage({ onTabSelected={resetIOUTypeIfChanged} tabBar={TabSelector} > - {() => } + + {() => ( + + )} + {() => } {shouldDisplayDistanceRequest && {() => }} diff --git a/src/pages/iou/request/step/IOURequestStepAmount.js b/src/pages/iou/request/step/IOURequestStepAmount.js index b392ee310205..0dd4d0247629 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.js +++ b/src/pages/iou/request/step/IOURequestStepAmount.js @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import lodashIsEmpty from 'lodash/isEmpty'; +import PropTypes from 'prop-types'; import React, {useCallback, useEffect, useRef} from 'react'; import {withOnyx} from 'react-native-onyx'; import transactionPropTypes from '@components/transactionPropTypes'; @@ -39,6 +40,9 @@ const propTypes = { /** The draft transaction object being modified in Onyx */ draftTransaction: transactionPropTypes, + + /** Whether the user input should be kept or not */ + shouldKeepUserInput: PropTypes.bool, }; const defaultProps = { @@ -46,6 +50,7 @@ const defaultProps = { transaction: {}, splitDraftTransaction: {}, draftTransaction: {}, + shouldKeepUserInput: false, }; function IOURequestStepAmount({ @@ -56,6 +61,7 @@ function IOURequestStepAmount({ transaction, splitDraftTransaction, draftTransaction, + shouldKeepUserInput, }) { const {translate} = useLocalize(); const textInput = useRef(null); @@ -125,7 +131,7 @@ function IOURequestStepAmount({ isSaveButtonPressed.current = true; const amountInSmallestCurrencyUnits = CurrencyUtils.convertToBackendAmount(Number.parseFloat(amount)); - IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true); + IOU.setMoneyRequestAmount_temporaryForRefactor(transactionID, amountInSmallestCurrencyUnits, currency || CONST.CURRENCY.USD, true, shouldKeepUserInput); if (backTo) { Navigation.goBack(backTo); @@ -183,6 +189,7 @@ function IOURequestStepAmount({ currency={currency} amount={Math.abs(transactionAmount)} ref={(e) => (textInput.current = e)} + shouldKeepUserInput={transaction.shouldShowOriginalAmount} onCurrencyButtonPress={navigateToCurrencySelectionPage} onSubmitButtonPress={saveAmountAndCurrency} selectedTab={iouRequestType} diff --git a/src/pages/iou/steps/MoneyRequestAmountForm.tsx b/src/pages/iou/steps/MoneyRequestAmountForm.tsx index c8b418f301c9..77a953dada90 100644 --- a/src/pages/iou/steps/MoneyRequestAmountForm.tsx +++ b/src/pages/iou/steps/MoneyRequestAmountForm.tsx @@ -49,6 +49,9 @@ type MoneyRequestAmountFormProps = { /** The current tab we have navigated to in the request modal. String that corresponds to the request type. */ selectedTab?: SelectedTabRequest; + + /** Whether the user input should be kept or not */ + shouldKeepUserInput?: boolean; }; type Selection = { @@ -66,7 +69,7 @@ const getNewSelection = (oldSelection: Selection, prevLength: number, newLength: const isAmountInvalid = (amount: string) => !amount.length || parseFloat(amount) < 0.01; const isTaxAmountInvalid = (currentAmount: string, taxAmount: number, isTaxAmountForm: boolean) => - isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmount(Math.abs(taxAmount)); + isTaxAmountForm && Number.parseFloat(currentAmount) > CurrencyUtils.convertToFrontendAmountAsInteger(Math.abs(taxAmount)); const AMOUNT_VIEW_ID = 'amountView'; const NUM_PAD_CONTAINER_VIEW_ID = 'numPadContainerView'; @@ -82,6 +85,7 @@ function MoneyRequestAmountForm( onCurrencyButtonPress, onSubmitButtonPress, selectedTab = CONST.TAB_REQUEST.MANUAL, + shouldKeepUserInput = false, }: MoneyRequestAmountFormProps, forwardedRef: ForwardedRef, ) { @@ -93,7 +97,7 @@ function MoneyRequestAmountForm( const isTaxAmountForm = Navigation.getActiveRoute().includes('taxAmount'); const decimals = CurrencyUtils.getCurrencyDecimals(currency); - const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmount(amount).toString() : ''; + const selectedAmountAsString = amount ? CurrencyUtils.convertToFrontendAmountAsString(amount) : ''; const [currentAmount, setCurrentAmount] = useState(selectedAmountAsString); const [formError, setFormError] = useState(''); @@ -135,7 +139,7 @@ function MoneyRequestAmountForm( }; const initializeAmount = useCallback((newAmount: number) => { - const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmount(newAmount).toString() : ''; + const frontendAmount = newAmount ? CurrencyUtils.convertToFrontendAmountAsString(newAmount) : ''; setCurrentAmount(frontendAmount); setSelection({ start: frontendAmount.length, @@ -144,13 +148,13 @@ function MoneyRequestAmountForm( }, []); useEffect(() => { - if (!currency || typeof amount !== 'number') { + if (!currency || typeof amount !== 'number' || shouldKeepUserInput) { return; } initializeAmount(amount); // we want to re-initialize the state only when the selected tab or amount changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedTab, amount]); + }, [selectedTab, amount, shouldKeepUserInput]); /** * Sets the selection and the amount accordingly to the value passed to the input @@ -264,13 +268,8 @@ function MoneyRequestAmountForm( return; } - // Update display amount string post-edit to ensure consistency with backend amount - // Reference: https://github.com/Expensify/App/issues/30505 - const backendAmount = CurrencyUtils.convertToBackendAmount(Number.parseFloat(currentAmount)); - initializeAmount(backendAmount); - onSubmitButtonPress({amount: currentAmount, currency}); - }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount, initializeAmount]); + }, [currentAmount, taxAmount, isTaxAmountForm, onSubmitButtonPress, currency, formattedTaxAmount]); /** * Input handler to check for a forward-delete key (or keyboard shortcut) press. diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 281b6b4228ce..6b4bd1fc0d19 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -215,6 +215,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** Indicates transaction loading */ isLoading?: boolean; + + /** Whether the user input should be kept */ + shouldShowOriginalAmount?: boolean; }, keyof Comment >; diff --git a/tests/unit/CurrencyUtilsTest.ts b/tests/unit/CurrencyUtilsTest.ts index a1e4b03fa715..089cdf8426a8 100644 --- a/tests/unit/CurrencyUtilsTest.ts +++ b/tests/unit/CurrencyUtilsTest.ts @@ -105,15 +105,29 @@ describe('CurrencyUtils', () => { }); }); - describe('convertToFrontendAmount', () => { + describe('convertToFrontendAmountAsInteger', () => { test.each([ [2500, 25], [2550, 25.5], [25, 0.25], [2500, 25], [2500.5, 25], // The backend should never send a decimal .5 value - ])('Correctly converts %s to amount in units handled in frontend', (amount, expectedResult) => { - expect(CurrencyUtils.convertToFrontendAmount(amount)).toBe(expectedResult); + ])('Correctly converts %s to amount in units handled in frontend as an integer', (amount, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsInteger(amount)).toBe(expectedResult); + }); + }); + + describe('convertToFrontendAmountAsString', () => { + test.each([ + [2500, '25.00'], + [2550, '25.50'], + [25, '0.25'], + [2500.5, '25.00'], + [null, ''], + [undefined, ''], + [0, '0.00'], + ])('Correctly converts %s to amount in units handled in frontend as a string', (input, expectedResult) => { + expect(CurrencyUtils.convertToFrontendAmountAsString(input)).toBe(expectedResult); }); }); From 23d62ea94184a054843ec5c3b20f273ceea6327c Mon Sep 17 00:00:00 2001 From: FitseTLT Date: Thu, 11 Apr 2024 22:42:57 +0300 Subject: [PATCH 012/861] fix border radius --- src/components/ConfirmedRoute.tsx | 7 ++++++- src/styles/utils/index.ts | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/ConfirmedRoute.tsx b/src/components/ConfirmedRoute.tsx index 596fc74dbbf6..fb0b4ddbb534 100644 --- a/src/components/ConfirmedRoute.tsx +++ b/src/components/ConfirmedRoute.tsx @@ -3,6 +3,7 @@ import type {ReactNode} from 'react'; import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as TransactionUtils from '@libs/TransactionUtils'; @@ -38,6 +39,7 @@ function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: Confirmed const coordinates = route?.geometry?.coordinates ?? []; const theme = useTheme(); const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); const getMarkerComponent = useCallback( (icon: IconAsset): ReactNode => ( @@ -105,7 +107,10 @@ function ConfirmedRoute({mapboxAccessToken, transaction, isSmallIcon}: Confirmed styleURL={CONST.MAPBOX.STYLE_URL} /> ) : ( - + ); } diff --git a/src/styles/utils/index.ts b/src/styles/utils/index.ts index e9efc84e8807..dd66a72faafa 100644 --- a/src/styles/utils/index.ts +++ b/src/styles/utils/index.ts @@ -419,6 +419,13 @@ function getBackgroundAndBorderStyle(backgroundColor: ColorValue | undefined): V }; } +/** + * Returns a style with the specified borderRadius + */ +function getBorderRadiusStyle(borderRadius: number): ViewStyle { + return {borderRadius}; +} + /** * Returns a style with the specified backgroundColor */ @@ -1108,6 +1115,7 @@ const staticStyleUtils = { getAvatarSize, getAvatarWidthStyle, getBackgroundAndBorderStyle, + getBorderRadiusStyle, getBackgroundColorStyle, getBackgroundColorWithOpacityStyle, getPaddingLeft, From b7cdaf83cbf947f9566005bca083f08e1a0047b9 Mon Sep 17 00:00:00 2001 From: gijoe0295 Date: Fri, 12 Apr 2024 04:04:11 +0700 Subject: [PATCH 013/861] feat: surfacing potential duplicates --- src/components/HoldBanner.tsx | 11 +++++-- src/components/MoneyRequestHeader.tsx | 29 +++++++++++++++-- src/languages/en.ts | 2 ++ src/languages/es.ts | 2 ++ src/libs/Permissions.ts | 2 +- src/libs/TransactionUtils.ts | 47 +++++++++++++++++++++++++-- src/types/onyx/Transaction.ts | 2 ++ 7 files changed, 87 insertions(+), 8 deletions(-) diff --git a/src/components/HoldBanner.tsx b/src/components/HoldBanner.tsx index af77d9076629..5376087ba849 100644 --- a/src/components/HoldBanner.tsx +++ b/src/components/HoldBanner.tsx @@ -5,14 +5,19 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Text from './Text'; import TextPill from './TextPill'; -function HoldBanner() { +type HoldBannerProps = { + isRequestDuplicate?: boolean; + shouldShowBorderBottom?: boolean; +}; + +function HoldBanner({isRequestDuplicate = false, shouldShowBorderBottom = false}: HoldBannerProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); return ( - + {translate('iou.hold')} - {translate('iou.requestOnHold')} + {isRequestDuplicate ? translate('iou.requestDuplicate') : translate('iou.requestOnHold')} ); } diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index f451f5f15581..6588b8167e62 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -16,6 +16,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx'; import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import Button from './Button'; import ConfirmModal from './ConfirmModal'; import HeaderWithBackButton from './HeaderWithBackButton'; import HoldBanner from './HoldBanner'; @@ -61,6 +62,7 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, const isSettled = ReportUtils.isSettled(moneyRequestReport?.reportID); const isApproved = ReportUtils.isReportApproved(moneyRequestReport); const isOnHold = TransactionUtils.isOnHold(transaction); + const isDuplicate = TransactionUtils.isDuplicate(transaction?.transactionID ?? ''); const {isSmallScreenWidth, windowWidth} = useWindowDimensions(); // Only the requestor can take delete the request, admins can only edit it. @@ -178,7 +180,15 @@ function MoneyRequestHeader({session, parentReport, report, parentReportAction, policy={policy} shouldShowBackButton={isSmallScreenWidth} onBackButtonPress={() => Navigation.goBack(undefined, false, true)} - /> + > + {isDuplicate && !isSmallScreenWidth && ( +