diff --git a/src/hooks/useRestartOnReceiptFailure.ts b/src/hooks/useRestartOnReceiptFailure.ts new file mode 100644 index 00000000000..88039307aa4 --- /dev/null +++ b/src/hooks/useRestartOnReceiptFailure.ts @@ -0,0 +1,52 @@ +import {useEffect} from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {checkIfScanFileCanBeRead, setMoneyRequestReceipt} from '@libs/actions/IOU'; +import {removeDraftTransactions} from '@libs/actions/TransactionEdit'; +import {isLocalFile as isLocalFileUtil} from '@libs/fileDownload/FileUtils'; +import {navigateToStartMoneyRequestStep} from '@libs/IOUUtils'; +import {getRequestType} from '@libs/TransactionUtils'; +import type {IOUAction, IOUType} from '@src/CONST'; +import CONST from '@src/CONST'; +import type {Transaction} from '@src/types/onyx'; + +const useRestartOnReceiptFailure = (transaction: OnyxEntry, reportID: string, iouType: IOUType, action: IOUAction) => { + // When the component mounts, if there is a receipt, see if the image can be read from the disk. If not, redirect the user to the starting step of the flow. + // This is because until the request is saved, the receipt file is only stored in the browsers memory as a blob:// and if the browser is refreshed, then + // the image ceases to exist. The best way for the user to recover from this is to start over from the start of the request process. + // skip this in case user is moving the transaction as the receipt path will be valid in that case + useEffect(() => { + let isScanFilesCanBeRead = true; + + if (!transaction || action !== CONST.IOU.ACTION.CREATE) { + return; + } + const itemReceiptFilename = transaction.filename; + const itemReceiptPath = transaction.receipt?.source; + const itemReceiptType = transaction.receipt?.type; + const isLocalFile = isLocalFileUtil(itemReceiptPath); + + if (!itemReceiptPath || !isLocalFile) { + return; + } + + const onFailure = () => { + isScanFilesCanBeRead = false; + setMoneyRequestReceipt(transaction.transactionID, '', '', true); + }; + + checkIfScanFileCanBeRead(itemReceiptFilename, itemReceiptPath, itemReceiptType, () => {}, onFailure)?.then(() => { + const requestType = getRequestType(transaction); + if (isScanFilesCanBeRead || requestType !== CONST.IOU.REQUEST_TYPE.SCAN) { + return; + } + + removeDraftTransactions(true); + navigateToStartMoneyRequestStep(requestType, iouType, transaction.transactionID, reportID); + }); + + // We want this hook to run on mounting only + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps + }, []); +}; + +export default useRestartOnReceiptFailure; diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 0eab9a12ac3..d3fc22d263d 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -4,6 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePrevious from '@hooks/usePrevious'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useTransactionViolations from '@hooks/useTransactionViolations'; import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; @@ -46,6 +47,7 @@ function IOURequestStepAttendees({ const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); const transactionViolations = useTransactionViolations(transactionID); + useRestartOnReceiptFailure(transaction, reportID, iouType, action); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { diff --git a/src/pages/iou/request/step/IOURequestStepCategory.tsx b/src/pages/iou/request/step/IOURequestStepCategory.tsx index f2e90b9e327..1bd4aa24204 100644 --- a/src/pages/iou/request/step/IOURequestStepCategory.tsx +++ b/src/pages/iou/request/step/IOURequestStepCategory.tsx @@ -14,6 +14,7 @@ import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; import {getIOURequestPolicyID, setDraftSplitTransaction, setMoneyRequestCategory, updateMoneyRequestCategory} from '@libs/actions/IOU'; @@ -40,7 +41,7 @@ function IOURequestStepCategory({ report: reportReal, reportDraft, route: { - params: {transactionID, backTo, action, iouType, reportActionID}, + params: {transactionID, backTo, action, iouType, reportActionID, reportID: routeReportID}, }, transaction, }: IOURequestStepCategoryProps) { @@ -67,6 +68,7 @@ function IOURequestStepCategory({ const isEditingSplit = (iouType === CONST.IOU.TYPE.SPLIT || iouType === CONST.IOU.TYPE.SPLIT_EXPENSE) && isEditing; const currentTransaction = isEditingSplit && !lodashIsEmpty(splitDraftTransaction) ? splitDraftTransaction : transaction; const transactionCategory = getTransactionDetails(currentTransaction)?.category ?? ''; + useRestartOnReceiptFailure(transaction, routeReportID, iouType, action); const categoryForDisplay = isCategoryMissing(transactionCategory) ? '' : transactionCategory; diff --git a/src/pages/iou/request/step/IOURequestStepDate.tsx b/src/pages/iou/request/step/IOURequestStepDate.tsx index 3693a746232..5a5570f5074 100644 --- a/src/pages/iou/request/step/IOURequestStepDate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDate.tsx @@ -9,6 +9,7 @@ import useDuplicateTransactionsAndViolations from '@hooks/useDuplicateTransactio import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; @@ -54,6 +55,7 @@ function IOURequestStepDate({ // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value const isEditingSplit = (isSplitBill || isSplitExpense) && isEditing; const currentCreated = isEditingSplit && !lodashIsEmpty(splitDraftTransaction) ? getFormattedCreated(splitDraftTransaction) : getFormattedCreated(transaction); + useRestartOnReceiptFailure(transaction, reportID, iouType, action); // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFound = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, report, transaction); diff --git a/src/pages/iou/request/step/IOURequestStepDescription.tsx b/src/pages/iou/request/step/IOURequestStepDescription.tsx index cce2418a917..d5a7382aa2c 100644 --- a/src/pages/iou/request/step/IOURequestStepDescription.tsx +++ b/src/pages/iou/request/step/IOURequestStepDescription.tsx @@ -10,6 +10,7 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; @@ -64,6 +65,7 @@ function IOURequestStepDescription({ const descriptionRef = useRef(currentDescriptionInMarkdown); const isSavedRef = useRef(false); + useRestartOnReceiptFailure(transaction, reportID, iouType, action); /** * @returns - An object containing the errors for each inputID diff --git a/src/pages/iou/request/step/IOURequestStepMerchant.tsx b/src/pages/iou/request/step/IOURequestStepMerchant.tsx index 346e0ae7e2e..c85da9ae8cc 100644 --- a/src/pages/iou/request/step/IOURequestStepMerchant.tsx +++ b/src/pages/iou/request/step/IOURequestStepMerchant.tsx @@ -8,6 +8,7 @@ import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; @@ -44,6 +45,8 @@ function IOURequestStepMerchant({ const {translate} = useLocalize(); const {inputCallbackRef, inputRef} = useAutoFocusInput(); const isEditing = action === CONST.IOU.ACTION.EDIT; + useRestartOnReceiptFailure(transaction, reportID, iouType, action); + // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, report, transaction); // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value diff --git a/src/pages/iou/request/step/IOURequestStepReport.tsx b/src/pages/iou/request/step/IOURequestStepReport.tsx index 594e8d52df3..0a0a182b3f0 100644 --- a/src/pages/iou/request/step/IOURequestStepReport.tsx +++ b/src/pages/iou/request/step/IOURequestStepReport.tsx @@ -6,6 +6,7 @@ import type {ListItem} from '@components/SelectionListWithSections/types'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useOnyx from '@hooks/useOnyx'; import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import {createNewReport} from '@libs/actions/Report'; import {changeTransactionsReport, setTransactionReport} from '@libs/actions/Transaction'; @@ -53,6 +54,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const {policyForMovingExpensesID, shouldSelectPolicy} = usePolicyForMovingExpenses(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const hasViolations = hasViolationsReportUtils(undefined, transactionViolations); + useRestartOnReceiptFailure(transaction, reportIDFromRoute, iouType, action); const handleGoBack = () => { if (isEditing) { diff --git a/src/pages/iou/request/step/IOURequestStepTag.tsx b/src/pages/iou/request/step/IOURequestStepTag.tsx index 88588e1f29b..3f14346214e 100644 --- a/src/pages/iou/request/step/IOURequestStepTag.tsx +++ b/src/pages/iou/request/step/IOURequestStepTag.tsx @@ -9,6 +9,7 @@ import Text from '@components/Text'; import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; import {setDraftSplitTransaction, setMoneyRequestTag, updateMoneyRequestTag} from '@libs/actions/IOU'; @@ -34,7 +35,7 @@ type IOURequestStepTagProps = WithWritableReportOrNotFoundProps(undefined); + useRestartOnReceiptFailure(transaction, reportID, iouType, action); const currentTransaction = isEditingSplitBill && !isEmptyObject(splitDraftTransaction) ? splitDraftTransaction : transaction; const transactionDetails = getTransactionDetails(currentTransaction); diff --git a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx index a51d21fef62..1b7a306c672 100644 --- a/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx +++ b/src/pages/iou/request/step/IOURequestStepTaxRatePage.tsx @@ -3,6 +3,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import TaxPicker from '@components/TaxPicker'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; +import useRestartOnReceiptFailure from '@hooks/useRestartOnReceiptFailure'; import {convertToBackendAmount} from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {TaxRatesOption} from '@libs/TaxOptionsListUtils'; @@ -31,7 +32,7 @@ function getTaxAmount(policy: OnyxEntry, transaction: OnyxEntry