From 02f287e6147399e508fecff4c1f5de511e12184c Mon Sep 17 00:00:00 2001 From: Michal Muzyk Date: Thu, 9 Nov 2023 15:09:31 +0100 Subject: [PATCH 01/13] feat: BankInfo step --- ios/Podfile.lock | 6 +- src/CONST.ts | 25 +++ src/components/AddPlaidBankAccount.js | 56 +++++- src/components/Form/FormWrapper.js | 24 ++- src/components/InteractiveStepSubHeader.js | 15 +- src/components/NewDatePicker/index.js | 36 +++- src/components/RadioButtons.tsx | 9 +- src/hooks/useSubStep.js | 2 +- src/languages/en.ts | 26 ++- src/languages/es.ts | 26 ++- src/pages/ReimbursementAccount/AddressForm.js | 2 - .../ReimbursementAccount/BankAccountStep.js | 6 + .../ReimbursementAccount/BankInfo/BankInfo.js | 138 ++++++++++++++ .../BankInfo/substeps/Confirmation.js | 136 ++++++++++++++ .../BankInfo/substeps/Manual.js | 121 ++++++++++++ .../BankInfo/substeps/Plaid.js | 124 +++++++++++++ .../PersonalInfo/HelpLinks.js | 51 +++++ .../PersonalInfo/PersonalInfo.js | 109 +++++++++++ .../PersonalInfo/substeps/Address.js | 103 +++++++++++ .../PersonalInfo/substeps/Confirmation.js | 139 ++++++++++++++ .../PersonalInfo/substeps/DateOfBirth.js | 100 ++++++++++ .../PersonalInfo/substeps/FullName.js | 86 +++++++++ .../substeps/SocialSecurityNumber.js | 87 +++++++++ .../ReimbursementAccountPage.js | 2 +- .../ReimbursementAccount/RequestorStep.js | 175 +----------------- .../ReimbursementAccount/subStepPropTypes.js | 14 ++ ...efaultValueForReimbursementAccountField.js | 14 ++ .../utils/getInitialSubstepForBankInfo.js | 30 +++ .../utils/getInitialSubstepForPersonalInfo.js | 35 ++++ .../utils/getPersonalInfoValues.js | 42 +++++ .../utils/getSubstepValues.js | 27 +++ .../InteractiveStepSubHeader.stories.js | 5 +- src/styles/styles.ts | 34 ++++ 33 files changed, 1606 insertions(+), 199 deletions(-) create mode 100644 src/pages/ReimbursementAccount/BankInfo/BankInfo.js create mode 100644 src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.js create mode 100644 src/pages/ReimbursementAccount/BankInfo/substeps/Manual.js create mode 100644 src/pages/ReimbursementAccount/BankInfo/substeps/Plaid.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/HelpLinks.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/PersonalInfo.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/substeps/Address.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/substeps/Confirmation.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/substeps/DateOfBirth.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/substeps/FullName.js create mode 100644 src/pages/ReimbursementAccount/PersonalInfo/substeps/SocialSecurityNumber.js create mode 100644 src/pages/ReimbursementAccount/subStepPropTypes.js create mode 100644 src/pages/ReimbursementAccount/utils/getDefaultValueForReimbursementAccountField.js create mode 100644 src/pages/ReimbursementAccount/utils/getInitialSubstepForBankInfo.js create mode 100644 src/pages/ReimbursementAccount/utils/getInitialSubstepForPersonalInfo.js create mode 100644 src/pages/ReimbursementAccount/utils/getPersonalInfoValues.js create mode 100644 src/pages/ReimbursementAccount/utils/getSubstepValues.js diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 97143f53b867..d94e36b0b3c9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -234,8 +234,8 @@ PODS: - libwebp/demux - libwebp/webp (1.2.4) - lottie-ios (4.3.3) - - lottie-react-native (6.3.1): - - lottie-ios (~> 4.3.0) + - lottie-react-native (6.4.0): + - lottie-ios (~> 4.3.3) - React-Core - MapboxCommon (23.6.0) - MapboxCoreMaps (10.14.0): @@ -1203,7 +1203,7 @@ SPEC CHECKSUMS: libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef lottie-ios: 25e7b2675dad5c3ddad369ac9baab03560c5bfdd - lottie-react-native: c9f1db4f4124dcce9f8159e65d8dc6e8bcb11fb4 + lottie-react-native: 3a3084faddd3891c276f23fd6e797b83f2021bbc MapboxCommon: 4a0251dd470ee37e7fadda8e285c01921a5e1eb0 MapboxCoreMaps: eb07203bbb0b1509395db5ab89cd3ad6c2e3c04c MapboxMaps: af50ec61a7eb3b032c3f7962c6bd671d93d2a209 diff --git a/src/CONST.ts b/src/CONST.ts index 9e7c1f007335..8b169e51e8cd 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -165,6 +165,30 @@ const CONST = { DOMAIN: '@expensify.sms', }, BANK_ACCOUNT: { + BANK_INFO_STEP: { + INPUT_KEY: { + BANK_ACCOUNT_ID: 'bankAccountId', + ROUTING_NUMBER: 'routingNumber', + ACCOUNT_NUMBER: 'accountNumber', + PLAID_MASK: 'plaidMask', + IS_SAVINGS: 'isSavings', + BANK_NAME: 'bankName', + PLAID_ACCOUNT_ID: 'plaidAccountID', + PLAID_ACCESS_TOKEN: 'plaidAccessToken', + }, + }, + PERSONAL_INFO_STEP: { + INPUT_KEY: { + FIRST_NAME: 'firstName', + LAST_NAME: 'lastName', + DOB: 'dob', + SSN_LAST_4: 'ssnLast4', + STREET: 'requestorAddressStreet', + CITY: 'requestorAddressCity', + STATE: 'requestorAddressState', + ZIP_CODE: 'requestorAddressZipCode', + }, + }, PLAID: { ALLOWED_THROTTLED_COUNT: 2, ERROR: { @@ -192,6 +216,7 @@ const CONST = { }, SUBSTEP: { MANUAL: 'manual', + PLAID: 'plaid', }, VERIFICATIONS: { ERROR_MESSAGE: 'verifications.errorMessage', diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 566b6c709423..ee4c8d8d1586 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -20,6 +20,7 @@ import Icon from './Icon'; import getBankIcon from './Icon/BankIcons'; import Picker from './Picker'; import PlaidLink from './PlaidLink'; +import RadioButtons from './RadioButtons'; import Text from './Text'; const propTypes = { @@ -55,6 +56,9 @@ const propTypes = { /** Are we adding a withdrawal account? */ allowDebit: PropTypes.bool, + + /** Is displayed in new VBBA */ + isDisplayedInNewVBBA: PropTypes.bool, }; const defaultProps = { @@ -68,6 +72,7 @@ const defaultProps = { allowDebit: false, bankAccountID: 0, isPlaidDisabled: false, + isDisplayedInNewVBBA: false, }; function AddPlaidBankAccount({ @@ -82,9 +87,19 @@ function AddPlaidBankAccount({ bankAccountID, allowDebit, isPlaidDisabled, + isDisplayedInNewVBBA, }) { + const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; + const defaultSelectedPlaidAccount = _.find(plaidBankAccounts, (account) => account.plaidAccountID === selectedPlaidAccountID); + const defaultSelectedPlaidAccountID = lodashGet(defaultSelectedPlaidAccount, 'plaidAccountID', ''); + const defaultSelectedPlaidAccountMask = lodashGet( + _.find(plaidBankAccounts, (account) => account.plaidAccountID === selectedPlaidAccountID), + 'mask', + '', + ); const subscribedKeyboardShortcuts = useRef([]); const previousNetworkState = useRef(); + const [selectedPlaidAccountMask, setSelectedPlaidAccountMask] = React.useState(defaultSelectedPlaidAccountMask); const {translate} = useLocalize(); const {isOffline} = useNetwork(); @@ -160,17 +175,27 @@ function AddPlaidBankAccount({ previousNetworkState.current = isOffline; }, [allowDebit, bankAccountID, isAuthenticatedWithPlaid, isOffline]); - const plaidBankAccounts = lodashGet(plaidData, 'bankAccounts') || []; const token = getPlaidLinkToken(); const options = _.map(plaidBankAccounts, (account) => ({ value: account.plaidAccountID, - label: `${account.addressName} ${account.mask}`, + label: account.addressName, })); const {icon, iconSize, iconStyles} = getBankIcon(); const plaidErrors = lodashGet(plaidData, 'errors'); const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : ''; const bankName = lodashGet(plaidData, 'bankName'); + /** + * @param {String} plaidAccountID + * + * When user selects one of plaid accounts we need to set the mask in order to display it on UI + */ + const handleSelectingPlaidAccount = (plaidAccountID) => { + const mask = _.find(plaidBankAccounts, (account) => account.plaidAccountID === plaidAccountID).mask; + setSelectedPlaidAccountMask(mask); + onSelect(plaidAccountID); + }; + if (isPlaidDisabled) { return ( @@ -227,6 +252,33 @@ function AddPlaidBankAccount({ ); } + if (isDisplayedInNewVBBA) { + return ( + + {translate('bankAccount.chooseAnAccount')} + + + + {bankName} + {selectedPlaidAccountMask.length > 0 && ( + {`${translate('bankAccount.accountEnding')} ${selectedPlaidAccountMask}`} + )} + + + {`${translate('bankAccount.chooseAnAccountBelow')}:`} + + + ); + } + // Plaid bank accounts view return ( diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 55abcc1fc923..5f20e25104f3 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -56,6 +56,10 @@ const propTypes = { /** Container styles */ style: stylePropTypes, + /** Submit button container styles */ + // eslint-disable-next-line react/forbid-prop-types + submitButtonStyles: PropTypes.arrayOf(PropTypes.object), + /** Custom content to display in the footer after submit button */ footerContent: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), @@ -74,10 +78,25 @@ const defaultProps = { scrollContextEnabled: false, footerContent: null, style: [], + submitButtonStyles: [], }; function FormWrapper(props) { - const {onSubmit, children, formState, errors, inputRefs, submitButtonText, footerContent, isSubmitButtonVisible, style, enabledWhenOffline, isSubmitActionDangerous, formID} = props; + const { + onSubmit, + children, + formState, + errors, + inputRefs, + submitButtonText, + footerContent, + isSubmitButtonVisible, + style, + enabledWhenOffline, + isSubmitActionDangerous, + formID, + submitButtonStyles, + } = props; const formRef = useRef(null); const formContentRef = useRef(null); const errorMessage = useMemo(() => { @@ -129,7 +148,7 @@ function FormWrapper(props) { focusInput.focus(); } }} - containerStyles={[styles.mh0, styles.mt5, styles.flex1]} + containerStyles={[styles.mh0, styles.mt5, styles.flex1, ...submitButtonStyles]} enabledWhenOffline={enabledWhenOffline} isSubmitActionDangerous={isSubmitActionDangerous} disablePressOnEnter @@ -150,6 +169,7 @@ function FormWrapper(props) { isSubmitActionDangerous, isSubmitButtonVisible, onSubmit, + submitButtonStyles, style, submitButtonText, ], diff --git a/src/components/InteractiveStepSubHeader.js b/src/components/InteractiveStepSubHeader.js index 75fdce8ad3b5..0405d2bd9ede 100644 --- a/src/components/InteractiveStepSubHeader.js +++ b/src/components/InteractiveStepSubHeader.js @@ -1,16 +1,15 @@ -import React, {forwardRef, useState, useImperativeHandle} from 'react'; -import PropTypes from 'prop-types'; import map from 'lodash/map'; +import PropTypes from 'prop-types'; +import React, {forwardRef, useImperativeHandle, useState} from 'react'; import {View} from 'react-native'; - -import CONST from '../CONST'; -import variables from '../styles/variables'; -import styles from '../styles/styles'; -import colors from '../styles/colors'; +import colors from '@styles/colors'; +import styles from '@styles/styles'; +import variables from '@styles/variables'; +import CONST from '@src/CONST'; +import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; import Text from './Text'; -import Icon from './Icon'; const propTypes = { /** List of the Route Name to navigate when the step is selected */ diff --git a/src/components/NewDatePicker/index.js b/src/components/NewDatePicker/index.js index f03b4e2cb796..66e4a337df2f 100644 --- a/src/components/NewDatePicker/index.js +++ b/src/components/NewDatePicker/index.js @@ -9,6 +9,7 @@ import TextInput from '@components/TextInput'; import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '@components/TextInput/baseTextInputPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import styles from '@styles/styles'; +import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; import CalendarPicker from './CalendarPicker'; @@ -33,6 +34,12 @@ const propTypes = { /** A maximum date of calendar to select */ maxDate: PropTypes.objectOf(Date), + /** Saves a draft of the input value when used in a form */ + shouldSaveDraft: PropTypes.bool, + + /** ID of the wrapping form */ + formID: PropTypes.string, + ...withLocalizePropTypes, ...baseTextInputPropTypes, }; @@ -42,17 +49,42 @@ const datePickerDefaultProps = { minDate: setYear(new Date(), CONST.CALENDAR_PICKER.MIN_YEAR), maxDate: setYear(new Date(), CONST.CALENDAR_PICKER.MAX_YEAR), value: undefined, + shouldSaveDraft: false, + formID: '', }; -function NewDatePicker({containerStyles, defaultValue, disabled, errorText, inputID, isSmallScreenWidth, label, maxDate, minDate, onInputChange, onTouched, placeholder, translate, value}) { +function NewDatePicker({ + containerStyles, + defaultValue, + disabled, + errorText, + inputID, + isSmallScreenWidth, + label, + maxDate, + minDate, + onInputChange, + onTouched, + placeholder, + translate, + value, + shouldSaveDraft, + formID, +}) { const [selectedDate, setSelectedDate] = useState(value || defaultValue || undefined); useEffect(() => { + // Value is provided to input via props and onChange never fires. We have to save draft manually. + if (shouldSaveDraft && formID !== '') { + FormActions.setDraftValues(formID, {[inputID]: selectedDate}); + } + if (selectedDate === value || _.isUndefined(value)) { return; } + setSelectedDate(value); - }, [selectedDate, value]); + }, [formID, inputID, selectedDate, shouldSaveDraft, value]); useEffect(() => { if (_.isFunction(onTouched)) { diff --git a/src/components/RadioButtons.tsx b/src/components/RadioButtons.tsx index 23b813759ecf..0d80863deeff 100644 --- a/src/components/RadioButtons.tsx +++ b/src/components/RadioButtons.tsx @@ -12,13 +12,18 @@ type RadioButtonsProps = { /** List of choices to display via radio buttons */ items: Choice[]; + /** Default checked value */ + defaultCheckedValue?: string; + /** Callback to fire when selecting a radio button */ onPress: (value: string) => void; }; -function RadioButtons({items, onPress}: RadioButtonsProps) { - const [checkedValue, setCheckedValue] = useState(''); +function RadioButtons({items, onPress, defaultCheckedValue = ''}: RadioButtonsProps) { + const [checkedValue, setCheckedValue] = useState(defaultCheckedValue); + console.log(items, ' items'); + console.log(checkedValue, ' checkedValue'); return ( {items.map((item) => ( diff --git a/src/hooks/useSubStep.js b/src/hooks/useSubStep.js index 86071e278913..bfd88f5d5c57 100644 --- a/src/hooks/useSubStep.js +++ b/src/hooks/useSubStep.js @@ -1,5 +1,5 @@ -import {useState, useRef, useCallback} from 'react'; import PropTypes from 'prop-types'; +import {useCallback, useRef, useState} from 'react'; const propTypes = { /** an array of substep components */ diff --git a/src/languages/en.ts b/src/languages/en.ts index c186a1fffedf..a115c2b5f79c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -167,6 +167,7 @@ export default { noPO: 'PO boxes and mail drop addresses are not allowed', city: 'City', state: 'State', + streetAddress: 'Street address', stateOrProvince: 'State / Province', country: 'Country', zip: 'Zip code', @@ -266,6 +267,7 @@ export default { tbd: 'TBD', selectCurrency: 'Select a currency', card: 'Card', + whyDoWeAskForThis: 'Why do we ask for this?', }, location: { useCurrent: 'Use current location', @@ -1139,8 +1141,16 @@ export default { return result; }, bankAccount: { + bankInfo: 'Bank info', + confirmBankInfo: 'Confirm bank info', + manuallyAdd: 'Manually add your bank account', + letsDoubleCheck: "Let's double check that everything looks right.", + accountEnding: 'Account ending in', + thisBankAccount: 'This bank account will be used for business payments on your workspace', + connectDifferentAccount: 'Connect a different account', accountNumber: 'Account number', routingNumber: 'Routing number', + chooseAnAccountBelow: 'Choose an account below', addBankAccount: 'Add bank account', chooseAnAccount: 'Choose an account', connectOnlineWithPlaid: 'Connect online with Plaid', @@ -1347,13 +1357,27 @@ export default { }, requestorStep: { headerTitle: 'Personal information', - subtitle: 'Please provide your personal information.', learnMore: 'Learn more', isMyDataSafe: 'Is my data safe?', onFidoConditions: 'By continuing with the request to add this bank account, you confirm that you have read, understand and accept ', isControllingOfficer: 'I am authorized to use my company bank account for business spend', isControllingOfficerError: 'You must be a controlling officer with authorization to operate the business bank account.', }, + personalInfoStep: { + personalInfo: 'Personal info', + enterYourLegalFirstAndLast: 'Enter your legal first and last name.', + legalFirstName: 'Legal first name', + legalLastName: 'Legal last name', + legalName: 'Legal name', + enterYourDateOfBirth: 'Enter your date of birth.', + enterTheLast4: 'Enter the last 4 of your SSN.', + dontWorry: "Don't worry, we don't do any personal credit checks!", + last4SSN: 'Last 4 Social Security Number', + enterYourAddress: 'Enter your address.', + address: 'Address', + letsDoubleCheck: "Let's double check everything looks right", + byAddingThisBankAccount: 'By adding this bank account, you confirm that you have read, understand and accept', + }, validationStep: { headerTitle: 'Validate Bank Account', buttonText: 'Finish setup', diff --git a/src/languages/es.ts b/src/languages/es.ts index a0a30bcf4141..72208fb001c5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -157,6 +157,7 @@ export default { noPO: 'No se aceptan apartados ni direcciones postales', city: 'Ciudad', state: 'Estado', + streetAddress: 'Dirección', stateOrProvince: 'Estado / Provincia', country: 'País', zip: 'Código postal', @@ -256,6 +257,7 @@ export default { tbd: 'Por determinar', selectCurrency: 'Selecciona una moneda', card: 'Tarjeta', + whyDoWeAskForThis: '¿Por qué pedimos esto?', }, location: { useCurrent: 'Usar ubicación actual', @@ -1153,8 +1155,16 @@ export default { return result; }, bankAccount: { + bankInfo: 'Información bancaria', + confirmBankInfo: 'Confirmar información bancaria', + manuallyAdd: 'Agregar manualmente tu cuenta bancaria', + letsDoubleCheck: 'Verifiquemos que todo esté correcto.', + accountEnding: 'Cuenta terminada en', + thisBankAccount: 'Esta cuenta bancaria se utilizará para pagos comerciales en tu espacio de trabajo', + connectDifferentAccount: 'Conectar una cuenta diferente', accountNumber: 'Número de cuenta', routingNumber: 'Número de ruta', + chooseAnAccountBelow: 'Elige una cuenta a continuación', addBankAccount: 'Añadir cuenta bancaria', chooseAnAccount: 'Elige una cuenta', connectOnlineWithPlaid: 'Conéctate a Plaid online', @@ -1366,13 +1376,27 @@ export default { }, requestorStep: { headerTitle: 'Información personal', - subtitle: 'Dé más información sobre tí.', learnMore: 'Más información', isMyDataSafe: '¿Están seguros mis datos?', onFidoConditions: 'Al continuar con la solicitud de añadir esta cuenta bancaria, confirma que ha leído, entiende y acepta ', isControllingOfficer: 'Estoy autorizado a utilizar la cuenta bancaria de mi compañía para gastos de empresa', isControllingOfficerError: 'Debe ser un oficial controlador con autorización para operar la cuenta bancaria de la compañía', }, + personalInfoStep: { + personalInfo: 'Información Personal', + enterYourLegalFirstAndLast: 'Ingrese su Nombre y Apellido', + legalFirstName: 'Nombre', + legalLastName: 'Apellido', + legalName: 'Nombre legal', + enterYourDateOfBirth: 'Ingrese su fecha de Cumple años', + enterTheLast4: 'Ingrese los últimos 4 dígitos de su NSS', + dontWorry: 'No se preocupe, no hacemos ninguna verificación de créditos', + last4SSN: 'Últimos 4 dígitos de su Número de Seguro Social', + enterYourAddress: 'Ingrese su dirección', + address: 'Dirección', + letsDoubleCheck: 'Revisemos que todo esté bien', + byAddingThisBankAccount: 'Agregando esta cuenta bancaria, confirmas que as leído, entendido y aceptado', + }, validationStep: { headerTitle: 'Validar cuenta bancaria', buttonText: 'Finalizar configuración', diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js index b8675fd9cc0e..0f63b6a3eb92 100644 --- a/src/pages/ReimbursementAccount/AddressForm.js +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -104,7 +104,6 @@ function AddressForm(props) { defaultValue={props.defaultValues.street} onInputChange={props.onFieldChange} errorText={props.errors.street ? props.translate('bankAccount.error.addressStreet') : ''} - hint={props.translate('common.noPO')} renamedInputKeys={props.inputKeys} maxInputLength={CONST.FORM_CHARACTER_LIMIT} isLimitedToUSA @@ -145,7 +144,6 @@ function AddressForm(props) { onChangeText={(value) => props.onFieldChange({zipCode: value})} errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} - hint={props.translate('common.zipCodeExampleFormat', {zipSampleFormat: CONST.COUNTRY_ZIP_REGEX_DATA.US.samples})} containerStyles={[styles.mt2]} /> diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index f1bc41d44ed5..16892c795756 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -27,6 +27,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import BankAccountManualStep from './BankAccountManualStep'; import BankAccountPlaidStep from './BankAccountPlaidStep'; +import BankInfo from './BankInfo/BankInfo'; import StepPropTypes from './StepPropTypes'; const propTypes = { @@ -76,6 +77,10 @@ function BankAccountStep(props) { ROUTES.WORKSPACE_INITIAL.getRoute(props.policyID), )}`; + if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID || subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { + return ; + } + if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { return ( getSubstepValues(bankInfoStepKeys, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); + + let setupType = getDefaultValueForReimbursementAccountField(reimbursementAccount, 'subStep'); + + const shouldReinitializePlaidLink = plaidLinkToken && receivedRedirectURI && setupType !== CONST.BANK_ACCOUNT.SUBSTEP.MANUAL; + if (shouldReinitializePlaidLink) { + setupType = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; + } + + const startFrom = useMemo(() => getInitialSubstepForBankInfo(values, setupType), [setupType, values]); + + const submit = useCallback(() => { + if (setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { + BankAccounts.connectBankAccountManually( + lodashGet(reimbursementAccount, 'achData', bankInfoStepKeys.BANK_ACCOUNT_ID) || 0, + values[bankInfoStepKeys.ACCOUNT_NUMBER], + values[bankInfoStepKeys.ROUTING_NUMBER], + values[bankInfoStepKeys.PLAID_MASK], + ); + } else if (setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID) { + const bankAccountID = lodashGet(reimbursementAccount, 'achData.bankAccountID') || 0; + BankAccounts.connectBankAccountWithPlaid(bankAccountID, { + [bankInfoStepKeys.ROUTING_NUMBER]: values[bankInfoStepKeys.ROUTING_NUMBER], + [bankInfoStepKeys.ACCOUNT_NUMBER]: values[bankInfoStepKeys.ACCOUNT_NUMBER], + [bankInfoStepKeys.PLAID_MASK]: values[bankInfoStepKeys.PLAID_MASK], + [bankInfoStepKeys.IS_SAVINGS]: values[bankInfoStepKeys.IS_SAVINGS], + [bankInfoStepKeys.BANK_NAME]: values[bankInfoStepKeys.BANK_NAME], + [bankInfoStepKeys.PLAID_ACCOUNT_ID]: values[bankInfoStepKeys.PLAID_ACCOUNT_ID], + [bankInfoStepKeys.PLAID_ACCESS_TOKEN]: values[bankInfoStepKeys.PLAID_ACCESS_TOKEN], + }); + } + }, [reimbursementAccount, setupType, values]); + + const bodyContent = setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID ? plaidSubsteps : manualSubsteps; + const {componentToRender: SubStep, isEditing, screenIndex, nextScreen, prevScreen, moveTo} = useSubStep({bodyContent, startFrom, onFinished: submit}); + + const handleBackButtonPress = () => { + if (screenIndex === 0) { + // TODO replace it with navigation to ReimbursementAccountPage once base is updated + BankAccounts.setBankAccountSubStep(null); + } else { + prevScreen(); + } + }; + + return ( + + + + {}} + // TODO Will be replaced with proper values + startStep={0} + stepNames={STEP_NAMES} + /> + + + + ); +} + +BankInfo.propTypes = propTypes; +BankInfo.defaultProps = defaultProps; +BankInfo.displayName = 'BankInfo'; + +export default withOnyx({ + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, + reimbursementAccountDraft: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, + }, + plaidLinkToken: { + key: ONYXKEYS.PLAID_LINK_TOKEN, + }, +})(BankInfo); diff --git a/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.js b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.js new file mode 100644 index 000000000000..2e805dd373eb --- /dev/null +++ b/src/pages/ReimbursementAccount/BankInfo/substeps/Confirmation.js @@ -0,0 +1,136 @@ +import lodashGet from 'lodash/get'; +import React, {useMemo} from 'react'; +import {ScrollView, Text, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import Button from '@components/Button'; +import Icon from '@components/Icon'; +import * as Expensicons from '@components/Icon/Expensicons'; +import MenuItem from '@components/MenuItem'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useLocalize from '@hooks/useLocalize'; +import reimbursementAccountDraftPropTypes from '@pages/ReimbursementAccount/ReimbursementAccountDraftPropTypes'; +import {reimbursementAccountPropTypes} from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '@pages/ReimbursementAccount/reimbursementAccountPropTypes'; +import subStepPropTypes from '@pages/ReimbursementAccount/subStepPropTypes'; +import getDefaultValueForReimbursementAccountField from '@pages/ReimbursementAccount/utils/getDefaultValueForReimbursementAccountField'; +import getSubstepValues from '@pages/ReimbursementAccount/utils/getSubstepValues'; +import styles from '@styles/styles'; +import themeColors from '@styles/themes/default'; +import * as BankAccounts from '@userActions/BankAccounts'; +import * as ReimbursementAccount from '@userActions/ReimbursementAccount'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; + +const propTypes = { + /** Reimbursement account from ONYX */ + reimbursementAccount: reimbursementAccountPropTypes, + + /** The draft values of the bank account being setup */ + reimbursementAccountDraft: reimbursementAccountDraftPropTypes, + + ...subStepPropTypes, +}; + +const defaultProps = { + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, + reimbursementAccountDraft: {}, +}; + +const bankInfoStepKeys = CONST.BANK_ACCOUNT.BANK_INFO_STEP.INPUT_KEY; + +function Confirmation({reimbursementAccount, reimbursementAccountDraft, onNext}) { + const {translate} = useLocalize(); + + const isLoading = lodashGet(reimbursementAccount, 'isLoading', false); + const setupType = getDefaultValueForReimbursementAccountField(reimbursementAccount, 'subStep'); + const values = useMemo(() => getSubstepValues(bankInfoStepKeys, reimbursementAccountDraft, reimbursementAccount), [reimbursementAccount, reimbursementAccountDraft]); + + const handleConnectDifferentAccount = () => { + const bankAccountData = { + [bankInfoStepKeys.ROUTING_NUMBER]: '', + [bankInfoStepKeys.ACCOUNT_NUMBER]: '', + [bankInfoStepKeys.PLAID_MASK]: '', + [bankInfoStepKeys.IS_SAVINGS]: '', + [bankInfoStepKeys.BANK_NAME]: '', + [bankInfoStepKeys.PLAID_ACCOUNT_ID]: '', + [bankInfoStepKeys.PLAID_ACCESS_TOKEN]: '', + }; + ReimbursementAccount.updateReimbursementAccountDraft(bankAccountData); + + BankAccounts.setBankAccountSubStep(null); + }; + + return ( + + + {translate('bankAccount.letsDoubleCheck')} + + {setupType === CONST.BANK_ACCOUNT.SUBSTEP.MANUAL && ( + + + + {translate('bankAccount.routingNumber')} + {values[bankInfoStepKeys.ROUTING_NUMBER]} + + + {translate('bankAccount.accountNumber')} + {values[bankInfoStepKeys.ACCOUNT_NUMBER]} + + + )} + {setupType === CONST.BANK_ACCOUNT.SUBSTEP.PLAID && ( + + )} + {translate('bankAccount.thisBankAccount')} + + + +