Skip to content

Commit

Permalink
Merge pull request #52384 from callstack-internal/feat/step-4-ui
Browse files Browse the repository at this point in the history
feat: Step 4 UI
  • Loading branch information
madmax330 authored Nov 19, 2024
2 parents 2fe1a06 + c5f7843 commit 9084293
Show file tree
Hide file tree
Showing 23 changed files with 1,202 additions and 115 deletions.
25 changes: 25 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -620,6 +620,31 @@ const CONST = {
AGREEMENTS: 'AgreementsStep',
FINISH: 'FinishStep',
},
BENEFICIAL_OWNER_INFO_STEP: {
SUBSTEP: {
IS_USER_BENEFICIAL_OWNER: 1,
IS_ANYONE_ELSE_BENEFICIAL_OWNER: 2,
BENEFICIAL_OWNER_DETAILS_FORM: 3,
ARE_THERE_MORE_BENEFICIAL_OWNERS: 4,
OWNERSHIP_CHART: 5,
BENEFICIAL_OWNERS_LIST: 6,
},
BENEFICIAL_OWNER_DATA: {
BENEFICIAL_OWNER_KEYS: 'beneficialOwnerKeys',
PREFIX: 'beneficialOwner',
FIRST_NAME: 'firstName',
LAST_NAME: 'lastName',
OWNERSHIP_PERCENTAGE: 'ownershipPercentage',
DOB: 'dob',
SSN_LAST_4: 'ssnLast4',
STREET: 'street',
CITY: 'city',
STATE: 'state',
ZIP_CODE: 'zipCode',
COUNTRY: 'country',
},
CURRENT_USER_KEY: 'currentUser',
},
STEP_NAMES: ['1', '2', '3', '4', '5', '6'],
STEP_HEADER_HEIGHT: 40,
SIGNER_INFO_STEP: {
Expand Down
38 changes: 38 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1954,6 +1954,7 @@ const translations = {
noDefaultDepositAccountOrDebitCardAvailable: 'Please add a default deposit account or debit card.',
validationAmounts: 'The validation amounts you entered are incorrect. Please double check your bank statement and try again.',
fullName: 'Please enter a valid full name.',
ownershipPercentage: 'Please enter a valid percentage number.',
},
},
addPersonalBankAccountPage: {
Expand Down Expand Up @@ -2230,6 +2231,43 @@ const translations = {
byAddingThisBankAccount: "By adding this bank account, you confirm that you've read, understand, and accept",
owners: 'Owners',
},
ownershipInfoStep: {
ownerInfo: 'Owner info',
businessOwner: 'Business owner',
signerInfo: 'Signer info',
doYouOwn: ({companyName}: CompanyNameParams) => `Do you own 25% or more of ${companyName}`,
doesAnyoneOwn: ({companyName}: CompanyNameParams) => `Does any individuals own 25% or more of ${companyName}`,
regulationsRequire: 'Regulations require us to verify the identity of any individual who owns more than 25% of the business.',
legalFirstName: 'Legal first name',
legalLastName: 'Legal last name',
whatsTheOwnersName: "What's the owner's legal name?",
whatsYourName: "What's your legal name?",
whatPercentage: 'What percentage of the business belongs to the owner?',
whatsYoursPercentage: 'What percentage of the business do you own?',
ownership: 'Ownership',
whatsTheOwnersDOB: "What's the owner's date of birth?",
whatsYourDOB: "What's your date of birth?",
whatsTheOwnersAddress: "What's the owner's address?",
whatsYourAddress: "What's your address?",
whatAreTheLast: "What are the last 4 digits of the owner's Social Security Number?",
whatsYourLast: 'What are the last 4 digits of your Social Security Number?',
dontWorry: "Don't worry, we don't do any personal credit checks!",
last4: 'Last 4 of SSN',
whyDoWeAsk: 'Why do we ask for this?',
letsDoubleCheck: 'Let’s double check that everything looks right.',
legalName: 'Legal name',
ownershipPercentage: 'Ownership percentage',
areThereOther: ({companyName}: CompanyNameParams) => `Are there other individuals who own 25% or more of ${companyName}`,
owners: 'Owners',
addCertified: 'Add a certified org chart that shows the beneficial owners',
regulationRequiresChart: 'Regulation requires us to collect a certified copy of the ownership chart that shows every individual or entity who owns 25% or more of the business.',
uploadEntity: 'Upload entity ownership chart',
noteEntity: 'Note: Entity ownership chart must be signed by your accountant, legal counsel, or notarized.',
certified: 'Certified entity ownership chart',
selectCountry: 'Select country',
findCountry: 'Find country',
address: 'Address',
},
validationStep: {
headerTitle: 'Validate bank account',
buttonText: 'Finish setup',
Expand Down
38 changes: 38 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1976,6 +1976,7 @@ const translations = {
noDefaultDepositAccountOrDebitCardAvailable: 'Por favor, añade una cuenta bancaria para depósitos o una tarjeta de débito.',
validationAmounts: 'Los importes de validación que introduciste son incorrectos. Por favor, comprueba tu cuenta bancaria e inténtalo de nuevo.',
fullName: 'Please enter a valid full name.',
ownershipPercentage: 'Por favor, ingrese un número de porcentaje válido.',
},
},
addPersonalBankAccountPage: {
Expand Down Expand Up @@ -2255,6 +2256,43 @@ const translations = {
byAddingThisBankAccount: 'Al añadir esta cuenta bancaria, confirmas que has leído, comprendido y aceptado',
owners: 'Dueños',
},
ownershipInfoStep: {
ownerInfo: 'Información del propietario',
businessOwner: 'Propietario del negocio',
signerInfo: 'Información del firmante',
doYouOwn: ({companyName}: CompanyNameParams) => `¿Posee el 25% o más de ${companyName}?`,
doesAnyoneOwn: ({companyName}: CompanyNameParams) => `¿Alguien posee el 25% o más de ${companyName}?`,
regulationsRequire: 'Las regulaciones requieren que verifiquemos la identidad de cualquier persona que posea más del 25% del negocio.',
legalFirstName: 'Nombre legal',
legalLastName: 'Apellido legal',
whatsTheOwnersName: '¿Cuál es el nombre legal del propietario?',
whatsYourName: '¿Cuál es su nombre legal?',
whatPercentage: '¿Qué porcentaje del negocio pertenece al propietario?',
whatsYoursPercentage: '¿Qué porcentaje del negocio posee?',
ownership: 'Propiedad',
whatsTheOwnersDOB: '¿Cuál es la fecha de nacimiento del propietario?',
whatsYourDOB: '¿Cuál es su fecha de nacimiento?',
whatsTheOwnersAddress: '¿Cuál es la dirección del propietario?',
whatsYourAddress: '¿Cuál es su dirección?',
whatAreTheLast: '¿Cuáles son los últimos 4 dígitos del número de seguro social del propietario?',
whatsYourLast: '¿Cuáles son los últimos 4 dígitos de su número de seguro social?',
dontWorry: 'No se preocupe, ¡no realizamos ninguna verificación de crédito personal!',
last4: 'Últimos 4 del SSN',
whyDoWeAsk: '¿Por qué solicitamos esto?',
letsDoubleCheck: 'Verifiquemos que todo esté correcto.',
legalName: 'Nombre legal',
ownershipPercentage: 'Porcentaje de propiedad',
areThereOther: ({companyName}: CompanyNameParams) => `¿Hay otras personas que posean el 25% o más de ${companyName}?`,
owners: 'Propietarios',
addCertified: 'Agregue un organigrama certificado que muestre los propietarios beneficiarios',
regulationRequiresChart: 'La regulación nos exige recopilar una copia certificada del organigrama que muestre a cada persona o entidad que posea el 25% o más del negocio.',
uploadEntity: 'Subir organigrama de propiedad de la entidad',
noteEntity: 'Nota: El organigrama de propiedad de la entidad debe estar firmado por su contador, asesor legal o notariado.',
certified: 'Organigrama certificado de propiedad de la entidad',
selectCountry: 'Seleccionar país',
findCountry: 'Buscar país',
address: 'Dirección',
},
validationStep: {
headerTitle: 'Validar cuenta bancaria',
buttonText: 'Finalizar configuración',
Expand Down
26 changes: 26 additions & 0 deletions src/libs/ValidationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,31 @@ function isValidZipCodeInternational(zipCode: string): boolean {
return /^[a-z0-9][a-z0-9\- ]{0,10}[a-z0-9]$/.test(zipCode);
}

/**
* Validates the given value if it is correct ownership percentage
* @param value
* @param totalOwnedPercentage
* @param ownerBeingModifiedID
*/
function isValidOwnershipPercentage(value: string, totalOwnedPercentage: Record<string, number>, ownerBeingModifiedID: string): boolean {
const parsedValue = Number(value);
const isValidNumber = !Number.isNaN(parsedValue) && parsedValue >= 25 && parsedValue <= 100;

let totalOwnedPercentageSum = 0;
const totalOwnedPercentageKeys = Object.keys(totalOwnedPercentage);
totalOwnedPercentageKeys.forEach((key) => {
if (key === ownerBeingModifiedID) {
return;
}

totalOwnedPercentageSum += totalOwnedPercentage[key];
});

const isTotalSumValid = totalOwnedPercentageSum + parsedValue <= 100;

return isValidNumber && isTotalSumValid;
}

export {
meetsMinimumAgeRequirement,
meetsMaximumAgeRequirement,
Expand Down Expand Up @@ -576,4 +601,5 @@ export {
isValidEmail,
isValidPhoneInternational,
isValidZipCodeInternational,
isValidOwnershipPercentage,
};

This file was deleted.

16 changes: 12 additions & 4 deletions src/pages/ReimbursementAccount/BeneficialOwnersStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import {Str} from 'expensify-common';
import React, {useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import InteractiveStepWrapper from '@components/InteractiveStepWrapper';
import YesNoStep from '@components/SubStepForms/YesNoStep';
import useLocalize from '@hooks/useLocalize';
import useSubStep from '@hooks/useSubStep';
import type {SubStepProps} from '@hooks/useSubStep/types';
import useThemeStyles from '@hooks/useThemeStyles';
import * as BankAccounts from '@userActions/BankAccounts';
import * as FormActions from '@userActions/FormActions';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import BeneficialOwnerCheckUBO from './BeneficialOwnerInfo/substeps/BeneficialOwnerCheckUBO';
import AddressUBO from './BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/AddressUBO';
import ConfirmationUBO from './BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/ConfirmationUBO';
import DateOfBirthUBO from './BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO';
Expand All @@ -30,6 +31,7 @@ const bodyContent: Array<React.ComponentType<BeneficialOwnerSubStepProps>> = [Le

function BeneficialOwnersStep({onBackButtonPress}: BeneficialOwnersStepProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();

const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT);
const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT);
Expand Down Expand Up @@ -214,16 +216,20 @@ function BeneficialOwnersStep({onBackButtonPress}: BeneficialOwnersStepProps) {
stepNames={CONST.BANK_ACCOUNT.STEP_NAMES}
>
{currentUBOSubstep === SUBSTEP.IS_USER_UBO && (
<BeneficialOwnerCheckUBO
<YesNoStep
title={`${translate('beneficialOwnerInfoStep.doYouOwn25percent')} ${companyName}?`}
description={translate('beneficialOwnerInfoStep.regulationRequiresUsToVerifyTheIdentity')}
submitButtonStyles={[styles.mb0]}
defaultValue={isUserUBO}
onSelectedValue={handleNextUBOSubstep}
/>
)}

{currentUBOSubstep === SUBSTEP.IS_ANYONE_ELSE_UBO && (
<BeneficialOwnerCheckUBO
<YesNoStep
title={`${translate('beneficialOwnerInfoStep.doAnyIndividualOwn25percent')} ${companyName}?`}
description={translate('beneficialOwnerInfoStep.regulationRequiresUsToVerifyTheIdentity')}
submitButtonStyles={[styles.mb0]}
defaultValue={isAnyoneElseUBO}
onSelectedValue={handleNextUBOSubstep}
/>
Expand All @@ -240,8 +246,10 @@ function BeneficialOwnersStep({onBackButtonPress}: BeneficialOwnersStepProps) {
)}

{currentUBOSubstep === SUBSTEP.ARE_THERE_MORE_UBOS && (
<BeneficialOwnerCheckUBO
<YesNoStep
title={`${translate('beneficialOwnerInfoStep.areThereMoreIndividualsWhoOwn25percent')} ${companyName}?`}
description={translate('beneficialOwnerInfoStep.regulationRequiresUsToVerifyTheIdentity')}
submitButtonStyles={[styles.mb0]}
onSelectedValue={handleNextUBOSubstep}
defaultValue={false}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, {useMemo, useState} from 'react';
import {useOnyx} from 'react-native-onyx';
import AddressStep from '@components/SubStepForms/AddressStep';
import useLocalize from '@hooks/useLocalize';
import useReimbursementAccountStepFormSubmit from '@hooks/useReimbursementAccountStepFormSubmit';
import type {SubStepProps} from '@hooks/useSubStep/types';
import CONST from '@src/CONST';
import type {Country} from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type NameProps = SubStepProps & {isUserEnteringHisOwnData: boolean; ownerBeingModifiedID: string};

const {STREET, CITY, STATE, ZIP_CODE, COUNTRY, PREFIX} = CONST.NON_USD_BANK_ACCOUNT.BENEFICIAL_OWNER_INFO_STEP.BENEFICIAL_OWNER_DATA;

function Address({onNext, isEditing, onMove, isUserEnteringHisOwnData, ownerBeingModifiedID}: NameProps) {
const {translate} = useLocalize();
const [reimbursementAccountDraft] = useOnyx(ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM_DRAFT);

const countryInputKey: `beneficialOwner_${string}_${string}` = `${PREFIX}_${ownerBeingModifiedID}_${COUNTRY}`;
const inputKeys = {
street: `${PREFIX}_${ownerBeingModifiedID}_${STREET}`,
city: `${PREFIX}_${ownerBeingModifiedID}_${CITY}`,
state: `${PREFIX}_${ownerBeingModifiedID}_${STATE}`,
zipCode: `${PREFIX}_${ownerBeingModifiedID}_${ZIP_CODE}`,
country: countryInputKey,
} as const;

const defaultValues = {
street: reimbursementAccountDraft?.[inputKeys.street] ?? '',
city: reimbursementAccountDraft?.[inputKeys.city] ?? '',
state: reimbursementAccountDraft?.[inputKeys.state] ?? '',
zipCode: reimbursementAccountDraft?.[inputKeys.zipCode] ?? '',
country: (reimbursementAccountDraft?.[inputKeys.country] ?? '') as Country | '',
};

const formTitle = translate(isUserEnteringHisOwnData ? 'ownershipInfoStep.whatsYourAddress' : 'ownershipInfoStep.whatsTheOwnersAddress');

// Has to be stored in state and updated on country change due to the fact that we can't relay on onyxValues when user is editing the form (draft values are not being saved in that case)
const [shouldDisplayStateSelector, setShouldDisplayStateSelector] = useState<boolean>(
defaultValues.country === CONST.COUNTRY.US || defaultValues.country === CONST.COUNTRY.CA || defaultValues.country === '',
);

const stepFieldsWithState = useMemo(
() => [inputKeys.street, inputKeys.city, inputKeys.state, inputKeys.zipCode, countryInputKey],
[countryInputKey, inputKeys.city, inputKeys.state, inputKeys.street, inputKeys.zipCode],
);
const stepFieldsWithoutState = useMemo(
() => [inputKeys.street, inputKeys.city, inputKeys.zipCode, countryInputKey],
[countryInputKey, inputKeys.city, inputKeys.street, inputKeys.zipCode],
);

const stepFields = shouldDisplayStateSelector ? stepFieldsWithState : stepFieldsWithoutState;

const handleCountryChange = (country: unknown) => {
if (typeof country !== 'string' || country === '') {
return;
}
setShouldDisplayStateSelector(country === CONST.COUNTRY.US || country === CONST.COUNTRY.CA);
};

const handleSubmit = useReimbursementAccountStepFormSubmit({
fieldIds: stepFields,
onNext,
shouldSaveDraft: isEditing,
});

return (
<AddressStep<typeof ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM>
isEditing={isEditing}
onNext={onNext}
onMove={onMove}
formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM}
formTitle={formTitle}
formPOBoxDisclaimer={translate('common.noPO')}
onSubmit={handleSubmit}
stepFields={stepFields}
inputFieldsIDs={inputKeys}
defaultValues={defaultValues}
onCountryChange={handleCountryChange}
shouldDisplayStateSelector={shouldDisplayStateSelector}
shouldDisplayCountrySelector
/>
);
}

Address.displayName = 'Address';

export default Address;
Loading

0 comments on commit 9084293

Please sign in to comment.