diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2ed9fbc3666e..854bd7b11303 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -573,6 +573,10 @@ const ROUTES = { getRoute: (contentType: string, backTo?: string) => getUrlWithBackToParam(`referral/${contentType}`, backTo), }, PROCESS_MONEY_REQUEST_HOLD: 'hold-request-educational', + TRANSACTION_RECEIPT: { + route: 'r/:reportID/transaction/:transactionID/receipt', + getRoute: (reportID: string, transactionID: string) => `r/${reportID}/transaction/${transactionID}/receipt` as const, + }, } as const; /** diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 6fc61aec61a0..35c4c9c578e6 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -276,6 +276,7 @@ const SCREENS = { GET_ASSISTANCE: 'GetAssistance', REFERRAL_DETAILS: 'Referral_Details', KEYBOARD_SHORTCUTS: 'KeyboardShortcuts', + TRANSACTION_RECEIPT: 'TransactionReceipt', } as const; type Screen = DeepValueOf; diff --git a/src/components/AttachmentModal.tsx b/src/components/AttachmentModal.tsx index eed40d75387e..2f80af7f572a 100755 --- a/src/components/AttachmentModal.tsx +++ b/src/components/AttachmentModal.tsx @@ -288,7 +288,7 @@ function AttachmentModal({ const deleteAndCloseModal = useCallback(() => { IOU.detachReceipt(transaction?.transactionID ?? ''); setIsDeleteReceiptConfirmModalVisible(false); - Navigation.dismissModal(report?.reportID); + Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '')); }, [transaction, report]); const isValidFile = useCallback((fileObject: FileObject) => { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 1d048640b30b..9410d809e55f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -262,7 +262,6 @@ function MoneyRequestView({ filename={receiptURIs?.filename} transaction={transaction} enablePreviewModal - canEditReceipt={canEditReceipt} /> )} diff --git a/src/components/ReportActionItem/ReportActionItemImage.tsx b/src/components/ReportActionItem/ReportActionItemImage.tsx index d47604738fbc..f71f98998026 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.tsx +++ b/src/components/ReportActionItem/ReportActionItemImage.tsx @@ -4,7 +4,6 @@ import type {ReactElement} from 'react'; import type {ImageSourcePropType, ViewStyle} from 'react-native'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import AttachmentModal from '@components/AttachmentModal'; import EReceiptThumbnail from '@components/EReceiptThumbnail'; import * as Expensicons from '@components/Icon/Expensicons'; import Image from '@components/Image'; @@ -14,10 +13,12 @@ import {ShowContextMenuContext} from '@components/ShowContextMenuContext'; import ThumbnailImage from '@components/ThumbnailImage'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; import type {Transaction} from '@src/types/onyx'; type ReportActionItemImageProps = { @@ -36,9 +37,6 @@ type ReportActionItemImageProps = { /** whether thumbnail is refer the local file or not */ isLocalFile?: boolean; - /** whether the receipt can be replaced */ - canEditReceipt?: boolean; - /** Filename of attachment */ filename?: string; @@ -52,16 +50,7 @@ type ReportActionItemImageProps = { * and optional preview modal as well. */ -function ReportActionItemImage({ - thumbnail, - image, - enablePreviewModal = false, - transaction, - canEditReceipt = false, - isLocalFile = false, - filename, - isSingleImage = true, -}: ReportActionItemImageProps) { +function ReportActionItemImage({thumbnail, image, enablePreviewModal = false, transaction, isLocalFile = false, filename, isSingleImage = true}: ReportActionItemImageProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const attachmentModalSource = tryResolveUrlFromApiRoot(image ?? ''); @@ -118,26 +107,14 @@ function ReportActionItemImage({ return ( {({report}) => ( - Navigation.navigate(ROUTES.TRANSACTION_RECEIPT.getRoute(report?.reportID ?? '', transaction?.transactionID ?? ''))} + accessibilityLabel={translate('accessibilityHints.viewAttachment')} + accessibilityRole={CONST.ROLE.BUTTON} > - {({show}) => ( - - {receiptImageComponent} - - )} - + {receiptImageComponent} + )} ); diff --git a/src/libs/Navigation/AppNavigator/AuthScreens.tsx b/src/libs/Navigation/AppNavigator/AuthScreens.tsx index 6f5dcdf9cda9..8c582b8ab259 100644 --- a/src/libs/Navigation/AppNavigator/AuthScreens.tsx +++ b/src/libs/Navigation/AppNavigator/AuthScreens.tsx @@ -63,6 +63,7 @@ const loadConciergePage = () => require('../../../pages/ConciergePage').default const loadProfileAvatar = () => require('../../../pages/settings/Profile/ProfileAvatar').default as React.ComponentType; const loadWorkspaceAvatar = () => require('../../../pages/workspace/WorkspaceAvatar').default as React.ComponentType; const loadReportAvatar = () => require('../../../pages/ReportAvatar').default as React.ComponentType; +const loadReceiptView = () => require('../../../pages/TransactionReceiptPage').default as React.ComponentType; const loadWorkspaceJoinUser = () => require('@pages/workspace/WorkspaceJoinUserPage').default as React.ComponentType; let timezone: Timezone | null; @@ -363,8 +364,18 @@ function AuthScreens({session, lastOpenedPublicRoomID, isUsingMemoryOnlyKeys = f headerShown: false, presentation: 'transparentModal', }} + listeners={modalScreenListeners} getComponent={loadWorkspaceJoinUser} /> + ); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 8a24dc177a80..3173e6781d62 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -22,6 +22,7 @@ const config: LinkingOptions['config'] = { [SCREENS.PROFILE_AVATAR]: ROUTES.PROFILE_AVATAR.route, [SCREENS.WORKSPACE_AVATAR]: ROUTES.WORKSPACE_AVATAR.route, [SCREENS.REPORT_AVATAR]: ROUTES.REPORT_AVATAR.route, + [SCREENS.TRANSACTION_RECEIPT]: ROUTES.TRANSACTION_RECEIPT.route, [SCREENS.WORKSPACE_JOIN_USER]: ROUTES.WORKSPACE_JOIN_USER.route, // Sidebar diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index decb905ac52f..2585231ae6db 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -592,6 +592,10 @@ type AuthScreensParamList = SharedScreensParamList & { [NAVIGATORS.RIGHT_MODAL_NAVIGATOR]: NavigatorScreenParams; [NAVIGATORS.FULL_SCREEN_NAVIGATOR]: NavigatorScreenParams; [SCREENS.DESKTOP_SIGN_IN_REDIRECT]: undefined; + [SCREENS.TRANSACTION_RECEIPT]: { + reportID: string; + transactionID: string; + }; }; type RootStackParamList = PublicScreensParamList & AuthScreensParamList; diff --git a/src/pages/TransactionReceiptPage.tsx b/src/pages/TransactionReceiptPage.tsx new file mode 100644 index 000000000000..8db9e05a5139 --- /dev/null +++ b/src/pages/TransactionReceiptPage.tsx @@ -0,0 +1,79 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React, {useEffect} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import AttachmentModal from '@components/AttachmentModal'; +import Navigation from '@libs/Navigation/Navigation'; +import type {AuthScreensParamList} from '@libs/Navigation/types'; +import * as ReceiptUtils from '@libs/ReceiptUtils'; +import * as ReportActionUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; +import * as ReportActions from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import type {Report, ReportMetadata, Transaction} from '@src/types/onyx'; + +type TransactionReceiptOnyxProps = { + report: OnyxEntry; + transaction: OnyxEntry; + reportMetadata: OnyxEntry; +}; + +type TransactionReceiptProps = TransactionReceiptOnyxProps & StackScreenProps; + +function TransactionReceipt({transaction, report, reportMetadata = {isLoadingInitialReportActions: true}, route}: TransactionReceiptProps) { + const receiptURIs = ReceiptUtils.getThumbnailAndImageURIs(transaction); + + const imageSource = tryResolveUrlFromApiRoot(receiptURIs.image); + + const isLocalFile = receiptURIs.isLocalFile; + + const parentReportAction = ReportActionUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.RECEIPT); + const isEReceipt = transaction && TransactionUtils.hasEReceipt(transaction); + + useEffect(() => { + if (report && transaction) { + return; + } + ReportActions.openReport(route.params.reportID); + // I'm disabling the warning, as it expects to use exhaustive deps, even though we want this useEffect to run only on the first render. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + { + Navigation.goBack(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID ?? '')); + }} + isLoading={!transaction && reportMetadata?.isLoadingInitialReportActions} + shouldShowNotFoundPage={(report?.parentReportID ?? '') !== transaction?.reportID} + /> + ); +} + +TransactionReceipt.displayName = 'TransactionReceipt'; + +export default withOnyx({ + report: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID ?? '0'}`, + }, + transaction: { + key: ({route}) => `${ONYXKEYS.COLLECTION.TRANSACTION}${route.params.transactionID ?? '0'}`, + }, + reportMetadata: { + key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT_METADATA}${route.params.reportID ?? '0'}`, + }, +})(TransactionReceipt);