From 995b84603e3889fcd30b3142b453391a84eb3bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 25 Nov 2025 08:14:28 +0100 Subject: [PATCH 01/27] feat: 75707 gather KYB files for USD flow --- src/languages/en.ts | 3 + .../USD/Documents/index.tsx | 74 +++++++++++++++++++ .../USD/USDVerifiedBankAccountFlow.tsx | 14 ++-- 3 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 src/pages/ReimbursementAccount/USD/Documents/index.tsx diff --git a/src/languages/en.ts b/src/languages/en.ts index 26c35982959..d939e8d1dc0 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3550,6 +3550,9 @@ const translations = { weTake: 'We take your security seriously. Please set up 2FA now to add an extra layer of protection to your account.', secure: 'Secure your account', }, + documentsStep: { + subheader: 'Bank info', + }, reimbursementAccountLoadingAnimation: { oneMoment: 'One moment', explanationLine: "We’re taking a look at your information. You'll be able to continue with next steps shortly.", diff --git a/src/pages/ReimbursementAccount/USD/Documents/index.tsx b/src/pages/ReimbursementAccount/USD/Documents/index.tsx new file mode 100644 index 00000000000..63d2d2f8d46 --- /dev/null +++ b/src/pages/ReimbursementAccount/USD/Documents/index.tsx @@ -0,0 +1,74 @@ +import React, {useCallback} from 'react'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; +import Text from '@components/Text'; +import UploadFile from '@components/UploadFile'; +import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import useThemeStyles from '@hooks/useThemeStyles'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type DocumentsProps = { + /** Goes to the previous step */ + onBackButtonPress: () => void; +}; + +function Documents({onBackButtonPress}: DocumentsProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: false}); + const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, {canBeMissing: true}); + + const validate = () => {}; + + const submit = useCallback(() => { + console.log('submit'); + }, []); + + return ( + + + {translate('docusignStep.pleaseUpload')} + { + handleSelectFile(files); + }} + onRemove={(fileName) => { + handleRemoveFile(fileName); + }} + acceptedFileTypes={[...CONST.NON_USD_BANK_ACCOUNT.ALLOWED_FILE_TYPES]} + value={uploadedFiles} + inputID={inputID as string} + setError={(error) => { + setUploadError(error); + }} + fileLimit={1} + /> + + + ); +} + +Documents.displayName = 'Documents'; + +export default Documents; diff --git a/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx b/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx index f3ca3b9c65d..10c6735ab67 100644 --- a/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx +++ b/src/pages/ReimbursementAccount/USD/USDVerifiedBankAccountFlow.tsx @@ -10,6 +10,7 @@ import BusinessInfo from './BusinessInfo/BusinessInfo'; import CompleteVerification from './CompleteVerification/CompleteVerification'; import ConnectBankAccount from './ConnectBankAccount/ConnectBankAccount'; import Country from './Country'; +import Documents from './Documents'; import RequestorStep from './Requestor/RequestorStep'; type USDVerifiedBankAccountFlowProps = { @@ -38,12 +39,13 @@ function USDVerifiedBankAccountFlow({ switch (USDBankAccountStep) { case CONST.BANK_ACCOUNT.STEP.COUNTRY: CurrentStep = ( - + + // ); break; case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: From b9d72c4bb619dc23a20edbfd05fa583ae285d431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 25 Nov 2025 15:51:54 +0100 Subject: [PATCH 02/27] feat: step v1 --- src/languages/en.ts | 30 +++- .../USD/Documents/index.tsx | 155 +++++++++++++++--- 2 files changed, 161 insertions(+), 24 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index d939e8d1dc0..c711c77fa3f 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -3551,7 +3551,35 @@ const translations = { secure: 'Secure your account', }, documentsStep: { - subheader: 'Bank info', + beforeYouGo: 'Before you go, we need some documents to verify some things', + subheader: 'Verification', + verificationFailed: "The verification failed, so we'll need some extra documents to verify you and your business", + taxIDVerification: 'Tax ID Verification', + taxIDVerificationDescription: dedent(` + Please upload one of the following files: + • IRS TIN/EIN Assignment Letter + • IRS TIN/EIN Application confirmation (Normally states "Congratulations! The EIN has been successfully assigned") + • IRS tax exemption letter that lists your company name and EIN`), + nameChangeDocument: 'Name Change Document', + nameChangeDocumentDescription: 'If your company’s name has changed since filing for the TIN/EIN we need this document to verify the Tax ID number you provided', + companyAddressVerification: 'Company address verification', + companyAddressVerificationDescription: dedent(` + Please upload one of the following files: + • Recent utility bill showing company name and address + • Bank Statement showing company name and address + • Current Lease/Rental Agreement including the signature page showing your company name and current address + • Insurance Statement showing company name and address + • TIN assignment doc showing company name and address`), + userAddressVerification: 'Address verification', + userAddressVerificationDescription: dedent(` + Please upload one of the following files: + • Voter Registration Card + • Drivers License + • Bank Statement + • Utility Bill`), + userDOBVerification: 'Date of birth verification', + userDOBVerificationDescription: 'Please upload a US issued ID', + finishViaChat: 'Finish via chat', }, reimbursementAccountLoadingAnimation: { oneMoment: 'One moment', diff --git a/src/pages/ReimbursementAccount/USD/Documents/index.tsx b/src/pages/ReimbursementAccount/USD/Documents/index.tsx index 63d2d2f8d46..8c27fd6dcdc 100644 --- a/src/pages/ReimbursementAccount/USD/Documents/index.tsx +++ b/src/pages/ReimbursementAccount/USD/Documents/index.tsx @@ -1,14 +1,19 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; import FormProvider from '@components/Form/FormProvider'; import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import Text from '@components/Text'; import UploadFile from '@components/UploadFile'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; +import {clearErrorFields, setDraftValues, setErrorFields} from '@userActions/FormActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {FileObject} from '@src/types/utils/Attachment'; type DocumentsProps = { /** Goes to the previous step */ @@ -22,12 +27,112 @@ function Documents({onBackButtonPress}: DocumentsProps) { const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {canBeMissing: false}); const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT, {canBeMissing: true}); - const validate = () => {}; - const submit = useCallback(() => { console.log('submit'); }, []); + const DOCUMENTS_CONFIG = useMemo( + () => + [ + { + inputID: 'companyTaxID', + title: 'documentsStep.taxIDVerification', + description: 'documentsStep.taxIDVerificationDescription', + required: true, + // required: reimbursementAccount?.achData?.requiredDocuments?.includes('companyTaxID') ?? false, + }, + { + inputID: 'nameChangeDocument', + title: 'documentsStep.nameChangeDocument', + description: 'documentsStep.nameChangeDocumentDescription', + required: true, + // required: reimbursementAccount?.achData?.requiredDocuments?.includes('nameChangeDocument') ?? false, + }, + { + inputID: 'companyAddressVerification', + title: 'documentsStep.companyAddressVerification', + description: 'documentsStep.companyAddressVerificationDescription', + required: true, + // required: reimbursementAccount?.achData?.requiredDocuments?.includes('companyAddressVerification') ?? false, + }, + { + inputID: 'userAddressVerification', + title: 'documentsStep.userAddressVerification', + description: 'documentsStep.userAddressVerificationDescription', + required: true, + // required: reimbursementAccount?.achData?.requiredDocuments?.includes('userAddressVerification') ?? false, + }, + { + inputID: 'userDOBVerification', + title: 'documentsStep.userDOBVerification', + description: 'documentsStep.userDOBVerificationDescription', + required: true, + // required: reimbursementAccount?.achData?.requiredDocuments?.includes('userDOBVerification') ?? false, + }, + ] as const, + [], + ); + + const [uploadedFiles, setUploadedFiles] = useState>({ + companyTaxID: [], + nameChangeDocument: [], + companyAddressVerification: [], + userAddressVerification: [], + userDOBVerification: [], + }); + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors: FormInputErrors = {}; + + DOCUMENTS_CONFIG.filter((document) => document.required).forEach((document) => { + const files = values[document.inputID] as FileObject[] | undefined; + if (!files || files.length === 0) { + errors[document.inputID] = translate('common.error.fieldRequired'); + } + }); + + return errors; + }, + [DOCUMENTS_CONFIG, translate], + ); + + const handleSelectFile = useCallback( + (files: FileObject[], inputID: string) => { + const updatedFiles = [...uploadedFiles[inputID], ...files]; + setUploadedFiles((prev) => ({...prev, [inputID]: updatedFiles})); + setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[inputID]: updatedFiles}); + }, + [uploadedFiles], + ); + + const handleRemoveFile = useCallback( + (fileName: string, inputID: string) => { + const updatedFiles = uploadedFiles[inputID].filter((file) => file.name !== fileName); + setUploadedFiles((prev) => ({...prev, [inputID]: updatedFiles})); + setDraftValues(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[inputID]: updatedFiles}); + }, + [uploadedFiles], + ); + + const setUploadError = useCallback((error: string, inputID: string) => { + if (!error) { + clearErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM); + return; + } + setErrorFields(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM, {[inputID]: {onUpload: error}}); + }, []); + + const requiredDocuments = DOCUMENTS_CONFIG.filter((document) => document.required); + const footer = ( +