From 63e60600c294f3d8cee3e4a20665f527f1cc4710 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Tue, 3 Mar 2026 09:59:49 +0300 Subject: [PATCH 01/12] feat: Allow Multiple Receipts to Be Attached to a Single Expense --- .../MoneyRequestReceiptView.tsx | 69 ++++++++++++++++++- src/styles/index.ts | 8 +++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index d4336c2987b92..6448e72d00c4b 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -4,24 +4,32 @@ import {View} from 'react-native'; import type {StyleProp, ViewStyle} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; +import AttachmentPicker from '@components/AttachmentPicker'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; +import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import ReceiptAudit, {ReceiptAuditMessages} from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import useActiveRoute from '@hooks/useActiveRoute'; +import useAncestors from '@hooks/useAncestors'; import useCardFeedErrors from '@hooks/useCardFeedErrors'; import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useEnvironment from '@hooks/useEnvironment'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; +import useHover from '@hooks/useHover'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useOriginalReportID from '@hooks/useOriginalReportID'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; import {getBrokenConnectionUrlToFixPersonalCard} from '@libs/CardUtils'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {getMicroSecondOnyxErrorWithTranslationKey, isReceiptError} from '@libs/ErrorUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getThumbnailAndImageURIs} from '@libs/ReceiptUtils'; @@ -47,7 +55,7 @@ import { import ViolationsUtils, {filterReceiptViolations} from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; import {cleanUpMoneyRequest, replaceReceipt} from '@userActions/IOU'; -import {navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; +import {addAttachmentWithComment, navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; import {clearAllRelatedReportActionErrors} from '@userActions/ReportActions'; import {clearError, getLastModifiedExpense, revert} from '@userActions/Transaction'; import CONST from '@src/CONST'; @@ -132,6 +140,10 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const isInvoice = isInvoiceReport(moneyRequestReport); const isChatReportArchived = useReportIsArchived(moneyRequestReport?.chatReportID); const {login: currentUserLogin, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); + const theme = useTheme(); + const ancestors = useAncestors(report); + const {hovered, bind: hoverBind} = useHover(); + const isTouchScreen = canUseTouchScreen(); // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... @@ -322,6 +334,8 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const isMapDistanceRequest = !!transaction && isDistanceRequest && !isManualDistanceRequest(transaction); + const canShowReceiptActions = hasReceipt && isEditable && !isTransactionScanning && !isMapDistanceRequest && !mergeTransactionID; + const receiptAuditMessagesRow = ( @@ -423,6 +437,8 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, showBorderlessLoading && styles.flex1, fillSpace && !shouldShowReceiptEmptyState && isMapDistanceRequest && styles.flex1, ]} + onMouseEnter={hoverBind.onMouseEnter} + onMouseLeave={hoverBind.onMouseLeave} > setIsLoading(false)} onLoadFailure={() => setIsLoading(false)} /> + {canShowReceiptActions && ( + + + Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly)) + } + style={styles.primaryMediumIcon} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + role={CONST.ROLE.BUTTON} + > + + + + {({openPicker}) => ( + { + openPicker({ + onPicked: (files) => { + if (!report?.reportID) { + return; + } + addAttachmentWithComment({ + report, + notifyReportID: report.reportID, + ancestors, + attachments: files, + currentUserAccountID: currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, + }); + }, + }); + }} + style={styles.primaryMediumIcon} + accessibilityLabel={translate('reportActionCompose.addAttachment')} + role={CONST.ROLE.BUTTON} + > + + + )} + + + )} )} {/* For WideRHP (fillSpace is true), we need to wait for the image to load to get the correct size, then display the violation message to avoid the jumping issue. diff --git a/src/styles/index.ts b/src/styles/index.ts index 84a6d82b3b38a..277cde805d4dd 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3614,6 +3614,14 @@ const staticStyles = (theme: ThemeColors) => right: 20, }, + receiptActionButtonsContainer: { + position: 'absolute', + top: 16, + right: 16, + flexDirection: 'row', + gap: 8, + }, + bgGreenSuccess: { backgroundColor: colors.green400, }, From e92af7d33ac4e829a5b2b216caefe71a8866a803 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Tue, 3 Mar 2026 10:14:29 +0300 Subject: [PATCH 02/12] fix: migrate icons to lazy loading --- .../ReportActionItem/MoneyRequestReceiptView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 6448e72d00c4b..db326547035b3 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -6,7 +6,6 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; -import * as Expensicons from '@components/Icon/Expensicons'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -20,6 +19,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useEnvironment from '@hooks/useEnvironment'; import useGetIOUReportFromReportAction from '@hooks/useGetIOUReportFromReportAction'; import useHover from '@hooks/useHover'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useOriginalReportID from '@hooks/useOriginalReportID'; @@ -144,6 +144,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const ancestors = useAncestors(report); const {hovered, bind: hoverBind} = useHover(); const isTouchScreen = canUseTouchScreen(); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Fullscreen', 'Plus']); // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... @@ -468,7 +469,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, role={CONST.ROLE.BUTTON} > Date: Tue, 3 Mar 2026 10:27:11 +0300 Subject: [PATCH 03/12] fix: add sentry labels to receipt action buttons --- src/CONST/index.ts | 2 ++ src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 99b67b2d50d3a..a646715ab12b1 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -8346,6 +8346,8 @@ const CONST = { }, RECEIPT: { IMAGE: 'Receipt-Image', + ENLARGE_BUTTON: 'Receipt-EnlargeButton', + ADD_ATTACHMENT_BUTTON: 'Receipt-AddAttachmentButton', }, RECEIPT_MODAL: { REPLACE_RECEIPT: 'ReceiptModal-ReplaceReceipt', diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index db326547035b3..c80ed5598899b 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -467,6 +467,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, style={styles.primaryMediumIcon} accessibilityLabel={translate('accessibilityHints.viewAttachment')} role={CONST.ROLE.BUTTON} + sentryLabel={CONST.SENTRY_LABEL.RECEIPT.ENLARGE_BUTTON} > Date: Tue, 3 Mar 2026 10:35:09 +0300 Subject: [PATCH 04/12] fix: pass user timezone to addAttachmentWithComment --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index c80ed5598899b..159bea1ae5bb4 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -139,7 +139,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const didReceiptScanSucceed = hasReceipt && didReceiptScanSucceedTransactionUtils(transaction); const isInvoice = isInvoiceReport(moneyRequestReport); const isChatReportArchived = useReportIsArchived(moneyRequestReport?.chatReportID); - const {login: currentUserLogin, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); + const {login: currentUserLogin, accountID: currentUserAccountID, timezone: currentUserTimezone} = useCurrentUserPersonalDetails(); const theme = useTheme(); const ancestors = useAncestors(report); const {hovered, bind: hoverBind} = useHover(); @@ -491,6 +491,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, ancestors, attachments: files, currentUserAccountID: currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, + timezone: currentUserTimezone, }); }, }); From 64b1f16e70ee55a2e517d5e0ec6b98ecea4cbb90 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Tue, 3 Mar 2026 11:27:08 +0300 Subject: [PATCH 05/12] fix: hide receipt action buttons until image is loaded --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 159bea1ae5bb4..b5cd980986dfb 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -335,7 +335,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const isMapDistanceRequest = !!transaction && isDistanceRequest && !isManualDistanceRequest(transaction); - const canShowReceiptActions = hasReceipt && isEditable && !isTransactionScanning && !isMapDistanceRequest && !mergeTransactionID; + const canShowReceiptActions = hasReceipt && !isLoading && isEditable && !isTransactionScanning && !isMapDistanceRequest && !mergeTransactionID; const receiptAuditMessagesRow = ( From b40ff14b8103333e615d4f6a9ad1ec6ce341d94e Mon Sep 17 00:00:00 2001 From: TaduJR Date: Tue, 3 Mar 2026 11:56:53 +0300 Subject: [PATCH 06/12] fix: prevent hover state during receipt image loading --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index b5cd980986dfb..1dd646499a11b 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -438,7 +438,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, showBorderlessLoading && styles.flex1, fillSpace && !shouldShowReceiptEmptyState && isMapDistanceRequest && styles.flex1, ]} - onMouseEnter={hoverBind.onMouseEnter} + onMouseEnter={() => !isLoading && hoverBind.onMouseEnter()} onMouseLeave={hoverBind.onMouseLeave} > Date: Tue, 3 Mar 2026 13:31:00 +0300 Subject: [PATCH 07/12] fix: notify parent report when adding attachment from transaction thread --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 1dd646499a11b..ee20037b077c6 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -487,7 +487,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, } addAttachmentWithComment({ report, - notifyReportID: report.reportID, + notifyReportID: moneyRequestReport?.reportID ?? report.reportID, ancestors, attachments: files, currentUserAccountID: currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, From 3a10b52a5e7e8bcba6b37ed4c42b24e600d083cf Mon Sep 17 00:00:00 2001 From: TaduJR Date: Wed, 4 Mar 2026 20:32:07 +0300 Subject: [PATCH 08/12] fix: use iconSizeSmall variable and remove redundant accountID fallback --- .../ReportActionItem/MoneyRequestReceiptView.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index b7c3ca482d581..96b48fa571149 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -54,6 +54,7 @@ import { } from '@libs/TransactionUtils'; import ViolationsUtils, {filterReceiptViolations} from '@libs/Violations/ViolationsUtils'; import Navigation from '@navigation/Navigation'; +import variables from '@styles/variables'; import {clearAllRelatedReportActionErrors} from '@userActions/ClearReportActionErrors'; import {cleanUpMoneyRequest, replaceReceipt} from '@userActions/IOU'; import {addAttachmentWithComment, navigateToConciergeChatAndDeleteReport} from '@userActions/Report'; @@ -471,8 +472,8 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, > @@ -490,7 +491,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, notifyReportID: moneyRequestReport?.reportID ?? report.reportID, ancestors, attachments: files, - currentUserAccountID: currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, + currentUserAccountID, timezone: currentUserTimezone, }); }, From 3271c15ff5b2850bec5423d26c3c2991abb83996 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Wed, 4 Mar 2026 20:49:58 +0300 Subject: [PATCH 09/12] fix: use expand and receipt-plus icons per design feedback --- .../MoneyRequestReceiptView.tsx | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 96b48fa571149..7e033abb8dcb3 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -145,7 +145,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const ancestors = useAncestors(report); const {hovered, bind: hoverBind} = useHover(); const isTouchScreen = canUseTouchScreen(); - const lazyIcons = useMemoizedLazyExpensifyIcons(['Fullscreen', 'Plus']); + const lazyIcons = useMemoizedLazyExpensifyIcons(['Expand', 'ReceiptPlus']); // Flags for allowing or disallowing editing an expense // Used for non-restricted fields such as: description, category, tag, billable, etc... @@ -461,22 +461,6 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, /> {canShowReceiptActions && ( - - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly)) - } - style={styles.primaryMediumIcon} - accessibilityLabel={translate('accessibilityHints.viewAttachment')} - role={CONST.ROLE.BUTTON} - sentryLabel={CONST.SENTRY_LABEL.RECEIPT.ENLARGE_BUTTON} - > - - {({openPicker}) => ( )} + + Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly)) + } + style={styles.primaryMediumIcon} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + role={CONST.ROLE.BUTTON} + sentryLabel={CONST.SENTRY_LABEL.RECEIPT.ENLARGE_BUTTON} + > + + )} From 29ab8d56511bad2aec0573e71e122a97851e7f51 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Wed, 4 Mar 2026 22:42:34 +0300 Subject: [PATCH 10/12] test: add receipt action buttons visibility tests and allow buttons during scanning --- .../MoneyRequestReceiptView.tsx | 2 +- .../MoneyRequestReceiptViewTest.tsx | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 7e033abb8dcb3..d5e09e7968648 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -336,7 +336,7 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, const isMapDistanceRequest = !!transaction && isDistanceRequest && !isManualDistanceRequest(transaction); - const canShowReceiptActions = hasReceipt && !isLoading && isEditable && !isTransactionScanning && !isMapDistanceRequest && !mergeTransactionID; + const canShowReceiptActions = hasReceipt && !isLoading && isEditable && !isMapDistanceRequest && !mergeTransactionID; const receiptAuditMessagesRow = ( diff --git a/tests/ui/components/MoneyRequestReceiptViewTest.tsx b/tests/ui/components/MoneyRequestReceiptViewTest.tsx index e4e81de60e4e3..6a14647d407e4 100644 --- a/tests/ui/components/MoneyRequestReceiptViewTest.tsx +++ b/tests/ui/components/MoneyRequestReceiptViewTest.tsx @@ -35,6 +35,51 @@ jest.mock( }, ); +jest.mock('@components/ReportActionItem/ReportActionItemImage', () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment + const {useEffect} = require('react'); + function MockReportActionItemImage({onLoad}: {onLoad?: () => void}) { + (useEffect as typeof React.useEffect)(() => { + onLoad?.(); + }, [onLoad]); + return null; + } + return MockReportActionItemImage; +}); + +jest.mock('@src/languages/IntlStore', () => { + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const en: Record = require('@src/languages/en').default; + // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + const flatten: (obj: Record) => Record = require('@src/languages/flattenObject').default; + const cache = new Map>(); + cache.set('en', flatten(en)); + return { + getCurrentLocale: jest.fn(() => 'en'), + load: jest.fn(() => Promise.resolve()), + get: jest.fn((key: string, locale?: string) => { + const translations = cache.get(locale ?? 'en'); + return translations?.[key] ?? null; + }), + }; +}); + +jest.mock('@assets/emojis', () => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const actual = jest.requireActual('@assets/emojis'); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return { + ...actual, + // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access + default: actual.default, + importEmojiLocale: jest.fn(() => Promise.resolve()), + }; +}); + +jest.mock('@libs/EmojiTrie', () => ({ + buildEmojisTrie: jest.fn(), +})); + // Override IDs so we control Onyx keys and can use evictableKeys for REPORT_ACTIONS const TEST_PARENT_REPORT_ID = 'testParentReportID'; const TEST_REPORT_ID = 'testReportID'; @@ -122,6 +167,22 @@ const transactionWithoutReceipt: Transaction = { originalCurrency: '', }; +const transactionWithReceipt: Transaction = { + ...transactionWithoutReceipt, + receipt: { + state: CONST.IOU.RECEIPT_STATE.OPEN, + source: 'https://example.com/receipt.jpg', + }, +}; + +const transactionWithScanningReceipt: Transaction = { + ...transactionWithoutReceipt, + receipt: { + state: CONST.IOU.RECEIPT_STATE.SCANNING, + source: 'https://example.com/receipt.jpg', + }, +}; + function Wrapper({children}: {children: React.ReactNode}) { return {children}; } @@ -175,4 +236,72 @@ describe('MoneyRequestReceiptView', () => { expect(onPicked).toBeDefined(); }); }); + + describe('receipt action buttons visibility', () => { + it('does not show action buttons when transaction has no receipt', async () => { + render( + + + , + ); + await waitForBatchedUpdatesWithAct(); + + expect(screen.queryByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeNull(); + expect(screen.queryByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeNull(); + }); + + it('shows action buttons when transaction has a receipt', async () => { + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TEST_TRANSACTION_ID}`, transactionWithReceipt); + }); + await waitForBatchedUpdatesWithAct(); + + render( + + + , + ); + await waitForBatchedUpdatesWithAct(); + + expect(screen.getByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeTruthy(); + expect(screen.getByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeTruthy(); + }); + + it('shows action buttons when receipt is scanning', async () => { + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TEST_TRANSACTION_ID}`, transactionWithScanningReceipt); + }); + await waitForBatchedUpdatesWithAct(); + + render( + + + , + ); + await waitForBatchedUpdatesWithAct(); + + expect(screen.getByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeTruthy(); + expect(screen.getByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeTruthy(); + }); + + it('does not show action buttons in readonly mode', async () => { + await act(async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${TEST_TRANSACTION_ID}`, transactionWithReceipt); + }); + await waitForBatchedUpdatesWithAct(); + + render( + + + , + ); + await waitForBatchedUpdatesWithAct(); + + expect(screen.queryByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeNull(); + expect(screen.queryByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeNull(); + }); + }); }); From 45dc1080a312bc01b89bee0d43ca016d5c5c67f9 Mon Sep 17 00:00:00 2001 From: TaduJR Date: Thu, 5 Mar 2026 10:16:31 +0300 Subject: [PATCH 11/12] fix: use PressableWithoutFeedback and square hit area for receipt action buttons --- .../MoneyRequestReceiptView.tsx | 44 ++++++++++--------- src/styles/index.ts | 7 +++ 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index d5e09e7968648..98414c851d598 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -8,7 +8,7 @@ import AttachmentPicker from '@components/AttachmentPicker'; import Icon from '@components/Icon'; import {ModalActions} from '@components/Modal/Global/ModalContext'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; -import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import ReceiptAudit, {ReceiptAuditMessages} from '@components/ReceiptAudit'; import ReceiptEmptyState from '@components/ReceiptEmptyState'; import useActiveRoute from '@hooks/useActiveRoute'; @@ -460,10 +460,10 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, onLoadFailure={() => setIsLoading(false)} /> {canShowReceiptActions && ( - + {({openPicker}) => ( - { openPicker({ onPicked: (files) => { @@ -481,36 +481,40 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, }, }); }} - style={styles.primaryMediumIcon} + style={styles.receiptActionButton} accessibilityLabel={translate('reportActionCompose.addAttachment')} role={CONST.ROLE.BUTTON} sentryLabel={CONST.SENTRY_LABEL.RECEIPT.ADD_ATTACHMENT_BUTTON} > - - + + + + )} - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly)) } - style={styles.primaryMediumIcon} + style={styles.receiptActionButton} accessibilityLabel={translate('accessibilityHints.viewAttachment')} role={CONST.ROLE.BUTTON} sentryLabel={CONST.SENTRY_LABEL.RECEIPT.ENLARGE_BUTTON} > - - + + + + )} diff --git a/src/styles/index.ts b/src/styles/index.ts index 86542271f32e2..f839bd0fa2511 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3630,6 +3630,13 @@ const staticStyles = (theme: ThemeColors) => gap: 8, }, + receiptActionButton: { + width: 40, + height: 40, + alignItems: 'center', + justifyContent: 'center', + }, + bgGreenSuccess: { backgroundColor: colors.green400, }, From 7af6ff954deec52a919e79e9f0af7ad6a02096fc Mon Sep 17 00:00:00 2001 From: TaduJR Date: Thu, 5 Mar 2026 10:25:39 +0300 Subject: [PATCH 12/12] fix: pass effective readonly flag to receipt enlarge button navigation --- src/components/ReportActionItem/MoneyRequestReceiptView.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 98414c851d598..299da8b18bafd 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -499,7 +499,9 @@ function MoneyRequestReceiptView({report, readonly = false, updatedTransaction, - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly)) + Navigation.navigate( + ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID, (updatedTransaction ?? transaction)?.transactionID, readonly || !canEditReceipt), + ) } style={styles.receiptActionButton} accessibilityLabel={translate('accessibilityHints.viewAttachment')}