Skip to content

Commit

Permalink
Merge pull request #13236 from Expensify/nat-back
Browse files Browse the repository at this point in the history
Fix and refactor VBBA setup flow
  • Loading branch information
Chris Kosuke Tseng authored Jan 20, 2023
2 parents 91c7467 + cba528a commit f0846a3
Show file tree
Hide file tree
Showing 34 changed files with 731 additions and 789 deletions.
3 changes: 3 additions & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export default {
// Plaid data (access tokens, bank accounts ...)
PLAID_DATA: 'plaidData',

// If we disabled Plaid because of too many attempts
IS_PLAID_DISABLED: 'isPlaidDisabled',

// Token needed to initialize Plaid link
PLAID_LINK_TOKEN: 'plaidLinkToken',

Expand Down
2 changes: 0 additions & 2 deletions src/components/AddPaymentMethodMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import withWindowDimensions from './withWindowDimensions';
import Permissions from '../libs/Permissions';
import PopoverMenu from './PopoverMenu';
import paypalMeDataPropTypes from './paypalMeDataPropTypes';
import * as BankAccounts from '../libs/actions/BankAccounts';

const propTypes = {
isVisible: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -50,7 +49,6 @@ const AddPaymentMethodMenu = props => (
text: props.translate('common.bankAccount'),
icon: Expensicons.Bank,
onSelected: () => {
BankAccounts.clearPlaid();
props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT);
},
},
Expand Down
34 changes: 13 additions & 21 deletions src/components/AddPlaidBankAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
View,
} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import Log from '../libs/Log';
import PlaidLink from './PlaidLink';
import * as BankAccounts from '../libs/actions/BankAccounts';
Expand All @@ -16,15 +16,15 @@ import themeColors from '../styles/themes/default';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import Picker from './Picker';
import plaidDataPropTypes from '../pages/ReimbursementAccount/plaidDataPropTypes';
import {plaidDataPropTypes} from '../pages/ReimbursementAccount/plaidDataPropTypes';
import Text from './Text';
import getBankIcon from './Icon/BankIcons';
import Icon from './Icon';
import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView';

const propTypes = {
/** Contains plaid data */
plaidData: plaidDataPropTypes,
plaidData: plaidDataPropTypes.isRequired,

/** Selected account ID from the Picker associated with the end of the Plaid flow */
selectedPlaidAccountID: PropTypes.string,
Expand Down Expand Up @@ -57,14 +57,6 @@ const propTypes = {
};

const defaultProps = {
plaidData: {
bankName: '',
plaidAccessToken: '',
bankAccounts: [],
isLoading: false,
error: '',
errors: {},
},
selectedPlaidAccountID: '',
plaidLinkToken: '',
onExitPlaid: () => {},
Expand All @@ -85,7 +77,9 @@ class AddPlaidBankAccount extends React.Component {

componentDidMount() {
// If we're coming from Plaid OAuth flow then we need to reuse the existing plaidLinkToken
if ((this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken) || !_.isEmpty(this.props.plaidData)) {
if ((this.props.receivedRedirectURI && this.props.plaidLinkOAuthToken)
|| !_.isEmpty(lodashGet(this.props.plaidData, 'bankAccounts'))
|| !_.isEmpty(lodashGet(this.props.plaidData, 'errors'))) {
return;
}

Expand All @@ -106,20 +100,22 @@ class AddPlaidBankAccount extends React.Component {
}

render() {
const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts', []);
const plaidBankAccounts = lodashGet(this.props.plaidData, 'bankAccounts') || [];
const token = this.getPlaidLinkToken();
const options = _.map(plaidBankAccounts, account => ({
value: account.plaidAccountID,
label: `${account.addressName} ${account.mask}`,
}));
const {icon, iconSize} = getBankIcon();
const plaidDataErrorMessage = !_.isEmpty(this.props.plaidData.errors) ? _.chain(this.props.plaidData.errors).values().first().value() : this.props.plaidData.error;
const plaidErrors = lodashGet(this.props.plaidData, 'errors');
const plaidDataErrorMessage = !_.isEmpty(plaidErrors) ? _.chain(plaidErrors).values().first().value() : '';
const bankName = lodashGet(this.props.plaidData, 'bankName');

// Plaid Link view
if (!plaidBankAccounts.length) {
return (
<FullPageOfflineBlockingView>
{this.props.plaidData.isLoading && (
{lodashGet(this.props.plaidData, 'isLoading') && (
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter]}>
<ActivityIndicator color={themeColors.spinner} size="large" />
</View>
Expand All @@ -129,13 +125,12 @@ class AddPlaidBankAccount extends React.Component {
{plaidDataErrorMessage}
</Text>
)}
{Boolean(token) && (
{Boolean(token) && !bankName && (
<PlaidLink
token={token}
onSuccess={({publicToken, metadata}) => {
Log.info('[PlaidLink] Success!');
BankAccounts.openPlaidBankAccountSelector(publicToken, metadata.institution.name, this.props.allowDebit);
BankAccounts.updatePlaidData({institution: metadata.institution});
}}
onError={(error) => {
Log.hmmm('[PlaidLink] Error: ', error.message);
Expand Down Expand Up @@ -163,7 +158,7 @@ class AddPlaidBankAccount extends React.Component {
height={iconSize}
width={iconSize}
/>
<Text style={[styles.ml3, styles.textStrong]}>{this.props.plaidData.bankName}</Text>
<Text style={[styles.ml3, styles.textStrong]}>{bankName}</Text>
</View>
<View style={[styles.mb5]}>
<Picker
Expand All @@ -188,9 +183,6 @@ AddPlaidBankAccount.defaultProps = defaultProps;
export default compose(
withLocalize,
withOnyx({
plaidData: {
key: ONYXKEYS.PLAID_DATA,
},
plaidLinkToken: {
key: ONYXKEYS.PLAID_LINK_TOKEN,
initWithStoredValues: false,
Expand Down
12 changes: 11 additions & 1 deletion src/components/ReimbursementAccountLoadingIndicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ import Navigation from '../libs/Navigation/Navigation';
import ScreenWrapper from './ScreenWrapper';
import FullScreenLoadingIndicator from './FullscreenLoadingIndicator';
import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlockingView';
import compose from '../libs/compose';
import {withNetwork} from './OnyxProvider';

const propTypes = {
/** Whether the user is submitting verifications data */
isSubmittingVerificationsData: PropTypes.bool.isRequired,

/** Method to trigger when pressing back button of the header */
onBackButtonPress: PropTypes.func.isRequired,
...withLocalizePropTypes,
};

Expand All @@ -23,6 +27,8 @@ const ReimbursementAccountLoadingIndicator = props => (
<HeaderWithCloseButton
title={props.translate('reimbursementAccountLoadingAnimation.oneMoment')}
onCloseButtonPress={Navigation.dismissModal}
shouldShowBackButton={props.network.isOffline}
onBackButtonPress={props.onBackButtonPress}
/>
<FullPageOfflineBlockingView>
{props.isSubmittingVerificationsData ? (
Expand All @@ -49,4 +55,8 @@ const ReimbursementAccountLoadingIndicator = props => (
ReimbursementAccountLoadingIndicator.propTypes = propTypes;
ReimbursementAccountLoadingIndicator.displayName = 'ReimbursementAccountLoadingIndicator';

export default withLocalize(ReimbursementAccountLoadingIndicator);
export default compose(
withLocalize,
withNetwork(),
)(ReimbursementAccountLoadingIndicator);

24 changes: 5 additions & 19 deletions src/libs/ReimbursementAccountUtils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from 'underscore';
import lodashGet from 'lodash/get';
import * as BankAccounts from './actions/BankAccounts';
import FormHelper from './FormHelper';
Expand All @@ -15,28 +14,16 @@ const clearErrors = (props, paths) => formHelper.clearErrors(props, paths);
/**
* Get the default state for input fields in the VBA flow
*
* @param {Object} props
* @param {Object} reimbursementAccountDraft
* @param {Object} reimbursementAccount
* @param {String} fieldName
* @param {*} defaultValue
*
* @returns {*}
*/
function getDefaultStateForField(props, fieldName, defaultValue = '') {
return lodashGet(props, ['reimbursementAccountDraft', fieldName])
|| lodashGet(props, ['reimbursementAccount', 'achData', fieldName], defaultValue);
}

/**
* @param {Object} props
* @param {Array} fieldNames
*
* @returns {*}
*/
function getBankAccountFields(props, fieldNames) {
return {
..._.pick(lodashGet(props, 'reimbursementAccount.achData'), ...fieldNames),
..._.pick(props.reimbursementAccountDraft, ...fieldNames),
};
function getDefaultStateForField(reimbursementAccountDraft, reimbursementAccount, fieldName, defaultValue = '') {
return lodashGet(reimbursementAccountDraft, fieldName)
|| lodashGet(reimbursementAccount, ['achData', fieldName], defaultValue);
}

/**
Expand All @@ -56,5 +43,4 @@ export {
clearError,
clearErrors,
getErrorText,
getBankAccountFields,
};
28 changes: 19 additions & 9 deletions src/libs/actions/BankAccounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as API from '../API';
import ONYXKEYS from '../../ONYXKEYS';
import * as Localize from '../Localize';
import DateUtils from '../DateUtils';
import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes';
import Navigation from '../Navigation/Navigation';
import ROUTES from '../../ROUTES';

export {
goToWithdrawalAccountSetupStep,
Expand All @@ -28,17 +31,23 @@ export {
acceptWalletTerms,
} from './Wallet';

function clearPersonalBankAccount() {
Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {});
}

function clearPlaid() {
Onyx.set(ONYXKEYS.PLAID_DATA, {});
Onyx.set(ONYXKEYS.PLAID_LINK_TOKEN, '');

return Onyx.set(ONYXKEYS.PLAID_DATA, PlaidDataProps.plaidDataDefaultProps);
}

function updatePlaidData(plaidData) {
Onyx.merge(ONYXKEYS.PLAID_DATA, plaidData);
function openPlaidView() {
clearPlaid().then(() => this.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID));
}

function openPersonalBankAccountSetupView() {
clearPlaid().then(() => Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT));
}

function clearPersonalBankAccount() {
clearPlaid();
Onyx.set(ONYXKEYS.PERSONAL_BANK_ACCOUNT, {});
}

function clearOnfidoToken() {
Expand Down Expand Up @@ -341,7 +350,7 @@ function updateBeneficialOwnersForBankAccount(params) {
/**
* Create the bank account with manually entered data.
*
* @param {String} [bankAccountID]
* @param {number} [bankAccountID]
* @param {String} [accountNumber]
* @param {String} [routingNumber]
* @param {String} [plaidMask]
Expand Down Expand Up @@ -387,14 +396,15 @@ export {
clearOnfidoToken,
clearPersonalBankAccount,
clearPlaid,
openPlaidView,
connectBankAccountManually,
connectBankAccountWithPlaid,
deletePaymentBankAccount,
openPersonalBankAccountSetupView,
openReimbursementAccountPage,
updateBeneficialOwnersForBankAccount,
updateCompanyInformationForBankAccount,
updatePersonalInformationForBankAccount,
updatePlaidData,
openWorkspaceView,
validateBankAccount,
verifyIdentityForBankAccount,
Expand Down
16 changes: 0 additions & 16 deletions src/libs/actions/PaymentMethods.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import _ from 'underscore';
import {createRef} from 'react';
import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
Expand All @@ -10,7 +9,6 @@ import * as Localize from '../Localize';
import Navigation from '../Navigation/Navigation';
import * as CardUtils from '../CardUtils';
import * as User from './User';
import * as store from './ReimbursementAccount/store';
import ROUTES from '../../ROUTES';

function deletePayPalMe() {
Expand All @@ -37,19 +35,6 @@ function continueSetup() {
kycWallRef.current.continue();
}

/**
* Clears local reimbursement account if it doesn't exist in bankAccounts
* @param {Object[]} bankAccounts
*/
function cleanLocalReimbursementData(bankAccounts) {
const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID');

// We check if the bank account list doesn't have the reimbursementAccount
if (!_.find(bankAccounts, bankAccount => bankAccount.bankAccountID === bankAccountID)) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: null, shouldShowResetModal: false});
}
}

function openPaymentsPage() {
const onyxData = {
optimisticData: [
Expand Down Expand Up @@ -359,7 +344,6 @@ export {
resetWalletTransferData,
saveWalletTransferAccountTypeAndID,
saveWalletTransferMethodType,
cleanLocalReimbursementData,
hasPaymentMethodError,
clearDeletePaymentMethodError,
clearAddPaymentMethodError,
Expand Down
19 changes: 18 additions & 1 deletion src/libs/actions/Plaid.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import getPlaidLinkTokenParameters from '../getPlaidLinkTokenParameters';
import ONYXKEYS from '../../ONYXKEYS';
import * as API from '../API';
import CONST from '../../CONST';
import * as PlaidDataProps from '../../pages/ReimbursementAccount/plaidDataPropTypes';

/**
* Gets the Plaid Link token used to initialize the Plaid SDK
Expand All @@ -12,7 +13,23 @@ function openPlaidBankLogin(allowDebit, bankAccountID) {
const params = getPlaidLinkTokenParameters();
params.allowDebit = allowDebit;
params.bankAccountID = bankAccountID;
API.read('OpenPlaidBankLogin', params);
const optimisticData = [{
onyxMethod: CONST.ONYX.METHOD.SET,
key: ONYXKEYS.PLAID_DATA,
value: {...PlaidDataProps.plaidDataDefaultProps, isLoading: true},
}, {
onyxMethod: CONST.ONYX.METHOD.SET,
key: ONYXKEYS.PLAID_LINK_TOKEN,
value: '',
}, {
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT,
value: {
plaidAccountID: '',
},
}];

API.read('OpenPlaidBankLogin', params, {optimisticData});
}

/**
Expand Down
6 changes: 2 additions & 4 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -923,10 +923,8 @@ function createWorkspace(ownerEmail = '', makeMeAdmin = false, policyName = '',
/**
*
* @param {string} policyID
* @param {string} subStep The sub step in first step of adding withdrawal bank account
* @param {*} localCurrentStep The locally stored current step of adding a withdrawal bank account
*/
function openWorkspaceReimburseView(policyID, subStep, localCurrentStep) {
function openWorkspaceReimburseView(policyID) {
if (!policyID) {
Log.warn('openWorkspaceReimburseView invalid params', {policyID});
return;
Expand All @@ -952,7 +950,7 @@ function openWorkspaceReimburseView(policyID, subStep, localCurrentStep) {
],
};

API.read('OpenWorkspaceReimburseView', {policyID, subStep, localCurrentStep}, onyxData);
API.read('OpenWorkspaceReimburseView', {policyID}, onyxData);
}

function openWorkspaceMembersPage(policyID, clientMemberEmails) {
Expand Down
7 changes: 5 additions & 2 deletions src/libs/actions/ReimbursementAccount/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ export {
} from './errors';

/**
* Set the current sub step in first step of adding withdrawal bank account
* Set the current sub step in first step of adding withdrawal bank account:
* - `null` if we want to go back to the view where the user selects between connecting via Plaid or connecting manually
* - CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL to ask them to enter their accountNumber and routingNumber
* - CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID to ask them to login to their bank via Plaid
*
* @param {String} subStep - One of {CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL, CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID, null}
* @param {String} subStep
*/
function setBankAccountSubStep(subStep) {
Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {subStep}});
Expand Down
Loading

0 comments on commit f0846a3

Please sign in to comment.