diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 77c3f0396cbc..8caa4b2997c4 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -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', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index e71e42e1d15a..0a2117d1ca7c 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -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, @@ -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); }, }, diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 386eea4c325c..992190e6b725 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -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'; @@ -16,7 +16,7 @@ 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'; @@ -24,7 +24,7 @@ import FullPageOfflineBlockingView from './BlockingViews/FullPageOfflineBlocking 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, @@ -57,14 +57,6 @@ const propTypes = { }; const defaultProps = { - plaidData: { - bankName: '', - plaidAccessToken: '', - bankAccounts: [], - isLoading: false, - error: '', - errors: {}, - }, selectedPlaidAccountID: '', plaidLinkToken: '', onExitPlaid: () => {}, @@ -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; } @@ -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 ( - {this.props.plaidData.isLoading && ( + {lodashGet(this.props.plaidData, 'isLoading') && ( @@ -129,13 +125,12 @@ class AddPlaidBankAccount extends React.Component { {plaidDataErrorMessage} )} - {Boolean(token) && ( + {Boolean(token) && !bankName && ( { 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); @@ -163,7 +158,7 @@ class AddPlaidBankAccount extends React.Component { height={iconSize} width={iconSize} /> - {this.props.plaidData.bankName} + {bankName} ( {props.isSubmittingVerificationsData ? ( @@ -49,4 +55,8 @@ const ReimbursementAccountLoadingIndicator = props => ( ReimbursementAccountLoadingIndicator.propTypes = propTypes; ReimbursementAccountLoadingIndicator.displayName = 'ReimbursementAccountLoadingIndicator'; -export default withLocalize(ReimbursementAccountLoadingIndicator); +export default compose( + withLocalize, + withNetwork(), +)(ReimbursementAccountLoadingIndicator); + diff --git a/src/libs/ReimbursementAccountUtils.js b/src/libs/ReimbursementAccountUtils.js index 4074f1e8ab6c..6c11b72fb5c4 100644 --- a/src/libs/ReimbursementAccountUtils.js +++ b/src/libs/ReimbursementAccountUtils.js @@ -1,4 +1,3 @@ -import _ from 'underscore'; import lodashGet from 'lodash/get'; import * as BankAccounts from './actions/BankAccounts'; import FormHelper from './FormHelper'; @@ -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); } /** @@ -56,5 +43,4 @@ export { clearError, clearErrors, getErrorText, - getBankAccountFields, }; diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 3c1593e864c5..b6aea981e5f6 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -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, @@ -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() { @@ -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] @@ -387,14 +396,15 @@ export { clearOnfidoToken, clearPersonalBankAccount, clearPlaid, + openPlaidView, connectBankAccountManually, connectBankAccountWithPlaid, deletePaymentBankAccount, + openPersonalBankAccountSetupView, openReimbursementAccountPage, updateBeneficialOwnersForBankAccount, updateCompanyInformationForBankAccount, updatePersonalInformationForBankAccount, - updatePlaidData, openWorkspaceView, validateBankAccount, verifyIdentityForBankAccount, diff --git a/src/libs/actions/PaymentMethods.js b/src/libs/actions/PaymentMethods.js index 66b593a328da..043556260c7b 100644 --- a/src/libs/actions/PaymentMethods.js +++ b/src/libs/actions/PaymentMethods.js @@ -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'; @@ -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() { @@ -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: [ @@ -359,7 +344,6 @@ export { resetWalletTransferData, saveWalletTransferAccountTypeAndID, saveWalletTransferMethodType, - cleanLocalReimbursementData, hasPaymentMethodError, clearDeletePaymentMethodError, clearAddPaymentMethodError, diff --git a/src/libs/actions/Plaid.js b/src/libs/actions/Plaid.js index 393b5bbc9961..0785a1fd9b3e 100644 --- a/src/libs/actions/Plaid.js +++ b/src/libs/actions/Plaid.js @@ -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 @@ -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}); } /** diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 58c70231e29d..f46285a76c67 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -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; @@ -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) { diff --git a/src/libs/actions/ReimbursementAccount/index.js b/src/libs/actions/ReimbursementAccount/index.js index 40dc5835ab0a..bcac825a630a 100644 --- a/src/libs/actions/ReimbursementAccount/index.js +++ b/src/libs/actions/ReimbursementAccount/index.js @@ -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}}); diff --git a/src/libs/actions/ReimbursementAccount/navigation.js b/src/libs/actions/ReimbursementAccount/navigation.js index f695137764fa..312479838c9f 100644 --- a/src/libs/actions/ReimbursementAccount/navigation.js +++ b/src/libs/actions/ReimbursementAccount/navigation.js @@ -1,95 +1,16 @@ -import _ from 'underscore'; -import lodashGet from 'lodash/get'; import Onyx from 'react-native-onyx'; -import * as store from './store'; -import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import ROUTES from '../../../ROUTES'; import Navigation from '../../Navigation/Navigation'; -const WITHDRAWAL_ACCOUNT_STEPS = [ - { - id: CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, - title: 'Bank Account', - }, - { - id: CONST.BANK_ACCOUNT.STEP.COMPANY, - title: 'Company Information', - }, - { - id: CONST.BANK_ACCOUNT.STEP.REQUESTOR, - title: 'Requestor Information', - }, - { - id: CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT, - title: 'Beneficial Owners', - }, - { - id: CONST.BANK_ACCOUNT.STEP.VALIDATION, - title: 'Validate', - }, - { - id: CONST.BANK_ACCOUNT.STEP.ENABLE, - title: 'Enable', - }, -]; - -/** - * Get step position in the array - * @private - * @param {String} stepID - * @return {Number} - */ -function getIndexByStepID(stepID) { - return _.findIndex(WITHDRAWAL_ACCOUNT_STEPS, step => step.id === stepID); -} - -/** - * Get next step ID - * @param {String} [stepID] - * @return {String} - */ -function getNextStepID(stepID) { - const nextStepIndex = Math.min( - getIndexByStepID(stepID || store.getReimbursementAccountInSetup().currentStep) + 1, - WITHDRAWAL_ACCOUNT_STEPS.length - 1, - ); - return lodashGet(WITHDRAWAL_ACCOUNT_STEPS, [nextStepIndex, 'id'], CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT); -} - -/** - * @param {Object} achData - * @returns {String} - */ -function getNextStepToComplete(achData) { - if (achData.currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && !achData.isOnfidoSetupComplete) { - return CONST.BANK_ACCOUNT.STEP.REQUESTOR; - } - - return getNextStepID(achData.currentStep); -} - /** * Navigate to a specific step in the VBA flow * * @param {String} stepID - * @param {Object} achData + * @param {Object} newAchData */ -function goToWithdrawalAccountSetupStep(stepID, achData) { - const newACHData = {...store.getReimbursementAccountInSetup()}; - - // If we go back to Requestor Step, reset any validation and previously answered questions from expectID. - if (!newACHData.useOnfido && stepID === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { - delete newACHData.questions; - delete newACHData.answers; - } - - // When going back to the BankAccountStep from the Company Step, show the manual form instead of Plaid - if (newACHData.currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && stepID === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) { - newACHData.subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL; - } - - Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newACHData, ...achData, currentStep: stepID}}); +function goToWithdrawalAccountSetupStep(stepID, newAchData) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {achData: {...newAchData, currentStep: stepID}}); } /** @@ -101,7 +22,5 @@ function navigateToBankAccountRoute() { export { goToWithdrawalAccountSetupStep, - getNextStepToComplete, - getNextStepID, navigateToBankAccountRoute, }; diff --git a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js index 24a7919fe824..4255b79d5d22 100644 --- a/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js +++ b/src/libs/actions/ReimbursementAccount/resetFreePlanBankAccount.js @@ -1,13 +1,15 @@ -import lodashGet from 'lodash/get'; +import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; import * as store from './store'; import * as API from '../../API'; +import * as PlaidDataProps from '../../../pages/ReimbursementAccount/plaidDataPropTypes'; +import * as ReimbursementAccountProps from '../../../pages/ReimbursementAccount/reimbursementAccountPropTypes'; /** * Reset user's reimbursement account. This will delete the bank account. + * @param {number} bankAccountID */ -function resetFreePlanBankAccount() { - const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID'); +function resetFreePlanBankAccount(bankAccountID) { if (!bankAccountID) { throw new Error('Missing bankAccountID when attempting to reset free plan bank account'); } @@ -28,27 +30,24 @@ function resetFreePlanBankAccount() { value: '', }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.PLAID_DATA, - value: {}, + value: PlaidDataProps.plaidDataDefaultProps, }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.PLAID_LINK_TOKEN, value: '', }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - value: { - achData: {}, - shouldShowResetModal: false, - }, + value: ReimbursementAccountProps.reimbursementAccountDefaultProps, }, { - onyxMethod: 'set', + onyxMethod: CONST.ONYX.METHOD.SET, key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - value: null, + value: {}, }, ], }); diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 5cf294a47cfe..d755d06e345f 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -15,11 +15,15 @@ import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; import Form from '../components/Form'; import ROUTES from '../ROUTES'; +import * as PlaidDataProps from './ReimbursementAccount/plaidDataPropTypes'; import ConfirmationPage from '../components/ConfirmationPage'; const propTypes = { ...withLocalizePropTypes, + /** Contains plaid data */ + plaidData: PlaidDataProps.plaidDataPropTypes, + /** The details about the Personal bank account we are adding saved in Onyx */ personalBankAccount: PropTypes.shape({ /** An error message to display to the user */ @@ -37,6 +41,7 @@ const propTypes = { }; const defaultProps = { + plaidData: PlaidDataProps.plaidDataDefaultProps, personalBankAccount: { error: '', shouldShowSuccess: false, @@ -93,7 +98,10 @@ class AddPersonalBankAccountPage extends React.Component { description={this.props.translate('addPersonalBankAccountPage.successMessage')} shouldShowButton buttonText={this.props.translate('common.continue')} - onButtonPress={() => Navigation.navigate(ROUTES.SETTINGS_PAYMENTS)} + onButtonPress={() => { + BankAccounts.clearPersonalBankAccount(); + Navigation.navigate(ROUTES.SETTINGS_PAYMENTS); + }} /> ) : (
{ this.setState({selectedPlaidAccountID}); }} + plaidData={this.props.plaidData} onExitPlaid={Navigation.goBack} receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()} selectedPlaidAccountID={this.state.selectedPlaidAccountID} diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index f6e21955b9b6..86aa55217a7d 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -3,34 +3,27 @@ import lodashGet from 'lodash/get'; import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import * as store from '../../libs/actions/ReimbursementAccount/store'; import Text from '../../components/Text'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; import IdentityForm from './IdentityForm'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import ReimbursementAccountForm from './ReimbursementAccountForm'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { + ...StepPropTypes, + /** Name of the company */ companyName: PropTypes.string.isRequired, - - ...withLocalizePropTypes, - - /** Bank account currently in setup */ - // eslint-disable-next-line react/no-unused-prop-types - reimbursementAccount: reimbursementAccountPropTypes.isRequired, }; class ACHContractStep extends React.Component { @@ -41,11 +34,11 @@ class ACHContractStep extends React.Component { this.submit = this.submit.bind(this); this.state = { - ownsMoreThan25Percent: ReimbursementAccountUtils.getDefaultStateForField(props, 'ownsMoreThan25Percent', false), - hasOtherBeneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'hasOtherBeneficialOwners', false), - acceptTermsAndConditions: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTermsAndConditions', false), - certifyTrueInformation: ReimbursementAccountUtils.getDefaultStateForField(props, 'certifyTrueInformation', false), - beneficialOwners: ReimbursementAccountUtils.getDefaultStateForField(props, 'beneficialOwners', []), + ownsMoreThan25Percent: props.getDefaultStateForField('ownsMoreThan25Percent', false), + hasOtherBeneficialOwners: props.getDefaultStateForField('hasOtherBeneficialOwners', false), + acceptTermsAndConditions: props.getDefaultStateForField('acceptTermsAndConditions', false), + certifyTrueInformation: props.getDefaultStateForField('certifyTrueInformation', false), + beneficialOwners: props.getDefaultStateForField('beneficialOwners', []), }; // These fields need to be filled out in order to submit the form (doesn't include IdentityForm fields) @@ -143,7 +136,7 @@ class ACHContractStep extends React.Component { return; } - const bankAccountID = lodashGet(store.getReimbursementAccountInSetup(), 'bankAccountID'); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; // If they did not select that there are other beneficial owners, then we need to clear out the array here. The // reason we do it here is that if they filled out several beneficial owners, but then toggled the checkbox, we @@ -169,15 +162,12 @@ class ACHContractStep extends React.Component { render() { return ( - <> + { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} + onBackButtonPress={this.props.onBackButtonPress} shouldShowGetAssistanceButton guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} shouldShowBackButton @@ -289,20 +279,10 @@ class ACHContractStep extends React.Component { errorText={this.getErrorText('certifyTrueInformation')} /> - + ); } } ACHContractStep.propTypes = propTypes; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, - }), -)(ACHContractStep); +export default withLocalize(ACHContractStep); diff --git a/src/pages/ReimbursementAccount/BankAccountManualStep.js b/src/pages/ReimbursementAccount/BankAccountManualStep.js index 1a0679b3fe0e..991f52e5c857 100644 --- a/src/pages/ReimbursementAccount/BankAccountManualStep.js +++ b/src/pages/ReimbursementAccount/BankAccountManualStep.js @@ -1,7 +1,6 @@ import React from 'react'; import {Image, View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -11,22 +10,17 @@ import TextInput from '../../components/TextInput'; import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import exampleCheckImage from './exampleCheckImage'; import Form from '../../components/Form'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import shouldDelayFocus from '../../libs/shouldDelayFocus'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - onBack: PropTypes.func, - ...withLocalizePropTypes, -}; - -const defaultProps = { - onBack: () => {}, + ...StepPropTypes, }; class BankAccountManualStep extends React.Component { @@ -62,25 +56,25 @@ class BankAccountManualStep extends React.Component { submit(values) { BankAccounts.connectBankAccountManually( - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0), + lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, values.accountNumber, values.routingNumber, - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidMask'), + this.props.getDefaultStateForField('plaidMask'), ); } render() { - const shouldDisableInputs = Boolean(ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID')); + const shouldDisableInputs = Boolean(lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID')); return ( - <> + )} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'acceptTerms', false)} + defaultValue={this.props.getDefaultStateForField('acceptTerms', false)} shouldSaveDraft /> - +
); } } BankAccountManualStep.propTypes = propTypes; -BankAccountManualStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - // Needed to retrieve errorFields - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, - }), -)(BankAccountManualStep); +export default withLocalize(BankAccountManualStep); diff --git a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js index 1142cfde7efd..b23e593e96c4 100644 --- a/src/pages/ReimbursementAccount/BankAccountPlaidStep.js +++ b/src/pages/ReimbursementAccount/BankAccountPlaidStep.js @@ -7,31 +7,34 @@ import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import AddPlaidBankAccount from '../../components/AddPlaidBankAccount'; import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import Form from '../../components/Form'; import styles from '../../styles/styles'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import * as PlaidDataProps from './plaidDataPropTypes'; +import StepPropTypes from './StepPropTypes'; const propTypes = { + ...StepPropTypes, + + /** Contains plaid data */ + plaidData: PlaidDataProps.plaidDataPropTypes, + /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ plaidLinkOAuthToken: PropTypes.string, - - onBack: PropTypes.func, - - ...withLocalizePropTypes, }; const defaultProps = { + plaidData: PlaidDataProps.plaidDataDefaultProps, receivedRedirectURI: null, plaidLinkOAuthToken: '', - onBack: () => {}, }; class BankAccountPlaidStep extends React.Component { @@ -42,7 +45,7 @@ class BankAccountPlaidStep extends React.Component { submit() { const selectedPlaidBankAccount = _.findWhere(lodashGet(this.props.plaidData, 'bankAccounts', []), { - plaidAccountID: ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidAccountID'), + plaidAccountID: this.props.getDefaultStateForField('plaidAccountID'), }); const bankAccountData = { @@ -50,29 +53,29 @@ class BankAccountPlaidStep extends React.Component { accountNumber: selectedPlaidBankAccount.accountNumber, plaidMask: selectedPlaidBankAccount.mask, isSavings: selectedPlaidBankAccount.isSavings, - bankName: this.props.plaidData.bankName, + bankName: lodashGet(this.props.plaidData, 'bankName') || '', plaidAccountID: selectedPlaidBankAccount.plaidAccountID, - plaidAccessToken: this.props.plaidData.plaidAccessToken, + plaidAccessToken: lodashGet(this.props.plaidData, 'plaidAccessToken') || '', }; ReimbursementAccount.updateReimbursementAccountDraft(bankAccountData); - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; BankAccounts.connectBankAccountWithPlaid(bankAccountID, bankAccountData); } render() { - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); - const selectedPlaidAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidAccountID', ''); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; + const selectedPlaidAccountID = this.props.getDefaultStateForField('plaidAccountID', ''); return ( - <> +
{ ReimbursementAccount.updateReimbursementAccountDraft({plaidAccountID}); }} + plaidData={this.props.plaidData} onExitPlaid={() => BankAccounts.setBankAccountSubStep(null)} receivedRedirectURI={this.props.receivedRedirectURI} plaidLinkOAuthToken={this.props.plaidLinkOAuthToken} @@ -96,7 +100,7 @@ class BankAccountPlaidStep extends React.Component { selectedPlaidAccountID={selectedPlaidAccountID} /> - +
); } } @@ -106,9 +110,6 @@ BankAccountPlaidStep.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, plaidData: { key: ONYXKEYS.PLAID_DATA, }, diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index 3bd4d00617a3..b92b322dc0e2 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -16,7 +16,7 @@ import Icon from '../../components/Icon'; import colors from '../../styles/colors'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import Text from '../../components/Text'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import ONYXKEYS from '../../ONYXKEYS'; @@ -27,12 +27,11 @@ import getPlaidDesktopMessage from '../../libs/getPlaidDesktopMessage'; import CONFIG from '../../CONFIG'; import ROUTES from '../../ROUTES'; import Button from '../../components/Button'; -import plaidDataPropTypes from './plaidDataPropTypes'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** Contains plaid data */ - plaidData: plaidDataPropTypes, + ...StepPropTypes, /** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */ receivedRedirectURI: PropTypes.string, @@ -40,36 +39,21 @@ const propTypes = { /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ plaidLinkOAuthToken: PropTypes.string, - /** Once the user has selected a sub step, clicking on back button should redirect to the continue button screen. */ - /** As such, we need to expose this handler */ - onSubStepBack: PropTypes.func, - - /** The bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes, - /** Object with various information about the user */ user: PropTypes.shape({ /** Is the user account validated? */ validated: PropTypes.bool, }), - - ...withLocalizePropTypes, }; const defaultProps = { receivedRedirectURI: null, plaidLinkOAuthToken: '', - plaidData: { - isPlaidDisabled: false, - }, - onSubStepBack: () => {}, - reimbursementAccount: {}, user: {}, }; const BankAccountStep = (props) => { - let subStep = lodashGet(props, 'reimbursementAccount.achData.subStep', ''); + let subStep = lodashGet(props.reimbursementAccount, 'achData.subStep', ''); const shouldReinitializePlaidLink = props.plaidLinkOAuthToken && props.receivedRedirectURI && subStep !== CONST.BANK_ACCOUNT.SUBSTEP.MANUAL; if (shouldReinitializePlaidLink) { subStep = CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; @@ -78,107 +62,114 @@ const BankAccountStep = (props) => { const bankAccountRoute = `${CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL}${ROUTES.BANK_ACCOUNT}`; if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL) { - return ; + return ( + + ); } if (subStep === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID) { - return ; + return ( + + ); } return ( - - { - // If we have a subStep then we will remove otherwise we will go back - if (subStep) { - BankAccounts.setBankAccountSubStep(null); - return; - } - Navigation.goBack(); - }} - shouldShowGetAssistanceButton - guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} - shouldShowBackButton - /> - -
- - {props.translate('bankAccount.toGetStarted')} - - {plaidDesktopMessage && ( - - - {props.translate(plaidDesktopMessage)} - + + + + +
+ + {props.translate('bankAccount.toGetStarted')} - )} -
- {!props.user.validated && ( - - - - {props.translate('bankAccount.validateAccountError')} - - - )} - - - {props.translate('common.privacy')} - - Linking.openURL('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} - > - - - {props.translate('bankAccount.yourDataIsSecure')} - - - - + {props.error && ( + + {props.error} + + )} + + BankAccounts.setBankAccountSubStep(CONST.BANK_ACCOUNT.SETUP_TYPE.MANUAL)} + shouldShowRightIcon + wrapperStyle={[styles.cardMenuItem]} + /> + +
+ {!props.user.validated && ( + + + + {props.translate('bankAccount.validateAccountError')} + - -
- -
+ )} + + + {props.translate('common.privacy')} + + Linking.openURL('https://community.expensify.com/discussion/5677/deep-dive-how-expensify-protects-your-information/')} + > + + + {props.translate('bankAccount.yourDataIsSecure')} + + + + + + + + + + + ); }; @@ -192,11 +183,8 @@ export default compose( user: { key: ONYXKEYS.USER, }, - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - plaidData: { - key: ONYXKEYS.PLAID_DATA, + isPlaidDisabled: { + key: ONYXKEYS.IS_PLAID_DISABLED, }, }), )(BankAccountStep); diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 0d771111836e..fefa825e0487 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -16,28 +16,19 @@ import styles from '../../styles/styles'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import TextLink from '../../components/TextLink'; import StatePicker from '../../components/StatePicker'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as ValidationUtils from '../../libs/ValidationUtils'; import * as LoginUtils from '../../libs/LoginUtils'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; import Picker from '../../components/Picker'; import AddressForm from './AddressForm'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; import Form from '../../components/Form'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** The bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The draft values of the bank account being setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, - - ...withLocalizePropTypes, + ...StepPropTypes, }; class CompanyStep extends React.Component { @@ -56,6 +47,18 @@ class CompanyStep extends React.Component { BankAccounts.resetReimbursementAccount(); } + /** + * @param {Array} fieldNames + * + * @returns {*} + */ + getBankAccountFields(fieldNames) { + return { + ..._.pick(lodashGet(this.props.reimbursementAccount, 'achData'), ...fieldNames), + ..._.pick(this.props.reimbursementAccountDraft, ...fieldNames), + }; + } + /** * @param {Object} values - form input values passed by the Form component * @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2} @@ -118,8 +121,10 @@ class CompanyStep extends React.Component { submit(values) { const bankAccount = { + bankAccountID: lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, + // Fields from BankAccount step - ...ReimbursementAccountUtils.getBankAccountFields(this.props, ['bankAccountID', 'routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), + ...this.getBankAccountFields(['routingNumber', 'accountNumber', 'bankName', 'plaidAccountID', 'plaidAccessToken', 'isSavings']), // Fields from Company step ...values, @@ -132,19 +137,19 @@ class CompanyStep extends React.Component { } render() { - const bankAccountID = ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0); - const shouldDisableCompanyName = bankAccountID && ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyName'); - const shouldDisableCompanyTaxID = bankAccountID && ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyTaxID'); + const bankAccountID = lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0; + const shouldDisableCompanyName = bankAccountID && this.props.getDefaultStateForField('companyName'); + const shouldDisableCompanyTaxID = bankAccountID && this.props.getDefaultStateForField('companyTaxID'); return ( - <> + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT)} + onBackButtonPress={this.props.onBackButtonPress} onCloseButtonPress={Navigation.dismissModal} />
@@ -201,7 +206,7 @@ class CompanyStep extends React.Component { keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD} disabled={shouldDisableCompanyTaxID} placeholder={this.props.translate('companyStep.taxIDNumberPlaceholder')} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'companyTaxID')} + defaultValue={this.props.getDefaultStateForField('companyTaxID')} shouldSaveDraft /> @@ -210,7 +215,7 @@ class CompanyStep extends React.Component { label={this.props.translate('companyStep.companyType')} items={_.map(this.props.translate('companyStep.incorporationTypes'), (label, value) => ({value, label}))} placeholder={{value: '', label: '-'}} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'incorporationType')} + defaultValue={this.props.getDefaultStateForField('incorporationType')} shouldSaveDraft /> @@ -219,7 +224,7 @@ class CompanyStep extends React.Component { inputID="incorporationDate" label={this.props.translate('companyStep.incorporationDate')} placeholder={this.props.translate('companyStep.incorporationDatePlaceholder')} - defaultValue={ReimbursementAccountUtils.getDefaultStateForField(this.props, 'incorporationDate')} + defaultValue={this.props.getDefaultStateForField('incorporationDate')} shouldSaveDraft /> @@ -227,13 +232,13 @@ class CompanyStep extends React.Component { ( <> {`${this.props.translate('companyStep.confirmCompanyIsNot')} `} @@ -249,7 +254,7 @@ class CompanyStep extends React.Component { shouldSaveDraft /> - +
); } } @@ -259,13 +264,6 @@ CompanyStep.propTypes = propTypes; export default compose( withLocalize, withOnyx({ - // Needed to retrieve errorFields - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, session: { key: ONYXKEYS.SESSION, }, diff --git a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js index 12c9e5c743df..76b490239c07 100644 --- a/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js +++ b/src/pages/ReimbursementAccount/ContinueBankAccountSetup.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import React from 'react'; import {ScrollView} from 'react-native'; import _ from 'underscore'; -import * as BankAccounts from '../../libs/actions/BankAccounts'; import * as Expensicons from '../../components/Icon/Expensicons'; import * as Illustrations from '../../components/Icon/Illustrations'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; @@ -19,11 +18,14 @@ import ScreenWrapper from '../../components/ScreenWrapper'; import Section from '../../components/Section'; import Text from '../../components/Text'; import withPolicy from '../workspace/withPolicy'; -import WorkspaceResetBankAccountModal from '../workspace/WorkspaceResetBankAccountModal'; const propTypes = { + /** Callback to continue to the next step of the setup */ continue: PropTypes.func.isRequired, + /** Callback to start over the setup */ + startOver: PropTypes.func.isRequired, + /** Policy values needed in the component */ policy: PropTypes.shape({ name: PropTypes.string, @@ -33,7 +35,7 @@ const propTypes = { }; const ContinueBankAccountSetup = props => ( - + ( - ); diff --git a/src/pages/ReimbursementAccount/EnableStep.js b/src/pages/ReimbursementAccount/EnableStep.js index 4293a5c2a74f..329f8c61cc9d 100644 --- a/src/pages/ReimbursementAccount/EnableStep.js +++ b/src/pages/ReimbursementAccount/EnableStep.js @@ -1,6 +1,7 @@ import React from 'react'; -import {View, ScrollView} from 'react-native'; +import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; @@ -13,17 +14,18 @@ import Button from '../../components/Button'; import * as Expensicons from '../../components/Icon/Expensicons'; import MenuItem from '../../components/MenuItem'; import getBankIcon from '../../components/Icon/BankIcons'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import userPropTypes from '../settings/userPropTypes'; import Section from '../../components/Section'; import * as Illustrations from '../../components/Icon/Illustrations'; -import * as BankAccounts from '../../libs/actions/BankAccounts'; import * as Link from '../../libs/actions/Link'; import * as User from '../../libs/actions/User'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import * as BankAccounts from '../../libs/actions/ReimbursementAccount'; const propTypes = { /** Bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /* Onyx Props */ user: userPropTypes.isRequired, @@ -33,17 +35,17 @@ const propTypes = { const EnableStep = (props) => { const isUsingExpensifyCard = props.user.isUsingExpensifyCard; - const reimbursementAccount = props.reimbursementAccount.achData || {}; - const {icon, iconSize} = getBankIcon(reimbursementAccount.bankName); - const formattedBankAccountNumber = reimbursementAccount.accountNumber + const achData = lodashGet(props.reimbursementAccount, 'achData') || {}; + const {icon, iconSize} = getBankIcon(achData.bankName); + const formattedBankAccountNumber = achData.accountNumber ? `${props.translate('paymentMethodList.accountLastFour')} ${ - reimbursementAccount.accountNumber.slice(-4) + achData.accountNumber.slice(-4) }` : ''; - const bankName = reimbursementAccount.addressName; + const bankName = achData.addressName; return ( - + { )} - + ); }; @@ -110,9 +112,6 @@ EnableStep.propTypes = propTypes; export default compose( withLocalize, withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, user: { key: ONYXKEYS.USER, }, diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js index dcab1ec499c8..c6c266f1a7d8 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js @@ -2,13 +2,10 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import FormAlertWithSubmitButton from '../../components/FormAlertWithSubmitButton'; import FormScrollView from '../../components/FormScrollView'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -16,7 +13,7 @@ import * as ErrorUtils from '../../libs/ErrorUtils'; const propTypes = { /** Data for the bank account actively being set up */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /** Called when the form is submitted */ onSubmit: PropTypes.func.isRequired, @@ -26,11 +23,6 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: { - isLoading: false, - errors: {}, - errorFields: {}, - }, buttonText: '', hideSubmitButton: false, }; @@ -77,11 +69,4 @@ class ReimbursementAccountForm extends React.Component { ReimbursementAccountForm.propTypes = propTypes; ReimbursementAccountForm.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(ReimbursementAccountForm); +export default withLocalize(ReimbursementAccountForm); diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js index c0e60c126d8f..17c630677ab1 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountPage.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountPage.js @@ -19,9 +19,6 @@ import getPlaidOAuthReceivedRedirectURI from '../../libs/getPlaidOAuthReceivedRe import Text from '../../components/Text'; import {withNetwork} from '../../components/OnyxProvider'; import networkPropTypes from '../../components/networkPropTypes'; -import * as store from '../../libs/actions/ReimbursementAccount/store'; - -// Steps import BankAccountStep from './BankAccountStep'; import CompanyStep from './CompanyStep'; import ContinueBankAccountSetup from './ContinueBankAccountSetup'; @@ -31,15 +28,23 @@ import ACHContractStep from './ACHContractStep'; import EnableStep from './EnableStep'; import ROUTES from '../../ROUTES'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import WorkspaceResetBankAccountModal from '../workspace/WorkspaceResetBankAccountModal'; +import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; +import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; const propTypes = { /** Plaid SDK token to use to initialize the widget */ plaidLinkToken: PropTypes.string, /** ACH data for the withdrawal account actively being set up */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, + + /** The draft values of the bank account being setup */ + reimbursementAccountDraft: reimbursementAccountDraftPropTypes, + + /** The token required to initialize the Onfido SDK */ + onfidoToken: PropTypes.string, /** Information about the network */ network: networkPropTypes.isRequired, @@ -63,9 +68,9 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: { - isLoading: true, - }, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, + reimbursementAccountDraft: {}, + onfidoToken: '', plaidLinkToken: '', route: { params: { @@ -78,11 +83,13 @@ class ReimbursementAccountPage extends React.Component { constructor(props) { super(props); this.continue = this.continue.bind(this); + this.getDefaultStateForField = this.getDefaultStateForField.bind(this); + this.goBack = this.goBack.bind(this); + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; - const achData = lodashGet(this.props, 'reimbursementAccount.achData', {}); - const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN; this.state = { - shouldShowContinueSetupButton: hasInProgressVBBA, + shouldHideContinueSetupButton: !hasInProgressVBBA, }; } @@ -94,17 +101,8 @@ class ReimbursementAccountPage extends React.Component { if (prevProps.network.isOffline && !this.props.network.isOffline) { this.fetchData(); } - const currentStep = lodashGet( - this.props, - 'reimbursementAccount.achData.currentStep', - CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, - ); - const previousStep = lodashGet( - prevProps, - 'reimbursementAccount.achData.currentStep', - CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, - ); - + const currentStep = lodashGet(this.props.reimbursementAccount, 'achData.currentStep') || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const previousStep = lodashGet(prevProps.reimbursementAccount, 'achData.currentStep') || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; if (currentStep === previousStep) { return; } @@ -117,6 +115,18 @@ class ReimbursementAccountPage extends React.Component { } /** + * @param {String} fieldName + * @param {*} defaultValue + * + * @returns {*} + */ + getDefaultStateForField(fieldName, defaultValue = '') { + return ReimbursementAccountUtils.getDefaultStateForField(this.props.reimbursementAccountDraft, this.props.reimbursementAccount, fieldName, defaultValue); + } + + /** + * We can pass stepToOpen in the URL to force which step to show. + * Mainly needed when user finished the flow in verifying state, and Ops ask them to modify some fields from a specific step. * @returns {String} */ getStepToOpenFromRouteParams() { @@ -160,20 +170,71 @@ class ReimbursementAccountPage extends React.Component { } } - fetchData() { + /** + * Retrieve verified business bank account currently being set up. + * @param {boolean} ignoreLocalCurrentStep Pass true if you want the last "updated" view (from db), not the last "viewed" view (from onyx). + */ + fetchData(ignoreLocalCurrentStep) { // We can specify a step to navigate to by using route params when the component mounts. // We want to use the same stepToOpen variable when the network state changes because we can be redirected to a different step when the account refreshes. const stepToOpen = this.getStepToOpenFromRouteParams(); - const reimbursementAccount = store.getReimbursementAccountInSetup(); - const subStep = reimbursementAccount.subStep || ''; - const localCurrentStep = reimbursementAccount.currentStep || ''; - BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, localCurrentStep); + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const subStep = achData.subStep || ''; + const localCurrentStep = achData.currentStep || ''; + BankAccounts.openReimbursementAccountPage(stepToOpen, subStep, ignoreLocalCurrentStep ? '' : localCurrentStep); } continue() { this.setState({ - shouldShowContinueSetupButton: false, + shouldHideContinueSetupButton: true, }); + this.fetchData(true); + } + + goBack() { + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); + const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + const subStep = achData.subStep; + const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; + const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN && achData.state !== BankAccount.STATE.LOCKED; + switch (currentStep) { + case CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT: + if (hasInProgressVBBA) { + this.setState({shouldHideContinueSetupButton: false}); + } + if (subStep) { + BankAccounts.setBankAccountSubStep(null); + } else { + Navigation.goBack(); + } + break; + case CONST.BANK_ACCOUNT.STEP.COMPANY: + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, {subStep: CONST.BANK_ACCOUNT.SUBSTEP.MANUAL}); + break; + case CONST.BANK_ACCOUNT.STEP.REQUESTOR: + if (shouldShowOnfido) { + BankAccounts.clearOnfidoToken(); + } else { + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.COMPANY); + } + break; + case CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT: + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + break; + case CONST.BANK_ACCOUNT.STEP.VALIDATION: + if (_.contains([BankAccount.STATE.VERIFYING, BankAccount.STATE.SETUP], achData.state)) { + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT); + } else if (achData.state === BankAccount.STATE.PENDING) { + this.setState({ + shouldHideContinueSetupButton: false, + }); + } else { + Navigation.goBack(); + } + break; + default: Navigation.goBack(); + } } render() { @@ -182,8 +243,9 @@ class ReimbursementAccountPage extends React.Component { // display. We can also specify a specific route to navigate to via route params when the component first // mounts which will set the achData.currentStep after the account data is fetched and overwrite the logical // next step. - const achData = lodashGet(this.props, 'reimbursementAccount.achData', {}); + const achData = lodashGet(this.props.reimbursementAccount, 'achData', {}); const currentStep = achData.currentStep || CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT; + if (this.props.reimbursementAccount.isLoading) { const isSubmittingVerificationsData = _.contains([ CONST.BANK_ACCOUNT.STEP.COMPANY, @@ -193,15 +255,33 @@ class ReimbursementAccountPage extends React.Component { return ( ); } - const hasInProgressVBBA = achData.bankAccountID && achData.state !== BankAccount.STATE.OPEN; - if (hasInProgressVBBA && this.state.shouldShowContinueSetupButton) { + if (this.props.reimbursementAccount.shouldShowResetModal && Boolean(achData.bankAccountID)) { + return ( + + ); + } + + // Show the "Continue with setup" button if a bank account setup is already in progress and no specific further step was passed in the url + if (!this.state.shouldHideContinueSetupButton + && Boolean(achData.bankAccountID) + && achData.state !== BankAccount.STATE.OPEN + && achData.state !== BankAccount.STATE.LOCKED + && ( + _.contains([CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT, ''], this.getStepToOpenFromRouteParams()) + || achData.state === BankAccount.STATE.PENDING + )) { return ( { + this.setState({shouldHideContinueSetupButton: true}); + BankAccounts.requestResetFreePlanBankAccount(); + }} /> ); } @@ -217,7 +297,7 @@ class ReimbursementAccountPage extends React.Component { ); } - const throttledDate = lodashGet(this.props, 'reimbursementAccount.throttledDate'); + const throttledDate = lodashGet(this.props.reimbursementAccount, 'throttledDate'); if (throttledDate) { errorComponent = ( @@ -239,33 +319,70 @@ class ReimbursementAccountPage extends React.Component { ); } - return ( - - {currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT && ( - (hasInProgressVBBA ? this.setState({shouldShowContinueSetupButton: true}) : BankAccounts.setBankAccountSubStep(null))} - /> - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION && ( - - )} - {currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE && ( - - )} - - - ); + + if (currentStep === CONST.BANK_ACCOUNT.STEP.BANK_ACCOUNT) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.COMPANY) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.REQUESTOR) { + const shouldShowOnfido = this.props.onfidoToken && !achData.isOnfidoSetupComplete; + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.ACH_CONTRACT) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.VALIDATION) { + return ( + + ); + } + + if (currentStep === CONST.BANK_ACCOUNT.STEP.ENABLE) { + return ( + + ); + } } } @@ -278,12 +395,18 @@ export default compose( reimbursementAccount: { key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, }, + reimbursementAccountDraft: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, + }, session: { key: ONYXKEYS.SESSION, }, plaidLinkToken: { key: ONYXKEYS.PLAID_LINK_TOKEN, }, + onfidoToken: { + key: ONYXKEYS.ONFIDO_TOKEN, + }, }), withLocalize, )(ReimbursementAccountPage); diff --git a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js index 60399a905aca..f03a3a2ad0a2 100644 --- a/src/pages/ReimbursementAccount/RequestorOnfidoStep.js +++ b/src/pages/ReimbursementAccount/RequestorOnfidoStep.js @@ -2,35 +2,26 @@ import React from 'react'; import {ScrollView} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import Onfido from '../../components/Onfido'; import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; -import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import Growl from '../../libs/Growl'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; -import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; import CONST from '../../CONST'; import FullPageOfflineBlockingView from '../../components/BlockingViews/FullPageOfflineBlockingView'; +import StepPropTypes from './StepPropTypes'; +import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; +import Navigation from '../../libs/Navigation/Navigation'; +import ScreenWrapper from '../../components/ScreenWrapper'; const propTypes = { - /** Bank account currently in setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The draft values of the bank account being setup */ - /* eslint-disable-next-line react/no-unused-prop-types */ - reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, + ...StepPropTypes, /** The token required to initialize the Onfido SDK */ onfidoToken: PropTypes.string.isRequired, - - /** A callback to call once the user completes the Onfido flow */ - onComplete: PropTypes.func.isRequired, - - ...withLocalizePropTypes, }; const defaultProps = {}; @@ -43,34 +34,46 @@ class RequestorOnfidoStep extends React.Component { submit(onfidoData) { BankAccounts.verifyIdentityForBankAccount( - ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0), + lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, onfidoData, ); - this.props.onComplete(); + + BankAccounts.updateReimbursementAccountDraft({isOnfidoSetupComplete: true}); } render() { return ( - - - { - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onError={() => { - // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. - Growl.error(this.props.translate('onfidoStep.genericError'), 10000); - BankAccounts.clearOnfidoToken(); - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); - }} - onSuccess={(onfidoData) => { - this.submit(onfidoData); - }} - /> - - + + + + + { + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }} + onError={() => { + // In case of any unexpected error we log it to the server, show a growl, and return the user back to the requestor step so they can try again. + Growl.error(this.props.translate('onfidoStep.genericError'), 10000); + BankAccounts.clearOnfidoToken(); + BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.REQUESTOR); + }} + onSuccess={(onfidoData) => { + this.submit(onfidoData); + }} + /> + + + ); } } @@ -81,14 +84,8 @@ RequestorOnfidoStep.defaultProps = defaultProps; export default compose( withLocalize, withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, onfidoToken: { key: ONYXKEYS.ONFIDO_TOKEN, }, - reimbursementAccountDraft: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT_DRAFT, - }, }), )(RequestorOnfidoStep); diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index b821f3d1b354..6823eedddfe3 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -1,11 +1,10 @@ import React from 'react'; -import lodashGet from 'lodash/get'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import moment from 'moment'; import PropTypes from 'prop-types'; +import lodashGet from 'lodash/get'; import styles from '../../styles/styles'; -import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import withLocalize from '../../components/withLocalize'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; import TextLink from '../../components/TextLink'; @@ -15,55 +14,25 @@ import Text from '../../components/Text'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import IdentityForm from './IdentityForm'; import * as ValidationUtils from '../../libs/ValidationUtils'; -import compose from '../../libs/compose'; import ONYXKEYS from '../../ONYXKEYS'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import RequestorOnfidoStep from './RequestorOnfidoStep'; import Form from '../../components/Form'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import StepPropTypes from './StepPropTypes'; const propTypes = { - /** The bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, - - /** The token required to initialize the Onfido SDK */ - onfidoToken: PropTypes.string, - - ...withLocalizePropTypes, -}; + ...StepPropTypes, -const defaultProps = { - onfidoToken: '', + /** If we should show Onfido flow */ + shouldShowOnfido: PropTypes.bool.isRequired, }; class RequestorStep extends React.Component { constructor(props) { super(props); - this.getDefaultStateForField = this.getDefaultStateForField.bind(this); this.validate = this.validate.bind(this); this.submit = this.submit.bind(this); - this.setOnfidoAsComplete = this.setOnfidoAsComplete.bind(this); - - this.state = { - isOnfidoSetupComplete: lodashGet(props, ['achData', 'isOnfidoSetupComplete'], false), - }; - } - - /** - * Update state to indicate that the user has completed the Onfido verification process - */ - setOnfidoAsComplete() { - this.setState({isOnfidoSetupComplete: true}); - } - - /** - * Get default value from reimbursementAccount or achData - * @param {String} fieldName - * @param {*} defaultValue - * @returns {String} - */ - getDefaultStateForField(fieldName, defaultValue) { - return lodashGet(this.props, ['reimbursementAccount', 'achData', fieldName], defaultValue); } /** @@ -122,7 +91,7 @@ class RequestorStep extends React.Component { submit(values) { const payload = { - bankAccountID: this.getDefaultStateForField('bankAccountID', 0), + bankAccountID: lodashGet(this.props.reimbursementAccount, 'achData.bankAccountID') || 0, ...values, dob: moment(values.dob).format(CONST.DATE.MOMENT_FORMAT_STRING), }; @@ -131,134 +100,119 @@ class RequestorStep extends React.Component { } render() { - const achData = this.props.reimbursementAccount.achData; - const shouldShowOnfido = achData.useOnfido && this.props.onfidoToken && !this.state.isOnfidoSetupComplete; + if (this.props.shouldShowOnfido) { + return ( + + ); + } return ( - <> + { - if (shouldShowOnfido) { - BankAccounts.clearOnfidoToken(); - } else { - BankAccounts.goToWithdrawalAccountSetupStep(CONST.BANK_ACCOUNT.STEP.COMPANY); - } - }} + onBackButtonPress={this.props.onBackButtonPress} onCloseButtonPress={Navigation.dismissModal} /> - {shouldShowOnfido ? ( - + {this.props.translate('requestorStep.subtitle')} + + + {`${this.props.translate('requestorStep.learnMore')}`} + + {' | '} + + {`${this.props.translate('requestorStep.isMyDataSafe')}`} + + + + ( + + + {this.props.translate('requestorStep.isControllingOfficer')} + + + )} + style={[styles.mt4]} + shouldSaveDraft /> - ) : ( -
- {this.props.translate('requestorStep.subtitle')} - - - {`${this.props.translate('requestorStep.learnMore')}`} - - {' | '} - - {`${this.props.translate('requestorStep.isMyDataSafe')}`} - - - - ( - - - {this.props.translate('requestorStep.isControllingOfficer')} - - - )} - style={[styles.mt4]} - shouldSaveDraft - /> - - {this.props.translate('requestorStep.onFidoConditions')} - - {this.props.translate('onfidoStep.facialScan')} - - {', '} - - {this.props.translate('common.privacy')} - - {` ${this.props.translate('common.and')} `} - - {this.props.translate('common.termsOfService')} - - - - )} - + + {this.props.translate('requestorStep.onFidoConditions')} + + {this.props.translate('onfidoStep.facialScan')} + + {', '} + + {this.props.translate('common.privacy')} + + {` ${this.props.translate('common.and')} `} + + {this.props.translate('common.termsOfService')} + + + +
); } } RequestorStep.propTypes = propTypes; -RequestorStep.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - onfidoToken: { - key: ONYXKEYS.ONFIDO_TOKEN, - }, - }), -)(RequestorStep); +export default withLocalize(RequestorStep); diff --git a/src/pages/ReimbursementAccount/StepPropTypes.js b/src/pages/ReimbursementAccount/StepPropTypes.js new file mode 100644 index 000000000000..dc9734c6ecf7 --- /dev/null +++ b/src/pages/ReimbursementAccount/StepPropTypes.js @@ -0,0 +1,20 @@ +import PropTypes from 'prop-types'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; +import reimbursementAccountDraftPropTypes from './ReimbursementAccountDraftPropTypes'; +import {withLocalizePropTypes} from '../../components/withLocalize'; + +export default { + /** The bank account currently in setup */ + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, + + /** The draft values of the bank account being setup */ + reimbursementAccountDraft: reimbursementAccountDraftPropTypes.isRequired, + + /** Goes to the previous step */ + onBackButtonPress: PropTypes.func.isRequired, + + /** Get a field value from Onyx reimbursementAccountDraft or reimbursementAccount */ + getDefaultStateForField: PropTypes.func.isRequired, + + ...withLocalizePropTypes, +}; diff --git a/src/pages/ReimbursementAccount/ValidationStep.js b/src/pages/ReimbursementAccount/ValidationStep.js index 7458f4271339..282c1c0fa974 100644 --- a/src/pages/ReimbursementAccount/ValidationStep.js +++ b/src/pages/ReimbursementAccount/ValidationStep.js @@ -1,9 +1,9 @@ import lodashGet from 'lodash/get'; import React from 'react'; import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; import Str from 'expensify-common/lib/str'; import _ from 'underscore'; +import PropTypes from 'prop-types'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import * as BankAccounts from '../../libs/actions/BankAccounts'; @@ -15,10 +15,9 @@ import Text from '../../components/Text'; import BankAccount from '../../libs/models/BankAccount'; import TextLink from '../../components/TextLink'; import ONYXKEYS from '../../ONYXKEYS'; -import compose from '../../libs/compose'; import * as ValidationUtils from '../../libs/ValidationUtils'; import EnableStep from './EnableStep'; -import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from './reimbursementAccountPropTypes'; import Form from '../../components/Form'; import * as Expensicons from '../../components/Icon/Expensicons'; import * as Illustrations from '../../components/Icon/Illustrations'; @@ -31,15 +30,9 @@ const propTypes = { ...withLocalizePropTypes, /** Bank account currently in setup */ - reimbursementAccount: reimbursementAccountPropTypes, -}; + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, -const defaultProps = { - reimbursementAccount: { - errorFields: {}, - errors: {}, - maxAttemptsReached: false, - }, + onBackButtonPress: PropTypes.func.isRequired, }; class ValidationStep extends React.Component { @@ -107,14 +100,14 @@ class ValidationStep extends React.Component { } render() { - const state = lodashGet(this.props, 'reimbursementAccount.achData.state'); + const state = lodashGet(this.props.reimbursementAccount, 'achData.state'); // If a user tries to navigate directly to the validate page we'll show them the EnableStep if (state === BankAccount.STATE.OPEN) { return ; } - const maxAttemptsReached = lodashGet(this.props, 'reimbursementAccount.maxAttemptsReached'); + const maxAttemptsReached = lodashGet(this.props.reimbursementAccount, 'maxAttemptsReached'); const isVerifying = !maxAttemptsReached && state === BankAccount.STATE.VERIFYING; return ( @@ -123,7 +116,7 @@ class ValidationStep extends React.Component { title={isVerifying ? this.props.translate('validationStep.headerTitle') : this.props.translate('workspace.common.testTransactions')} stepCounter={{step: 5, total: 5}} onCloseButtonPress={Navigation.dismissModal} - onBackButtonPress={() => Navigation.goBack()} + onBackButtonPress={this.props.onBackButtonPress} shouldShowGetAssistanceButton guidesCallTaskID={CONST.GUIDES_CALL_TASK_IDS.WORKSPACE_BANK_ACCOUNT} shouldShowBackButton @@ -219,13 +212,5 @@ class ValidationStep extends React.Component { } ValidationStep.propTypes = propTypes; -ValidationStep.defaultProps = defaultProps; - -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - }), -)(ValidationStep); + +export default withLocalize(ValidationStep); diff --git a/src/pages/ReimbursementAccount/plaidDataPropTypes.js b/src/pages/ReimbursementAccount/plaidDataPropTypes.js index ae4f3b896b4c..e8db5c087551 100644 --- a/src/pages/ReimbursementAccount/plaidDataPropTypes.js +++ b/src/pages/ReimbursementAccount/plaidDataPropTypes.js @@ -1,14 +1,17 @@ import PropTypes from 'prop-types'; -export default PropTypes.shape({ +const plaidDataPropTypes = PropTypes.shape({ /** Whether we are fetching the bank accounts from the API */ isLoading: PropTypes.bool, - /** Error message */ - error: PropTypes.string, + /** Any additional error message to show */ + errors: PropTypes.objectOf(PropTypes.string), - /** Whether we should prevent the user from connecting with Plaid */ - isPlaidDisabled: PropTypes.bool, + /** Name of the bank */ + bankName: PropTypes.string, + + /** Access token returned by Plaid once the user has logged into their bank. This token can be used along with internal credentials to query for Plaid Balance or Assets */ + plaidAccessToken: PropTypes.string, /** List of plaid bank accounts */ bankAccounts: PropTypes.arrayOf(PropTypes.shape({ @@ -34,3 +37,14 @@ export default PropTypes.shape({ plaidAccessToken: PropTypes.string, })), }); + +const plaidDataDefaultProps = { + bankName: '', + plaidAccessToken: '', + bankAccounts: [], + isLoading: false, + error: '', + errors: {}, +}; + +export {plaidDataPropTypes, plaidDataDefaultProps}; diff --git a/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js b/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js index e077b8668cce..abac3c89a80e 100644 --- a/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js +++ b/src/pages/ReimbursementAccount/reimbursementAccountPropTypes.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; +import BankAccount from '../../libs/models/BankAccount'; -export default PropTypes.shape({ +const reimbursementAccountPropTypes = PropTypes.shape({ /** Whether we are loading the data via the API */ isLoading: PropTypes.bool, @@ -33,3 +34,16 @@ export default PropTypes.shape({ /** Any additional error message to show */ errors: PropTypes.objectOf(PropTypes.string), }); + +const reimbursementAccountDefaultProps = { + achData: { + state: BankAccount.STATE.SETUP, + }, + isLoading: false, + errorFields: {}, + errors: {}, + maxAttemptsReached: false, + shouldShowResetModal: false, +}; + +export {reimbursementAccountPropTypes, reimbursementAccountDefaultProps}; diff --git a/src/pages/settings/Payments/ChooseTransferAccountPage.js b/src/pages/settings/Payments/ChooseTransferAccountPage.js index 99d0b1fe2623..665b69b822ed 100644 --- a/src/pages/settings/Payments/ChooseTransferAccountPage.js +++ b/src/pages/settings/Payments/ChooseTransferAccountPage.js @@ -15,6 +15,7 @@ import compose from '../../../libs/compose'; import ONYXKEYS from '../../../ONYXKEYS'; import walletTransferPropTypes from './walletTransferPropTypes'; import styles from '../../../styles/styles'; +import * as BankAccounts from '../../../libs/actions/BankAccounts'; const propTypes = { /** Wallet transfer propTypes */ @@ -52,7 +53,7 @@ const ChooseTransferAccountPage = (props) => { Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD); return; } - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + BankAccounts.openPersonalBankAccountSetupView(); }; return ( diff --git a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js index 4279fb928b04..df20970bf278 100644 --- a/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage/BasePaymentsPage.js @@ -215,7 +215,7 @@ class BasePaymentsPage extends React.Component { } if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { - Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + BankAccounts.openPersonalBankAccountSetupView(); return; } diff --git a/src/pages/workspace/WorkspacePageWithSections.js b/src/pages/workspace/WorkspacePageWithSections.js index 89b2a4ec1950..eb5d17c0a3c2 100644 --- a/src/pages/workspace/WorkspacePageWithSections.js +++ b/src/pages/workspace/WorkspacePageWithSections.js @@ -14,7 +14,7 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import ONYXKEYS from '../../ONYXKEYS'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import BankAccount from '../../libs/models/BankAccount'; -import reimbursementAccountPropTypes from '../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../ReimbursementAccount/reimbursementAccountPropTypes'; import userPropTypes from '../settings/userPropTypes'; import withPolicy from './withPolicy'; import {withNetwork} from '../../components/OnyxProvider'; @@ -42,7 +42,7 @@ const propTypes = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, /** User Data from Onyx */ user: userPropTypes, @@ -70,7 +70,7 @@ const propTypes = { const defaultProps = { children: () => {}, user: {}, - reimbursementAccount: {}, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, footer: null, guidesCallTaskID: '', shouldUseScrollView: false, diff --git a/src/pages/workspace/WorkspaceResetBankAccountModal.js b/src/pages/workspace/WorkspaceResetBankAccountModal.js index 63464ae7f6ee..835bb43ee9cd 100644 --- a/src/pages/workspace/WorkspaceResetBankAccountModal.js +++ b/src/pages/workspace/WorkspaceResetBankAccountModal.js @@ -1,39 +1,26 @@ -import _ from 'underscore'; import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; import React from 'react'; -import {withOnyx} from 'react-native-onyx'; import ConfirmModal from '../../components/ConfirmModal'; import * as BankAccounts from '../../libs/actions/BankAccounts'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import reimbursementAccountPropTypes from '../ReimbursementAccount/reimbursementAccountPropTypes'; -import compose from '../../libs/compose'; -import ONYXKEYS from '../../ONYXKEYS'; -import bankAccountPropTypes from '../../components/bankAccountPropTypes'; +import * as ReimbursementAccountProps from '../ReimbursementAccount/reimbursementAccountPropTypes'; import Text from '../../components/Text'; import styles from '../../styles/styles'; import BankAccount from '../../libs/models/BankAccount'; const propTypes = { /** Reimbursement account data */ - reimbursementAccount: reimbursementAccountPropTypes, - - /** List of bank accounts */ - bankAccountList: PropTypes.objectOf(bankAccountPropTypes), + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, ...withLocalizePropTypes, }; -const defaultProps = { - reimbursementAccount: {}, - bankAccountList: {}, -}; - const WorkspaceResetBankAccountModal = (props) => { - const isInOpenState = lodashGet(props.reimbursementAccount, 'achData.state') === BankAccount.STATE.OPEN; - const bankAccountID = lodashGet(props.reimbursementAccount, 'achData.bankAccountID'); - const account = _.find(props.bankAccountList, bankAccount => bankAccount.bankAccountID === bankAccountID); - const bankShortName = account ? `${lodashGet(account, 'addressName', '')} ${lodashGet(account, 'accountNumber', '').slice(-4)}` : ''; + const achData = lodashGet(props.reimbursementAccount, 'achData') || {}; + const isInOpenState = achData.state === BankAccount.STATE.OPEN; + const bankAccountID = achData.bankAccountID; + const bankShortName = `${achData.addressName || ''} ${(achData.accountNumber || '').slice(-4)}`; + return ( { ) : props.translate('workspace.bankAccount.clearProgress')} danger onCancel={BankAccounts.cancelResetFreePlanBankAccount} - onConfirm={() => BankAccounts.resetFreePlanBankAccount()} + onConfirm={() => BankAccounts.resetFreePlanBankAccount(bankAccountID)} shouldShowCancelButton - isVisible={lodashGet(props.reimbursementAccount, 'shouldShowResetModal', false)} + isVisible /> ); }; WorkspaceResetBankAccountModal.displayName = 'WorkspaceResetBankAccountModal'; WorkspaceResetBankAccountModal.propTypes = propTypes; -WorkspaceResetBankAccountModal.defaultProps = defaultProps; -export default compose( - withLocalize, - withOnyx({ - reimbursementAccount: { - key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, - }, - bankAccountList: { - key: ONYXKEYS.BANK_ACCOUNT_LIST, - initWithStoredValues: false, - }, - }), -)(WorkspaceResetBankAccountModal); +export default withLocalize(WorkspaceResetBankAccountModal); diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index d6d940c4e09f..20f443bf5671 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -12,7 +12,7 @@ import Section from '../../../components/Section'; import * as Link from '../../../libs/actions/Link'; import Button from '../../../components/Button'; import BankAccount from '../../../libs/models/BankAccount'; -import reimbursementAccountPropTypes from '../../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes'; import * as ReimbursementAccount from '../../../libs/actions/ReimbursementAccount'; import networkPropTypes from '../../../components/networkPropTypes'; import CONST from '../../../CONST'; @@ -24,7 +24,7 @@ const propTypes = { }).isRequired, /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes.isRequired, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes.isRequired, /** Information about the network */ network: networkPropTypes.isRequired, diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index 68369a2d8b14..f6f8367d210e 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -18,7 +18,7 @@ import compose from '../../../libs/compose'; import * as Policy from '../../../libs/actions/Policy'; import CONST from '../../../CONST'; import ONYXKEYS from '../../../ONYXKEYS'; -import reimbursementAccountPropTypes from '../../ReimbursementAccount/reimbursementAccountPropTypes'; +import * as ReimbursementAccountProps from '../../ReimbursementAccount/reimbursementAccountPropTypes'; import getPermittedDecimalSeparator from '../../../libs/getPermittedDecimalSeparator'; import {withNetwork} from '../../../components/OnyxProvider'; import OfflineWithFeedback from '../../../components/OfflineWithFeedback'; @@ -53,7 +53,7 @@ const propTypes = { /** From Onyx */ /** Bank account attached to free plan */ - reimbursementAccount: reimbursementAccountPropTypes, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountPropTypes, /** Information about the network */ network: networkPropTypes.isRequired, @@ -62,7 +62,7 @@ const propTypes = { }; const defaultProps = { - reimbursementAccount: {isLoading: true}, + reimbursementAccount: ReimbursementAccountProps.reimbursementAccountDefaultProps, }; class WorkspaceReimburseView extends React.Component { @@ -179,14 +179,11 @@ class WorkspaceReimburseView extends React.Component { } fetchData() { - const subStep = this.props.reimbursementAccount.subStep || ''; - const localCurrentStep = this.props.reimbursementAccount.currentStep || ''; - // Instead of setting the reimbursement account loading within the optimistic data of the API command, use a separate action so that the Onyx value is updated right away. // openWorkspaceReimburseView uses API.read which will not make the request until all WRITE requests in the sequential queue have finished responding, so there would be a delay in // updating Onyx with the optimistic data. BankAccounts.setReimbursementAccountLoading(true); - Policy.openWorkspaceReimburseView(this.props.policy.id, subStep, localCurrentStep); + Policy.openWorkspaceReimburseView(this.props.policy.id); } debounceUpdateOnCursorMove(event) {