diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 4579fe11166a..2dc6668a213b 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -680,10 +680,13 @@ const CONST = { PERSONAL: 'PERSONAL', }, }, - NON_USD_BANK_ACCOUNT: { + CORPAY_DOCUMENT: { ALLOWED_FILE_TYPES: ['pdf', 'jpg', 'jpeg', 'png'], FILE_LIMIT: 1, + MAX_FILE_SIZE: 2097152, TOTAL_FILES_SIZE_LIMIT: 5242880, + }, + NON_USD_BANK_ACCOUNT: { PURPOSE_OF_TRANSACTION_ID: 'Intercompany_Payment', CURRENT_USER_KEY: 'currentUser', CORPAY_UNDEFINED_OPTION_VALUE: 'Undefined', diff --git a/src/components/SubStepForms/DocusignFullStep.tsx b/src/components/SubStepForms/DocusignFullStep.tsx index 0ff243359d99..cb461095c5b3 100644 --- a/src/components/SubStepForms/DocusignFullStep.tsx +++ b/src/components/SubStepForms/DocusignFullStep.tsx @@ -1,8 +1,8 @@ -import React, {useCallback, useState} from 'react'; +import React, {useCallback, useRef, useState} from 'react'; import Button from '@components/Button'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues, FormRef} from '@components/Form/types'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import Text from '@components/Text'; import UploadFile from '@components/UploadFile'; @@ -61,6 +61,8 @@ function DocusignFullStep({ const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); + const formRef = useRef(null); + const [uploadedFiles, setUploadedFiles] = useState(defaultValue); const validate = useCallback( @@ -87,6 +89,8 @@ function DocusignFullStep({ return; } + formRef.current?.resetFormFieldError(inputID as string); + clearErrors(formID); setErrorFields(formID, {[inputID]: {onUpload: error}}); }; @@ -106,6 +110,7 @@ function DocusignFullStep({ startStepIndex={startStepIndex} > ({ onRemove={(fileName) => { handleRemoveFile(fileName); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedFiles} inputID={inputID as string} setError={(error) => { setUploadError(error); }} fileLimit={1} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} /> diff --git a/src/components/UploadFile.tsx b/src/components/UploadFile.tsx index ca2e9eead5fc..a465dd76636f 100644 --- a/src/components/UploadFile.tsx +++ b/src/components/UploadFile.tsx @@ -49,6 +49,9 @@ type UploadFileProps = { /** The total size limit of the files that can be selected. */ totalFilesSizeLimit?: number; + + /** The maximum size of a single file that can be selected. */ + maxFileSize?: number; }; function UploadFile({ @@ -63,6 +66,7 @@ function UploadFile({ onInputChange = () => {}, totalFilesSizeLimit = 0, fileLimit = 0, + maxFileSize = 0, }: UploadFileProps) { const icons = useMemoizedLazyExpensifyIcons(['Close', 'Paperclip']); const {translate} = useLocalize(); @@ -73,6 +77,14 @@ function UploadFile({ const totalSize = resultedFiles.reduce((sum, file) => sum + (file.size ?? 0), 0); + if (maxFileSize) { + const oversizedFile = files.find((file) => (file.size ?? 0) > maxFileSize); + if (oversizedFile) { + setError(translate('attachmentPicker.sizeExceededWithLimit', maxFileSize / (1024 * 1024))); + return; + } + } + if (totalFilesSizeLimit) { if (totalSize > totalFilesSizeLimit) { setError(translate('attachmentPicker.sizeExceededWithValue', totalFilesSizeLimit / (1024 * 1024))); diff --git a/src/pages/ReimbursementAccount/EnterSignerInfo/subSteps/UploadDocuments.tsx b/src/pages/ReimbursementAccount/EnterSignerInfo/subSteps/UploadDocuments.tsx index e3d63c5dfbb5..a5027689fb32 100644 --- a/src/pages/ReimbursementAccount/EnterSignerInfo/subSteps/UploadDocuments.tsx +++ b/src/pages/ReimbursementAccount/EnterSignerInfo/subSteps/UploadDocuments.tsx @@ -140,7 +140,7 @@ function UploadDocuments({onNext, isEditing, policyID}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedIDs, copyOfIDInputID, setUploadedID); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedIDs} inputID={copyOfIDInputID} setError={(error) => { @@ -168,7 +168,7 @@ function UploadDocuments({onNext, isEditing, policyID}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedProofsOfAddress, addressProofInputID, setUploadedProofOfAddress); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedProofsOfAddress} inputID={addressProofInputID} setError={(error) => { @@ -195,7 +195,7 @@ function UploadDocuments({onNext, isEditing, policyID}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedProofsOfDirectors, directorsProofInputID, setUploadedProofsOfDirectors); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedProofsOfDirectors} inputID={directorsProofInputID} setError={(error) => { diff --git a/src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Documents.tsx b/src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Documents.tsx index 214a419118e6..5f6395c3f561 100644 --- a/src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Documents.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/BeneficialOwnerInfo/BeneficialOwnerDetailsFormSubSteps/Documents.tsx @@ -1,8 +1,8 @@ -import React, {useCallback, useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxValues, FormRef} from '@components/Form/types'; import Text from '@components/Text'; import UploadFile from '@components/UploadFile'; import useLocalize from '@hooks/useLocalize'; @@ -13,7 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import getCurrencyForNonUSDBankAccount from '@pages/ReimbursementAccount/NonUSD/utils/getCurrencyForNonUSDBankAccount'; import getNeededDocumentsStatusForBeneficialOwner from '@pages/ReimbursementAccount/NonUSD/utils/getNeededDocumentsStatusForBeneficialOwner'; -import {clearErrorFields, setDraftValues, setErrorFields} from '@userActions/FormActions'; +import {clearErrorFields, clearErrors, setDraftValues, setErrorFields} from '@userActions/FormActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {FileObject} from '@src/types/utils/Attachment'; @@ -45,6 +45,8 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { [codiceFiscaleInputID]: Array.isArray(reimbursementAccountDraft?.[codiceFiscaleInputID]) ? (reimbursementAccountDraft?.[codiceFiscaleInputID] ?? []) : [], }; + const formRef = useRef(null); + const [uploadedProofOfOwnership, setUploadedProofOfOwnership] = useState(defaultValues[proofOfOwnershipInputID]); const [uploadedCopyOfID, setUploadedCopyOfID] = useState(defaultValues[copyOfIDInputID]); const [uploadedAddressProof, setUploadedAddressProof] = useState(defaultValues[addressProofInputID]); @@ -78,6 +80,8 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { return; } + formRef.current?.resetFormFieldError(inputID); + clearErrors(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM); setErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[inputID]: {onUpload: error}}); }; @@ -97,6 +101,7 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { return ( { setUploadError(error, proofOfOwnershipInputID); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={defaultValues[proofOfOwnershipInputID]} inputID={proofOfOwnershipInputID} /> @@ -152,8 +158,9 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { setError={(error) => { setUploadError(error, copyOfIDInputID); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={defaultValues[copyOfIDInputID]} inputID={copyOfIDInputID} /> @@ -178,8 +185,9 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { setError={(error) => { setUploadError(error, addressProofInputID); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={defaultValues[addressProofInputID]} inputID={addressProofInputID} /> @@ -204,8 +212,9 @@ function Documents({onNext, isEditing, ownerBeingModifiedID}: DocumentsProps) { setError={(error) => { setUploadError(error, codiceFiscaleInputID); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={defaultValues[codiceFiscaleInputID]} inputID={codiceFiscaleInputID} /> diff --git a/src/pages/ReimbursementAccount/NonUSD/SignerInfo/subSteps/UploadDocuments.tsx b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/subSteps/UploadDocuments.tsx index 6e8279bab6f0..b36fc2eab876 100644 --- a/src/pages/ReimbursementAccount/NonUSD/SignerInfo/subSteps/UploadDocuments.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/subSteps/UploadDocuments.tsx @@ -1,10 +1,10 @@ -import React, {useEffect, useState} from 'react'; +import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import Button from '@components/Button'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; -import type {FormInputErrors, FormOnyxKeys, FormOnyxValues} from '@components/Form/types'; +import type {FormInputErrors, FormOnyxKeys, FormOnyxValues, FormRef} from '@components/Form/types'; import Text from '@components/Text'; import UploadFile from '@components/UploadFile'; import useLocalize from '@hooks/useLocalize'; @@ -17,7 +17,7 @@ import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import getCurrencyForNonUSDBankAccount from '@pages/ReimbursementAccount/NonUSD/utils/getCurrencyForNonUSDBankAccount'; import getNeededDocumentsStatusForSignerInfo from '@pages/ReimbursementAccount/utils/getNeededDocumentsStatusForSignerInfo'; import WhyLink from '@pages/ReimbursementAccount/WhyLink'; -import {clearErrorFields, setDraftValues, setErrorFields} from '@userActions/FormActions'; +import {clearErrorFields, clearErrors, setDraftValues, setErrorFields} from '@userActions/FormActions'; import {openExternalLink} from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -50,6 +50,8 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { [CODICE_FISCALE]: Array.isArray(reimbursementAccountDraft?.[CODICE_FISCALE]) ? (reimbursementAccountDraft?.[CODICE_FISCALE] ?? []) : [], }; + const formRef = useRef(null); + const [uploadedIDs, setUploadedID] = useState(defaultValues[COPY_OF_ID]); const [uploadedProofsOfAddress, setUploadedProofOfAddress] = useState(defaultValues[ADDRESS_PROOF]); const [uploadedProofsOfDirectors, setUploadedProofsOfDirectors] = useState(defaultValues[PROOF_OF_DIRECTORS]); @@ -97,6 +99,8 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { return; } + formRef.current?.resetFormFieldError(inputID); + clearErrors(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM); setErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[inputID]: {onUpload: error}}); }; @@ -108,6 +112,7 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { return ( { handleRemoveFile(fileName, uploadedIDs, COPY_OF_ID, setUploadedID); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedIDs} inputID={COPY_OF_ID} setError={(error) => { setUploadError(error, COPY_OF_ID); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} /> {translate('ownershipInfoStep.copyOfIDDescription')} {(isDocumentNeededStatus.isAddressProofNeeded || @@ -159,13 +165,14 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedProofsOfAddress, ADDRESS_PROOF, setUploadedProofOfAddress); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedProofsOfAddress} inputID={ADDRESS_PROOF} setError={(error) => { setUploadError(error, ADDRESS_PROOF); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} /> {translate('ownershipInfoStep.proofOfAddressDescription')} {(isDocumentNeededStatus.isProofOfDirectorsNeeded || isDocumentNeededStatus.isCodiceFiscaleNeeded || isDocumentNeededStatus.isPRDAndFSGNeeded) && ( @@ -186,13 +193,14 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedProofsOfDirectors, PROOF_OF_DIRECTORS, setUploadedProofsOfDirectors); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedProofsOfDirectors} inputID={PROOF_OF_DIRECTORS} setError={(error) => { setUploadError(error, PROOF_OF_DIRECTORS); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} /> {translate('signerInfoStep.proofOfDirectorsDescription')} {(isDocumentNeededStatus.isCodiceFiscaleNeeded || isDocumentNeededStatus.isPRDAndFSGNeeded) && } @@ -211,13 +219,14 @@ function UploadDocuments({onNext, isEditing}: UploadDocumentsProps) { onRemove={(fileName) => { handleRemoveFile(fileName, uploadedCodiceFiscale, CODICE_FISCALE, setUploadedCodiceFiscale); }} - acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + acceptedFileTypes={[...CONST.CORPAY_DOCUMENT.ALLOWED_FILE_TYPES]} value={uploadedCodiceFiscale} inputID={CODICE_FISCALE} setError={(error) => { setUploadError(error, CODICE_FISCALE); }} - fileLimit={CONST.NON_USD_BANK_ACCOUNT.FILE_LIMIT} + fileLimit={CONST.CORPAY_DOCUMENT.FILE_LIMIT} + maxFileSize={CONST.CORPAY_DOCUMENT.MAX_FILE_SIZE} /> {translate('signerInfoStep.codiceFiscaleDescription')} {isDocumentNeededStatus.isPRDAndFSGNeeded && }