From 2865a78f8fa8a1d2dbdf68ed763ed29ae5f5af36 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 11:24:00 -0300 Subject: [PATCH 01/21] Add clearErrors to clear multiple errors on one call --- src/libs/ReimbursementAccountUtils.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/libs/ReimbursementAccountUtils.js b/src/libs/ReimbursementAccountUtils.js index 5de61144092b..832fe59b66d0 100644 --- a/src/libs/ReimbursementAccountUtils.js +++ b/src/libs/ReimbursementAccountUtils.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import lodashGet from 'lodash/get'; import lodashUnset from 'lodash/unset'; import lodashCloneDeep from 'lodash/cloneDeep'; @@ -25,23 +26,33 @@ function getErrors(props) { return lodashGet(props, ['reimbursementAccount', 'errors'], {}); } + /** * @param {Object} props - * @param {String} path + * @param {String[]} paths */ -function clearError(props, path) { +function clearErrors(props, paths) { const errors = getErrors(props); - if (!lodashGet(errors, path, false)) { + const pathsWithErrors = _.filter(paths, path => lodashGet(errors, path, false)); + if (_.size(pathsWithErrors) === 0) { // No error found for this path return; } // Clear the existing errors const newErrors = lodashCloneDeep(errors); - lodashUnset(newErrors, path); + _.forEach(pathsWithErrors, path => lodashUnset(newErrors, path)); BankAccounts.setBankAccountFormValidationErrors(newErrors); } +/** + * @param {Object} props + * @param {String} path + */ +function clearError(props, path) { + clearErrors(props, [path]); +} + /** * @param {Object} props * @param {Object} errorTranslationKeys @@ -57,5 +68,6 @@ export { getDefaultStateForField, getErrors, clearError, + clearErrors, getErrorText, }; From 8f8d148c84ff0e16df5d359a1853cd0748ebae96 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 11:25:54 -0300 Subject: [PATCH 02/21] Replace onChangeText with onChange. onChange callback gets called with an object, and only once when a result is selected --- src/components/AddressSearch.js | 71 +++++++++++++++++---------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 855f7f7fe4d2..f6690a0d3e89 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -23,7 +23,7 @@ const propTypes = { value: PropTypes.string, /** A callback function when the value of this field has changed */ - onChangeText: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, /** Customize the ExpensiTextInput container */ containerStyles: PropTypes.arrayOf(PropTypes.object), @@ -51,35 +51,40 @@ const AddressSearch = (props) => { const saveLocationDetails = (details) => { const addressComponents = details.address_components; - if (GooglePlacesUtils.isAddressValidForVBA(addressComponents)) { - // Gather the values from the Google details - const streetNumber = GooglePlacesUtils.getAddressComponent(addressComponents, 'street_number', 'long_name'); - const streetName = GooglePlacesUtils.getAddressComponent(addressComponents, 'route', 'long_name'); - let city = GooglePlacesUtils.getAddressComponent(addressComponents, 'locality', 'long_name'); - if (!city) { - city = GooglePlacesUtils.getAddressComponent(addressComponents, 'sublocality', 'long_name'); - Log.hmmm('[AddressSearch] Replacing missing locality with sublocality: ', {address: details.formatted_address, sublocality: city}); - } - const state = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); - const zipCode = GooglePlacesUtils.getAddressComponent(addressComponents, 'postal_code', 'long_name'); - - // Trigger text change events for each of the individual fields being saved on the server - props.onChangeText('addressStreet', `${streetNumber} ${streetName}`); - props.onChangeText('addressCity', city); - props.onChangeText('addressState', state); - props.onChangeText('addressZipCode', zipCode); - } else { - // Clear the values associated to the address, so our validations catch the problem - Log.hmmm('[AddressSearch] Search result failed validation: ', { - address: details.formatted_address, - address_components: addressComponents, - place_id: details.place_id, - }); - props.onChangeText('addressStreet', null); - props.onChangeText('addressCity', null); - props.onChangeText('addressState', null); - props.onChangeText('addressZipCode', null); + if (!addressComponents) { + return; + } + + // Gather the values from the Google details + const streetNumber = GooglePlacesUtils.getAddressComponent(addressComponents, 'street_number', 'long_name') || ''; + const streetName = GooglePlacesUtils.getAddressComponent(addressComponents, 'route', 'long_name') || ''; + const addressStreet = `${streetNumber} ${streetName}`.trim(); + let addressCity = GooglePlacesUtils.getAddressComponent(addressComponents, 'locality', 'long_name'); + if (!addressCity) { + addressCity = GooglePlacesUtils.getAddressComponent(addressComponents, 'sublocality', 'long_name'); + Log.hmmm('[AddressSearch] Replacing missing locality with sublocality: ', {address: details.formatted_address, sublocality: addressCity}); + } + const addressZipCode = GooglePlacesUtils.getAddressComponent(addressComponents, 'postal_code', 'long_name'); + const addressState = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); + + const values = {}; + if (addressStreet) { + values.addressStreet = addressStreet; + } + if (addressCity) { + values.addressCity = addressCity; + } + if (addressZipCode) { + values.addressZipCode = addressZipCode; } + if (addressState) { + values.addressState = addressState; + } + if (_.size(values) === 0) { + return; + } + + props.onChange(values); }; return ( @@ -88,6 +93,7 @@ const AddressSearch = (props) => { fetchDetails suppressDefaultStyles enablePoweredByContainer={false} + placeholder="" onPress={(data, details) => { saveLocationDetails(details); @@ -110,12 +116,7 @@ const AddressSearch = (props) => { containerStyles: props.containerStyles, errorText: props.errorText, onChangeText: (text) => { - const isTextValid = !_.isEmpty(text) && _.isEqual(text, props.value); - - // Ensure whether an address is selected already or has address value initialized. - if (!_.isEmpty(googlePlacesRef.current.getAddressText()) && !isTextValid) { - saveLocationDetails({}); - } + props.onChange({addressStreet: text}); // If the text is empty, we set displayListViewBorder to false to prevent UI flickering if (_.isEmpty(text)) { From 32a9eb9652fad09c4e02964b4530d792b5b46926 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 11:26:11 -0300 Subject: [PATCH 03/21] Update CompanyStep with new AddressSearch --- src/pages/ReimbursementAccount/CompanyStep.js | 116 ++++++------------ 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 90598691e239..841f91bb9333 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -97,23 +97,6 @@ class CompanyStep extends React.Component { this.clearDateErrorsAndSetValue = this.clearDateErrorsAndSetValue.bind(this); } - getFormattedAddressValue() { - let addressString = ''; - if (this.state.addressStreet) { - addressString += `${this.state.addressStreet}, `; - } - if (this.state.addressCity) { - addressString += `${this.state.addressCity}, `; - } - if (this.state.addressState) { - addressString += `${this.state.addressState}, `; - } - if (this.state.addressZipCode) { - addressString += `${this.state.addressZipCode}`; - } - return addressString; - } - /** * @param {String} value */ @@ -150,14 +133,12 @@ class CompanyStep extends React.Component { validate() { const errors = {}; - if (this.state.manualAddress) { - if (!ValidationUtils.isValidAddress(this.state.addressStreet)) { - errors.addressStreet = true; - } + if (!ValidationUtils.isValidAddress(this.state.addressStreet)) { + errors.addressStreet = true; + } - if (!ValidationUtils.isValidZipCode(this.state.addressZipCode)) { - errors.addressZipCode = true; - } + if (!ValidationUtils.isValidZipCode(this.state.addressZipCode)) { + errors.addressZipCode = true; } if (!ValidationUtils.isValidURL(this.state.website)) { @@ -226,61 +207,44 @@ class CompanyStep extends React.Component { disabled={shouldDisableCompanyName} errorText={this.getErrorText('companyName')} /> - {!this.state.manualAddress && ( - <> - this.clearErrorAndSetValue(fieldName, value)} - errorText={this.getErrorText('addressStreet')} - /> - this.setState({manualAddress: true})} - > - Can't find your address? Enter it manually - - - )} - {this.state.manualAddress && ( - <> + { + _.each(values, (value, key) => { + this.setValue({[key]: value}); + }); + ReimbursementAccountUtils.clearErrors(this.props, _.keys(values)); + }} + errorText={this.getErrorText('addressStreet')} + /> + {this.props.translate('common.noPO')} + + this.clearErrorAndSetValue('addressStreet', value)} - value={this.state.addressStreet} - errorText={this.getErrorText('addressStreet')} + label={this.props.translate('common.city')} + onChangeText={value => this.clearErrorAndSetValue('addressCity', value)} + value={this.state.addressCity} + errorText={this.getErrorText('addressCity')} /> - {this.props.translate('common.noPO')} - - - this.clearErrorAndSetValue('addressCity', value)} - value={this.state.addressCity} - errorText={this.getErrorText('addressCity')} - /> - - - this.clearErrorAndSetValue('addressState', value)} - value={this.state.addressState} - hasError={this.getErrors().addressState} - /> - - - this.clearErrorAndSetValue('addressZipCode', value)} - value={this.state.addressZipCode} - errorText={this.getErrorText('addressZipCode')} + + + this.clearErrorAndSetValue('addressState', value)} + value={this.state.addressState} + hasError={this.getErrors().addressState} /> - - )} - + + + this.clearErrorAndSetValue('addressZipCode', value)} + value={this.state.addressZipCode} + errorText={this.getErrorText('addressZipCode')} + /> Date: Thu, 2 Dec 2021 12:30:29 -0300 Subject: [PATCH 04/21] Add two-way binding for AddressSearch text input Had to add again `skippedFirstOnChangeText` state to work around the library's feature that is clearing the input at the beginning. --- src/components/AddressSearch.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index f6690a0d3e89..a24d7a819f73 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useEffect, useState, useRef} from 'react'; +import React, {useState} from 'react'; import PropTypes from 'prop-types'; import {LogBox} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; @@ -39,15 +39,8 @@ const defaultProps = { // Relevant thread: https://expensify.slack.com/archives/C03TQ48KC/p1634088400387400 // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 const AddressSearch = (props) => { - const googlePlacesRef = useRef(); const [displayListViewBorder, setDisplayListViewBorder] = useState(false); - useEffect(() => { - if (!googlePlacesRef.current) { - return; - } - - googlePlacesRef.current.setAddressText(props.value); - }, []); + const [skippedFirstOnChangeText, setSkippedFirstOnChangeText] = useState(false); const saveLocationDetails = (details) => { const addressComponents = details.address_components; @@ -83,13 +76,11 @@ const AddressSearch = (props) => { if (_.size(values) === 0) { return; } - props.onChange(values); }; return ( { label: props.label, containerStyles: props.containerStyles, errorText: props.errorText, + value: props.value, onChangeText: (text) => { - props.onChange({addressStreet: text}); + // We use `skippedFirstOnChangeText` to work around a feature of the library: + // The library is calling onChangeText with '' at the start and we don't need this + // https://github.com/FaridSafi/react-native-google-places-autocomplete/blob/47d7223dd48f85da97e80a0729a985bbbcee353f/GooglePlacesAutocomplete.js#L148 + if (skippedFirstOnChangeText) { + props.onChange({addressStreet: text}); + } else { + setSkippedFirstOnChangeText(true); + } // If the text is empty, we set displayListViewBorder to false to prevent UI flickering if (_.isEmpty(text)) { From bf6346829dc44ec8431c6b5092bd636b406800f7 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 15:18:17 -0300 Subject: [PATCH 05/21] Update requestor step with new address fields Update IndentityForm input names, now it reads and writes using the same input names for address related fields. --- src/libs/ValidationUtils.js | 10 +- src/pages/ReimbursementAccount/CompanyStep.js | 3 +- .../ReimbursementAccount/IdentityForm.js | 122 ++++++------------ .../ReimbursementAccount/RequestorStep.js | 82 +++++++----- 4 files changed, 95 insertions(+), 122 deletions(-) diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index c782868a3435..a342fb0400da 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -206,7 +206,7 @@ function isValidURL(url) { * @returns {Object} */ function validateIdentity(identity) { - const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob']; + const requiredFields = ['firstName', 'lastName', 'addressStreet', 'addressCity', 'addressZipCode', 'addressState', 'ssnLast4', 'dob']; const errors = {}; // Check that all required fields are filled @@ -217,12 +217,12 @@ function validateIdentity(identity) { errors[fieldName] = true; }); - if (!isValidAddress(identity.street)) { - errors.street = true; + if (!isValidAddress(identity.addressStreet)) { + errors.addressStreet = true; } - if (!isValidZipCode(identity.zipCode)) { - errors.zipCode = true; + if (!isValidZipCode(identity.addressZipCode)) { + errors.addressZipCode = true; } // dob field has multiple validations/errors, we are handling it temporarily like this. diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 841f91bb9333..1c5febd32f75 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -93,6 +93,7 @@ class CompanyStep extends React.Component { this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); + this.clearErrors = inputKeys => ReimbursementAccountUtils.clearError(this.props, inputKeys); this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); this.clearDateErrorsAndSetValue = this.clearDateErrorsAndSetValue.bind(this); } @@ -215,7 +216,7 @@ class CompanyStep extends React.Component { _.each(values, (value, key) => { this.setValue({[key]: value}); }); - ReimbursementAccountUtils.clearErrors(this.props, _.keys(values)); + this.clearErrors(_.keys(values)); }} errorText={this.getErrorText('addressStreet')} /> diff --git a/src/pages/ReimbursementAccount/IdentityForm.js b/src/pages/ReimbursementAccount/IdentityForm.js index fd6481815652..f2be6925c165 100644 --- a/src/pages/ReimbursementAccount/IdentityForm.js +++ b/src/pages/ReimbursementAccount/IdentityForm.js @@ -7,7 +7,6 @@ import styles from '../../styles/styles'; import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; import CONST from '../../CONST'; import DatePicker from '../../components/DatePicker'; -import TextLink from '../../components/TextLink'; import StatePicker from '../../components/StatePicker'; import Text from '../../components/Text'; @@ -28,25 +27,22 @@ const propTypes = { lastName: PropTypes.string, /** Address street field */ - street: PropTypes.string, + addressStreet: PropTypes.string, /** Address city field */ - city: PropTypes.string, + addressCity: PropTypes.string, /** Address state field */ - state: PropTypes.string, + addressState: PropTypes.string, /** Address zip code field */ - zipCode: PropTypes.string, + addressZipCode: PropTypes.string, /** Date of birth field */ dob: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), /** Last 4 digits of SSN */ ssnLast4: PropTypes.string, - - /** Whether the address pieces should be entered manually */ - manualAddress: PropTypes.bool, }), /** Any errors that can arise from form validation */ @@ -60,13 +56,12 @@ const defaultProps = { values: { firstName: '', lastName: '', - street: '', - city: '', - state: '', - zipCode: '', + addressStreet: '', + addressCity: '', + addressState: '', + addressZipCode: '', dob: '', ssnLast4: '', - manualAddress: false, }, errors: {}, }; @@ -77,23 +72,6 @@ const IdentityForm = (props) => { const dobErrorText = (props.errors.dob ? props.translate('bankAccount.error.dob') : '') || (props.errors.dobAge ? props.translate('bankAccount.error.age') : ''); - const getFormattedAddressValue = () => { - let addressString = ''; - if (props.values.street) { - addressString += `${props.values.street}, `; - } - if (props.values.city) { - addressString += `${props.values.city}, `; - } - if (props.values.state) { - addressString += `${props.values.state}, `; - } - if (props.values.zipCode) { - addressString += `${props.values.zipCode}`; - } - return addressString; - }; - return ( @@ -131,61 +109,41 @@ const IdentityForm = (props) => { errorText={props.errors.ssnLast4 ? props.translate('bankAccount.error.ssnLast4') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} /> - {props.values.manualAddress ? ( - <> - props.onFieldChange('addressStreet', value)} - errorText={props.errors.street ? props.translate('bankAccount.error.address') : ''} - /> - {props.translate('common.noPO')} - - - props.onFieldChange('addressCity', value)} - errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} - /> - - - props.onFieldChange('addressState', value)} - errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} - hasError={Boolean(props.errors.state)} - /> - - + + {props.translate('common.noPO')} + + props.onFieldChange('addressZipCode', value)} - errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} - maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} + label={props.translate('common.city')} + value={props.values.addressCity} + onChangeText={value => props.onFieldChange('addressCity', value)} + errorText={props.errors.addressCity ? props.translate('bankAccount.error.addressCity') : ''} /> - - ) : ( - <> - props.onFieldChange(fieldName, value)} - errorText={props.errors.street ? props.translate('bankAccount.error.addressStreet') : ''} + + + props.onFieldChange('addressState', value)} + errorText={props.errors.addressState ? props.translate('bankAccount.error.addressState') : ''} + hasError={Boolean(props.errors.addressState)} /> - props.onFieldChange('manualAddress', true)} - > - Can't find your address? Enter it manually - - - )} + + + props.onFieldChange('addressZipCode', value)} + errorText={props.errors.addressZipCode ? props.translate('bankAccount.error.zipCode') : ''} + maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} + /> ); }; diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 13555bab55a6..8b580b46ab0e 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -55,8 +55,6 @@ class RequestorStep extends React.Component { // Required fields not validated by `validateIdentity` this.requiredFields = [ - 'firstName', - 'lastName', 'isControllingOfficer', ]; @@ -68,9 +66,40 @@ class RequestorStep extends React.Component { }; this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); + this.clearErrors = inputKeys => ReimbursementAccountUtils.clearErrors(this.props, inputKeys); this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); } + /** + * Clear the errors associated to keys in fieldUpdates if found and store the new values in the state. + * + * @param {Object} fieldUpdates + */ + clearErrorsAndSetValues(fieldUpdates) { + const renamedFields = { + addressStreet: 'requestorAddressStreet', + addressCity: 'requestorAddressCity', + addressState: 'requestorAddressState', + addressZipCode: 'requestorAddressZipCode', + }; + const newState = {}; + _.each(fieldUpdates, (value, inputKey) => { + const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); + newState[renamedInputKey] = value; + }); + this.setState(newState); + BankAccounts.updateReimbursementAccountDraft(newState); + + // Prepare inputKeys for clearing errors + const inputKeys = _.keys(fieldUpdates); + + // dob field has multiple validations/errors, we are handling it temporarily like this. + if (_.contains(inputKeys, 'dob')) { + inputKeys.push('dobAge'); + } + this.clearErrors(inputKeys); + } + /** * Clear the error associated to inputKey if found and store the inputKey new value in the state. * @@ -78,28 +107,7 @@ class RequestorStep extends React.Component { * @param {String|Boolean} value */ clearErrorAndSetValue(inputKey, value) { - if (inputKey === 'manualAddress') { - this.setState({ - manualAddress: value, - }); - } else { - const renamedFields = { - addressStreet: 'requestorAddressStreet', - addressCity: 'requestorAddressCity', - addressState: 'requestorAddressState', - addressZipCode: 'requestorAddressZipCode', - }; - const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); - const newState = {[renamedInputKey]: value}; - this.setState(newState); - BankAccounts.updateReimbursementAccountDraft(newState); - - // dob field has multiple validations/errors, we are handling it temporarily like this. - if (inputKey === 'dob') { - this.clearError('dobAge'); - } - this.clearError(inputKey); - } + this.clearErrorsAndSetValues({[inputKey]: value}); } /** @@ -109,10 +117,10 @@ class RequestorStep extends React.Component { const errors = ValidationUtils.validateIdentity({ firstName: this.state.firstName, lastName: this.state.lastName, - street: this.state.requestorAddressStreet, - state: this.state.requestorAddressState, - city: this.state.requestorAddressCity, - zipCode: this.state.requestorAddressZipCode, + addressStreet: this.state.requestorAddressStreet, + addressState: this.state.requestorAddressState, + addressCity: this.state.requestorAddressCity, + addressZipCode: this.state.requestorAddressZipCode, dob: this.state.dob, ssnLast4: this.state.ssnLast4, }); @@ -198,17 +206,23 @@ class RequestorStep extends React.Component { { + if (_.isString(inputKeyOrUpdatesBatch)) { + this.clearErrorAndSetValue(inputKeyOrUpdatesBatch, value); + } else { + // While we have batched updates to handle clearing errors properly + this.clearErrorsAndSetValues(inputKeyOrUpdatesBatch); + } + }} values={{ firstName: this.state.firstName, lastName: this.state.lastName, - street: this.state.requestorAddressStreet, - city: this.state.requestorAddressCity, - state: this.state.requestorAddressState, - zipCode: this.state.requestorAddressZipCode, + addressStreet: this.state.requestorAddressStreet, + addressState: this.state.requestorAddressState, + addressCity: this.state.requestorAddressCity, + addressZipCode: this.state.requestorAddressZipCode, dob: this.state.dob, ssnLast4: this.state.ssnLast4, - manualAddress: this.state.manualAddress, }} errors={this.props.reimbursementAccount.errors} /> From 0ea709ab5129340e935c690405cce38089cee49f Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 18:22:32 -0300 Subject: [PATCH 06/21] Update ACHContract step handling of IdentityForm --- .../ReimbursementAccount/ACHContractStep.js | 67 +++++++++++++------ 1 file changed, 46 insertions(+), 21 deletions(-) diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index be970f2407d8..31689312a571 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -59,24 +59,28 @@ class ACHContractStep extends React.Component { certifyTrueInformation: 'beneficialOwnersStep.error.certify', }; + this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); + this.clearErrors = values => ReimbursementAccountUtils.clearErrors(this.props, values); this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); } - /** - * @returns {Object} - */ - getErrors() { - return lodashGet(this.props, ['reimbursementAccount', 'errors'], {}); - } - /** * @returns {Boolean} */ validate() { let beneficialOwnersErrors = []; if (this.state.hasOtherBeneficialOwners) { - beneficialOwnersErrors = _.map(this.state.beneficialOwners, ValidationUtils.validateIdentity); + beneficialOwnersErrors = _.map(this.state.beneficialOwners, beneficialOwner => ValidationUtils.validateIdentity({ + firstName: beneficialOwner.firstName, + lastName: beneficialOwner.lastName, + addressStreet: beneficialOwner.street, + addressState: beneficialOwner.state, + addressCity: beneficialOwner.city, + addressZipCode: beneficialOwner.zipCode, + dob: beneficialOwner.dob, + ssnLast4: beneficialOwner.ssnLast4, + })); } const errors = {}; @@ -122,10 +126,9 @@ class ACHContractStep extends React.Component { * Clear the error associated to inputKey if found and store the inputKey new value in the state. * * @param {Integer} ownerIndex - * @param {String} inputKey - * @param {String} value + * @param {Object} values */ - clearErrorAndSetBeneficialOwnerValue(ownerIndex, inputKey, value) { + clearErrorAndSetBeneficialOwnerValues(ownerIndex, values) { this.setState((prevState) => { const renamedFields = { addressStreet: 'street', @@ -133,18 +136,34 @@ class ACHContractStep extends React.Component { addressState: 'state', addressZipCode: 'zipCode', }; - const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); const beneficialOwners = [...prevState.beneficialOwners]; - beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], [renamedInputKey]: value}; + _.each(values, (value, inputKey) => { + const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); + beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], [renamedInputKey]: value}; + }); BankAccounts.updateReimbursementAccountDraft({beneficialOwners}); return {beneficialOwners}; }); + // Prepare inputKeys for clearing errors + const inputKeys = _.keys(values); + // dob field has multiple validations/errors, we are handling it temporarily like this. - if (inputKey === 'dob') { - this.clearError(`beneficialOwnersErrors.${ownerIndex}.dobAge`); + if (_.contains(inputKeys, 'dob')) { + inputKeys.push('dobAge'); } - this.clearError(`beneficialOwnersErrors.${ownerIndex}.${inputKey}`); + this.clearErrors(_.map(inputKeys, inputKey => `beneficialOwnersErrors.${ownerIndex}.${inputKey}`)); + } + + /** + * Clear the error associated to inputKey if found and store the inputKey new value in the state. + * + * @param {Integer} ownerIndex + * @param {String} inputKey + * @param {String} value + */ + clearErrorAndSetBeneficialOwnerValue(ownerIndex, inputKey, value) { + this.clearErrorAndSetBeneficialOwnerValues(ownerIndex, {[inputKey]: value}); } submit() { @@ -233,14 +252,20 @@ class ACHContractStep extends React.Component { this.clearErrorAndSetBeneficialOwnerValue(index, inputKey, value)} + onFieldChange={(inputKeyOrValues, value) => { + if (_.isString(inputKeyOrValues)) { + this.clearErrorAndSetBeneficialOwnerValue(index, inputKeyOrValues, value); + } else { + this.clearErrorAndSetBeneficialOwnerValues(index, inputKeyOrValues); + } + }} values={{ firstName: owner.firstName || '', lastName: owner.lastName || '', - street: owner.street || '', - city: owner.city || '', - state: owner.state || '', - zipCode: owner.zipCode || '', + addressStreet: owner.street || '', + addressState: owner.state || '', + addressCity: owner.city || '', + addressZipCode: owner.zipCode || '', dob: owner.dob || '', ssnLast4: owner.ssnLast4 || '', }} From 9295c476b79daf0dcf50447ec8a4aae8e348d180 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 19:09:11 -0300 Subject: [PATCH 07/21] Update AddDebitCardPage with new AddressSearch --- .../settings/Payments/AddDebitCardPage.js | 41 +++++++++++++++++-- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/pages/settings/Payments/AddDebitCardPage.js b/src/pages/settings/Payments/AddDebitCardPage.js index 636087d5cc0e..392bbca1a7cb 100644 --- a/src/pages/settings/Payments/AddDebitCardPage.js +++ b/src/pages/settings/Payments/AddDebitCardPage.js @@ -18,6 +18,7 @@ import * as PaymentMethods from '../../../libs/actions/PaymentMethods'; import KeyboardAvoidingView from '../../../components/KeyboardAvoidingView'; import * as ValidationUtils from '../../../libs/ValidationUtils'; import CheckboxWithLabel from '../../../components/CheckboxWithLabel'; +import StatePicker from '../../../components/StatePicker'; import ExpensiTextInput from '../../../components/ExpensiTextInput'; import CONST from '../../../CONST'; import FormAlertWithSubmitButton from '../../../components/FormAlertWithSubmitButton'; @@ -130,12 +131,18 @@ class DebitCardPage extends Component { errors.securityCode = true; } - if (!ValidationUtils.isValidAddress(this.state.addressStreet) - || !this.state.addressState - || !ValidationUtils.isValidZipCode(this.state.addressZipCode)) { + if (!ValidationUtils.isValidAddress(this.state.addressStreet)) { errors.addressStreet = true; } + if (!ValidationUtils.isValidZipCode(this.state.addressZipCode)) { + errors.addressZipCode = true; + } + + if (!this.state.addressState) { + errors.addressState = true; + } + if (!this.state.acceptedTerms) { errors.acceptedTerms = true; } @@ -226,9 +233,35 @@ class DebitCardPage extends Component { label={this.props.translate('addDebitCardPage.billingAddress')} containerStyles={[styles.mt4]} value={this.state.addressStreet} - onChangeText={(fieldName, value) => this.clearErrorAndSetValue(fieldName, value)} + onChange={(values) => { + _.each(values, (value, key) => { + if (key === 'addressCity') { + return; + } + this.clearErrorAndSetValue(key, value); + }); + }} errorText={this.getErrorText('addressStreet')} /> + {this.props.translate('common.noPO')} + + + this.clearErrorAndSetValue('addressZipCode', value)} + value={this.state.addressZipCode} + errorText={this.getErrorText('addressZipCode')} + /> + + + this.clearErrorAndSetValue('addressState', value)} + value={this.state.addressState} + hasError={lodashGet(this.state.errors, 'addressState', false)} + /> + + { From 27b0d84d0f6188b9649679f01ab29d6af34766a3 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 19:36:43 -0300 Subject: [PATCH 08/21] Remove unused validation --- src/libs/GooglePlacesUtils.js | 36 +--------- tests/unit/GooglePlacesUtilsTest.js | 103 ---------------------------- 2 files changed, 1 insertion(+), 138 deletions(-) diff --git a/src/libs/GooglePlacesUtils.js b/src/libs/GooglePlacesUtils.js index 71c738062d11..5a875e7f102a 100644 --- a/src/libs/GooglePlacesUtils.js +++ b/src/libs/GooglePlacesUtils.js @@ -21,41 +21,7 @@ function getAddressComponent(addressComponents, type, key) { .value(); } -/** - * Validates this contains the minimum address components - * - * @param {Array} addressComponents - * @returns {Boolean} - */ -function isAddressValidForVBA(addressComponents) { - if (!addressComponents) { - return false; - } - if (!_.some(addressComponents, component => _.includes(component.types, 'street_number'))) { - // Missing Street number - return false; - } - if (!_.some(addressComponents, component => _.includes(component.types, 'postal_code'))) { - // Missing zip code - return false; - } - if (!_.some(addressComponents, component => _.includes(component.types, 'administrative_area_level_1'))) { - // Missing state - return false; - } - if (!_.some(addressComponents, component => _.includes(component.types, 'locality')) - && !_.some(addressComponents, component => _.includes(component.types, 'sublocality'))) { - // Missing city - return false; - } - if (_.some(addressComponents, component => _.includes(component.types, 'post_box'))) { - // Reject PO box - return false; - } - return true; -} - export { + // eslint-disable-next-line import/prefer-default-export getAddressComponent, - isAddressValidForVBA, }; diff --git a/tests/unit/GooglePlacesUtilsTest.js b/tests/unit/GooglePlacesUtilsTest.js index 9c8552286b1c..2993254a491b 100644 --- a/tests/unit/GooglePlacesUtilsTest.js +++ b/tests/unit/GooglePlacesUtilsTest.js @@ -1,109 +1,6 @@ import * as GooglePlacesUtils from '../../src/libs/GooglePlacesUtils'; describe('GooglePlacesUtilsTest', () => { - describe('isAddressValidForVBA', () => { - it('should reject Google Places result with missing street number', () => { - // This result appears when searching for "25220 Quail Ridge Road, Escondido, CA, 97027" - const googlePlacesRouteResult = { - address_components: [ - { - long_name: 'Quail Ridge Road', - short_name: 'Quail Ridge Rd', - types: ['route'], - }, - { - long_name: 'Escondido', - short_name: 'Escondido', - types: ['locality', 'political'], - }, - { - long_name: 'San Diego County', - short_name: 'San Diego County', - types: ['administrative_area_level_2', 'political'], - }, - { - long_name: 'California', - short_name: 'CA', - types: ['administrative_area_level_1', 'political'], - }, - { - long_name: 'United States', - short_name: 'US', - types: ['country', 'political'], - }, - { - long_name: '92027', - short_name: '92027', - types: ['postal_code'], - }, - ], - formatted_address: 'Quail Ridge Rd, Escondido, CA 92027, USA', - place_id: 'EihRdWFpbCBSaWRnZSBSZCwgRXNjb25kaWRvLCBDQSA5MjAyNywgVVNBIi4qLAoUChIJIQBiT7Pz24ARmaXMgCMhqAUSFAoSCXtDwoFe89uAEd_FlncPyNEB', - types: ['route'], - }; - const isValid = GooglePlacesUtils.isAddressValidForVBA(googlePlacesRouteResult.address_components); - expect(isValid).toStrictEqual(false); - }); - - it('should accept Google Places result with missing locality if sublocality is available', () => { - // This result appears when searching for "64 Noll Street, Brooklyn, NY, USA" - const brooklynAddressResult = { - address_components: [ - { - long_name: '64', - short_name: '64', - types: ['street_number'], - }, - { - long_name: 'Noll Street', - short_name: 'Noll St', - types: ['route'], - }, - { - long_name: 'Bushwick', - short_name: 'Bushwick', - types: ['neighborhood', 'political'], - }, - { - long_name: 'Brooklyn', - short_name: 'Brooklyn', - types: ['sublocality_level_1', 'sublocality', 'political'], - }, - { - long_name: 'Kings County', - short_name: 'Kings County', - types: ['administrative_area_level_2', 'political'], - }, - { - long_name: 'New York', - short_name: 'NY', - types: ['administrative_area_level_1', 'political'], - }, - { - long_name: 'United States', - short_name: 'US', - types: ['country', 'political'], - }, - { - long_name: '11206', - short_name: '11206', - types: ['postal_code'], - }, - { - long_name: '4604', - short_name: '4604', - types: ['postal_code_suffix'], - }, - ], - formatted_address: '64 Noll St, Brooklyn, NY 11206, USA', - // eslint-disable-next-line max-len - place_id: 'EiM2NCBOb2xsIFN0LCBCcm9va2x5biwgTlkgMTEyMDYsIFVTQSJQEk4KNAoyCReOha8HXMKJETjOQzBxX7M3Gh4LEO7B7qEBGhQKEgmJzguI-VvCiRFYR8sAAcN5KAwQQCoUChIJH0FG4AZcwokRvrvwkhWA_6A', - types: ['street_address'], - }; - const isValid = GooglePlacesUtils.isAddressValidForVBA(brooklynAddressResult.address_components); - expect(isValid).toStrictEqual(true); - }); - }); describe('getAddressComponent', () => { it('should find address components by type', () => { const addressComponents = [ From 3f73b084ce03620a75b7afaf504ac4488aec8d4c Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 20:05:35 -0300 Subject: [PATCH 09/21] Don't replace street if user typed something longer --- src/components/AddressSearch.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index a24d7a819f73..1d59fdc8d8ff 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -61,7 +61,8 @@ const AddressSearch = (props) => { const addressState = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); const values = {}; - if (addressStreet) { + if (addressStreet && addressStreet.length > props.value.length) { + // Don't replace if the user has typed something longer. I.e. maybe the user entered the Apt # values.addressStreet = addressStreet; } if (addressCity) { From 5adf0c22e690322cd151f9b5618473c7b75ffac4 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 2 Dec 2021 20:11:41 -0300 Subject: [PATCH 10/21] Update variable name --- src/pages/ReimbursementAccount/RequestorStep.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 8b580b46ab0e..455d7da7d536 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -71,11 +71,11 @@ class RequestorStep extends React.Component { } /** - * Clear the errors associated to keys in fieldUpdates if found and store the new values in the state. + * Clear the errors associated to keys in values if found and store the new values in the state. * - * @param {Object} fieldUpdates + * @param {Object} values */ - clearErrorsAndSetValues(fieldUpdates) { + clearErrorsAndSetValues(values) { const renamedFields = { addressStreet: 'requestorAddressStreet', addressCity: 'requestorAddressCity', @@ -83,7 +83,7 @@ class RequestorStep extends React.Component { addressZipCode: 'requestorAddressZipCode', }; const newState = {}; - _.each(fieldUpdates, (value, inputKey) => { + _.each(values, (value, inputKey) => { const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); newState[renamedInputKey] = value; }); @@ -91,7 +91,7 @@ class RequestorStep extends React.Component { BankAccounts.updateReimbursementAccountDraft(newState); // Prepare inputKeys for clearing errors - const inputKeys = _.keys(fieldUpdates); + const inputKeys = _.keys(values); // dob field has multiple validations/errors, we are handling it temporarily like this. if (_.contains(inputKeys, 'dob')) { From 7d44cd8891b7481904c249e0d713bbfcf2bfef52 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 Dec 2021 11:11:46 -0300 Subject: [PATCH 11/21] Fix CompanyStep not clearing errors --- src/pages/ReimbursementAccount/CompanyStep.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index 1c5febd32f75..f42540147c11 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -93,7 +93,7 @@ class CompanyStep extends React.Component { this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); - this.clearErrors = inputKeys => ReimbursementAccountUtils.clearError(this.props, inputKeys); + this.clearErrors = inputKeys => ReimbursementAccountUtils.clearErrors(this.props, inputKeys); this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); this.clearDateErrorsAndSetValue = this.clearDateErrorsAndSetValue.bind(this); } From 93642a83b56dec6032364cd3cf3a90fa2ece5e03 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Fri, 3 Dec 2021 11:19:49 -0300 Subject: [PATCH 12/21] Resolve conflicts --- src/pages/settings/Payments/AddDebitCardPage.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Payments/AddDebitCardPage.js b/src/pages/settings/Payments/AddDebitCardPage.js index 76606b77d788..a6dd70dcd514 100644 --- a/src/pages/settings/Payments/AddDebitCardPage.js +++ b/src/pages/settings/Payments/AddDebitCardPage.js @@ -243,7 +243,6 @@ class DebitCardPage extends Component { }} errorText={this.getErrorText('addressStreet')} /> - {this.props.translate('common.noPO')} Date: Fri, 3 Dec 2021 11:25:46 -0300 Subject: [PATCH 13/21] Move comment near state declaration --- src/components/AddressSearch.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 1d59fdc8d8ff..75e1ebc68de5 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -40,6 +40,10 @@ const defaultProps = { // Reference: https://github.com/FaridSafi/react-native-google-places-autocomplete/issues/609#issuecomment-886133839 const AddressSearch = (props) => { const [displayListViewBorder, setDisplayListViewBorder] = useState(false); + + // We use `skippedFirstOnChangeText` to work around a feature of the library: + // The library is calling onChangeText with '' at the start and we don't need this + // https://github.com/FaridSafi/react-native-google-places-autocomplete/blob/47d7223dd48f85da97e80a0729a985bbbcee353f/GooglePlacesAutocomplete.js#L148 const [skippedFirstOnChangeText, setSkippedFirstOnChangeText] = useState(false); const saveLocationDetails = (details) => { @@ -109,9 +113,6 @@ const AddressSearch = (props) => { errorText: props.errorText, value: props.value, onChangeText: (text) => { - // We use `skippedFirstOnChangeText` to work around a feature of the library: - // The library is calling onChangeText with '' at the start and we don't need this - // https://github.com/FaridSafi/react-native-google-places-autocomplete/blob/47d7223dd48f85da97e80a0729a985bbbcee353f/GooglePlacesAutocomplete.js#L148 if (skippedFirstOnChangeText) { props.onChange({addressStreet: text}); } else { From aed7b8ce82353bef02a5b97d598c5da5485478e0 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 12:42:59 -0300 Subject: [PATCH 14/21] Use ref instead of state to skip first onChangeText('') --- src/components/AddressSearch.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index dab7842a6fb6..ca5d149e424d 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -1,5 +1,5 @@ import _ from 'underscore'; -import React, {useState} from 'react'; +import React, {useRef, useState} from 'react'; import PropTypes from 'prop-types'; import {LogBox} from 'react-native'; import {GooglePlacesAutocomplete} from 'react-native-google-places-autocomplete'; @@ -42,10 +42,10 @@ const defaultProps = { const AddressSearch = (props) => { const [displayListViewBorder, setDisplayListViewBorder] = useState(false); - // We use `skippedFirstOnChangeText` to work around a feature of the library: + // We use `skippedFirstOnChangeTextRef` to work around a feature of the library: // The library is calling onChangeText with '' at the start and we don't need this // https://github.com/FaridSafi/react-native-google-places-autocomplete/blob/47d7223dd48f85da97e80a0729a985bbbcee353f/GooglePlacesAutocomplete.js#L148 - const [skippedFirstOnChangeText, setSkippedFirstOnChangeText] = useState(false); + const skippedFirstOnChangeTextRef = useRef(false); const saveLocationDetails = (details) => { const addressComponents = details.address_components; @@ -114,10 +114,10 @@ const AddressSearch = (props) => { errorText: props.errorText, value: props.value, onChangeText: (text) => { - if (skippedFirstOnChangeText) { + if (skippedFirstOnChangeTextRef.current) { props.onChange({addressStreet: text}); } else { - setSkippedFirstOnChangeText(true); + skippedFirstOnChangeTextRef.current = true; } // If the text is empty, we set displayListViewBorder to false to prevent UI flickering From e7ff88aca5209d90e543d3c03787b4d5cdd487ed Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 12:50:21 -0300 Subject: [PATCH 15/21] Remove unnecesary new line --- src/libs/ReimbursementAccountUtils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReimbursementAccountUtils.js b/src/libs/ReimbursementAccountUtils.js index 832fe59b66d0..2cbf51c8cc93 100644 --- a/src/libs/ReimbursementAccountUtils.js +++ b/src/libs/ReimbursementAccountUtils.js @@ -26,7 +26,6 @@ function getErrors(props) { return lodashGet(props, ['reimbursementAccount', 'errors'], {}); } - /** * @param {Object} props * @param {String[]} paths From fd5316e242362e40d8d9f318e14d8bc0972e2d46 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 12:52:25 -0300 Subject: [PATCH 16/21] Remove unnecesary empty placeholder --- src/components/AddressSearch.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index ca5d149e424d..85619b78bfa1 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -90,7 +90,6 @@ const AddressSearch = (props) => { fetchDetails suppressDefaultStyles enablePoweredByContainer={false} - placeholder="" onPress={(data, details) => { saveLocationDetails(details); From 3fd626a01dc6935c8758f9cb712d478c6dfa0ff5 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 13:59:30 -0300 Subject: [PATCH 17/21] Remove "address" prefix from address related input --- src/components/AddressSearch.js | 32 +++++++-------- src/libs/ValidationUtils.js | 10 ++--- .../ReimbursementAccount/ACHContractStep.js | 29 +++----------- src/pages/ReimbursementAccount/CompanyStep.js | 17 ++++++-- .../ReimbursementAccount/IdentityForm.js | 40 +++++++++---------- .../ReimbursementAccount/RequestorStep.js | 24 +++++------ .../settings/Payments/AddDebitCardPage.js | 12 ++++-- 7 files changed, 82 insertions(+), 82 deletions(-) diff --git a/src/components/AddressSearch.js b/src/components/AddressSearch.js index 85619b78bfa1..9d3cacceca6d 100644 --- a/src/components/AddressSearch.js +++ b/src/components/AddressSearch.js @@ -56,28 +56,28 @@ const AddressSearch = (props) => { // Gather the values from the Google details const streetNumber = GooglePlacesUtils.getAddressComponent(addressComponents, 'street_number', 'long_name') || ''; const streetName = GooglePlacesUtils.getAddressComponent(addressComponents, 'route', 'long_name') || ''; - const addressStreet = `${streetNumber} ${streetName}`.trim(); - let addressCity = GooglePlacesUtils.getAddressComponent(addressComponents, 'locality', 'long_name'); - if (!addressCity) { - addressCity = GooglePlacesUtils.getAddressComponent(addressComponents, 'sublocality', 'long_name'); - Log.hmmm('[AddressSearch] Replacing missing locality with sublocality: ', {address: details.formatted_address, sublocality: addressCity}); + const street = `${streetNumber} ${streetName}`.trim(); + let city = GooglePlacesUtils.getAddressComponent(addressComponents, 'locality', 'long_name'); + if (!city) { + city = GooglePlacesUtils.getAddressComponent(addressComponents, 'sublocality', 'long_name'); + Log.hmmm('[AddressSearch] Replacing missing locality with sublocality: ', {address: details.formatted_address, sublocality: city}); } - const addressZipCode = GooglePlacesUtils.getAddressComponent(addressComponents, 'postal_code', 'long_name'); - const addressState = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); + const zipCode = GooglePlacesUtils.getAddressComponent(addressComponents, 'postal_code', 'long_name'); + const state = GooglePlacesUtils.getAddressComponent(addressComponents, 'administrative_area_level_1', 'short_name'); const values = {}; - if (addressStreet && addressStreet.length > props.value.length) { + if (street && street.length > props.value.length) { // Don't replace if the user has typed something longer. I.e. maybe the user entered the Apt # - values.addressStreet = addressStreet; + values.street = street; } - if (addressCity) { - values.addressCity = addressCity; + if (city) { + values.city = city; } - if (addressZipCode) { - values.addressZipCode = addressZipCode; + if (zipCode) { + values.zipCode = zipCode; } - if (addressState) { - values.addressState = addressState; + if (state) { + values.state = state; } if (_.size(values) === 0) { return; @@ -114,7 +114,7 @@ const AddressSearch = (props) => { value: props.value, onChangeText: (text) => { if (skippedFirstOnChangeTextRef.current) { - props.onChange({addressStreet: text}); + props.onChange({street: text}); } else { skippedFirstOnChangeTextRef.current = true; } diff --git a/src/libs/ValidationUtils.js b/src/libs/ValidationUtils.js index 77d3794c9b3b..c56271b1f468 100644 --- a/src/libs/ValidationUtils.js +++ b/src/libs/ValidationUtils.js @@ -219,7 +219,7 @@ function isValidURL(url) { * @returns {Object} */ function validateIdentity(identity) { - const requiredFields = ['firstName', 'lastName', 'addressStreet', 'addressCity', 'addressZipCode', 'addressState', 'ssnLast4', 'dob']; + const requiredFields = ['firstName', 'lastName', 'street', 'city', 'zipCode', 'state', 'ssnLast4', 'dob']; const errors = {}; // Check that all required fields are filled @@ -230,12 +230,12 @@ function validateIdentity(identity) { errors[fieldName] = true; }); - if (!isValidAddress(identity.addressStreet)) { - errors.addressStreet = true; + if (!isValidAddress(identity.street)) { + errors.street = true; } - if (!isValidZipCode(identity.addressZipCode)) { - errors.addressZipCode = true; + if (!isValidZipCode(identity.zipCode)) { + errors.zipCode = true; } // dob field has multiple validations/errors, we are handling it temporarily like this. diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 52121ed81ffa..54acf6338981 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -71,16 +71,7 @@ class ACHContractStep extends React.Component { validate() { let beneficialOwnersErrors = []; if (this.state.hasOtherBeneficialOwners) { - beneficialOwnersErrors = _.map(this.state.beneficialOwners, beneficialOwner => ValidationUtils.validateIdentity({ - firstName: beneficialOwner.firstName, - lastName: beneficialOwner.lastName, - addressStreet: beneficialOwner.street, - addressState: beneficialOwner.state, - addressCity: beneficialOwner.city, - addressZipCode: beneficialOwner.zipCode, - dob: beneficialOwner.dob, - ssnLast4: beneficialOwner.ssnLast4, - })); + beneficialOwnersErrors = _.map(this.state.beneficialOwners, ValidationUtils.validateIdentity); } const errors = {}; @@ -130,16 +121,9 @@ class ACHContractStep extends React.Component { */ clearErrorAndSetBeneficialOwnerValues(ownerIndex, values) { this.setState((prevState) => { - const renamedFields = { - addressStreet: 'street', - addressCity: 'city', - addressState: 'state', - addressZipCode: 'zipCode', - }; const beneficialOwners = [...prevState.beneficialOwners]; _.each(values, (value, inputKey) => { - const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); - beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], [renamedInputKey]: value}; + beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], [inputKey]: value}; }); BankAccounts.updateReimbursementAccountDraft({beneficialOwners}); return {beneficialOwners}; @@ -262,13 +246,12 @@ class ACHContractStep extends React.Component { values={{ firstName: owner.firstName || '', lastName: owner.lastName || '', - addressStreet: owner.street || '', - addressState: owner.state || '', - addressCity: owner.city || '', - addressZipCode: owner.zipCode || '', + street: owner.street || '', + city: owner.city || '', + state: owner.state || '', + zipCode: owner.zipCode || '', dob: owner.dob || '', ssnLast4: owner.ssnLast4 || '', - manualAddress: owner.manualAddress, }} errors={lodashGet(this.getErrors(), `beneficialOwnersErrors[${index}]`, {})} /> diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index f42540147c11..e7714f0d81a1 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -91,6 +91,14 @@ class CompanyStep extends React.Component { hasNoConnectionToCannabis: 'bankAccount.error.restrictedBusiness', }; + // AddressSearch uses different keys for these fields + this.renamedFields = { + street: 'addressStreet', + state: 'addressState', + city: 'addressCity', + zipCode: 'addressZipCode', + }; + this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); this.clearErrors = inputKeys => ReimbursementAccountUtils.clearErrors(this.props, inputKeys); @@ -213,10 +221,13 @@ class CompanyStep extends React.Component { containerStyles={[styles.mt4]} value={this.state.addressStreet} onChange={(values) => { - _.each(values, (value, key) => { - this.setValue({[key]: value}); + const keysToClear = []; + _.each(values, (value, inputKey) => { + const renamedInputKey = lodashGet(this.renamedFields, inputKey, inputKey); + this.setValue({[renamedInputKey]: value}); + keysToClear.push(renamedInputKey); }); - this.clearErrors(_.keys(values)); + this.clearErrors(keysToClear); }} errorText={this.getErrorText('addressStreet')} /> diff --git a/src/pages/ReimbursementAccount/IdentityForm.js b/src/pages/ReimbursementAccount/IdentityForm.js index f2be6925c165..ddb791571c61 100644 --- a/src/pages/ReimbursementAccount/IdentityForm.js +++ b/src/pages/ReimbursementAccount/IdentityForm.js @@ -27,16 +27,16 @@ const propTypes = { lastName: PropTypes.string, /** Address street field */ - addressStreet: PropTypes.string, + street: PropTypes.string, /** Address city field */ - addressCity: PropTypes.string, + city: PropTypes.string, /** Address state field */ - addressState: PropTypes.string, + state: PropTypes.string, /** Address zip code field */ - addressZipCode: PropTypes.string, + zipCode: PropTypes.string, /** Date of birth field */ dob: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Date)]), @@ -56,10 +56,10 @@ const defaultProps = { values: { firstName: '', lastName: '', - addressStreet: '', - addressCity: '', - addressState: '', - addressZipCode: '', + street: '', + city: '', + state: '', + zipCode: '', dob: '', ssnLast4: '', }, @@ -112,26 +112,26 @@ const IdentityForm = (props) => { {props.translate('common.noPO')} props.onFieldChange('addressCity', value)} - errorText={props.errors.addressCity ? props.translate('bankAccount.error.addressCity') : ''} + value={props.values.city} + onChangeText={value => props.onFieldChange('city', value)} + errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} /> props.onFieldChange('addressState', value)} - errorText={props.errors.addressState ? props.translate('bankAccount.error.addressState') : ''} - hasError={Boolean(props.errors.addressState)} + value={props.values.state} + onChange={value => props.onFieldChange('state', value)} + errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} + hasError={Boolean(props.errors.state)} /> @@ -139,9 +139,9 @@ const IdentityForm = (props) => { label={props.translate('common.zip')} containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.NUMERIC} - value={props.values.addressZipCode} - onChangeText={value => props.onFieldChange('addressZipCode', value)} - errorText={props.errors.addressZipCode ? props.translate('bankAccount.error.zipCode') : ''} + value={props.values.zipCode} + onChangeText={value => props.onFieldChange('zipCode', value)} + errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} /> diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index 455d7da7d536..ab869034304f 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -77,10 +77,10 @@ class RequestorStep extends React.Component { */ clearErrorsAndSetValues(values) { const renamedFields = { - addressStreet: 'requestorAddressStreet', - addressCity: 'requestorAddressCity', - addressState: 'requestorAddressState', - addressZipCode: 'requestorAddressZipCode', + street: 'requestorAddressStreet', + city: 'requestorAddressCity', + state: 'requestorAddressState', + zipCode: 'requestorAddressZipCode', }; const newState = {}; _.each(values, (value, inputKey) => { @@ -117,10 +117,10 @@ class RequestorStep extends React.Component { const errors = ValidationUtils.validateIdentity({ firstName: this.state.firstName, lastName: this.state.lastName, - addressStreet: this.state.requestorAddressStreet, - addressState: this.state.requestorAddressState, - addressCity: this.state.requestorAddressCity, - addressZipCode: this.state.requestorAddressZipCode, + street: this.state.requestorAddressStreet, + state: this.state.requestorAddressState, + city: this.state.requestorAddressCity, + zipCode: this.state.requestorAddressZipCode, dob: this.state.dob, ssnLast4: this.state.ssnLast4, }); @@ -217,10 +217,10 @@ class RequestorStep extends React.Component { values={{ firstName: this.state.firstName, lastName: this.state.lastName, - addressStreet: this.state.requestorAddressStreet, - addressState: this.state.requestorAddressState, - addressCity: this.state.requestorAddressCity, - addressZipCode: this.state.requestorAddressZipCode, + street: this.state.requestorAddressStreet, + state: this.state.requestorAddressState, + city: this.state.requestorAddressCity, + zipCode: this.state.requestorAddressZipCode, dob: this.state.dob, ssnLast4: this.state.ssnLast4, }} diff --git a/src/pages/settings/Payments/AddDebitCardPage.js b/src/pages/settings/Payments/AddDebitCardPage.js index f2b50d7d8131..83dc77abd556 100644 --- a/src/pages/settings/Payments/AddDebitCardPage.js +++ b/src/pages/settings/Payments/AddDebitCardPage.js @@ -237,11 +237,17 @@ class DebitCardPage extends Component { containerStyles={[styles.mt4]} value={this.state.addressStreet} onChange={(values) => { - _.each(values, (value, key) => { - if (key === 'addressCity') { + const renamedFields = { + street: 'addressStreet', + state: 'addressState', + zipCode: 'addressZipCode', + }; + _.each(values, (value, inputKey) => { + if (inputKey === 'city') { return; } - this.clearErrorAndSetValue(key, value); + const renamedInputKey = lodashGet(renamedFields, inputKey, inputKey); + this.clearErrorAndSetValue(renamedInputKey, value); }); }} errorText={this.getErrorText('addressStreet')} From c30f08a0d308334385d499584201f679287b756c Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 14:13:43 -0300 Subject: [PATCH 18/21] IdentityForm calls onChange always passing an object --- .../ReimbursementAccount/ACHContractStep.js | 8 ++------ src/pages/ReimbursementAccount/IdentityForm.js | 16 ++++++++-------- src/pages/ReimbursementAccount/RequestorStep.js | 10 ++-------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index 54acf6338981..c1183c5a6401 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -236,12 +236,8 @@ class ACHContractStep extends React.Component { { - if (_.isString(inputKeyOrValues)) { - this.clearErrorAndSetBeneficialOwnerValue(index, inputKeyOrValues, value); - } else { - this.clearErrorAndSetBeneficialOwnerValues(index, inputKeyOrValues); - } + onFieldChange={(values) => { + this.clearErrorAndSetBeneficialOwnerValues(index, values); }} values={{ firstName: owner.firstName || '', diff --git a/src/pages/ReimbursementAccount/IdentityForm.js b/src/pages/ReimbursementAccount/IdentityForm.js index ddb791571c61..49624f4ca524 100644 --- a/src/pages/ReimbursementAccount/IdentityForm.js +++ b/src/pages/ReimbursementAccount/IdentityForm.js @@ -15,7 +15,7 @@ const propTypes = { /** Style for wrapping View */ style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), - /** Callback fired when a field changes. Passes args as fieldName, val */ + /** Callback fired when a field changes. Passes args as {[fieldName]: val} */ onFieldChange: PropTypes.func.isRequired, /** Form values */ @@ -79,7 +79,7 @@ const IdentityForm = (props) => { props.onFieldChange('firstName', value)} + onChangeText={value => props.onFieldChange({firstName: value})} errorText={props.errors.firstName ? props.translate('bankAccount.error.firstName') : ''} /> @@ -87,7 +87,7 @@ const IdentityForm = (props) => { props.onFieldChange('lastName', value)} + onChangeText={value => props.onFieldChange({lastName: value})} errorText={props.errors.lastName ? props.translate('bankAccount.error.lastName') : ''} /> @@ -97,7 +97,7 @@ const IdentityForm = (props) => { containerStyles={[styles.mt4]} placeholder={props.translate('common.dateFormat')} value={props.values.dob} - onChange={value => props.onFieldChange('dob', value)} + onChange={value => props.onFieldChange({dob: value})} errorText={dobErrorText} /> { containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.NUMERIC} value={props.values.ssnLast4} - onChangeText={value => props.onFieldChange('ssnLast4', value)} + onChangeText={value => props.onFieldChange({ssnLast4: value})} errorText={props.errors.ssnLast4 ? props.translate('bankAccount.error.ssnLast4') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} /> @@ -122,14 +122,14 @@ const IdentityForm = (props) => { props.onFieldChange('city', value)} + onChangeText={value => props.onFieldChange({city: value})} errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} /> props.onFieldChange('state', value)} + onChange={value => props.onFieldChange({state: value})} errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} hasError={Boolean(props.errors.state)} /> @@ -140,7 +140,7 @@ const IdentityForm = (props) => { containerStyles={[styles.mt4]} keyboardType={CONST.KEYBOARD_TYPE.NUMERIC} value={props.values.zipCode} - onChangeText={value => props.onFieldChange('zipCode', value)} + onChangeText={value => props.onFieldChange({zipCode: value})} errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} /> diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index ab869034304f..de8b28ee0d83 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -38,6 +38,7 @@ class RequestorStep extends React.Component { this.submit = this.submit.bind(this); this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this); + this.clearErrorsAndSetValues = this.clearErrorsAndSetValues.bind(this); this.state = { firstName: ReimbursementAccountUtils.getDefaultStateForField(props, 'firstName'), @@ -206,14 +207,7 @@ class RequestorStep extends React.Component { { - if (_.isString(inputKeyOrUpdatesBatch)) { - this.clearErrorAndSetValue(inputKeyOrUpdatesBatch, value); - } else { - // While we have batched updates to handle clearing errors properly - this.clearErrorsAndSetValues(inputKeyOrUpdatesBatch); - } - }} + onFieldChange={this.clearErrorsAndSetValues} values={{ firstName: this.state.firstName, lastName: this.state.lastName, From ec758cc27c54a0c087ed3223a8b4e101df1688c7 Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 14:32:23 -0300 Subject: [PATCH 19/21] Cleanup contract step --- .../ReimbursementAccount/ACHContractStep.js | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/pages/ReimbursementAccount/ACHContractStep.js b/src/pages/ReimbursementAccount/ACHContractStep.js index c1183c5a6401..110ef0aa9e89 100644 --- a/src/pages/ReimbursementAccount/ACHContractStep.js +++ b/src/pages/ReimbursementAccount/ACHContractStep.js @@ -61,7 +61,7 @@ class ACHContractStep extends React.Component { this.getErrors = () => ReimbursementAccountUtils.getErrors(this.props); this.clearError = inputKey => ReimbursementAccountUtils.clearError(this.props, inputKey); - this.clearErrors = values => ReimbursementAccountUtils.clearErrors(this.props, values); + this.clearErrors = inputKeys => ReimbursementAccountUtils.clearErrors(this.props, inputKeys); this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey); } @@ -122,9 +122,7 @@ class ACHContractStep extends React.Component { clearErrorAndSetBeneficialOwnerValues(ownerIndex, values) { this.setState((prevState) => { const beneficialOwners = [...prevState.beneficialOwners]; - _.each(values, (value, inputKey) => { - beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], [inputKey]: value}; - }); + beneficialOwners[ownerIndex] = {...beneficialOwners[ownerIndex], ...values}; BankAccounts.updateReimbursementAccountDraft({beneficialOwners}); return {beneficialOwners}; }); @@ -139,17 +137,6 @@ class ACHContractStep extends React.Component { this.clearErrors(_.map(inputKeys, inputKey => `beneficialOwnersErrors.${ownerIndex}.${inputKey}`)); } - /** - * Clear the error associated to inputKey if found and store the inputKey new value in the state. - * - * @param {Integer} ownerIndex - * @param {String} inputKey - * @param {String} value - */ - clearErrorAndSetBeneficialOwnerValue(ownerIndex, inputKey, value) { - this.clearErrorAndSetBeneficialOwnerValues(ownerIndex, {[inputKey]: value}); - } - submit() { if (!this.validate()) { return; @@ -236,9 +223,7 @@ class ACHContractStep extends React.Component { { - this.clearErrorAndSetBeneficialOwnerValues(index, values); - }} + onFieldChange={values => this.clearErrorAndSetBeneficialOwnerValues(index, values)} values={{ firstName: owner.firstName || '', lastName: owner.lastName || '', From 8466121ccbe21ef8a3acd5ecb0837c0d51c92c0d Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 14:42:22 -0300 Subject: [PATCH 20/21] Remove unused function --- src/pages/ReimbursementAccount/RequestorStep.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/pages/ReimbursementAccount/RequestorStep.js b/src/pages/ReimbursementAccount/RequestorStep.js index de8b28ee0d83..7359a15ac6af 100644 --- a/src/pages/ReimbursementAccount/RequestorStep.js +++ b/src/pages/ReimbursementAccount/RequestorStep.js @@ -37,7 +37,6 @@ class RequestorStep extends React.Component { super(props); this.submit = this.submit.bind(this); - this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this); this.clearErrorsAndSetValues = this.clearErrorsAndSetValues.bind(this); this.state = { @@ -101,16 +100,6 @@ class RequestorStep extends React.Component { this.clearErrors(inputKeys); } - /** - * Clear the error associated to inputKey if found and store the inputKey new value in the state. - * - * @param {String} inputKey - * @param {String|Boolean} value - */ - clearErrorAndSetValue(inputKey, value) { - this.clearErrorsAndSetValues({[inputKey]: value}); - } - /** * @returns {Boolean} */ From 3cddfcf3b599bc275252f1dcdc0f160f773cc6ce Mon Sep 17 00:00:00 2001 From: Aldo Canepa Date: Thu, 23 Dec 2021 15:20:19 -0300 Subject: [PATCH 21/21] Refactored address related fields into new AddressForm component --- src/pages/ReimbursementAccount/AddressForm.js | 90 +++++++++++++++++++ src/pages/ReimbursementAccount/CompanyStep.js | 60 ++++--------- .../ReimbursementAccount/IdentityForm.js | 42 ++------- 3 files changed, 114 insertions(+), 78 deletions(-) create mode 100644 src/pages/ReimbursementAccount/AddressForm.js diff --git a/src/pages/ReimbursementAccount/AddressForm.js b/src/pages/ReimbursementAccount/AddressForm.js new file mode 100644 index 000000000000..acfcc5d8650f --- /dev/null +++ b/src/pages/ReimbursementAccount/AddressForm.js @@ -0,0 +1,90 @@ +import React from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import ExpensiTextInput from '../../components/ExpensiTextInput'; +import AddressSearch from '../../components/AddressSearch'; +import styles from '../../styles/styles'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import CONST from '../../CONST'; +import StatePicker from '../../components/StatePicker'; +import Text from '../../components/Text'; + +const propTypes = { + /** Callback fired when a field changes. Passes args as {[fieldName]: val} */ + onFieldChange: PropTypes.func.isRequired, + + /** Form values */ + values: PropTypes.shape({ + /** Address street field */ + street: PropTypes.string, + + /** Address city field */ + city: PropTypes.string, + + /** Address state field */ + state: PropTypes.string, + + /** Address zip code field */ + zipCode: PropTypes.string, + }), + + /** Any errors that can arise from form validation */ + errors: PropTypes.objectOf(PropTypes.bool), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + values: { + street: '', + city: '', + state: '', + zipCode: '', + }, + errors: {}, +}; + +const AddressForm = props => ( + <> + + {props.translate('common.noPO')} + + + props.onFieldChange({city: value})} + errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} + /> + + + props.onFieldChange({state: value})} + errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} + hasError={Boolean(props.errors.state)} + /> + + + props.onFieldChange({zipCode: value})} + errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} + maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} + /> + +); + +AddressForm.propTypes = propTypes; +AddressForm.defaultProps = defaultProps; +AddressForm.displayName = 'AddressForm'; +export default withLocalize(AddressForm); diff --git a/src/pages/ReimbursementAccount/CompanyStep.js b/src/pages/ReimbursementAccount/CompanyStep.js index e7714f0d81a1..14379652b4f7 100644 --- a/src/pages/ReimbursementAccount/CompanyStep.js +++ b/src/pages/ReimbursementAccount/CompanyStep.js @@ -24,7 +24,7 @@ import ExpensiPicker from '../../components/ExpensiPicker'; import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils'; import reimbursementAccountPropTypes from './reimbursementAccountPropTypes'; import ReimbursementAccountForm from './ReimbursementAccountForm'; -import AddressSearch from '../../components/AddressSearch'; +import AddressForm from './AddressForm'; const propTypes = { /** Bank account currently in setup */ @@ -78,10 +78,6 @@ class CompanyStep extends React.Component { // Map a field to the key of the error's translation this.errorTranslationKeys = { companyName: 'bankAccount.error.companyName', - addressStreet: 'bankAccount.error.addressStreet', - addressCity: 'bankAccount.error.addressCity', - addressState: 'bankAccount.error.addressState', - addressZipCode: 'bankAccount.error.zipCode', companyPhone: 'bankAccount.error.phoneNumber', website: 'bankAccount.error.website', companyTaxID: 'bankAccount.error.taxID', @@ -216,46 +212,28 @@ class CompanyStep extends React.Component { disabled={shouldDisableCompanyName} errorText={this.getErrorText('companyName')} /> - { - const keysToClear = []; + { + const renamedValues = {}; _.each(values, (value, inputKey) => { const renamedInputKey = lodashGet(this.renamedFields, inputKey, inputKey); - this.setValue({[renamedInputKey]: value}); - keysToClear.push(renamedInputKey); + renamedValues[renamedInputKey] = value; }); - this.clearErrors(keysToClear); + this.setValue(renamedValues); + this.clearErrors(_.keys(renamedValues)); }} - errorText={this.getErrorText('addressStreet')} - /> - {this.props.translate('common.noPO')} - - - this.clearErrorAndSetValue('addressCity', value)} - value={this.state.addressCity} - errorText={this.getErrorText('addressCity')} - /> - - - this.clearErrorAndSetValue('addressState', value)} - value={this.state.addressState} - hasError={this.getErrors().addressState} - /> - - - this.clearErrorAndSetValue('addressZipCode', value)} - value={this.state.addressZipCode} - errorText={this.getErrorText('addressZipCode')} /> { errorText={props.errors.ssnLast4 ? props.translate('bankAccount.error.ssnLast4') : ''} maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.SSN} /> - - {props.translate('common.noPO')} - - - props.onFieldChange({city: value})} - errorText={props.errors.city ? props.translate('bankAccount.error.addressCity') : ''} - /> - - - props.onFieldChange({state: value})} - errorText={props.errors.state ? props.translate('bankAccount.error.addressState') : ''} - hasError={Boolean(props.errors.state)} - /> - - - props.onFieldChange({zipCode: value})} - errorText={props.errors.zipCode ? props.translate('bankAccount.error.zipCode') : ''} - maxLength={CONST.BANK_ACCOUNT.MAX_LENGTH.ZIP_CODE} + );