diff --git a/src/components/EReceiptThumbnail.tsx b/src/components/EReceiptThumbnail.tsx index c0206ba41974..4b0c0caa1035 100644 --- a/src/components/EReceiptThumbnail.tsx +++ b/src/components/EReceiptThumbnail.tsx @@ -21,7 +21,7 @@ type IconSize = 'x-small' | 'small' | 'medium' | 'large'; type EReceiptThumbnailProps = { /** TransactionID of the transaction this EReceipt corresponds to. */ - transactionID?: string; + transactionID: string; /** Border radius to be applied on the parent view. */ borderRadius?: number; diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index fd5e9a295835..0c934ff12500 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -22,8 +22,8 @@ import {hasEnabledTags} from '@libs/TagsOptionsListUtils'; import {getTagForDisplay, getTaxAmount, getTaxName, isAmountMissing, isCreatedMissing, shouldShowAttendees as shouldShowAttendeesTransactionUtils} from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; import ToggleSettingOptionRow from '@pages/workspace/workflows/ToggleSettingsOptionRow'; -import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; +import type {IOUAction, IOUType} from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; @@ -827,7 +827,6 @@ function MoneyRequestConfirmationListFooter({ ? receiptThumbnailContent : shouldShowReceiptEmptyState && ( { if (!transactionID) { return; @@ -837,7 +836,6 @@ function MoneyRequestConfirmationListFooter({ ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, Navigation.getActiveRouteWithoutParams()), ); }} - shouldAllowReceiptDrop /> ))} {primaryFields} diff --git a/src/components/ReceiptEmptyState.tsx b/src/components/ReceiptEmptyState.tsx index d8d9d70cd168..08d83b6962af 100644 --- a/src/components/ReceiptEmptyState.tsx +++ b/src/components/ReceiptEmptyState.tsx @@ -1,22 +1,11 @@ -import {Str} from 'expensify-common'; -import React, {useState} from 'react'; +import React from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {setMoneyRequestReceipt} from '@libs/actions/IOU'; -import {resizeImageIfNeeded} from '@libs/fileDownload/FileUtils'; -import {validateReceipt} from '@libs/ReceiptUtils'; -import ReceiptDropUI from '@pages/iou/ReceiptDropUI'; import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; -import type {FileObject} from './AttachmentModal'; -import ConfirmModal from './ConfirmModal'; -import FullScreenLoadingIndicator from './FullscreenLoadingIndicator'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; -import PDFThumbnail from './PDFThumbnail'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; type ReceiptEmptyStateProps = { @@ -29,85 +18,13 @@ type ReceiptEmptyStateProps = { disabled?: boolean; isThumbnail?: boolean; - - shouldAllowReceiptDrop?: boolean; - - transactionID: string | undefined; }; // Returns an SVG icon indicating that the user should attach a receipt -function ReceiptEmptyState({hasError = false, onPress, disabled = false, isThumbnail = false, shouldAllowReceiptDrop = false, transactionID}: ReceiptEmptyStateProps) { +function ReceiptEmptyState({hasError = false, onPress, disabled = false, isThumbnail = false}: ReceiptEmptyStateProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const theme = useTheme(); - const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); - const [attachmentInvalidReasonTitle, setAttachmentInvalidReasonTitle] = useState(); - const [attachmentInvalidReason, setAttachmentValidReason] = useState(); - const [pdfFile, setPdfFile] = useState(null); - const [isLoadingReceipt, setIsLoadingReceipt] = useState(false); - - /** - * Sets the upload receipt error modal content when an invalid receipt is uploaded - */ - const setUploadReceiptError = (isInvalid: boolean, title: TranslationPaths, reason: TranslationPaths) => { - setIsAttachmentInvalid(isInvalid); - setAttachmentInvalidReasonTitle(title); - setAttachmentValidReason(reason); - setPdfFile(null); - }; - - const setReceipt = (originalFile: FileObject, isPdfValidated?: boolean) => { - validateReceipt(originalFile).then((result) => { - if (!result.isValid) { - if (result.title && result.reason) { - setUploadReceiptError(true, result.title, result.reason); - } - return; - } - - // If we have a pdf file and if it is not validated then set the pdf file for validation and return - if (Str.isPDF(originalFile.name ?? '') && !isPdfValidated) { - setPdfFile(originalFile); - return; - } - - // With the image size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE, we use manipulateAsync to resize the image. - // It takes a long time so we should display a loading indicator while the resize image progresses. - if (Str.isImage(originalFile.name ?? '') && (originalFile?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { - setIsLoadingReceipt(true); - } - - resizeImageIfNeeded(originalFile).then((file) => { - setIsLoadingReceipt(false); - const source = URL.createObjectURL(file as Blob); - if (transactionID) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - setMoneyRequestReceipt(transactionID, source, file.name || '', true); - } - }); - }); - }; - - const hideReceiptModal = () => { - setIsAttachmentInvalid(false); - }; - - const PDFThumbnailView = pdfFile ? ( - { - setPdfFile(null); - setReceipt(pdfFile, true); - }} - onPassword={() => { - setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.protectedPDFNotSupported'); - }} - onLoadError={() => { - setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment'); - }} - /> - ) : null; const Wrapper = onPress ? PressableWithoutFeedback : View; @@ -121,14 +38,11 @@ function ReceiptEmptyState({hasError = false, onPress, disabled = false, isThumb style={[ styles.alignItemsCenter, styles.justifyContentCenter, - styles.moneyRequestImage, + styles.moneyRequestViewImage, isThumbnail ? styles.moneyRequestAttachReceiptThumbnail : styles.moneyRequestAttachReceipt, hasError && styles.borderColorDanger, ]} > - {isLoadingReceipt && } - {PDFThumbnailView} - )} - - {shouldAllowReceiptDrop && !disabled && ( - { - const file = e?.dataTransfer?.files[0]; - if (file) { - file.uri = URL.createObjectURL(file); - setReceipt(file); - } - }} - /> - )} - ); } diff --git a/src/components/ReceiptImage.tsx b/src/components/ReceiptImage.tsx index 0f558dad1c6d..bca1ef8b74e6 100644 --- a/src/components/ReceiptImage.tsx +++ b/src/components/ReceiptImage.tsx @@ -114,7 +114,6 @@ function ReceiptImage({ isThumbnail onPress={onPress} disabled={!onPress} - transactionID={transactionID} /> ); } @@ -133,7 +132,7 @@ function ReceiptImage({ return ( ; + report: OnyxEntry; /** Whether we should display the animated banner above the component */ shouldShowAnimatedBackground: boolean; @@ -88,17 +88,17 @@ type MoneyRequestViewProps = { isFromReviewDuplicates?: boolean; /** Updated transaction to show in duplicate transaction flow */ - updatedTransaction?: OnyxEntry; + updatedTransaction?: OnyxEntry; }; -const receiptImageViolationNames: ViolationName[] = [ +const receiptImageViolationNames: OnyxTypes.ViolationName[] = [ CONST.VIOLATIONS.RECEIPT_REQUIRED, CONST.VIOLATIONS.RECEIPT_NOT_SMART_SCANNED, CONST.VIOLATIONS.CASH_EXPENSE_WITH_NO_RECEIPT, CONST.VIOLATIONS.SMARTSCAN_FAILED, ]; -const receiptFieldViolationNames: ViolationName[] = [CONST.VIOLATIONS.MODIFIED_AMOUNT, CONST.VIOLATIONS.MODIFIED_DATE]; +const receiptFieldViolationNames: OnyxTypes.ViolationName[] = [CONST.VIOLATIONS.MODIFIED_AMOUNT, CONST.VIOLATIONS.MODIFIED_DATE]; function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = false, updatedTransaction, isFromReviewDuplicates = false}: MoneyRequestViewProps) { const styles = useThemeStyles(); @@ -219,7 +219,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const {getViolationsForField} = useViolations(transactionViolations ?? [], isReceiptBeingScanned || !isPaidGroupPolicy(report)); const hasViolations = useCallback( - (field: ViolationField, data?: TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean => + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string): boolean => getViolationsForField(field, data, policyHasDependentTags, tagValue).length > 0, [getViolationsForField], ); @@ -293,7 +293,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const getPendingFieldAction = (fieldPath: TransactionPendingFieldsKey) => (pendingAction ? undefined : transaction?.pendingFields?.[fieldPath]); const getErrorForField = useCallback( - (field: ViolationField, data?: TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string) => { + (field: ViolationField, data?: OnyxTypes.TransactionViolation['data'], policyHasDependentTags = false, tagValue?: string) => { // Checks applied when creating a new expense // NOTE: receipt field can return multiple violations, so we need to handle it separately const fieldChecks: Partial> = { @@ -538,7 +538,6 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals { if (!transaction?.transactionID || !report?.reportID) { return; diff --git a/src/libs/ReceiptUtils.ts b/src/libs/ReceiptUtils.ts index 10355d63fcfa..5085f0e2384e 100644 --- a/src/libs/ReceiptUtils.ts +++ b/src/libs/ReceiptUtils.ts @@ -1,15 +1,12 @@ import {Str} from 'expensify-common'; import findLast from 'lodash/findLast'; import type {OnyxEntry} from 'react-native-onyx'; -import type {TupleToUnion} from 'type-fest'; -import type {FileObject} from '@components/AttachmentModal'; import CONST from '@src/CONST'; -import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import type {Transaction} from '@src/types/onyx'; import type {ReceiptError, ReceiptSource} from '@src/types/onyx/Transaction'; -import {isLocalFile as isLocalFileFileUtils, splitExtensionFromFileName, validateImageForCorruption} from './fileDownload/FileUtils'; -import {hasReceipt, hasReceiptSource, isFetchingWaypointsFromServer} from './TransactionUtils'; +import * as FileUtils from './fileDownload/FileUtils'; +import * as TransactionUtils from './TransactionUtils'; type ThumbnailAndImageURI = { image?: string; @@ -30,10 +27,10 @@ type ThumbnailAndImageURI = { * @param receiptFileName */ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPath: ReceiptSource | null = null, receiptFileName: string | null = null): ThumbnailAndImageURI { - if (!hasReceipt(transaction) && !receiptPath && !receiptFileName) { + if (!TransactionUtils.hasReceipt(transaction) && !receiptPath && !receiptFileName) { return {isEmptyReceipt: true}; } - if (isFetchingWaypointsFromServer(transaction)) { + if (TransactionUtils.isFetchingWaypointsFromServer(transaction)) { return {isThumbnail: true, isLocalFile: true}; } // If there're errors, we need to display them in preview. We can store many files in errors, but we just need to get the last one @@ -43,7 +40,7 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa // filename of uploaded image or last part of remote URI const filename = errors?.filename ?? transaction?.filename ?? receiptFileName ?? ''; const isReceiptImage = Str.isImage(filename); - const hasEReceipt = !hasReceiptSource(transaction) && transaction?.hasEReceipt; + const hasEReceipt = !TransactionUtils.hasReceiptSource(transaction) && transaction?.hasEReceipt; const isReceiptPDF = Str.isPDF(filename); if (hasEReceipt) { @@ -63,60 +60,11 @@ function getThumbnailAndImageURIs(transaction: OnyxEntry, receiptPa return {thumbnail: `${path.substring(0, path.length - 4)}.jpg.1024.jpg`, image: path, filename}; } - const isLocalFile = isLocalFileFileUtils(path); - const {fileExtension} = splitExtensionFromFileName(filename); + const isLocalFile = FileUtils.isLocalFile(path); + const {fileExtension} = FileUtils.splitExtensionFromFileName(filename); return {isThumbnail: true, fileExtension: Object.values(CONST.IOU.FILE_TYPES).find((type) => type === fileExtension), image: path, isLocalFile, filename}; } -type ValidateReceiptResult = { - isValid: boolean; - title?: TranslationPaths; - reason?: TranslationPaths; -}; -/** - * Validate a given receipt file for correctness and adherence to file constraints - */ -function validateReceipt(file: FileObject): Promise { - return validateImageForCorruption(file) - .then(() => { - const {fileExtension} = splitExtensionFromFileName(file?.name ?? ''); - const extension = fileExtension.toLowerCase() as TupleToUnion; - - if (!CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes(extension)) { - return { - isValid: false, - title: 'attachmentPicker.wrongFileType' as TranslationPaths, - reason: 'attachmentPicker.notAllowedExtension' as TranslationPaths, - }; - } - - if (!Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE) { - return { - isValid: false, - title: 'attachmentPicker.attachmentTooLarge' as TranslationPaths, - reason: 'attachmentPicker.sizeExceededWithLimit' as TranslationPaths, - }; - } - - if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { - return { - isValid: false, - title: 'attachmentPicker.attachmentTooSmall' as TranslationPaths, - reason: 'attachmentPicker.sizeNotMet' as TranslationPaths, - }; - } - - return {isValid: true}; - }) - .catch(() => { - return { - isValid: false, - title: 'attachmentPicker.attachmentError' as TranslationPaths, - reason: 'attachmentPicker.errorWhileSelectingCorruptedAttachment' as TranslationPaths, - }; - }); -} - // eslint-disable-next-line import/prefer-default-export -export {getThumbnailAndImageURIs, validateReceipt}; +export {getThumbnailAndImageURIs}; export type {ThumbnailAndImageURI}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 799d6846cf16..b6ce81b0a3d4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3798,16 +3798,13 @@ function updateMoneyRequestDate( /** Updates the billable field of an expense */ function updateMoneyRequestBillable( - transactionID: string | undefined, - transactionThreadReportID: string | undefined, + transactionID: string, + transactionThreadReportID: string, value: boolean, policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, ) { - if (!transactionID || !transactionThreadReportID) { - return; - } const transactionChanges: TransactionChanges = { billable: value, }; diff --git a/src/libs/actions/ReportActions.ts b/src/libs/actions/ReportActions.ts index 272ce2c1465c..e8d7949464d8 100644 --- a/src/libs/actions/ReportActions.ts +++ b/src/libs/actions/ReportActions.ts @@ -4,21 +4,20 @@ import {getLinkedTransactionID, getReportAction, getReportActionMessage, isCreat import {getOriginalReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Report} from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; -import type {ReportActions} from '@src/types/onyx/ReportAction'; import {deleteReport} from './Report'; type IgnoreDirection = 'parent' | 'child'; -let allReportActions: OnyxCollection; +let allReportActions: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, waitForCollectionCallback: true, callback: (value) => (allReportActions = value), }); -let allReports: OnyxCollection; +let allReports: OnyxCollection; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, waitForCollectionCallback: true, diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index beddbd587b02..d2f996c3e965 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -473,10 +473,7 @@ function abandonReviewDuplicateTransactions() { Onyx.set(ONYXKEYS.REVIEW_DUPLICATES, null); } -function clearError(transactionID: string | undefined) { - if (!transactionID) { - return; - } +function clearError(transactionID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {errors: null, errorFields: {route: null, waypoints: null, routes: null}}); } diff --git a/src/pages/iou/ReceiptDropUI.tsx b/src/pages/iou/ReceiptDropUI.tsx index d7a434fe70bb..db7ff0c6c57a 100644 --- a/src/pages/iou/ReceiptDropUI.tsx +++ b/src/pages/iou/ReceiptDropUI.tsx @@ -16,13 +16,13 @@ type ReceiptDropUIProps = { receiptImageTopPosition?: number; }; -function ReceiptDropUI({onDrop, receiptImageTopPosition}: ReceiptDropUIProps) { +function ReceiptDropUI({onDrop, receiptImageTopPosition = 0}: ReceiptDropUIProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); return ( - + + - - Navigation.goBack(route.params.backTo)} - /> - - {isScanning && ( - - - } - description={translate('iou.receiptScanInProgressDescription')} - shouldStyleFlexGrow={false} - /> - + Navigation.goBack(route.params.backTo)} + /> + + {isScanning && ( + + + } + description={translate('iou.receiptScanInProgressDescription')} + shouldStyleFlexGrow={false} + /> + + )} + + {!!participants.length && ( + { + setDraftSplitTransaction(transaction?.transactionID, {billable}); + }} + isConfirmed={isConfirmed} + /> )} - - {!!participants.length && ( - { - setDraftSplitTransaction(transaction?.transactionID, {billable}); - }} - isConfirmed={isConfirmed} - /> - )} - - - + + ); diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 88b963cd0452..a54c882259c8 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -2,7 +2,6 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; -import DragAndDropProvider from '@components/DragAndDrop/Provider'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -126,7 +125,6 @@ function IOURequestStepConfirmation({ const gpsRequired = transaction?.amount === 0 && iouType !== CONST.IOU.TYPE.SPLIT && receiptFile; const [isConfirmed, setIsConfirmed] = useState(false); - const [isDraggingOver, setIsDraggingOver] = useState(false); const headerTitle = useMemo(() => { if (isCategorizingTrackExpense) { @@ -675,68 +673,65 @@ function IOURequestStepConfirmation({ - - - - {isLoading && } - {!!gpsRequired && ( - setStartLocationPermissionFlow(false)} - onGrant={() => createTransaction(selectedParticipantList, true)} - onDeny={() => { - updateLastLocationPermissionPrompt(); - createTransaction(selectedParticipantList, false); - }} - /> - )} - + + {isLoading && } + {!!gpsRequired && ( + setStartLocationPermissionFlow(false)} + onGrant={() => createTransaction(selectedParticipantList, true)} + onDeny={() => { + updateLastLocationPermissionPrompt(); + createTransaction(selectedParticipantList, false); + }} /> - - + )} + + ); } diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 1140f23011f5..ec32b709ad8f 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -455,7 +455,7 @@ function IOURequestStepScan({ return; } - // With the image size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE, we use manipulateAsync to resize the image. + // With the image size > 24MB, we use manipulateAsync to resize the image. // It takes a long time so we should display a loading indicator while the resize image progresses. if (Str.isImage(originalFile.name ?? '') && (originalFile?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setIsLoadingReceipt(true); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index b0900d36965e..e31b13a4091e 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -4,6 +4,7 @@ import React, {useCallback, useContext, useEffect, useMemo, useReducer, useRef, import {ActivityIndicator, PanResponder, PixelRatio, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import type Webcam from 'react-webcam'; +import type {TupleToUnion} from 'type-fest'; import Hand from '@assets/images/hand.svg'; import ReceiptUpload from '@assets/images/receipt-upload.svg'; import Shutter from '@assets/images/shutter.svg'; @@ -28,13 +29,12 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {isMobile, isMobileWebKit} from '@libs/Browser'; -import {base64ToFile, resizeImageIfNeeded} from '@libs/fileDownload/FileUtils'; +import {base64ToFile, resizeImageIfNeeded, splitExtensionFromFileName, validateImageForCorruption} from '@libs/fileDownload/FileUtils'; import getCurrentPosition from '@libs/getCurrentPosition'; import {shouldStartLocationPermissionFlow} from '@libs/IOUUtils'; import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; -import {validateReceipt} from '@libs/ReceiptUtils'; import {isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils'; import playSound, {SOUNDS} from '@libs/Sound'; import {getDefaultTaxCode} from '@libs/TransactionUtils'; @@ -199,7 +199,7 @@ function IOURequestStepScan({ // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps }, [isTabActive]); - const hideReceiptModal = () => { + const hideRecieptModal = () => { setIsAttachmentInvalid(false); }; @@ -213,6 +213,36 @@ function IOURequestStepScan({ setPdfFile(null); }; + function validateReceipt(file: FileObject) { + return validateImageForCorruption(file) + .then(() => { + const {fileExtension} = splitExtensionFromFileName(file?.name ?? ''); + if ( + !CONST.API_ATTACHMENT_VALIDATIONS.ALLOWED_RECEIPT_EXTENSIONS.includes( + fileExtension.toLowerCase() as TupleToUnion, + ) + ) { + setUploadReceiptError(true, 'attachmentPicker.wrongFileType', 'attachmentPicker.notAllowedExtension'); + return false; + } + + if (!Str.isImage(file.name ?? '') && (file?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.RECEIPT_MAX_SIZE) { + setUploadReceiptError(true, 'attachmentPicker.attachmentTooLarge', 'attachmentPicker.sizeExceededWithLimit'); + return false; + } + + if ((file?.size ?? 0) < CONST.API_ATTACHMENT_VALIDATIONS.MIN_SIZE) { + setUploadReceiptError(true, 'attachmentPicker.attachmentTooSmall', 'attachmentPicker.sizeNotMet'); + return false; + } + return true; + }) + .catch(() => { + setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment'); + return false; + }); + } + const navigateBack = useCallback(() => { Navigation.goBack(backTo); }, [backTo]); @@ -441,11 +471,8 @@ function IOURequestStepScan({ * Sets the Receipt objects and navigates the user to the next page */ const setReceiptAndNavigate = (originalFile: FileObject, isPdfValidated?: boolean) => { - validateReceipt(originalFile).then((result) => { - if (!result.isValid) { - if (result.title && result.reason) { - setUploadReceiptError(true, result.title, result.reason); - } + validateReceipt(originalFile).then((isFileValid) => { + if (!isFileValid) { return; } @@ -455,7 +482,7 @@ function IOURequestStepScan({ return; } - // With the image size > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE, we use manipulateAsync to resize the image. + // With the image size > 24MB, we use manipulateAsync to resize the image. // It takes a long time so we should display a loading indicator while the resize image progresses. if (Str.isImage(originalFile.name ?? '') && (originalFile?.size ?? 0) > CONST.API_ATTACHMENT_VALIDATIONS.MAX_SIZE) { setIsLoadingReceipt(true); @@ -780,8 +807,8 @@ function IOURequestStepScan({ />