From 9aa0509b3b155e9adbb95d8a32f78f7d3822c350 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Tue, 7 Dec 2021 14:44:53 -1000 Subject: [PATCH 1/2] AddPaymentMethodMenu Refactor Add Payment method menu and link to add personal bank account flow. fix password requirement on plaid modal. Rm console.log Only add ability to add deposit account for now require password param add password requirement back to plaid form but make it optional add some loading spinner polish --- src/CONST.js | 6 ++ src/ROUTES.js | 1 + src/components/AddPaymentMethodMenu.js | 71 +++++++++++++++++++ src/components/AddPlaidBankAccount.js | 41 ++++++++++- src/languages/en.js | 2 + src/languages/es.js | 2 + .../AppNavigator/ModalStackNavigators.js | 4 ++ src/libs/Navigation/linkingConfig.js | 4 ++ src/libs/actions/BankAccounts.js | 22 +++++- src/pages/AddPersonalBankAccountPage.js | 3 + .../ReimbursementAccountForm.js | 1 + src/pages/settings/Payments/PaymentsPage.js | 50 +++++-------- 12 files changed, 169 insertions(+), 38 deletions(-) create mode 100644 src/components/AddPaymentMethodMenu.js diff --git a/src/CONST.js b/src/CONST.js index 83922e707316..78e1ac329947 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -432,6 +432,12 @@ const CONST = { OTHER: 'other', }, + PAYMENT_METHODS: { + PAYPAL: 'payPalMe', + DEBIT_CARD: 'debitCard', + BANK_ACCOUNT: 'bankAccount', + }, + IOU: { // Note: These payment types are used when building IOU reportAction message values in the server and should // not be changed. diff --git a/src/ROUTES.js b/src/ROUTES.js index f3b856a469de..463ecdd1126f 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -29,6 +29,7 @@ export default { SETTINGS_PAYMENTS: 'settings/payments', SETTINGS_ADD_PAYPAL_ME: 'settings/payments/add-paypal-me', SETTINGS_ADD_DEBIT_CARD: 'settings/payments/add-debit-card', + SETTINGS_ADD_BANK_ACCOUNT: 'settings/payments/add-bank-account', SETTINGS_ADD_LOGIN: 'settings/addlogin/:type', getSettingsAddLoginRoute: type => `settings/addlogin/${type}`, NEW_GROUP: 'new/group', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js new file mode 100644 index 000000000000..5e77e2fd47b8 --- /dev/null +++ b/src/components/AddPaymentMethodMenu.js @@ -0,0 +1,71 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import Popover from './Popover'; +import MenuItem from './MenuItem'; +import * as Expensicons from './Icon/Expensicons'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; +import styles from '../styles/styles'; +import compose from '../libs/compose'; +import ONYXKEYS from '../ONYXKEYS'; +import CONST from '../CONST'; + +const propTypes = { + isVisible: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + anchorPosition: PropTypes.shape({ + top: PropTypes.number, + left: PropTypes.number, + }), + + /** Username for PayPal.Me */ + payPalMeUsername: PropTypes.string, + + ...withLocalizePropTypes, +}; + +const defaultProps = { + anchorPosition: {}, + payPalMeUsername: '', +}; + +const AddPaymentMethodMenu = props => ( + + props.onItemSelected(CONST.PAYMENT_METHODS.BANK_ACCOUNT)} + wrapperStyle={styles.pr15} + /> + props.onItemSelected(CONST.PAYMENT_METHODS.DEBIT_CARD)} + wrapperStyle={styles.pr15} + /> + {!props.payPalMeUsername && ( + props.onItemSelected(CONST.PAYMENT_METHODS.PAYPAL)} + wrapperStyle={styles.pr15} + /> + )} + +); + +AddPaymentMethodMenu.propTypes = propTypes; +AddPaymentMethodMenu.defaultProps = defaultProps; + +export default compose( + withLocalize, + withOnyx({ + payPalMeUsername: { + key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, + }, + }), +)(AddPaymentMethodMenu); diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index abd2f6b5b8cb..264683ae7ba9 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -12,6 +12,7 @@ import PlaidLink from './PlaidLink'; import * as BankAccounts from '../libs/actions/BankAccounts'; import ONYXKEYS from '../ONYXKEYS'; import styles from '../styles/styles'; +import canFocusInputOnScreenFocus from '../libs/canFocusInputOnScreenFocus'; import themeColors from '../styles/themes/default'; import compose from '../libs/compose'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; @@ -21,10 +22,9 @@ import * as ReimbursementAccountUtils from '../libs/ReimbursementAccountUtils'; import ReimbursementAccountForm from '../pages/ReimbursementAccount/ReimbursementAccountForm'; import getBankIcon from './Icon/BankIcons'; import Icon from './Icon'; +import ExpensiTextInput from './ExpensiTextInput'; const propTypes = { - ...withLocalizePropTypes, - /** Plaid SDK token to use to initialize the widget */ plaidLinkToken: PropTypes.string, @@ -78,6 +78,11 @@ const propTypes = { /** During the OAuth flow we need to use the plaidLink token that we initially connected with */ plaidLinkOAuthToken: PropTypes.string, + + /** Should we require a password to create a bank account? */ + isPasswordRequired: PropTypes.bool, + + ...withLocalizePropTypes, }; const defaultProps = { @@ -90,6 +95,7 @@ const defaultProps = { text: '', receivedRedirectURI: null, plaidLinkOAuthToken: '', + isPasswordRequired: false, }; class AddPlaidBankAccount extends React.Component { @@ -102,10 +108,14 @@ class AddPlaidBankAccount extends React.Component { this.state = { selectedIndex: undefined, institution: {}, + password: '', }; this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); + this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, { + password: 'passwordForm.error.incorrectLoginOrPassword', + }, inputKey); } componentDidMount() { @@ -119,6 +129,10 @@ class AddPlaidBankAccount extends React.Component { BankAccounts.fetchPlaidLinkToken(); } + componentWillUnmount() { + BankAccounts.setBankAccountFormValidationErrors({}); + } + /** * Get list of bank accounts * @@ -149,6 +163,12 @@ class AddPlaidBankAccount extends React.Component { if (_.isUndefined(this.state.selectedIndex)) { errors.selectedBank = true; } + + + if (this.props.isPasswordRequired && _.isEmpty(this.state.password)) { + errors.password = true; + } + BankAccounts.setBankAccountFormValidationErrors(errors); return _.size(errors) === 0; } @@ -165,6 +185,7 @@ class AddPlaidBankAccount extends React.Component { bankName, account, plaidLinkToken: this.getPlaidLinkToken(), + password: this.state.password, }); } @@ -233,6 +254,22 @@ class AddPlaidBankAccount extends React.Component { hasError={this.getErrors().selectedBank} /> + {!_.isUndefined(this.state.selectedIndex) && this.props.isPasswordRequired && ( + + this.setState({password: text})} + errorText={this.getErrorText('password')} + hasError={this.getErrors().password} + /> + + )} )} diff --git a/src/languages/en.js b/src/languages/en.js index 85f944a7380d..94a88cfc9599 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -89,6 +89,7 @@ export default { more: 'More', debitCard: 'Debit card', payPalMe: 'PayPal.me', + bankAccount: 'Bank account', }, attachmentPicker: { cameraPermissionRequired: 'Camera permission required', @@ -475,6 +476,7 @@ export default { }, }, addPersonalBankAccountPage: { + enterPassword: 'Enter Expensify password', alreadyAdded: 'This account has already been added.', chooseAccountLabel: 'Account', }, diff --git a/src/languages/es.js b/src/languages/es.js index db90ccfbec0c..b319106a03bd 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -89,6 +89,7 @@ export default { more: 'Más', debitCard: 'Tarjeta de débito', payPalMe: 'PayPal.me', + bankAccount: 'Cuenta bancaria', }, attachmentPicker: { cameraPermissionRequired: 'Se necesita permiso para usar la cámara', @@ -475,6 +476,7 @@ export default { }, }, addPersonalBankAccountPage: { + enterPassword: 'Escribe tu contraseña de Expensify', alreadyAdded: 'Esta cuenta ya ha sido agregada.', chooseAccountLabel: 'Cuenta', }, diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 4b998a632ee4..c8d5677ea301 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -182,6 +182,10 @@ const SettingsModalStackNavigator = createModalStackNavigator([ Component: SettingsAddDebitCardPage, name: 'Settings_Add_Debit_Card', }, + { + Component: AddPersonalBankAccountPage, + name: 'Settings_Add_Bank_Account', + }, { Component: WorkspaceInitialPage, name: 'Workspace_Initial', diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 4e89c80ec813..be838f7391c3 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -56,6 +56,10 @@ export default { path: ROUTES.SETTINGS_ADD_DEBIT_CARD, exact: true, }, + Settings_Add_Bank_Account: { + path: ROUTES.SETTINGS_ADD_BANK_ACCOUNT, + exact: true, + }, Settings_Profile: { path: ROUTES.SETTINGS_PROFILE, exact: true, diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index 587b33c94ae2..245c4fb02a83 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -1,7 +1,12 @@ import _ from 'underscore'; +import Onyx from 'react-native-onyx'; import CONST from '../../CONST'; import * as API from '../API'; import * as Plaid from './Plaid'; +import * as ReimbursementAccount from './ReimbursementAccount'; +import Navigation from '../Navigation/Navigation'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as PaymentMethods from './PaymentMethods'; export { setupWithdrawalAccount, @@ -41,6 +46,7 @@ export { * @param {String} plaidLinkToken */ function addPersonalBankAccount(account, password, plaidLinkToken) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: true}); const unmaskedAccount = _.find(Plaid.getPlaidBankAccounts(), bankAccount => ( bankAccount.plaidAccountID === account.plaidAccountID )); @@ -71,12 +77,22 @@ function addPersonalBankAccount(account, password, plaidLinkToken) { }), }) .then((response) => { - if (response.jsonCode !== 200) { - alert('There was a problem adding this bank account.'); + if (response.jsonCode === 200) { + PaymentMethods.getPaymentMethods() + .then(() => { + Navigation.goBack(); + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + }); return; } - alert('Bank account added successfully.'); + if (response.message === 'Incorrect Expensify password entered') { + ReimbursementAccount.setBankAccountFormValidationErrors({password: true}); + } + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); + }) + .catch(() => { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {loading: false}); }); } diff --git a/src/pages/AddPersonalBankAccountPage.js b/src/pages/AddPersonalBankAccountPage.js index 0736f19bca85..187b65ee696f 100644 --- a/src/pages/AddPersonalBankAccountPage.js +++ b/src/pages/AddPersonalBankAccountPage.js @@ -27,6 +27,8 @@ const AddPersonalBankAccountPage = props => ( Navigation.goBack()} /> { @@ -35,6 +37,7 @@ const AddPersonalBankAccountPage = props => ( onExitPlaid={Navigation.dismissModal} receivedRedirectURI={getPlaidOAuthReceivedRedirectURI()} plaidLinkOAuthToken={props.plaidLinkToken} + isPasswordRequired /> ); diff --git a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js index e31669167471..ea4d2ef7c519 100644 --- a/src/pages/ReimbursementAccount/ReimbursementAccountForm.js +++ b/src/pages/ReimbursementAccount/ReimbursementAccountForm.js @@ -61,6 +61,7 @@ class ReimbursementAccountForm extends React.Component { }} message={this.props.reimbursementAccount.errorModalMessage} isMessageHtml={this.props.reimbursementAccount.isErrorModalMessageHtml} + isLoading={this.props.reimbursementAccount.loading} /> ); diff --git a/src/pages/settings/Payments/PaymentsPage.js b/src/pages/settings/Payments/PaymentsPage.js index d686c3163662..a49b2ada357f 100644 --- a/src/pages/settings/Payments/PaymentsPage.js +++ b/src/pages/settings/Payments/PaymentsPage.js @@ -13,16 +13,12 @@ import compose from '../../../libs/compose'; import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView/index'; import ExpensifyText from '../../../components/ExpensifyText'; import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; -import Popover from '../../../components/Popover'; -import * as Expensicons from '../../../components/Icon/Expensicons'; -import MenuItem from '../../../components/MenuItem'; import getClickedElementLocation from '../../../libs/getClickedElementLocation'; import CurrentWalletBalance from '../../../components/CurrentWalletBalance'; import ONYXKEYS from '../../../ONYXKEYS'; import Permissions from '../../../libs/Permissions'; - -const PAYPAL = 'payPalMe'; -const DEBIT_CARD = 'debitCard'; +import AddPaymentMethodMenu from '../../../components/AddPaymentMethodMenu'; +import CONST from '../../../CONST'; const propTypes = { ...withLocalizePropTypes, @@ -32,15 +28,11 @@ const propTypes = { /** Are we loading payment methods? */ isLoadingPaymentMethods: PropTypes.bool, - - /** Username for PayPal.Me */ - payPalMeUsername: PropTypes.string, }; const defaultProps = { betas: [], isLoadingPaymentMethods: true, - payPalMeUsername: '', }; class PaymentsPage extends React.Component { @@ -70,7 +62,7 @@ class PaymentsPage extends React.Component { */ paymentMethodPressed(nativeEvent, account) { if (account) { - if (account === PAYPAL) { + if (account === CONST.PAYMENT_METHODS.PAYPAL) { Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME); } } else { @@ -93,13 +85,22 @@ class PaymentsPage extends React.Component { addPaymentMethodTypePressed(paymentType) { this.hideAddPaymentMenu(); - if (paymentType === PAYPAL) { + if (paymentType === CONST.PAYMENT_METHODS.PAYPAL) { Navigation.navigate(ROUTES.SETTINGS_ADD_PAYPAL_ME); + return; } - if (paymentType === DEBIT_CARD) { + if (paymentType === CONST.PAYMENT_METHODS.DEBIT_CARD) { Navigation.navigate(ROUTES.SETTINGS_ADD_DEBIT_CARD); + return; + } + + if (paymentType === CONST.PAYMENT_METHODS.BANK_ACCOUNT) { + Navigation.navigate(ROUTES.SETTINGS_ADD_BANK_ACCOUNT); + return; } + + throw new Error('Invalid payment method type selected'); } /** @@ -135,29 +136,15 @@ class PaymentsPage extends React.Component { isAddPaymentMenuActive={this.state.shouldShowAddPaymentMenu} /> - - {!this.props.payPalMeUsername && ( - this.addPaymentMethodTypePressed(PAYPAL)} - wrapperStyle={styles.pr15} - /> - )} - this.addPaymentMethodTypePressed(DEBIT_CARD)} - wrapperStyle={styles.pr15} - /> - + onItemSelected={method => this.addPaymentMethodTypePressed(method)} + /> ); @@ -177,8 +164,5 @@ export default compose( key: ONYXKEYS.IS_LOADING_PAYMENT_METHODS, initWithStoredValues: false, }, - payPalMeUsername: { - key: ONYXKEYS.NVP_PAYPAL_ME_ADDRESS, - }, }), )(PaymentsPage); From 964d81b6c8ffeedde9af35f0eb9ad192efe00884 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 8 Dec 2021 08:44:52 -1000 Subject: [PATCH 2/2] remove empty new line --- src/components/AddPlaidBankAccount.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 264683ae7ba9..2950755bd77e 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -164,7 +164,6 @@ class AddPlaidBankAccount extends React.Component { errors.selectedBank = true; } - if (this.props.isPasswordRequired && _.isEmpty(this.state.password)) { errors.password = true; }