diff --git a/assets/images/simple-illustrations/simple-illustration__pillow.svg b/assets/images/simple-illustrations/simple-illustration__pillow.svg new file mode 100644 index 000000000000..97a0811266ae --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__pillow.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CONST.ts b/src/CONST.ts index 7c8a6791d65b..c32248e6dcf3 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -621,6 +621,14 @@ const CONST = { }, STEP_NAMES: ['1', '2', '3', '4', '5', '6'], STEP_HEADER_HEIGHT: 40, + SIGNER_INFO_STEP: { + SUBSTEP: { + IS_DIRECTOR: 1, + ENTER_EMAIL: 2, + SIGNER_DETAILS_FORM: 3, + HANG_TIGHT: 4, + }, + }, }, INCORPORATION_TYPES: { LLC: 'LLC', diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index 6b625f312709..0068fd30ed60 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -114,6 +114,7 @@ import PalmTree from '@assets/images/simple-illustrations/simple-illustration__p import Pencil from '@assets/images/simple-illustrations/simple-illustration__pencil.svg'; import PerDiem from '@assets/images/simple-illustrations/simple-illustration__perdiem.svg'; import PiggyBank from '@assets/images/simple-illustrations/simple-illustration__piggybank.svg'; +import Pillow from '@assets/images/simple-illustrations/simple-illustration__pillow.svg'; import Profile from '@assets/images/simple-illustrations/simple-illustration__profile.svg'; import QRCode from '@assets/images/simple-illustrations/simple-illustration__qr-code.svg'; import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustration__receipt-envelope.svg'; @@ -234,6 +235,7 @@ export { ExpensifyCardIllustration, SplitBill, PiggyBank, + Pillow, Accounting, Car, Coins, diff --git a/src/components/RadioButton.tsx b/src/components/RadioButton.tsx index 0bf7e370e480..1ee885681700 100644 --- a/src/components/RadioButton.tsx +++ b/src/components/RadioButton.tsx @@ -41,8 +41,8 @@ function RadioButton({isChecked, onPress, accessibilityLabel, hasError = false, )} diff --git a/src/components/SubStepForms/DateOfBirthStep.tsx b/src/components/SubStepForms/DateOfBirthStep.tsx index 42077cef2ba1..934a50581f30 100644 --- a/src/components/SubStepForms/DateOfBirthStep.tsx +++ b/src/components/SubStepForms/DateOfBirthStep.tsx @@ -9,7 +9,6 @@ import useLocalize from '@hooks/useLocalize'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ValidationUtils from '@libs/ValidationUtils'; -import HelpLinks from '@pages/ReimbursementAccount/PersonalInfo/HelpLinks'; import CONST from '@src/CONST'; import type {OnyxFormValuesMapping} from '@src/ONYXKEYS'; @@ -35,8 +34,8 @@ type DateOfBirthStepProps = SubStep /** The default value for the date of birth input */ dobDefaultValue: string; - /** Whether the component should show help links */ - shouldShowHelpLinks?: boolean; + /** Optional footer component */ + footerComponent?: React.ReactNode; }; function DateOfBirthStep({ @@ -48,7 +47,7 @@ function DateOfBirthStep({ dobInputID, dobDefaultValue, isEditing, - shouldShowHelpLinks = true, + footerComponent, }: DateOfBirthStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -96,7 +95,7 @@ function DateOfBirthStep({ maxDate={maxDate} shouldSaveDraft={!isEditing} /> - {shouldShowHelpLinks && } + {footerComponent} ); } diff --git a/src/components/SubStepForms/SingleFieldStep.tsx b/src/components/SubStepForms/SingleFieldStep.tsx index 7ab709e3d5c1..be9b3c033f96 100644 --- a/src/components/SubStepForms/SingleFieldStep.tsx +++ b/src/components/SubStepForms/SingleFieldStep.tsx @@ -75,7 +75,7 @@ function SingleFieldStep({ submitButtonStyles={[styles.mb0]} > - {formTitle} + {formTitle} {!!formDisclaimer && {formDisclaimer}} ({ defaultValue={defaultValue} maxLength={maxLength} shouldSaveDraft={!isEditing} + autoFocus /> {shouldShowHelpLinks && } diff --git a/src/components/SubStepForms/YesNoStep.tsx b/src/components/SubStepForms/YesNoStep.tsx new file mode 100644 index 000000000000..8e1f26e30011 --- /dev/null +++ b/src/components/SubStepForms/YesNoStep.tsx @@ -0,0 +1,73 @@ +import React, {useMemo, useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import FormProvider from '@components/Form/FormProvider'; +import type {Choice} from '@components/RadioButtons'; +import RadioButtons from '@components/RadioButtons'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import ONYXKEYS from '@src/ONYXKEYS'; + +type YesNoStepProps = { + /** The title of the question */ + title: string; + + /** The description of the question */ + description: string; + + /** The default value of the radio button */ + defaultValue: boolean; + + /** Callback when the value is selected */ + onSelectedValue: (value: boolean) => void; + + /** The style of the submit button */ + submitButtonStyles?: StyleProp; +}; + +function YesNoStep({title, description, defaultValue, onSelectedValue, submitButtonStyles}: YesNoStepProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const [value, setValue] = useState(defaultValue); + + const handleSubmit = () => { + onSelectedValue(value); + }; + const handleSelectValue = (newValue: string) => setValue(newValue === 'true'); + const options = useMemo( + () => [ + { + label: translate('common.yes'), + value: 'true', + }, + { + label: translate('common.no'), + value: 'false', + }, + ], + [translate], + ); + + return ( + + {title} + {description} + + + ); +} + +YesNoStep.displayName = 'YesNoStep'; + +export default YesNoStep; diff --git a/src/languages/en.ts b/src/languages/en.ts index 910d4397d0a0..1a703f1bea1b 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -41,6 +41,7 @@ import type { CharacterLimitParams, CompanyCardBankName, CompanyCardFeedNameParams, + CompanyNameParams, ConfirmThatParams, ConnectionNameParams, ConnectionParams, @@ -1929,6 +1930,7 @@ const translations = { website: 'Please enter a valid website.', zipCode: `Please enter a valid ZIP code using the format: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}.`, phoneNumber: 'Please enter a valid phone number.', + email: 'Please enter a valid email address.', companyName: 'Please enter a valid business name.', addressCity: 'Please enter a valid city.', addressStreet: 'Please enter a valid street address.', @@ -1950,6 +1952,7 @@ const translations = { lastName: 'Please enter a valid last name.', 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.', }, }, addPersonalBankAccountPage: { @@ -2303,6 +2306,26 @@ const translations = { }, signerInfoStep: { signerInfo: 'Signer info', + areYouDirector: ({companyName}: CompanyNameParams) => `Are you a director or senior officer at ${companyName}?`, + regulationRequiresUs: 'Regulation requires us to verify if the signer has the authority to take this action on behalf of the business.', + whatsYourName: "What's your legal name", + fullName: 'Legal full name', + whatsYourJobTitle: "What's your job title?", + jobTitle: 'Job title', + whatsYourDOB: "What's your date of birth?", + uploadID: 'Upload ID and proof of address', + id: "ID (driver's license or passport)", + personalAddress: 'Proof of personal address (e.g. utility bill)', + letsDoubleCheck: 'Let’s double check that everything looks right.', + legalName: 'Legal name', + proofOf: 'Proof of personal address', + enterOneEmail: 'Enter the email of director or senior officer at', + regulationRequiresOneMoreDirector: 'Regulation requires one more director or senior officer as a signer.', + hangTight: 'Hang tight...', + enterTwoEmails: 'Enter the emails of two directors or senior officers at', + sendReminder: 'Send a reminder', + chooseFile: 'Choose file', + weAreWaiting: "We're waiting for others to verify their identities as directors or senior officers of the business.", }, agreementsStep: { agreements: 'Agreements', diff --git a/src/languages/es.ts b/src/languages/es.ts index 0944c3c638a1..2bb66cec6548 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -40,6 +40,7 @@ import type { CharacterLimitParams, CompanyCardBankName, CompanyCardFeedNameParams, + CompanyNameParams, ConfirmThatParams, ConnectionNameParams, ConnectionParams, @@ -1950,6 +1951,7 @@ const translations = { website: 'Por favor, introduce un sitio web válido.', zipCode: `Formato de código postal incorrecto. Formato aceptable: ${CONST.COUNTRY_ZIP_REGEX_DATA.US.samples}.`, phoneNumber: 'Por favor, introduce un teléfono válido.', + email: 'Por favor, introduce una dirección de correo electrónico válida.', companyName: 'Por favor, introduce un nombre comercial legal válido.', addressCity: 'Por favor, introduce una ciudad válida.', addressStreet: 'Por favor, introduce una dirección válida que no sea un apartado postal.', @@ -1972,6 +1974,7 @@ const translations = { lastName: 'Por favor, introduce los apellidos.', 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.', }, }, addPersonalBankAccountPage: { @@ -2328,6 +2331,26 @@ const translations = { }, signerInfoStep: { signerInfo: 'Información del firmante', + areYouDirector: ({companyName}: CompanyNameParams) => `¿Es usted director o alto funcionario de ${companyName}?`, + regulationRequiresUs: 'La regulación requiere que verifiquemos si el firmante tiene la autoridad para realizar esta acción en nombre de la empresa.', + whatsYourName: '¿Cuál es tu nombre legal?', + fullName: 'Nombre legal completo', + whatsYourJobTitle: '¿Cuál es tu puesto de trabajo?', + jobTitle: 'Título profesional', + whatsYourDOB: '¿Cual es tu fecha de nacimiento?', + uploadID: 'Subir documento de identidad y prueba de domicilio', + id: 'Identificación (licencia de conducir o pasaporte)', + personalAddress: 'Prueba de domicilio personal (por ejemplo, factura de servicios públicos)', + letsDoubleCheck: 'Vamos a comprobar que todo está bien.', + legalName: 'Nombre legal', + proofOf: 'Comprobante de domicilio personal', + enterOneEmail: 'Introduce el correo electrónico del director o alto funcionario en', + regulationRequiresOneMoreDirector: 'El reglamento exige que haya otro director o funcionario superior como firmante.', + hangTight: 'Espera un momento...', + enterTwoEmails: 'Introduce los correos electrónicos de dos directores o altos funcionarios en', + sendReminder: 'Enviar un recordatorio', + chooseFile: 'Seleccionar archivo', + weAreWaiting: 'Estamos esperando que otros verifiquen sus identidades como directores o altos funcionarios de la empresa.', }, agreementsStep: { agreements: 'Acuerdos', diff --git a/src/languages/params.ts b/src/languages/params.ts index 7574fe96bd60..87a322775cca 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -555,6 +555,10 @@ type CurrencyCodeParams = { currencyCode: string; }; +type CompanyNameParams = { + companyName: string; +}; + export type { AuthenticationErrorParams, ImportMembersSuccessfullDescriptionParams, @@ -756,4 +760,5 @@ export type { AssignCardParams, ImportedTypesParams, CurrencyCodeParams, + CompanyNameParams, }; diff --git a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx index f00fb912cce5..84788d0458d0 100644 --- a/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx +++ b/src/pages/MissingPersonalDetails/substeps/DateOfBirth.tsx @@ -28,7 +28,6 @@ function DateOfBirth({isEditing, onNext, onMove, personalDetailsValues}: CustomS stepFields={STEP_FIELDS} dobInputID={INPUT_IDS.DATE_OF_BIRTH} dobDefaultValue={personalDetailsValues[INPUT_IDS.DATE_OF_BIRTH]} - shouldShowHelpLinks={false} /> ); } diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerCheckUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerCheckUBO.tsx index 438551cf4044..478642416e30 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerCheckUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerCheckUBO.tsx @@ -1,11 +1,7 @@ -import React, {useMemo, useState} from 'react'; -import FormProvider from '@components/Form/FormProvider'; -import type {Choice} from '@components/RadioButtons'; -import RadioButtons from '@components/RadioButtons'; -import Text from '@components/Text'; +import React from 'react'; +import YesNoStep from '@components/SubStepForms/YesNoStep'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import ONYXKEYS from '@src/ONYXKEYS'; type BeneficialOwnerCheckUBOProps = { /** The title of the question */ @@ -21,43 +17,15 @@ type BeneficialOwnerCheckUBOProps = { function BeneficialOwnerCheckUBO({title, onSelectedValue, defaultValue}: BeneficialOwnerCheckUBOProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [value, setValue] = useState(defaultValue); - - const handleSubmit = () => { - onSelectedValue(value); - }; - const handleSelectUBOValue = (newValue: string) => setValue(newValue === 'true'); - const options = useMemo( - () => [ - { - label: translate('common.yes'), - value: 'true', - }, - { - label: translate('common.no'), - value: 'false', - }, - ], - [translate], - ); return ( - - {title} - {translate('beneficialOwnerInfoStep.regulationRequiresUsToVerifyTheIdentity')} - - + /> ); } diff --git a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx index c2cd95784596..8cd94653909a 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx +++ b/src/pages/ReimbursementAccount/BeneficialOwnerInfo/substeps/BeneficialOwnerDetailsFormSubsteps/DateOfBirthUBO.tsx @@ -37,7 +37,6 @@ function DateOfBirthUBO({onNext, onMove, isEditing, beneficialOwnerBeingModified stepFields={[dobInputID]} dobInputID={dobInputID} dobDefaultValue={dobDefaultValue} - shouldShowHelpLinks={false} /> ); } diff --git a/src/pages/ReimbursementAccount/NonUSD/SignerInfo/DirectorCheck.tsx b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/DirectorCheck.tsx new file mode 100644 index 000000000000..7535f72a3970 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/DirectorCheck.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import YesNoStep from '@components/SubStepForms/YesNoStep'; +import useLocalize from '@hooks/useLocalize'; + +type DirectorCheckProps = { + /** The title of the question */ + title: string; + + /** The default value of the radio button */ + defaultValue: boolean; + + /** Callback when the value is selected */ + onSelectedValue: (value: boolean) => void; +}; + +function DirectorCheck({title, onSelectedValue, defaultValue}: DirectorCheckProps) { + const {translate} = useLocalize(); + + return ( + + ); +} + +DirectorCheck.displayName = 'DirectorCheck'; + +export default DirectorCheck; diff --git a/src/pages/ReimbursementAccount/NonUSD/SignerInfo/EnterEmail.tsx b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/EnterEmail.tsx new file mode 100644 index 000000000000..5d0de4eb7fd9 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/EnterEmail.tsx @@ -0,0 +1,85 @@ +import {Str} from 'expensify-common'; +import React, {useCallback} from 'react'; +import {useOnyx} from 'react-native-onyx'; +import FormProvider from '@components/Form/FormProvider'; +import InputWrapper from '@components/Form/InputWrapper'; +import type {FormInputErrors, FormOnyxValues} from '@components/Form/types'; +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as ValidationUtils from '@libs/ValidationUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import INPUT_IDS from '@src/types/form/ReimbursementAccountForm'; + +type EnterEmailProps = { + /** Callback when the form is submitted */ + onSubmit: () => void; + + /** Whether the user is a director */ + isUserDirector: boolean; +}; + +const {SIGNER_EMAIL, SECOND_SIGNER_EMAIL} = INPUT_IDS.ADDITIONAL_DATA.CORPAY; + +function EnterEmail({onSubmit, isUserDirector}: EnterEmailProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const [reimbursementAccount] = useOnyx(ONYXKEYS.REIMBURSEMENT_ACCOUNT); + const policyID = reimbursementAccount?.achData?.policyID ?? '-1'; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`); + const currency = policy?.outputCurrency ?? ''; + const shouldGatherBothEmails = currency === CONST.CURRENCY.AUD && !isUserDirector; + + const validate = useCallback( + (values: FormOnyxValues): FormInputErrors => { + const errors = ValidationUtils.getFieldRequiredErrors(values, shouldGatherBothEmails ? [SIGNER_EMAIL, SECOND_SIGNER_EMAIL] : [SIGNER_EMAIL]); + if (values[SIGNER_EMAIL] && !Str.isValidEmail(values[SIGNER_EMAIL])) { + errors[SIGNER_EMAIL] = translate('bankAccount.error.email'); + } + + if (shouldGatherBothEmails && values[SECOND_SIGNER_EMAIL] && !Str.isValidEmail(values[SECOND_SIGNER_EMAIL])) { + errors[SECOND_SIGNER_EMAIL] = translate('bankAccount.error.email'); + } + + return errors; + }, + [shouldGatherBothEmails, translate], + ); + + return ( + + {translate(shouldGatherBothEmails ? 'signerInfoStep.enterTwoEmails' : 'signerInfoStep.enterOneEmail')} + + {shouldGatherBothEmails && ( + + )} + + ); +} + +EnterEmail.displayName = 'EnterEmail'; + +export default EnterEmail; diff --git a/src/pages/ReimbursementAccount/NonUSD/SignerInfo/HangTight.tsx b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/HangTight.tsx new file mode 100644 index 000000000000..15fea5e46691 --- /dev/null +++ b/src/pages/ReimbursementAccount/NonUSD/SignerInfo/HangTight.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import {View} from 'react-native'; +import Button from '@components/Button'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; +import SafeAreaConsumer from '@components/SafeAreaConsumer'; +import ScrollView from '@components/ScrollView'; +import Text from '@components/Text'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; + +function HangTight({tempSubmit}: {tempSubmit: () => void}) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + + const handleSendReminder = () => { + // TODO remove that + tempSubmit(); + }; + + return ( + + {({safeAreaPaddingBottomStyle}) => ( + + + + + + {translate('signerInfoStep.hangTight')} + {translate('signerInfoStep.weAreWaiting')} + + +