Skip to content

Commit

Permalink
Merge pull request Expensify#10900 from jayeshmangwani/form_refactor_…
Browse files Browse the repository at this point in the history
…bankaccountstep

Form refactor bankaccountstep
  • Loading branch information
youssef-lr authored Sep 30, 2022
2 parents 1ac0be4 + 6d88418 commit 384ca7c
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,5 +180,6 @@ export default {
FORMS: {
ADD_DEBIT_CARD_FORM: 'addDebitCardForm',
REQUEST_CALL_FORM: 'requestCallForm',
REIMBURSEMENT_ACCOUNT_FORM: 'reimbursementAccount',
},
};
24 changes: 18 additions & 6 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {withOnyx} from 'react-native-onyx';
import compose from '../libs/compose';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as FormActions from '../libs/actions/FormActions';
import * as ErrorUtils from '../libs/ErrorUtils';
import styles from '../styles/styles';
import FormAlertWithSubmitButton from './FormAlertWithSubmitButton';

Expand All @@ -16,6 +17,9 @@ const propTypes = {
/** Text to be displayed in the submit button */
submitButtonText: PropTypes.string.isRequired,

/** Controls the submit button's visibility */
isSubmitButtonVisible: PropTypes.bool,

/** Callback to validate the form */
validate: PropTypes.func.isRequired,

Expand All @@ -32,8 +36,8 @@ const propTypes = {
/** Controls the loading state of the form */
isLoading: PropTypes.bool,

/** Server side error message */
error: PropTypes.string,
/** Server side errors keyed by microtime */
errors: PropTypes.objectOf(PropTypes.string),
}),

/** Contains draft values for each input in the form */
Expand All @@ -44,9 +48,10 @@ const propTypes = {
};

const defaultProps = {
isSubmitButtonVisible: true,
formState: {
isLoading: false,
error: '',
errors: null,
},
draftValues: {},
};
Expand Down Expand Up @@ -75,6 +80,11 @@ class Form extends React.Component {
this.touchedInputs[inputID] = true;
}

getErrorMessage() {
const latestErrorMessage = ErrorUtils.getLatestErrorMessage(this.props.formState);
return this.props.formState.error || (typeof latestErrorMessage === 'string' ? latestErrorMessage : '');
}

submit() {
// Return early if the form is already submitting to avoid duplicate submission
if (this.props.formState.isLoading) {
Expand All @@ -100,7 +110,7 @@ class Form extends React.Component {
* @returns {Object} - An object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2}
*/
validate(values) {
FormActions.setErrorMessage(this.props.formID, '');
FormActions.setErrors(this.props.formID, null);
const validationErrors = this.props.validate(values);

if (!_.isObject(validationErrors)) {
Expand Down Expand Up @@ -184,17 +194,19 @@ class Form extends React.Component {
>
<View style={[this.props.style]}>
{this.childrenWrapperWithProps(this.props.children)}
{this.props.isSubmitButtonVisible && (
<FormAlertWithSubmitButton
buttonText={this.props.submitButtonText}
isAlertVisible={_.size(this.state.errors) > 0 || Boolean(this.props.formState.error)}
isAlertVisible={_.size(this.state.errors) > 0 || Boolean(this.getErrorMessage())}
isLoading={this.props.formState.isLoading}
message={this.props.formState.error}
message={this.getErrorMessage()}
onSubmit={this.submit}
onFixTheErrorsLinkPressed={() => {
this.inputRefs[_.first(_.keys(this.state.errors))].focus();
}}
containerStyles={[styles.mh0, styles.mt5]}
/>
)}
</View>
</ScrollView>
</>
Expand Down
8 changes: 4 additions & 4 deletions src/libs/actions/FormActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ function setIsLoading(formID, isLoading) {

/**
* @param {String} formID
* @param {String} error
* @param {String} errors
*/
function setErrorMessage(formID, error) {
Onyx.merge(formID, {error});
function setErrors(formID, errors) {
Onyx.merge(formID, {errors});
}

/**
Expand All @@ -26,6 +26,6 @@ function setDraftValues(formID, draftValues) {

export {
setIsLoading,
setErrorMessage,
setErrors,
setDraftValues,
};
88 changes: 30 additions & 58 deletions src/pages/ReimbursementAccount/BankAccountManualStep.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from 'underscore';
import React from 'react';
import {Image, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -15,9 +14,8 @@ import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize
import * as ValidationUtils from '../../libs/ValidationUtils';
import compose from '../../libs/compose';
import ONYXKEYS from '../../ONYXKEYS';
import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount';
import exampleCheckImage from './exampleCheckImage';
import ReimbursementAccountForm from './ReimbursementAccountForm';
import Form from '../../components/Form';
import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils';

const propTypes = {
Expand All @@ -27,69 +25,40 @@ const propTypes = {
class BankAccountManualStep extends React.Component {
constructor(props) {
super(props);

this.submit = this.submit.bind(this);
this.clearErrorAndSetValue = this.clearErrorAndSetValue.bind(this);
this.getErrorText = inputKey => ReimbursementAccountUtils.getErrorText(this.props, this.errorTranslationKeys, inputKey);
this.state = {
acceptTerms: ReimbursementAccountUtils.getDefaultStateForField(props, 'acceptTerms', true),
routingNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'routingNumber'),
accountNumber: ReimbursementAccountUtils.getDefaultStateForField(props, 'accountNumber'),
};

// Map a field to the key of the error's translation
this.errorTranslationKeys = {
routingNumber: 'bankAccount.error.routingNumber',
accountNumber: 'bankAccount.error.accountNumber',
acceptTerms: 'common.error.acceptedTerms',
};
this.validate = this.validate.bind(this);
}

/**
* @returns {Boolean}
* @param {Object} values - form input values passed by the Form component
* @returns {Object}
*/
validate() {
validate(values) {
const errorFields = {};
const routingNumber = this.state.routingNumber.trim();
const routingNumber = values.routingNumber && values.routingNumber.trim();

if (!CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(this.state.accountNumber.trim())) {
errorFields.accountNumber = true;
if (!values.accountNumber || !CONST.BANK_ACCOUNT.REGEX.US_ACCOUNT_NUMBER.test(values.accountNumber.trim())) {
errorFields.accountNumber = this.props.translate('bankAccount.error.accountNumber');
}
if (!CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber)) {
errorFields.routingNumber = true;
if (!routingNumber || !CONST.BANK_ACCOUNT.REGEX.SWIFT_BIC.test(routingNumber) || !ValidationUtils.isValidRoutingNumber(routingNumber)) {
errorFields.routingNumber = this.props.translate('bankAccount.error.routingNumber');
}
if (!this.state.acceptTerms) {
errorFields.acceptTerms = true;
if (!values.acceptedTerms) {
errorFields.acceptedTerms = this.props.translate('common.error.acceptedTerms');
}

ReimbursementAccount.setBankAccountFormValidationErrors(errorFields);

return _.size(errorFields) === 0;
return errorFields;
}

submit() {
if (!this.validate()) {
return;
}
submit(values) {
BankAccounts.connectBankAccountManually(
ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID', 0),
this.state.accountNumber,
this.state.routingNumber,
values.accountNumber,
values.routingNumber,
ReimbursementAccountUtils.getDefaultStateForField(this.props, 'plaidMask'),
);
}

/**
* @param {String} inputKey
* @param {String} value
*/
clearErrorAndSetValue(inputKey, value) {
const newState = {[inputKey]: value};
this.setState(newState);
ReimbursementAccount.updateReimbursementAccountDraft(newState);
ReimbursementAccountUtils.clearError(this.props, inputKey);
}

render() {
const shouldDisableInputs = Boolean(ReimbursementAccountUtils.getDefaultStateForField(this.props, 'bankAccountID'));

Expand All @@ -104,7 +73,13 @@ class BankAccountManualStep extends React.Component {
onBackButtonPress={() => BankAccounts.setBankAccountSubStep(null)}
onCloseButtonPress={Navigation.dismissModal}
/>
<ReimbursementAccountForm onSubmit={this.submit}>
<Form
formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM}
onSubmit={this.submit}
validate={this.validate}
submitButtonText={this.props.translate('common.saveAndContinue')}
style={[styles.mh5, styles.flexGrow1]}
>
<Text style={[styles.mb5]}>
{this.props.translate('bankAccount.checkHelpLine')}
</Text>
Expand All @@ -114,26 +89,23 @@ class BankAccountManualStep extends React.Component {
source={exampleCheckImage(this.props.preferredLocale)}
/>
<TextInput
inputID="routingNumber"
label={this.props.translate('bankAccount.routingNumber')}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
defaultValue={this.state.routingNumber}
onChangeText={value => this.clearErrorAndSetValue('routingNumber', value)}
disabled={shouldDisableInputs}
errorText={this.getErrorText('routingNumber')}
shouldSaveDraft
/>
<TextInput
inputID="accountNumber"
containerStyles={[styles.mt4]}
label={this.props.translate('bankAccount.accountNumber')}
keyboardType={CONST.KEYBOARD_TYPE.NUMBER_PAD}
defaultValue={this.state.accountNumber}
onChangeText={value => this.clearErrorAndSetValue('accountNumber', value)}
disabled={shouldDisableInputs}
errorText={this.getErrorText('accountNumber')}
shouldSaveDraft
/>
<CheckboxWithLabel
style={styles.mt4}
isChecked={this.state.acceptTerms}
onInputChange={value => this.clearErrorAndSetValue('acceptTerms', value)}
inputID="acceptedTerms"
LabelComponent={() => (
<View style={[styles.flexRow, styles.alignItemsCenter]}>
<Text>
Expand All @@ -144,9 +116,9 @@ class BankAccountManualStep extends React.Component {
</TextLink>
</View>
)}
errorText={this.getErrorText('acceptTerms')}
shouldSaveDraft
/>
</ReimbursementAccountForm>
</Form>
</>
);
}
Expand Down
14 changes: 11 additions & 3 deletions src/pages/ReimbursementAccount/BankAccountPlaidStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import compose from '../../libs/compose';
import ONYXKEYS from '../../ONYXKEYS';
import AddPlaidBankAccount from '../../components/AddPlaidBankAccount';
import * as ReimbursementAccount from '../../libs/actions/ReimbursementAccount';
import ReimbursementAccountForm from './ReimbursementAccountForm';
import * as ReimbursementAccountUtils from '../../libs/ReimbursementAccountUtils';
import Form from '../../components/Form';
import styles from '../../styles/styles';

const propTypes = {
/** The OAuth URI + stateID needed to re-initialize the PlaidLink after the user logs into their bank */
Expand Down Expand Up @@ -69,7 +70,14 @@ class BankAccountPlaidStep extends React.Component {
onBackButtonPress={() => BankAccounts.setBankAccountSubStep(null)}
onCloseButtonPress={Navigation.dismissModal}
/>
<ReimbursementAccountForm onSubmit={this.submit} hideSubmitButton={_.isUndefined(this.props.plaidData.selectedPlaidBankAccount)}>
<Form
formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM}
validate={() => ({})}
onSubmit={this.submit}
submitButtonText={this.props.translate('common.saveAndContinue')}
style={[styles.mh5, styles.flexGrow1]}
isSubmitButtonVisible={!_.isUndefined(this.props.plaidData.selectedPlaidBankAccount)}
>
<AddPlaidBankAccount
text={this.props.translate('bankAccount.plaidBodyCopy')}
onSelect={(params) => {
Expand All @@ -81,7 +89,7 @@ class BankAccountPlaidStep extends React.Component {
allowDebit
bankAccountID={bankAccountID}
/>
</ReimbursementAccountForm>
</Form>
</>
);
}
Expand Down

0 comments on commit 384ca7c

Please sign in to comment.