diff --git a/assets/images/receipt-slash.svg b/assets/images/receipt-slash.svg new file mode 100644 index 000000000000..2af3fcbc60e6 --- /dev/null +++ b/assets/images/receipt-slash.svg @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/src/components/AttachmentPicker/index.native.tsx b/src/components/AttachmentPicker/index.native.tsx index f6730f4b81d9..f0cd69f28401 100644 --- a/src/components/AttachmentPicker/index.native.tsx +++ b/src/components/AttachmentPicker/index.native.tsx @@ -212,7 +212,7 @@ function AttachmentPicker({type = CONST.ATTACHMENT_PICKER_TYPE.FILE, children, s * An attachment error dialog when user selected malformed images */ const showImageCorruptionAlert = useCallback(() => { - Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedImage')); + Alert.alert(translate('attachmentPicker.attachmentError'), translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); }, [translate]); /** diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index a963a0d0908d..5c181bfdb29f 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -139,6 +139,7 @@ import QuestionMark from '@assets/images/question-mark-circle.svg'; import ReceiptPlus from '@assets/images/receipt-plus.svg'; import ReceiptScan from '@assets/images/receipt-scan.svg'; import ReceiptSearch from '@assets/images/receipt-search.svg'; +import ReceiptSlash from '@assets/images/receipt-slash.svg'; import Receipt from '@assets/images/receipt.svg'; import RemoveMembers from '@assets/images/remove-members.svg'; import Rotate from '@assets/images/rotate-image.svg'; @@ -313,6 +314,7 @@ export { Receipt, ReceiptPlus, ReceiptScan, + ReceiptSlash, RemoveMembers, ReceiptSearch, Rotate, diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index a82cbbe61668..ebec9b71fc04 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -328,6 +328,7 @@ function MoneyRequestConfirmationList({ const [didConfirmSplit, setDidConfirmSplit] = useState(false); const [isAttachmentInvalid, setIsAttachmentInvalid] = useState(false); + const [invalidAttachmentPromt, setInvalidAttachmentPromt] = useState(translate('attachmentPicker.protectedPDFNotSupported')); const navigateBack = useCallback( () => Navigation.goBack(ROUTES.MONEY_REQUEST_CREATE_TAB_SCAN.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID)), @@ -1100,7 +1101,14 @@ function MoneyRequestConfirmationList({ previewSourceURL={resolvedReceiptImage as string} // We don't support scanning password protected PDF receipt enabled={!isAttachmentInvalid} - onPassword={() => setIsAttachmentInvalid(true)} + onPassword={() => { + setIsAttachmentInvalid(true); + setInvalidAttachmentPromt(translate('attachmentPicker.protectedPDFNotSupported')); + }} + onLoadError={() => { + setInvalidAttachmentPromt(translate('attachmentPicker.errorWhileSelectingCorruptedAttachment')); + setIsAttachmentInvalid(true); + }} /> ) : ( {shouldShowAllFields && supplementaryFields} @@ -1226,6 +1235,7 @@ function MoneyRequestConfirmationList({ transaction, transactionID, translate, + invalidAttachmentPromt, ], ); diff --git a/src/components/PDFThumbnail/PDFThumbnailError.tsx b/src/components/PDFThumbnail/PDFThumbnailError.tsx new file mode 100644 index 000000000000..0598a995e030 --- /dev/null +++ b/src/components/PDFThumbnail/PDFThumbnailError.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {View} from 'react-native'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import variables from '@styles/variables'; + +function PDFThumbnailError() { + const styles = useThemeStyles(); + const theme = useTheme(); + + return ( + + + + ); +} + +export default PDFThumbnailError; diff --git a/src/components/PDFThumbnail/index.native.tsx b/src/components/PDFThumbnail/index.native.tsx index 0232dba99f05..27d41ede3263 100644 --- a/src/components/PDFThumbnail/index.native.tsx +++ b/src/components/PDFThumbnail/index.native.tsx @@ -1,19 +1,21 @@ -import React from 'react'; +import React, {useState} from 'react'; import {View} from 'react-native'; import Pdf from 'react-native-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import PDFThumbnailError from './PDFThumbnailError'; import type PDFThumbnailProps from './types'; -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); const sizeStyles = [styles.w100, styles.h100]; + const [failedToLoad, setFailedToLoad] = useState(false); return ( - - {enabled && ( + + {enabled && !failedToLoad && ( { - if (!('message' in error && typeof error.message === 'string' && error.message.match(/password/i))) { - return; + if (onLoadError) { + onLoadError(); } - if (!onPassword) { + if ('message' in error && typeof error.message === 'string' && error.message.match(/password/i) && onPassword) { + onPassword(); return; } - onPassword(); + setFailedToLoad(true); }} /> )} + {failedToLoad && } ); diff --git a/src/components/PDFThumbnail/index.tsx b/src/components/PDFThumbnail/index.tsx index ce631f3b611f..8e79c027cf03 100644 --- a/src/components/PDFThumbnail/index.tsx +++ b/src/components/PDFThumbnail/index.tsx @@ -1,18 +1,20 @@ import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; -import React, {useMemo} from 'react'; +import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {Document, pdfjs, Thumbnail} from 'react-pdf'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; +import PDFThumbnailError from './PDFThumbnailError'; import type PDFThumbnailProps from './types'; if (!pdfjs.GlobalWorkerOptions.workerSrc) { pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'})); } -function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword}: PDFThumbnailProps) { +function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, enabled = true, onPassword, onLoadError}: PDFThumbnailProps) { const styles = useThemeStyles(); + const [failedToLoad, setFailedToLoad] = useState(false); const thumbnail = useMemo( () => ( @@ -25,18 +27,31 @@ function PDFThumbnail({previewSourceURL, style, isAuthTokenRequired = false, ena }} externalLinkTarget="_blank" onPassword={onPassword} + onLoad={() => { + setFailedToLoad(false); + }} + onLoadError={() => { + if (onLoadError) { + onLoadError(); + } + setFailedToLoad(true); + }} + error={() => null} > ), - [isAuthTokenRequired, previewSourceURL, onPassword], + [isAuthTokenRequired, previewSourceURL, onPassword, onLoadError], ); return ( - - {enabled && thumbnail} + + + {enabled && !failedToLoad && thumbnail} + {failedToLoad && } + ); } diff --git a/src/components/PDFThumbnail/types.ts b/src/components/PDFThumbnail/types.ts index 11253e462aca..349669ecc33e 100644 --- a/src/components/PDFThumbnail/types.ts +++ b/src/components/PDFThumbnail/types.ts @@ -15,6 +15,9 @@ type PDFThumbnailProps = { /** Callback to call if PDF is password protected */ onPassword?: () => void; + + /** Callback to call if PDF can't be loaded(corrupted) */ + onLoadError?: () => void; }; export default PDFThumbnailProps; diff --git a/src/languages/en.ts b/src/languages/en.ts index 653697e271a7..8ed0ef8207f1 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -356,7 +356,7 @@ export default { expensifyDoesntHaveAccessToCamera: "Expensify can't take photos without access to your camera. Tap Settings to update permissions.", attachmentError: 'Attachment error', errorWhileSelectingAttachment: 'An error occurred while selecting an attachment, please try again.', - errorWhileSelectingCorruptedImage: 'An error occurred while selecting a corrupted attachment, please try another file.', + errorWhileSelectingCorruptedAttachment: 'An error occurred while selecting a corrupted attachment, please try another file.', takePhoto: 'Take photo', chooseFromGallery: 'Choose from gallery', chooseDocument: 'Choose document', diff --git a/src/languages/es.ts b/src/languages/es.ts index 5d184677655e..2316a5d09c9f 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -350,7 +350,7 @@ export default { expensifyDoesntHaveAccessToCamera: 'Expensify no puede tomar fotos sin acceso a la cámara. Haz click en Configuración para actualizar los permisos.', attachmentError: 'Error al adjuntar archivo', errorWhileSelectingAttachment: 'Ha ocurrido un error al seleccionar un archivo adjunto. Por favor, inténtalo de nuevo.', - errorWhileSelectingCorruptedImage: 'Ha ocurrido un error al seleccionar un archivo adjunto corrupto. Por favor, inténtalo con otro archivo.', + errorWhileSelectingCorruptedAttachment: 'Ha ocurrido un error al seleccionar un archivo adjunto corrupto. Por favor, inténtalo con otro archivo.', takePhoto: 'Hacer una foto', chooseFromGallery: 'Elegir de la galería', chooseDocument: 'Elegir documento', diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index ede79e009a49..0e74b7c392ae 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -209,7 +209,7 @@ function IOURequestStepScan({ return true; }) .catch(() => { - setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedImage'); + setUploadReceiptError(true, 'attachmentPicker.attachmentError', 'attachmentPicker.errorWhileSelectingCorruptedAttachment'); return false; }); } diff --git a/src/styles/index.ts b/src/styles/index.ts index bb3c69b47598..718942582801 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4419,6 +4419,16 @@ const styles = (theme: ThemeColors) => maxWidth: 400, }, + pdfErrorPlaceholder: { + overflow: 'hidden', + borderWidth: 2, + borderColor: theme.cardBG, + borderRadius: variables.componentBorderRadiusLarge, + maxWidth: 400, + height: '100%', + backgroundColor: theme.highlightBG, + }, + moneyRequestAttachReceipt: { backgroundColor: theme.highlightBG, borderColor: theme.border, diff --git a/src/styles/variables.ts b/src/styles/variables.ts index 6f1cac46d729..f81e2ad9fd51 100644 --- a/src/styles/variables.ts +++ b/src/styles/variables.ts @@ -190,6 +190,8 @@ export default { eReceiptBGHeight: 540, eReceiptBGHWidth: 335, eReceiptTextContainerWidth: 263, + receiptPlaceholderIconWidth: 80, + receiptPlaceholderIconHeight: 80, reportPreviewMaxWidth: 335, reportActionImagesSingleImageHeight: 147, reportActionImagesDoubleImageHeight: 138,