From f59d7925d92b245d83e34b14f2acfafd66c9e800 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Thu, 12 Mar 2026 16:40:24 +0100 Subject: [PATCH 01/17] refactor: move odometer image stitching to confirmation page --- .../MoneyRequestConfirmationList.tsx | 5 +++ .../MoneyRequestConfirmationListFooter.tsx | 19 ++++++++ .../step/IOURequestStepConfirmation.tsx | 45 +++++++++++++++++++ .../step/IOURequestStepDistanceOdometer.tsx | 39 ++-------------- 4 files changed, 72 insertions(+), 36 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 7287cd3038d0..d1342b295fb3 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -165,6 +165,9 @@ type MoneyRequestConfirmationListProps = { /** Whether the expense is an odometer distance expense */ isOdometerDistanceRequest?: boolean; + /** Whether the odometer receipt is currently being stitched */ + isLoadingReceipt?: boolean; + /** Whether the expense is a GPS distance expense */ isGPSDistanceRequest: boolean; @@ -236,6 +239,7 @@ function MoneyRequestConfirmationList({ isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest = false, + isLoadingReceipt = false, isGPSDistanceRequest, isPerDiemRequest = false, isPolicyExpenseChat = false, @@ -1309,6 +1313,7 @@ function MoneyRequestConfirmationList({ isDistanceRequest={isDistanceRequest} isManualDistanceRequest={isManualDistanceRequest} isOdometerDistanceRequest={isOdometerDistanceRequest} + isLoadingReceipt={isLoadingReceipt} isGPSDistanceRequest={isGPSDistanceRequest} isPerDiemRequest={isPerDiemRequest} isTimeRequest={isTimeRequest} diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 4296ab5808e3..899bbf9d913b 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -55,6 +55,7 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Attendee, Participant} from '@src/types/onyx/IOU'; import type {Unit} from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import ActivityIndicator from './ActivityIndicator'; import Badge from './Badge'; import Button from './Button'; import ConfirmedRoute from './ConfirmedRoute'; @@ -139,6 +140,9 @@ type MoneyRequestConfirmationListFooterProps = { /** Flag indicating if it is an odometer distance request */ isOdometerDistanceRequest?: boolean; + /** Whether the receipt is currently being stitched */ + isLoadingReceipt?: boolean; + /** Flag indicating if it is a GPS distance request */ isGPSDistanceRequest: boolean; @@ -281,6 +285,7 @@ function MoneyRequestConfirmationListFooter({ isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest = false, + isLoadingReceipt = false, isGPSDistanceRequest, isPerDiemRequest, isTimeRequest, @@ -1268,7 +1273,21 @@ function MoneyRequestConfirmationListFooter({ )} + {(!shouldShowMap || isManualDistanceRequest || isOdometerDistanceRequest) && !hasReceiptImageOrThumbnail && isLoadingReceipt && ( + + + + )} {(!shouldShowMap || isManualDistanceRequest || isOdometerDistanceRequest) && + !isLoadingReceipt && (hasReceiptImageOrThumbnail ? receiptThumbnailContent : showReceiptEmptyState && ( diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index f02a48fff78b..4b26c7cffd8a 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -5,6 +5,7 @@ import {View} from 'react-native'; import DragAndDropConsumer from '@components/DragAndDrop/Consumer'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import DropZoneUI from '@components/DropZone/DropZoneUI'; +import FormHelpMessage from '@components/FormHelpMessage'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import LocationPermissionModal from '@components/LocationPermissionModal'; @@ -64,6 +65,7 @@ import { isReportOutstanding, isSelectedManagerMcTest, } from '@libs/ReportUtils'; +import stitchOdometerImages from '@libs/stitchOdometerImages'; import {cancelSpan, endSpan, getSpan, startSpan} from '@libs/telemetry/activeSpans'; import getSubmitExpenseScenario from '@libs/telemetry/getSubmitExpenseScenario'; import markSubmitExpenseEnd from '@libs/telemetry/markSubmitExpenseEnd'; @@ -285,6 +287,8 @@ function IOURequestStepConfirmation({ const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && Object.values(receiptFiles).length && !isTestTransaction; const [isConfirmed, setIsConfirmed] = useState(false); const [isConfirming, setIsConfirming] = useState(false); + const [isStitchingReceipt, setIsStitchingReceipt] = useState(false); + const [stitchError, setStitchError] = useState(''); const headerTitle = useMemo(() => { if (isCategorizingTrackExpense) { @@ -382,6 +386,45 @@ function IOURequestStepConfirmation({ } }, [isOffline, policy?.pendingAction, policyExpenseChatPolicyID, senderPolicyID]); + const odometerStartImage = transaction?.comment?.odometerStartImage; + const odometerEndImage = transaction?.comment?.odometerEndImage; + + useEffect(() => { + if (!isOdometerDistanceRequest) { + return; + } + + if (!odometerStartImage && !odometerEndImage) { + return; + } + + setIsStitchingReceipt(true); + setStitchError(''); + + stitchOdometerImages(odometerStartImage, odometerEndImage) + .then((stitchedImage) => { + if (!(stitchedImage ?? odometerStartImage ?? odometerEndImage)) { + return; + } + const uri = + stitchedImage?.uri ?? + (typeof odometerStartImage === 'string' ? odometerStartImage : odometerStartImage?.uri) ?? + (typeof odometerEndImage === 'string' ? odometerEndImage : odometerEndImage?.uri) ?? + ''; + const name = + stitchedImage?.name ?? + (typeof odometerStartImage !== 'string' ? odometerStartImage?.name : odometerStartImage?.split('/').pop()) ?? + (typeof odometerEndImage !== 'string' ? odometerEndImage?.name : odometerEndImage?.split('/').pop()) ?? + ''; + setMoneyRequestReceipt(currentTransactionID, uri, name, shouldUseTransactionDraft(action)); + }) + .catch((error: unknown) => { + Log.warn('stitchOdometerImages failed on confirmation page', {error}); + setStitchError(translate('iou.error.stitchOdometerImagesFailed')); + }) + .finally(() => setIsStitchingReceipt(false)); + }, [isOdometerDistanceRequest, currentTransactionID, odometerStartImage, odometerEndImage, action, translate]); + const defaultBillable = !!policy?.defaultBillable; useEffect(() => { if (isMovingTransactionFromTrackExpense) { @@ -1594,6 +1637,7 @@ function IOURequestStepConfirmation({ }} /> )} + {!!stitchError && } (''); const [endReading, setEndReading] = useState(''); const [formError, setFormError] = useState(''); - const [isSubmitting, setIsSubmitting] = useState(false); // Key to force TextInput remount when resetting state after tab switch const [inputKey, setInputKey] = useState(0); @@ -370,7 +367,7 @@ function IOURequestStepDistanceOdometer({ const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS); const [betas] = useOnyx(ONYXKEYS.BETAS); // Navigate to next page following Manual tab pattern - const navigateToNextPage = async () => { + const navigateToNextPage = () => { const start = parseFloat(DistanceRequestUtils.normalizeOdometerText(startReading, fromLocaleDigit)); const end = parseFloat(DistanceRequestUtils.normalizeOdometerText(endReading, fromLocaleDigit)); setMoneyRequestOdometerReading(transactionID, start, end, isTransactionDraft); @@ -378,25 +375,6 @@ function IOURequestStepDistanceOdometer({ const calculatedDistance = roundToTwoDecimalPlaces(distance); setMoneyRequestDistance(transactionID, calculatedDistance, isTransactionDraft, unit); - let stitchedImage: FileObject | null = null; - try { - stitchedImage = await stitchOdometerImages(odometerStartImage, odometerEndImage); - } catch (error) { - Log.warn('stitchOdometerImages failed', {error}); - setFormError(translate('iou.error.stitchOdometerImagesFailed')); - return; - } - - if (stitchedImage ?? odometerStartImage ?? odometerEndImage) { - const uri = stitchedImage?.uri ?? startImageSource ?? endImageSource ?? ''; - const name = - stitchedImage?.name ?? - (typeof odometerStartImage !== 'string' ? odometerStartImage?.name : odometerStartImage?.split('/').pop()) ?? - (typeof odometerEndImage !== 'string' ? odometerEndImage?.name : odometerEndImage?.split('/').pop()) ?? - ''; - setMoneyRequestReceipt(transactionID, uri, name, isTransactionDraft); - } - if (isEditing) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { @@ -497,10 +475,6 @@ function IOURequestStepDistanceOdometer({ // Handle form submission with validation const handleNext = () => { - if (isSubmitting) { - return; - } - // Validation: Start and end readings must not be empty if (!startReading || !endReading) { setFormError(translate('iou.error.invalidReadings')); @@ -533,13 +507,7 @@ function IOURequestStepDistanceOdometer({ } // When validation passes, call navigateToNextPage - setIsSubmitting(true); - navigateToNextPage() - .catch((error) => { - Log.warn('navigateToNextPage failed', {error}); - setFormError(translate('common.genericErrorMessage')); - }) - .finally(() => setIsSubmitting(false)); + navigateToNextPage(); }; return ( @@ -659,7 +627,6 @@ function IOURequestStepDistanceOdometer({ success allowBubble={!isEditing} pressOnEnter - isLoading={isSubmitting} medium={isExtraSmallScreenHeight} large={!isExtraSmallScreenHeight} style={[styles.w100]} From d39c77be7f3d12588c551158d899a0d6220dc183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Fri, 13 Mar 2026 11:53:10 +0100 Subject: [PATCH 02/17] improvement: extract stitched odometer filename prefix to constants --- src/libs/stitchOdometerImages/constants.ts | 3 +++ src/libs/stitchOdometerImages/index.native.ts | 5 +++-- src/libs/stitchOdometerImages/index.ts | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 src/libs/stitchOdometerImages/constants.ts diff --git a/src/libs/stitchOdometerImages/constants.ts b/src/libs/stitchOdometerImages/constants.ts new file mode 100644 index 000000000000..cdee9e92d037 --- /dev/null +++ b/src/libs/stitchOdometerImages/constants.ts @@ -0,0 +1,3 @@ +const STITCHED_ODOMETER_FILENAME_PREFIX = 'stitched_odometer'; + +export default STITCHED_ODOMETER_FILENAME_PREFIX; diff --git a/src/libs/stitchOdometerImages/index.native.ts b/src/libs/stitchOdometerImages/index.native.ts index ed02089e95ce..4bef73052c8f 100644 --- a/src/libs/stitchOdometerImages/index.native.ts +++ b/src/libs/stitchOdometerImages/index.native.ts @@ -2,6 +2,7 @@ import {Skia} from '@shopify/react-native-skia'; import RNFS from 'react-native-fs'; import Log from '@libs/Log'; import type {FileObject} from '@src/types/utils/Attachment'; +import STITCHED_ODOMETER_FILENAME_PREFIX from './constants'; import calculateStitchLayout from './stitchLayout'; async function stitchOdometerImages(image1: FileObject | string | undefined, image2: FileObject | string | undefined): Promise { @@ -46,13 +47,13 @@ async function stitchOdometerImages(image1: FileObject | string | undefined, ima // Delete any previously stitched files before creating a new one try { const tempDirContents = await RNFS.readDir(RNFS.TemporaryDirectoryPath); - const oldStitchedFiles = tempDirContents.filter((f) => f.name.startsWith('stitched_odometer_') && f.name.endsWith('.jpg')); + const oldStitchedFiles = tempDirContents.filter((f) => f.name.startsWith(`${STITCHED_ODOMETER_FILENAME_PREFIX}_`) && f.name.endsWith('.jpg')); await Promise.all(oldStitchedFiles.map((f) => RNFS.unlink(f.path))); } catch (error) { Log.warn('stitchOdometerImages (native) failed to clean up old stitched files', {error}); } - const filename = `stitched_odometer_${Date.now()}.jpg`; + const filename = `${STITCHED_ODOMETER_FILENAME_PREFIX}_${Date.now()}.jpg`; const tempPath = `${RNFS.TemporaryDirectoryPath}/${filename}`; await RNFS.writeFile(tempPath, base64, 'base64'); diff --git a/src/libs/stitchOdometerImages/index.ts b/src/libs/stitchOdometerImages/index.ts index f388673f5bff..51bfac1c8a1d 100644 --- a/src/libs/stitchOdometerImages/index.ts +++ b/src/libs/stitchOdometerImages/index.ts @@ -1,4 +1,5 @@ import type {FileObject} from '@src/types/utils/Attachment'; +import STITCHED_ODOMETER_FILENAME_PREFIX from './constants'; import calculateStitchLayout from './stitchLayout'; // Tracks the single active stitched blob URL so that we can revoke it on the next call so at most one blob URL exists at a time @@ -45,7 +46,7 @@ function stitchOdometerImages(image1: FileObject | string | undefined, image2: F } const uri = URL.createObjectURL(blob); previousBlobUrl = uri; - resolve({uri, name: 'stitched_odometer.jpg', type: 'image/jpeg'}); + resolve({uri, name: `${STITCHED_ODOMETER_FILENAME_PREFIX}.jpg`, type: 'image/jpeg'}); }, 'image/jpeg'); }); }); From 2307c1fb581774953a0bf67c7d38f47a3b075738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Fri, 13 Mar 2026 12:00:36 +0100 Subject: [PATCH 03/17] fix: guard stale async state updates in odometer image stitching effect --- .../step/IOURequestStepConfirmation.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 4b26c7cffd8a..6bf5dce51179 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -398,11 +398,15 @@ function IOURequestStepConfirmation({ return; } + let ignore = false; setIsStitchingReceipt(true); setStitchError(''); stitchOdometerImages(odometerStartImage, odometerEndImage) .then((stitchedImage) => { + if (ignore) { + return; + } if (!(stitchedImage ?? odometerStartImage ?? odometerEndImage)) { return; } @@ -419,10 +423,22 @@ function IOURequestStepConfirmation({ setMoneyRequestReceipt(currentTransactionID, uri, name, shouldUseTransactionDraft(action)); }) .catch((error: unknown) => { - Log.warn('stitchOdometerImages failed on confirmation page', {error}); + if (ignore) { + return; + } + Log.warn('stitchOdometerImages failed', {error}); setStitchError(translate('iou.error.stitchOdometerImagesFailed')); }) - .finally(() => setIsStitchingReceipt(false)); + .finally(() => { + if (ignore) { + return; + } + setIsStitchingReceipt(false); + }); + + return () => { + ignore = true; + }; }, [isOdometerDistanceRequest, currentTransactionID, odometerStartImage, odometerEndImage, action, translate]); const defaultBillable = !!policy?.defaultBillable; From 16b00646c51131a555c161b2b5314f7feec0ee75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Fri, 13 Mar 2026 12:03:44 +0100 Subject: [PATCH 04/17] feat: add util to detect stitched odometer receipt filenames --- src/components/MoneyRequestConfirmationListFooter.tsx | 4 ++-- src/libs/ReceiptUtils.ts | 7 ++++++- .../routes/TransactionReceiptModalContent.tsx | 4 ++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 899bbf9d913b..250f5099ad69 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -27,7 +27,7 @@ import Navigation from '@libs/Navigation/Navigation'; import {getDestinationForDisplay, getSubratesFields, getSubratesForDisplay, getTimeDifferenceIntervals, getTimeForDisplay} from '@libs/PerDiemRequestUtils'; import {canSendInvoice, getPerDiemCustomUnit} from '@libs/PolicyUtils'; import type {ThumbnailAndImageURI} from '@libs/ReceiptUtils'; -import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; +import {getThumbnailAndImageURIs, isStitchedOdometerReceiptFilename} from '@libs/ReceiptUtils'; import {getReportName} from '@libs/ReportNameUtils'; import {generateReportID, getDefaultWorkspaceAvatar, getOutstandingReportsForUser, isMoneyRequestReport, isReportOutstanding} from '@libs/ReportUtils'; import {getTagVisibility, hasEnabledTags} from '@libs/TagsOptionsListUtils'; @@ -450,7 +450,7 @@ function MoneyRequestConfirmationListFooter({ } = receiptPath && receiptFilename ? getThumbnailAndImageURIs(transaction, receiptPath, receiptFilename) : ({} as ThumbnailAndImageURI); const resolvedThumbnail = isLocalFile ? receiptThumbnail : tryResolveUrlFromApiRoot(receiptThumbnail ?? ''); const resolvedReceiptImage = isLocalFile ? receiptImage : tryResolveUrlFromApiRoot(receiptImage ?? ''); - const isStitchedOdometerReceipt = !!receiptFilename?.startsWith('stitched_odometer'); + const isStitchedOdometerReceipt = isStitchedOdometerReceiptFilename(receiptFilename); const shouldNavigateToUpgradePath = !policyForMovingExpensesID && !shouldSelectPolicy; // Time requests appear as regular expenses after they're created, with editable amount and merchant, not hours and rate diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index dceabfe0ca4f..57f13f5fa404 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -7,6 +7,7 @@ import ROUTES from '@src/ROUTES'; import type {ShareTempFile, Transaction} from '@src/types/onyx'; import type {ReceiptError, ReceiptSource} from '@src/types/onyx/Transaction'; import {isLocalFile as isLocalFileUtils, splitExtensionFromFileName} from './fileDownload/FileUtils'; +import STITCHED_ODOMETER_FILENAME_PREFIX from './stitchOdometerImages/constants'; import {hasReceipt, hasReceiptSource, isFetchingWaypointsFromServer} from './TransactionUtils'; type ThumbnailAndImageURI = { @@ -94,6 +95,10 @@ const shouldValidateFile = (file: ShareTempFile | undefined) => { return file?.mimeType === CONST.SHARE_FILE_MIMETYPE.HEIC || file?.mimeType === CONST.SHARE_FILE_MIMETYPE.IMG; }; +function isStitchedOdometerReceiptFilename(filename: string | undefined): boolean { + return !!filename?.startsWith(STITCHED_ODOMETER_FILENAME_PREFIX); +} + // eslint-disable-next-line import/prefer-default-export -export {getThumbnailAndImageURIs, shouldValidateFile, constructReceiptSourceFromFilename}; +export {getThumbnailAndImageURIs, shouldValidateFile, constructReceiptSourceFromFilename, isStitchedOdometerReceiptFilename}; export type {ThumbnailAndImageURI}; diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index ff1923ddf57a..e82511cc58de 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -19,7 +19,7 @@ import fetchImage from '@libs/fetchImage'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; -import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; +import {getThumbnailAndImageURIs, isStitchedOdometerReceiptFilename} from '@libs/ReceiptUtils'; import {getReportAction, isTrackExpenseAction} from '@libs/ReportActionsUtils'; import {canEditFieldOfMoneyRequest, isMoneyRequestReport, isTrackExpenseReport} from '@libs/ReportUtils'; import {getRequestType, hasEReceipt, hasMissingSmartscanFields, hasReceipt, hasReceiptSource, isReceiptBeingScanned} from '@libs/TransactionUtils'; @@ -131,7 +131,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre const receiptFilename = transaction?.receipt?.filename; const isImage = !!receiptFilename && Str.isImage(receiptFilename); - const isStitchedOdometerReceipt = !!receiptFilename?.startsWith('stitched_odometer'); + const isStitchedOdometerReceipt = isStitchedOdometerReceiptFilename(receiptFilename); const shouldShowReplaceReceiptButton = ((canEditReceipt && !readonly) || isDraftTransaction) && !transaction?.receipt?.isTestDriveReceipt && !isStitchedOdometerReceipt; const shouldShowDeleteReceiptButton = canDeleteReceipt && !readonly && !isDraftTransaction && !transaction?.receipt?.isTestDriveReceipt; From 1211833c139c2cebf8893a8405b3dc85dce44153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Tue, 17 Mar 2026 21:45:23 +0100 Subject: [PATCH 05/17] refactor: skip stitching when only one odometer image is present --- .../step/IOURequestStepConfirmation.tsx | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 78ad4850da35..75e0b8508d70 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -395,7 +395,17 @@ function IOURequestStepConfirmation({ return; } - if (!odometerStartImage && !odometerEndImage) { + if (!odometerStartImage || !odometerEndImage) { + const singleImage = odometerStartImage ?? odometerEndImage; + + if (!singleImage) { + return; + } + + const getImageUri = (img: typeof singleImage): string => (typeof img === 'string' ? img : (img.uri ?? '')); + const getImageName = (img: typeof singleImage): string => (typeof img === 'string' ? (img.split('/').pop() ?? '') : (img.name ?? '')); + + setMoneyRequestReceipt(currentTransactionID, getImageUri(singleImage), getImageName(singleImage), shouldUseTransactionDraft(action)); return; } @@ -405,23 +415,10 @@ function IOURequestStepConfirmation({ stitchOdometerImages(odometerStartImage, odometerEndImage) .then((stitchedImage) => { - if (ignore) { - return; - } - if (!(stitchedImage ?? odometerStartImage ?? odometerEndImage)) { + if (ignore || !stitchedImage) { return; } - const uri = - stitchedImage?.uri ?? - (typeof odometerStartImage === 'string' ? odometerStartImage : odometerStartImage?.uri) ?? - (typeof odometerEndImage === 'string' ? odometerEndImage : odometerEndImage?.uri) ?? - ''; - const name = - stitchedImage?.name ?? - (typeof odometerStartImage !== 'string' ? odometerStartImage?.name : odometerStartImage?.split('/').pop()) ?? - (typeof odometerEndImage !== 'string' ? odometerEndImage?.name : odometerEndImage?.split('/').pop()) ?? - ''; - setMoneyRequestReceipt(currentTransactionID, uri, name, shouldUseTransactionDraft(action)); + setMoneyRequestReceipt(currentTransactionID, stitchedImage.uri ?? '', stitchedImage.name ?? '', shouldUseTransactionDraft(action)); }) .catch((error: unknown) => { if (ignore) { From e35804a8e60033221e90265ae37689db0bc0e62f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Tue, 17 Mar 2026 21:57:41 +0100 Subject: [PATCH 06/17] refactor: remove unused isStitchedOdometerReceiptFilename function --- src/libs/ReceiptUtils.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 57f13f5fa404..dceabfe0ca4f 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -7,7 +7,6 @@ import ROUTES from '@src/ROUTES'; import type {ShareTempFile, Transaction} from '@src/types/onyx'; import type {ReceiptError, ReceiptSource} from '@src/types/onyx/Transaction'; import {isLocalFile as isLocalFileUtils, splitExtensionFromFileName} from './fileDownload/FileUtils'; -import STITCHED_ODOMETER_FILENAME_PREFIX from './stitchOdometerImages/constants'; import {hasReceipt, hasReceiptSource, isFetchingWaypointsFromServer} from './TransactionUtils'; type ThumbnailAndImageURI = { @@ -95,10 +94,6 @@ const shouldValidateFile = (file: ShareTempFile | undefined) => { return file?.mimeType === CONST.SHARE_FILE_MIMETYPE.HEIC || file?.mimeType === CONST.SHARE_FILE_MIMETYPE.IMG; }; -function isStitchedOdometerReceiptFilename(filename: string | undefined): boolean { - return !!filename?.startsWith(STITCHED_ODOMETER_FILENAME_PREFIX); -} - // eslint-disable-next-line import/prefer-default-export -export {getThumbnailAndImageURIs, shouldValidateFile, constructReceiptSourceFromFilename, isStitchedOdometerReceiptFilename}; +export {getThumbnailAndImageURIs, shouldValidateFile, constructReceiptSourceFromFilename}; export type {ThumbnailAndImageURI}; From 9e28da62a1908227ea596c2af47470d11c4b6450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Wed, 18 Mar 2026 16:17:13 +0100 Subject: [PATCH 07/17] fix: remove image preview flickering by moving activity indicator to same container --- .../MoneyRequestConfirmationList.tsx | 2 +- .../MoneyRequestConfirmationListFooter.tsx | 46 ++++++++----------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 902bf2f649c0..ca59f2771432 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1414,5 +1414,5 @@ export default memo( prevProps.isTimeRequest === nextProps.isTimeRequest && prevProps.iouTimeCount === nextProps.iouTimeCount && prevProps.iouTimeRate === nextProps.iouTimeRate && - prevProps.shouldHideToSection === nextProps.shouldHideToSection, + prevProps.isLoadingReceipt === nextProps.isLoadingReceipt, ); diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index b20c287efd78..f84cd0547e23 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -1094,10 +1094,11 @@ function MoneyRequestConfirmationListFooter({ return ( - {isLocalFile && Str.isPDF(receiptFilename) ? ( + {isLoadingReceipt && } + {!isLoadingReceipt && (isLocalFile && Str.isPDF(receiptFilename) ? ( { if (!transactionID) { @@ -1161,24 +1162,23 @@ function MoneyRequestConfirmationListFooter({ resizeMode={isOdometerDistanceRequest ? 'contain' : undefined} /> - )} + ))} ); }, [ isCompactMode, compactReceiptContainerStyle, styles.expenseViewImageSmall, - styles.moneyRequestImage, - styles.flex1, styles.h100, + styles.flex1, + styles.moneyRequestImage, + styles.justifyContentCenter, + styles.alignItemsCenter, styles.cursorDefault, + isLoadingReceipt, + handleCompactReceiptContainerLayout, isLocalFile, receiptFilename, - transactionID, - isReceiptEditable, - reportID, - action, - iouType, translate, shouldDisplayReceipt, resolvedReceiptImage, @@ -1189,9 +1189,13 @@ function MoneyRequestConfirmationListFooter({ receiptThumbnail, fileExtension, isDistanceRequest, - isOdometerDistanceRequest, handleReceiptLoad, - handleCompactReceiptContainerLayout, + isOdometerDistanceRequest, + transactionID, + isReceiptEditable, + reportID, + action, + iouType, ]); const visibleFields = fields.filter((field) => field.shouldShow); @@ -1274,22 +1278,8 @@ function MoneyRequestConfirmationListFooter({ )} - {(!shouldShowMap || isManualDistanceRequest || isOdometerDistanceRequest) && !hasReceiptImageOrThumbnail && isLoadingReceipt && ( - - - - )} {(!shouldShowMap || isManualDistanceRequest || isOdometerDistanceRequest) && - !isLoadingReceipt && - (hasReceiptImageOrThumbnail + (hasReceiptImageOrThumbnail || isLoadingReceipt ? receiptThumbnailContent : showReceiptEmptyState && ( Date: Wed, 18 Mar 2026 16:19:37 +0100 Subject: [PATCH 08/17] chore: prettier run --- .../MoneyRequestConfirmationListFooter.tsx | 125 +++++++++--------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index f84cd0547e23..a4b2a5443ea6 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -1098,71 +1098,72 @@ function MoneyRequestConfirmationListFooter({ onLayout={isCompactMode ? handleCompactReceiptContainerLayout : undefined} > {isLoadingReceipt && } - {!isLoadingReceipt && (isLocalFile && Str.isPDF(receiptFilename) ? ( - { - if (!transactionID) { - return; - } + {!isLoadingReceipt && + (isLocalFile && Str.isPDF(receiptFilename) ? ( + { + if (!transactionID) { + return; + } - Navigation.navigate( - isReceiptEditable - ? ROUTES.MONEY_REQUEST_RECEIPT_PREVIEW.getRoute(reportID, transactionID, action, iouType) - : ROUTES.TRANSACTION_RECEIPT.getRoute(reportID, transactionID), - ); - }} - accessibilityRole={CONST.ROLE.BUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.PDF_RECEIPT_THUMBNAIL} - disabled={!shouldDisplayReceipt} - disabledStyle={styles.cursorDefault} - style={styles.h100} - > - - - ) : ( - { - if (!transactionID) { - return; - } + > + + + ) : ( + { + if (!transactionID) { + return; + } - Navigation.navigate( - isReceiptEditable - ? ROUTES.MONEY_REQUEST_RECEIPT_PREVIEW.getRoute(reportID, transactionID, action, iouType) - : ROUTES.TRANSACTION_RECEIPT.getRoute(reportID, transactionID), - ); - }} - disabled={!shouldDisplayReceipt || isThumbnail} - accessibilityRole={CONST.ROLE.BUTTON} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.RECEIPT_THUMBNAIL} - disabledStyle={styles.cursorDefault} - style={receiptThumbnailStyle} - > - - - ))} + Navigation.navigate( + isReceiptEditable + ? ROUTES.MONEY_REQUEST_RECEIPT_PREVIEW.getRoute(reportID, transactionID, action, iouType) + : ROUTES.TRANSACTION_RECEIPT.getRoute(reportID, transactionID), + ); + }} + disabled={!shouldDisplayReceipt || isThumbnail} + accessibilityRole={CONST.ROLE.BUTTON} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + sentryLabel={CONST.SENTRY_LABEL.REQUEST_CONFIRMATION_LIST.RECEIPT_THUMBNAIL} + disabledStyle={styles.cursorDefault} + style={receiptThumbnailStyle} + > + + + ))} ); }, [ From 0ea59707e415a3b589dc327f686adb63cd3773e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Wed, 18 Mar 2026 16:31:38 +0100 Subject: [PATCH 09/17] fix: revert faulty removal of props from memo comparison --- src/components/MoneyRequestConfirmationList.tsx | 1 + src/components/MoneyRequestConfirmationListFooter.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index ca59f2771432..601a6c0c2773 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1414,5 +1414,6 @@ export default memo( prevProps.isTimeRequest === nextProps.isTimeRequest && prevProps.iouTimeCount === nextProps.iouTimeCount && prevProps.iouTimeRate === nextProps.iouTimeRate && + prevProps.shouldHideToSection === nextProps.shouldHideToSection && prevProps.isLoadingReceipt === nextProps.isLoadingReceipt, ); diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index a4b2a5443ea6..9421ff7724c3 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -1374,5 +1374,6 @@ export default memo( prevProps.showMoreFields === nextProps.showMoreFields && prevProps.isTimeRequest === nextProps.isTimeRequest && prevProps.iouTimeCount === nextProps.iouTimeCount && + prevProps.iouTimeRate === nextProps.iouTimeRate && prevProps.isLoadingReceipt === nextProps.isLoadingReceipt, ); From 90808ab203f452b9caeee05b22ae28a3c58b0280 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 01:28:02 +0100 Subject: [PATCH 10/17] fix: odometer receipt type helpers scope and MIME type resolution --- .../request/step/IOURequestStepConfirmation.tsx | 14 ++++++++------ .../step/IOURequestStepDistanceOdometer.tsx | 1 - 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 1cbb570ffbfd..838762097f53 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -38,7 +38,7 @@ import {getCurrencySymbol} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import {isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils'; +import {getMimeTypeFromUri, isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils'; import validateReceiptFile from '@libs/fileDownload/validateReceiptFile'; import getCurrentPosition from '@libs/getCurrentPosition'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; @@ -423,6 +423,11 @@ function IOURequestStepConfirmation({ return; } + const getImageUri = (img: FileObject | string | null | undefined): string => (typeof img === 'string' ? img : (img?.uri ?? '')); + const getImageName = (img: FileObject | string | null | undefined): string => (typeof img === 'string' ? (img.split('/').pop() ?? '') : (img?.name ?? '')); + const getImageType = (img: FileObject | string | null | undefined): string | undefined => + typeof img === 'string' ? getMimeTypeFromUri(img) : (img?.type ?? getMimeTypeFromUri(img?.uri ?? '')); + if (!odometerStartImage || !odometerEndImage) { const singleImage = odometerStartImage ?? odometerEndImage; @@ -430,10 +435,7 @@ function IOURequestStepConfirmation({ return; } - const getImageUri = (img: typeof singleImage): string => (typeof img === 'string' ? img : (img.uri ?? '')); - const getImageName = (img: typeof singleImage): string => (typeof img === 'string' ? (img.split('/').pop() ?? '') : (img.name ?? '')); - - setMoneyRequestReceipt(currentTransactionID, getImageUri(singleImage), getImageName(singleImage), shouldUseTransactionDraft(action)); + setMoneyRequestReceipt(currentTransactionID, getImageUri(singleImage), getImageName(singleImage), shouldUseTransactionDraft(action), getImageType(singleImage)); return; } @@ -446,7 +448,7 @@ function IOURequestStepConfirmation({ if (ignore || !stitchedImage) { return; } - setMoneyRequestReceipt(currentTransactionID, stitchedImage.uri ?? '', stitchedImage.name ?? '', shouldUseTransactionDraft(action)); + setMoneyRequestReceipt(currentTransactionID, getImageUri(stitchedImage), getImageName(stitchedImage), shouldUseTransactionDraft(action), getImageType(stitchedImage)); }) .catch((error: unknown) => { if (ignore) { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index b74bc5fc3bdc..10f334f1d7aa 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -29,7 +29,6 @@ import {setMoneyRequestDistance, setMoneyRequestOdometerReading, updateMoneyRequ import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest'; import {setDraftSplitTransaction} from '@libs/actions/IOU/Split'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import {getMimeTypeFromUri} from '@libs/fileDownload/FileUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; From 11f0d76ecbe49b39e520ec13c852531e5c3ce9bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 01:37:22 +0100 Subject: [PATCH 11/17] fix: add required reasonAttributes to ActivityIndicator in MoneyRequestConfirmationListFooter --- src/components/MoneyRequestConfirmationListFooter.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 9421ff7724c3..92ee6f16849b 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -1097,7 +1097,7 @@ function MoneyRequestConfirmationListFooter({ style={[styles.moneyRequestImage, receiptContainerStyle, isLoadingReceipt && [styles.justifyContentCenter, styles.alignItemsCenter]]} onLayout={isCompactMode ? handleCompactReceiptContainerLayout : undefined} > - {isLoadingReceipt && } + {isLoadingReceipt && } {!isLoadingReceipt && (isLocalFile && Str.isPDF(receiptFilename) ? ( Date: Mon, 23 Mar 2026 01:46:07 +0100 Subject: [PATCH 12/17] fix: disable submit button while odometer receipt is stitching --- src/components/MoneyRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 601a6c0c2773..9106b74322f9 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1241,7 +1241,7 @@ function MoneyRequestConfirmationList({ enterKeyEventListenerPriority={1} useKeyboardShortcuts // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - isLoading={isConfirmed || isConfirming} + isLoading={isConfirmed || isConfirming || isLoadingReceipt} sentryLabel={CONST.SENTRY_LABEL.MONEY_REQUEST.CONFIRMATION_SUBMIT_BUTTON} /> From 108e2f3322be9bc7abfcf95574b32f5739a0ffc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 13:49:05 +0100 Subject: [PATCH 13/17] fix: add isLoadingReceipt to footerContent dependency array --- src/components/MoneyRequestConfirmationList.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 9106b74322f9..b96f3aa65a0b 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -1280,6 +1280,7 @@ function MoneyRequestConfirmationList({ shouldShowProductTrainingTooltip, renderProductTrainingTooltip, isConfirming, + isLoadingReceipt, reportID, ]); From 62a8d41af3282bdf356c928562bed4553039299d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 14:02:57 +0100 Subject: [PATCH 14/17] fix: pass iouType to shouldUseTransactionDraft in IOURequestStepConfirmation --- .../iou/request/step/IOURequestStepConfirmation.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 838762097f53..29062e1c3041 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -358,7 +358,7 @@ function IOURequestStepConfirmation({ const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); - useFetchRoute(transaction, transaction?.comment?.waypoints, action, shouldUseTransactionDraft(action) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT); + useFetchRoute(transaction, transaction?.comment?.waypoints, action, shouldUseTransactionDraft(action, iouType) ? CONST.TRANSACTION.STATE.DRAFT : CONST.TRANSACTION.STATE.CURRENT); useEffect(() => { endSpan(CONST.TELEMETRY.SPAN_OPEN_CREATE_EXPENSE); @@ -435,7 +435,7 @@ function IOURequestStepConfirmation({ return; } - setMoneyRequestReceipt(currentTransactionID, getImageUri(singleImage), getImageName(singleImage), shouldUseTransactionDraft(action), getImageType(singleImage)); + setMoneyRequestReceipt(currentTransactionID, getImageUri(singleImage), getImageName(singleImage), shouldUseTransactionDraft(action, iouType), getImageType(singleImage)); return; } @@ -448,7 +448,13 @@ function IOURequestStepConfirmation({ if (ignore || !stitchedImage) { return; } - setMoneyRequestReceipt(currentTransactionID, getImageUri(stitchedImage), getImageName(stitchedImage), shouldUseTransactionDraft(action), getImageType(stitchedImage)); + setMoneyRequestReceipt( + currentTransactionID, + getImageUri(stitchedImage), + getImageName(stitchedImage), + shouldUseTransactionDraft(action, iouType), + getImageType(stitchedImage), + ); }) .catch((error: unknown) => { if (ignore) { From 05858e7d799005b35ea6b2d57a58abe97892d73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 15:30:45 +0100 Subject: [PATCH 15/17] fix: add iouType to useEffect dependency array in IOURequestStepConfirmation --- src/pages/iou/request/step/IOURequestStepConfirmation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 29062e1c3041..e941b0f05385 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -473,7 +473,7 @@ function IOURequestStepConfirmation({ return () => { ignore = true; }; - }, [isOdometerDistanceRequest, currentTransactionID, odometerStartImage, odometerEndImage, action, translate]); + }, [isOdometerDistanceRequest, currentTransactionID, odometerStartImage, odometerEndImage, action, translate, iouType]); const defaultBillable = !!policy?.defaultBillable; useEffect(() => { From ca6fc8feab616ce4acb9707cc717039308085e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Mon, 23 Mar 2026 18:33:59 +0100 Subject: [PATCH 16/17] fix: navigate to correct screen after delete/rotate/crop in odometer preview modal --- .../routes/TransactionReceiptModalContent.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index ca25690930bd..ad7104dfc617 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -202,7 +202,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre const receiptPath = transaction?.receipt?.source; useEffect(() => { - if (!isDraftTransaction || !iouType || !transaction) { + if (!isDraftTransaction || !iouType || !transaction || isOdometerImage) { return; } @@ -267,8 +267,12 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre return; } removeMoneyRequestOdometerImage(transaction.transactionID, imageType, isDraftTransaction); - navigation.goBack(); - }, [transaction?.transactionID, imageType, isDraftTransaction, navigation]); + const goBackRoute = + isEditingConfirmation === true + ? ROUTES.MONEY_REQUEST_STEP_DISTANCE_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID) + : ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backToReport); + Navigation.goBack(goBackRoute); + }, [transaction?.transactionID, imageType, isDraftTransaction, isEditingConfirmation, action, iouType, transactionID, reportID, backToReport]); const onDownloadAttachment = useDownloadAttachment({ isAuthTokenRequired, From 5009c79f4838192fa152090765e7a9ca0f26abac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Kali=C5=84ski?= Date: Tue, 24 Mar 2026 11:34:47 +0100 Subject: [PATCH 17/17] fix: guard odometerGoBackRoute behind isOdometerImage check --- .../routes/TransactionReceiptModalContent.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index ad7104dfc617..98c8460c87c2 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -267,12 +267,13 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre return; } removeMoneyRequestOdometerImage(transaction.transactionID, imageType, isDraftTransaction); - const goBackRoute = - isEditingConfirmation === true + const odometerGoBackRoute = + isOdometerImage && + (isEditingConfirmation === true ? ROUTES.MONEY_REQUEST_STEP_DISTANCE_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID) - : ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backToReport); - Navigation.goBack(goBackRoute); - }, [transaction?.transactionID, imageType, isDraftTransaction, isEditingConfirmation, action, iouType, transactionID, reportID, backToReport]); + : ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backToReport)); + Navigation.goBack(odometerGoBackRoute || undefined); + }, [transaction?.transactionID, imageType, isOdometerImage, isDraftTransaction, isEditingConfirmation, action, iouType, transactionID, reportID, backToReport]); const onDownloadAttachment = useDownloadAttachment({ isAuthTokenRequired,