diff --git a/src/components/TextInputWithLabel.js b/src/components/TextInputWithLabel.js index 40ac02a9fcbc..310a4a91ca31 100644 --- a/src/components/TextInputWithLabel.js +++ b/src/components/TextInputWithLabel.js @@ -54,7 +54,12 @@ const TextInputWithLabel = props => ( )} diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index 28b3075e45ab..8c6522a040c2 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -1,7 +1,7 @@ import moment from 'moment'; import CONST from '../CONST'; -import Growl from './Growl'; import {translateLocal} from './translate'; +import {showBankAccountFormValidationError} from './actions/BankAccounts'; /** * Validating that this is a valid address (PO boxes are not allowed) @@ -74,27 +74,27 @@ function isValidSSNLastFour(ssnLast4) { */ function isValidIdentity(identity) { if (!isValidAddress(identity.street)) { - Growl.error(translateLocal('bankAccount.error.address')); + showBankAccountFormValidationError(translateLocal('bankAccount.error.address')); return false; } if (identity.state === '') { - Growl.error(translateLocal('bankAccount.error.addressState')); + showBankAccountFormValidationError(translateLocal('bankAccount.error.addressState')); return false; } if (!isValidZipCode(identity.zipCode)) { - Growl.error(translateLocal('bankAccount.error.zipCode')); + showBankAccountFormValidationError(translateLocal('bankAccount.error.zipCode')); return false; } if (!isValidDate(identity.dob)) { - Growl.error(translateLocal('bankAccount.error.dob')); + showBankAccountFormValidationError(translateLocal('bankAccount.error.dob')); return false; } if (!isValidSSNLastFour(identity.ssnLast4)) { - Growl.error(translateLocal('bankAccount.error.ssnLast4')); + showBankAccountFormValidationError(translateLocal('bankAccount.error.ssnLast4')); return false; } diff --git a/src/libs/actions/BankAccounts.js b/src/libs/actions/BankAccounts.js index bd5ebe15bf8e..e0964b1b1a43 100644 --- a/src/libs/actions/BankAccounts.js +++ b/src/libs/actions/BankAccounts.js @@ -592,6 +592,10 @@ function validateBankAccount(bankAccountID, validateCode) { }); } +function showBankAccountFormValidationError(error) { + Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error}).then(() => Growl.error(error)); +} + /** * Create or update the bank account in db with the updated data. * @@ -763,7 +767,7 @@ function setupWithdrawalAccount(data) { goToWithdrawalAccountSetupStep(nextStep, achData); if (error) { - Growl.error(error, 5000); + showBankAccountFormValidationError(error); } }) .catch((response) => { @@ -773,7 +777,7 @@ function setupWithdrawalAccount(data) { }); } -function hideExistingOwnersError() { +function hideBankAccountErrors() { Onyx.merge(ONYXKEYS.REIMBURSEMENT_ACCOUNT, {error: '', existingOwnersList: ''}); } @@ -789,5 +793,6 @@ export { goToWithdrawalAccountSetupStep, setupWithdrawalAccount, validateBankAccount, - hideExistingOwnersError, + hideBankAccountErrors, + showBankAccountFormValidationError, }; diff --git a/src/pages/ReimbursementAccount/BankAccountStep.js b/src/pages/ReimbursementAccount/BankAccountStep.js index bcedfdb74cd2..3fe264dc5e4a 100644 --- a/src/pages/ReimbursementAccount/BankAccountStep.js +++ b/src/pages/ReimbursementAccount/BankAccountStep.js @@ -23,7 +23,7 @@ import Text from '../../components/Text'; import ExpensiTextInput from '../../components/ExpensiTextInput'; import { goToWithdrawalAccountSetupStep, - hideExistingOwnersError, + hideBankAccountErrors, setupWithdrawalAccount, } from '../../libs/actions/BankAccounts'; import ConfirmModal from '../../components/ConfirmModal'; @@ -130,8 +130,8 @@ class BankAccountStep extends React.Component { const isFromPlaid = this.props.achData.setupType === CONST.BANK_ACCOUNT.SETUP_TYPE.PLAID; const shouldDisableInputs = Boolean(this.props.achData.bankAccountID) || isFromPlaid; const existingOwners = this.props.reimbursementAccount.existingOwners; - const isExistingOwnersErrorVisible = Boolean(this.props.reimbursementAccount.error - && existingOwners); + const error = this.props.reimbursementAccount.error; + const isExistingOwnersErrorVisible = Boolean(error && existingOwners); return ( this.setState({routingNumber})} + onChangeText={(routingNumber) => { + if (error === this.props.translate('bankAccount.error.routingNumber')) { + hideBankAccountErrors(); + } + this.setState({routingNumber}); + }} disabled={shouldDisableInputs} + errorText={error === this.props.translate('bankAccount.error.routingNumber') + ? error : ''} /> diff --git a/src/pages/ReimbursementAccount/BeneficialOwnersStep.js b/src/pages/ReimbursementAccount/BeneficialOwnersStep.js index 8562fbef130a..e86f22f44bf4 100644 --- a/src/pages/ReimbursementAccount/BeneficialOwnersStep.js +++ b/src/pages/ReimbursementAccount/BeneficialOwnersStep.js @@ -2,6 +2,7 @@ import _ from 'underscore'; import React from 'react'; import PropTypes from 'prop-types'; import {ScrollView, View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; import Text from '../../components/Text'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import styles from '../../styles/styles'; @@ -11,17 +12,28 @@ import Button from '../../components/Button'; import IdentityForm from './IdentityForm'; import FixedFooter from '../../components/FixedFooter'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; -import {goToWithdrawalAccountSetupStep, setupWithdrawalAccount} from '../../libs/actions/BankAccounts'; +import { + goToWithdrawalAccountSetupStep, + setupWithdrawalAccount, +} from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; import CONST from '../../CONST'; import {isValidIdentity} from '../../libs/ValidationUtils'; import Growl from '../../libs/Growl'; +import ONYXKEYS from '../../ONYXKEYS'; +import compose from '../../libs/compose'; const propTypes = { /** Name of the company */ companyName: PropTypes.string.isRequired, ...withLocalizePropTypes, + + /** Bank account currently in setup */ + reimbursementAccount: PropTypes.shape({ + /** Error set when handling the API response */ + error: PropTypes.string, + }).isRequired, }; class BeneficialOwnersStep extends React.Component { @@ -172,6 +184,7 @@ class BeneficialOwnersStep extends React.Component { dob: owner.dob || '', ssnLast4: owner.ssnLast4 || '', }} + error={this.props.reimbursementAccount.error} /> {this.state.beneficialOwners.length > 1 && ( this.removeBeneficialOwner(owner)}> @@ -230,5 +243,11 @@ class BeneficialOwnersStep extends React.Component { } BeneficialOwnersStep.propTypes = propTypes; - -export default withLocalize(BeneficialOwnersStep); +export default compose( + withLocalize, + withOnyx({ + reimbursementAccount: { + key: ONYXKEYS.REIMBURSEMENT_ACCOUNT, + }, + }), +)(BeneficialOwnersStep); diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 5260a4ff1306..3b7a51563df2 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -4,9 +4,15 @@ import React from 'react'; import {View, ScrollView} from 'react-native'; import Str from 'expensify-common/lib/str'; import moment from 'moment'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; import CONST from '../../CONST'; -import {goToWithdrawalAccountSetupStep, setupWithdrawalAccount} from '../../libs/actions/BankAccounts'; +import { + goToWithdrawalAccountSetupStep, hideBankAccountErrors, + setupWithdrawalAccount, + showBankAccountFormValidationError, +} from '../../libs/actions/BankAccounts'; import Navigation from '../../libs/Navigation/Navigation'; import Text from '../../components/Text'; import ExpensiTextInput from '../../components/ExpensiTextInput'; @@ -20,9 +26,21 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize import { isValidAddress, isValidDate, isValidIndustryCode, isValidZipCode, } from '../../libs/ValidationUtils'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; import ConfirmModal from '../../components/ConfirmModal'; import ExpensiPicker from '../../components/ExpensiPicker'; +const propTypes = { + /** Bank account currently in setup */ + reimbursementAccount: PropTypes.shape({ + /** Error set when handling the API response */ + error: PropTypes.string, + }).isRequired, + + ...withLocalizePropTypes, +}; + class CompanyStep extends React.Component { constructor(props) { super(props); @@ -68,38 +86,47 @@ class CompanyStep extends React.Component { */ validate() { if (!this.state.password.trim()) { + showBankAccountFormValidationError(this.props.translate('common.passwordCannotBeBlank')); return false; } if (!isValidAddress(this.state.addressStreet)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.addressStreet')); return false; } if (this.state.addressState === '') { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.addressState')); return false; } if (!isValidZipCode(this.state.addressZipCode)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.zipCode')); return false; } if (!Str.isValidURL(this.state.website)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.website')); return false; } if (!/[0-9]{9}/.test(this.state.companyTaxID)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.taxID')); return false; } if (!isValidDate(this.state.incorporationDate)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.incorporationDate')); return false; } if (!isValidIndustryCode(this.state.industryCode)) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.industryCode')); return false; } if (!this.state.hasNoConnectionToCannabis) { + showBankAccountFormValidationError(this.props.translate('bankAccount.error.restrictedBusiness')); return false; } @@ -121,6 +148,7 @@ class CompanyStep extends React.Component { const shouldDisableCompanyTaxID = Boolean(this.props.achData.bankAccountID && this.props.achData.companyTaxID); const missingRequiredFields = this.requiredFields.reduce((acc, curr) => acc || !this.state[curr].trim(), false); const shouldDisableSubmitButton = !this.state.hasNoConnectionToCannabis || missingRequiredFields; + const error = this.props.reimbursementAccount.error; return ( <> @@ -143,8 +171,16 @@ class CompanyStep extends React.Component { this.setState({addressStreet})} + onChangeText={(addressStreet) => { + if (error === this.props.translate('bankAccount.error.addressStreet')) { + hideBankAccountErrors(); + } + this.setState({addressStreet}); + }} value={this.state.addressStreet} + errorText={error === this.props.translate('bankAccount.error.addressStreet') + ? this.props.translate('bankAccount.error.addressStreet') + : ''} /> @@ -165,8 +201,16 @@ class CompanyStep extends React.Component { this.setState({addressZipCode})} + onChangeText={(addressZipCode) => { + if (error === this.props.translate('bankAccount.error.zipCode')) { + hideBankAccountErrors(); + } + this.setState({addressZipCode}); + }} value={this.state.addressZipCode} + errorText={error === this.props.translate('bankAccount.error.zipCode') + ? this.props.translate('bankAccount.error.zipCode') + : ''} /> this.setState({website})} + onChangeText={(website) => { + if (error === this.props.translate('bankAccount.error.website')) { + hideBankAccountErrors(); + } + this.setState({website}); + }} value={this.state.website} + errorText={error === this.props.translate('bankAccount.error.website') + ? this.props.translate('bankAccount.error.website') + : ''} /> this.setState({companyTaxID})} + onChangeText={(companyTaxID) => { + if (error === this.props.translate('bankAccount.error.taxID')) { + hideBankAccountErrors(); + } + this.setState({companyTaxID}); + }} value={this.state.companyTaxID} disabled={shouldDisableCompanyTaxID} + errorText={error === this.props.translate('bankAccount.error.taxID') + ? this.props.translate('bankAccount.error.taxID') + : ''} /> this.setState({incorporationDate})} + onChangeText={(incorporationDate) => { + if (error === this.props.translate('bankAccount.error.incorporationDate')) { + hideBankAccountErrors(); + } + this.setState({incorporationDate}); + }} value={this.state.incorporationDate} placeholder={this.props.translate('companyStep.incorporationDatePlaceholder')} + errorText={error === this.props.translate('bankAccount.error.incorporationDate') + ? this.props.translate('bankAccount.error.incorporationDate') + : ''} /> @@ -223,8 +291,16 @@ class CompanyStep extends React.Component { helpLinkText={this.props.translate('common.whatThis')} helpLinkURL="https://www.naics.com/search/" containerStyles={[styles.mt4]} - onChangeText={industryCode => this.setState({industryCode})} + onChangeText={(industryCode) => { + if (error === this.props.translate('bankAccount.error.industryCode')) { + hideBankAccountErrors(); + } + this.setState({industryCode}); + }} value={this.state.industryCode} + errorText={error === this.props.translate('bankAccount.error.industryCode') + ? this.props.translate('bankAccount.error.industryCode') + : ''} /> this.setState({password})} + onChangeText={(password) => { + if (error === this.props.translate('common.passwordCannotBeBlank')) { + hideBankAccountErrors(); + } + this.setState({password}); + }} value={this.state.password} onSubmitEditing={this.submit} + errorText={error === this.props.translate('common.passwordCannotBeBlank') + ? this.props.translate('common.passwordCannotBeBlank') + : ''} /> { const { firstName, lastName, street, city, state, zipCode, dob, ssnLast4, @@ -87,19 +93,37 @@ const IdentityForm = ({ containerStyles={[styles.mt4]} placeholder={translate('common.dateFormat')} value={dob} - onChangeText={val => onFieldChange('dob', val)} + onChangeText={(val) => { + if (error === translateLocal('bankAccount.error.dob')) { + hideBankAccountErrors(); + } + onFieldChange('dob', val); + }} + errorText={error === translateLocal('bankAccount.error.dob') ? error : ''} /> onFieldChange('ssnLast4', val)} + onChangeText={(val) => { + if (error === translateLocal('bankAccount.error.ssnLast4')) { + hideBankAccountErrors(); + } + onFieldChange('ssnLast4', val); + }} + errorText={error === translateLocal('bankAccount.error.ssnLast4') ? error : ''} /> onFieldChange('street', val)} + onChangeText={(val) => { + if (error === translateLocal('bankAccount.error.address')) { + hideBankAccountErrors(); + } + onFieldChange('street', val); + }} + errorText={error === translateLocal('bankAccount.error.address') ? error : ''} /> @@ -121,7 +145,13 @@ const IdentityForm = ({ label={translate('common.zip')} containerStyles={[styles.mt4]} value={zipCode} - onChangeText={val => onFieldChange('zipCode', val)} + onChangeText={(val) => { + if (error === translateLocal('bankAccount.error.zipCode')) { + hideBankAccountErrors(); + } + onFieldChange('zipCode', val); + }} + errorText={error === translateLocal('bankAccount.error.zipCode') ? error : ''} /> ); diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 2287724daa9b..122b5825acbf 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -1,6 +1,8 @@ import React from 'react'; import lodashGet from 'lodash/get'; import {View, ScrollView} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; @@ -9,13 +11,28 @@ import TextLink from '../../components/TextLink'; import Navigation from '../../libs/Navigation/Navigation'; import CheckboxWithLabel from '../../components/CheckboxWithLabel'; import Text from '../../components/Text'; -import {goToWithdrawalAccountSetupStep, setupWithdrawalAccount} from '../../libs/actions/BankAccounts'; +import { + goToWithdrawalAccountSetupStep, + setupWithdrawalAccount, +} from '../../libs/actions/BankAccounts'; import Button from '../../components/Button'; import FixedFooter from '../../components/FixedFooter'; import IdentityForm from './IdentityForm'; import {isValidIdentity} from '../../libs/ValidationUtils'; import Growl from '../../libs/Growl'; import Onfido from '../../components/Onfido'; +import compose from '../../libs/compose'; +import ONYXKEYS from '../../ONYXKEYS'; + +const propTypes = { + /** Bank account currently in setup */ + reimbursementAccount: PropTypes.shape({ + /** Error set when handling the API response */ + error: PropTypes.string, + }).isRequired, + + ...withLocalizePropTypes, +}; class RequestorStep extends React.Component { constructor(props) { @@ -116,6 +133,7 @@ class RequestorStep extends React.Component { dob: this.state.dob, ssnLast4: this.state.ssnLast4, }} + error={this.props.reimbursementAccount.error} />