Skip to content

Commit

Permalink
feat: BankInfo step
Browse files Browse the repository at this point in the history
  • Loading branch information
MrMuzyk committed Nov 9, 2023
1 parent 7d807c7 commit 02f287e
Show file tree
Hide file tree
Showing 33 changed files with 1,606 additions and 199 deletions.
6 changes: 3 additions & 3 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -192,6 +216,7 @@ const CONST = {
},
SUBSTEP: {
MANUAL: 'manual',
PLAID: 'plaid',
},
VERIFICATIONS: {
ERROR_MESSAGE: 'verifications.errorMessage',
Expand Down
56 changes: 54 additions & 2 deletions src/components/AddPlaidBankAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 = {
Expand All @@ -68,6 +72,7 @@ const defaultProps = {
allowDebit: false,
bankAccountID: 0,
isPlaidDisabled: false,
isDisplayedInNewVBBA: false,
};

function AddPlaidBankAccount({
Expand All @@ -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();
Expand Down Expand Up @@ -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 (
<View>
Expand Down Expand Up @@ -227,6 +252,33 @@ function AddPlaidBankAccount({
);
}

if (isDisplayedInNewVBBA) {
return (
<FullPageOfflineBlockingView>
<Text style={[styles.mb5, styles.textHeadline]}>{translate('bankAccount.chooseAnAccount')}</Text>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mb5]}>
<Icon
src={icon}
height={iconSize}
width={iconSize}
/>
<View>
<Text style={[styles.ml3, styles.textStrong]}>{bankName}</Text>
{selectedPlaidAccountMask.length > 0 && (
<Text style={[styles.ml3, styles.textLabelSupporting]}>{`${translate('bankAccount.accountEnding')} ${selectedPlaidAccountMask}`}</Text>
)}
</View>
</View>
<Text style={[styles.textLabelSupporting]}>{`${translate('bankAccount.chooseAnAccountBelow')}:`}</Text>
<RadioButtons
items={options}
defaultCheckedValue={defaultSelectedPlaidAccountID}
onPress={handleSelectingPlaidAccount}
/>
</FullPageOfflineBlockingView>
);
}

// Plaid bank accounts view
return (
<FullPageOfflineBlockingView>
Expand Down
24 changes: 22 additions & 2 deletions src/components/Form/FormWrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]),

Expand All @@ -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(() => {
Expand Down Expand Up @@ -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
Expand All @@ -150,6 +169,7 @@ function FormWrapper(props) {
isSubmitActionDangerous,
isSubmitButtonVisible,
onSubmit,
submitButtonStyles,
style,
submitButtonText,
],
Expand Down
15 changes: 7 additions & 8 deletions src/components/InteractiveStepSubHeader.js
Original file line number Diff line number Diff line change
@@ -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 */
Expand Down
36 changes: 34 additions & 2 deletions src/components/NewDatePicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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,
};
Expand All @@ -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)) {
Expand Down
9 changes: 7 additions & 2 deletions src/components/RadioButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<View>
{items.map((item) => (
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useSubStep.js
Original file line number Diff line number Diff line change
@@ -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 */
Expand Down
Loading

0 comments on commit 02f287e

Please sign in to comment.