Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix show multiple error messages simultaneously #1

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,13 @@ class Form extends React.Component {
FormActions.setErrorFields(this.props.formID, null);
const validationErrors = this.props.validate(values);

if (!_.isObject(validationErrors)) {
if (!_.every(validationErrors, _.isObject)) {
throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}');
}

const errors = _.pick(validationErrors, (inputValue, inputID) => (
Boolean(this.touchedInputs[inputID])
));
const uniqueErrors = ErrorUtils.getUniqueErrorMessages(validationErrors);

const errors = _.pick(uniqueErrors, (inputValue, inputID) => (Boolean(this.touchedInputs[inputID])));

if (!_.isEqual(errors, this.state.errors)) {
this.setState({errors});
Expand Down
20 changes: 20 additions & 0 deletions src/libs/ErrorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,28 @@ function getLatestErrorMessage(onyxData) {
.value();
}

/**
* @param {Array} errorMessages
* @returns {Object}
*/
function getUniqueErrorMessages(errorMessages) {
const errors = _.reduce(errorMessages, (acc, error) => {
const [key] = _.keys(error);
if (acc[key]) {
acc[key] = `${acc[key]}. ${error[key]}`;
} else {
acc[key] = error[key];
}

return acc;
}, {});

return errors;
}

export {
// eslint-disable-next-line import/prefer-default-export
getAuthenticateErrorMessage,
getLatestErrorMessage,
getUniqueErrorMessages,
};
2 changes: 1 addition & 1 deletion src/pages/AddPersonalBankAccountPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class AddPersonalBankAccountPage extends React.Component {
* @returns {Object}
*/
validate() {
return {};
return [];
}

submit() {
Expand Down
24 changes: 12 additions & 12 deletions src/pages/EnablePayments/AdditionalDetailsStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,52 +114,52 @@ class AdditionalDetailsStep extends React.Component {
* @returns {Object}
*/
validate(values) {
const errors = {};
const errors = [];

if (_.isEmpty(values[INPUT_IDS.LEGAL_FIRST_NAME])) {
errors[INPUT_IDS.LEGAL_FIRST_NAME] = this.props.translate(this.errorTranslationKeys.legalFirstName);
errors.push({[INPUT_IDS.LEGAL_FIRST_NAME]: this.props.translate(this.errorTranslationKeys.legalFirstName)});
}

if (_.isEmpty(values[INPUT_IDS.LEGAL_LAST_NAME])) {
errors[INPUT_IDS.LEGAL_LAST_NAME] = this.props.translate(this.errorTranslationKeys.legalLastName);
errors.push({[INPUT_IDS.LEGAL_LAST_NAME]: this.props.translate(this.errorTranslationKeys.legalLastName)});
}

if (!ValidationUtils.isValidPastDate(values[INPUT_IDS.DOB])) {
errors[INPUT_IDS.DOB] = this.props.translate(this.errorTranslationKeys.dob);
errors.push({[INPUT_IDS.DOB]: this.props.translate(this.errorTranslationKeys.dob)});
}

if (!ValidationUtils.meetsAgeRequirements(values[INPUT_IDS.DOB])) {
errors[INPUT_IDS.DOB] = this.props.translate(this.errorTranslationKeys.age);
errors.push({[INPUT_IDS.DOB]: this.props.translate(this.errorTranslationKeys.age)});
}

if (!ValidationUtils.isValidAddress(values[INPUT_IDS.ADDRESS.street]) || _.isEmpty(values[INPUT_IDS.ADDRESS.street])) {
errors[INPUT_IDS.ADDRESS.street] = this.props.translate('bankAccount.error.addressStreet');
errors.push({[INPUT_IDS.ADDRESS.street]: this.props.translate('bankAccount.error.addressStreet')});
}

if (_.isEmpty(values[INPUT_IDS.ADDRESS.city])) {
errors[INPUT_IDS.ADDRESS.city] = this.props.translate('bankAccount.error.addressCity');
errors.push({[INPUT_IDS.ADDRESS.city]: this.props.translate('bankAccount.error.addressCity')});
}

if (_.isEmpty(values[INPUT_IDS.ADDRESS.state])) {
errors[INPUT_IDS.ADDRESS.state] = this.props.translate('bankAccount.error.addressState');
errors.push({[INPUT_IDS.ADDRESS.state]: this.props.translate('bankAccount.error.addressState')});
}

if (!ValidationUtils.isValidZipCode(values[INPUT_IDS.ADDRESS.zipCode])) {
errors[INPUT_IDS.ADDRESS.zipCode] = this.props.translate('bankAccount.error.zipCode');
errors.push({[INPUT_IDS.ADDRESS.zipCode]: this.props.translate('bankAccount.error.zipCode')});
}

if (!ValidationUtils.isValidUSPhone(values[INPUT_IDS.PHONE_NUMBER], true)) {
errors[INPUT_IDS.PHONE_NUMBER] = this.props.translate(this.errorTranslationKeys.phoneNumber);
errors.push({[INPUT_IDS.PHONE_NUMBER]: this.props.translate(this.errorTranslationKeys.phoneNumber)});
}

// this.props.walletAdditionalDetails stores errors returned by the server. If the server returns an SSN error
// then the user needs to provide the full 9 digit SSN.
if (this.props.walletAdditionalDetails.errorCode === CONST.WALLET.ERROR.SSN) {
if (!ValidationUtils.isValidSSNFullNine(values[INPUT_IDS.SSN])) {
errors[INPUT_IDS.SSN] = this.props.translate(this.errorTranslationKeys.ssnFull9);
errors.push({[INPUT_IDS.SSN]: this.props.translate(this.errorTranslationKeys.ssnFull9)});
}
} else if (!ValidationUtils.isValidSSNLastFour(values[INPUT_IDS.SSN])) {
errors[INPUT_IDS.SSN] = this.props.translate(this.errorTranslationKeys.ssn);
errors.push({[INPUT_IDS.SSN]: this.props.translate(this.errorTranslationKeys.ssn)});
}

return errors;
Expand Down
17 changes: 9 additions & 8 deletions src/pages/ReimbursementAccount/ACHContractStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,48 +48,49 @@ class ACHContractStep extends React.Component {
* @returns {Object}
*/
validate(values) {
const errors = {};
const errors = [];

const errorKeys = {
street: 'address',
city: 'addressCity',
state: 'addressState',
};

const requiredFields = ['firstName', 'lastName', 'dob', 'ssnLast4', 'street', 'city', 'zipCode', 'state'];
if (values.hasOtherBeneficialOwners) {
_.each(this.state.beneficialOwners, (ownerKey) => {
// eslint-disable-next-line rulesdir/prefer-early-return
_.each(requiredFields, (inputKey) => {
if (!ValidationUtils.isRequiredFulfilled(values[`beneficialOwner_${ownerKey}_${inputKey}`])) {
const errorKey = errorKeys[inputKey] || inputKey;
errors[`beneficialOwner_${ownerKey}_${inputKey}`] = this.props.translate(`bankAccount.error.${errorKey}`);
errors.push({[`beneficialOwner_${ownerKey}_${inputKey}`]: this.props.translate(`bankAccount.error.${errorKey}`)});
}
});

if (values[`beneficialOwner_${ownerKey}_dob`] && !ValidationUtils.meetsAgeRequirements(values[`beneficialOwner_${ownerKey}_dob`])) {
errors[`beneficialOwner_${ownerKey}_dob`] = this.props.translate('bankAccount.error.age');
errors.push({[`beneficialOwner_${ownerKey}_dob`]: this.props.translate('bankAccount.error.age')});
}

if (values[`beneficialOwner_${ownerKey}_ssnLast4`] && !ValidationUtils.isValidSSNLastFour(values[`beneficialOwner_${ownerKey}_ssnLast4`])) {
errors[`beneficialOwner_${ownerKey}_ssnLast4`] = this.props.translate('bankAccount.error.ssnLast4');
errors.push({[`beneficialOwner_${ownerKey}_ssnLast4`]: this.props.translate('bankAccount.error.ssnLast4')});
}

if (values[`beneficialOwner_${ownerKey}_street`] && !ValidationUtils.isValidAddress(values[`beneficialOwner_${ownerKey}_street`])) {
errors[`beneficialOwner_${ownerKey}_street`] = this.props.translate('bankAccount.error.addressStreet');
errors.push({[`beneficialOwner_${ownerKey}_street`]: this.props.translate('bankAccount.error.addressStreet')});
}

if (values[`beneficialOwner_${ownerKey}_zipCode`] && !ValidationUtils.isValidZipCode(values[`beneficialOwner_${ownerKey}_zipCode`])) {
errors[`beneficialOwner_${ownerKey}_zipCode`] = this.props.translate('bankAccount.error.zipCode');
errors.push({[`beneficialOwner_${ownerKey}_zipCode`]: this.props.translate('bankAccount.error.zipCode')});
}
});
}

if (!ValidationUtils.isRequiredFulfilled(values.acceptTermsAndConditions)) {
errors.acceptTermsAndConditions = this.props.translate('common.error.acceptTerms');
errors.push({acceptTermsAndConditions: this.props.translate('common.error.acceptTerms')});
}

if (!ValidationUtils.isRequiredFulfilled(values.certifyTrueInformation)) {
errors.certifyTrueInformation = this.props.translate('beneficialOwnersStep.error.certify');
errors.push({certifyTrueInformation: this.props.translate('beneficialOwnersStep.error.certify')});
}

return errors;
Expand Down
8 changes: 4 additions & 4 deletions src/pages/ReimbursementAccount/BankAccountManualStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ class BankAccountManualStep extends React.Component {
* @returns {Object}
*/
validate(values) {
const errorFields = {};
const errorFields = [];
const routingNumber = values.routingNumber && values.routingNumber.trim();

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

return errorFields;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/ReimbursementAccount/BankAccountPlaidStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class BankAccountPlaidStep extends React.Component {
/>
<Form
formID={ONYXKEYS.FORMS.REIMBURSEMENT_ACCOUNT_FORM}
validate={() => ({})}
validate={() => []}
onSubmit={this.submit}
scrollContextEnabled
submitButtonText={this.props.translate('common.saveAndContinue')}
Expand Down
32 changes: 17 additions & 15 deletions src/pages/ReimbursementAccount/CompanyStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,56 +63,58 @@ class CompanyStep extends React.Component {
* @returns {Object} - Object containing the errors for each inputID, e.g. {inputID1: error1, inputID2: error2}
*/
validate(values) {
const errors = {};
const errors = [];

if (!values.companyName) {
errors.companyName = this.props.translate('bankAccount.error.companyName');
errors.push({companyName: this.props.translate('bankAccount.error.companyName')});
}

if (!values.addressStreet || !ValidationUtils.isValidAddress(values.addressStreet)) {
errors.addressStreet = this.props.translate('bankAccount.error.addressStreet');
errors.push({addressStreet: this.props.translate('bankAccount.error.addressStreet')});
}

if (!values.addressZipCode || !ValidationUtils.isValidZipCode(values.addressZipCode)) {
errors.addressZipCode = this.props.translate('bankAccount.error.zipCode');
errors.push({addressZipCode: this.props.translate('bankAccount.error.zipCode')});
}

if (!values.addressCity) {
errors.addressCity = this.props.translate('bankAccount.error.addressCity');
errors.push({addressCity: this.props.translate('bankAccount.error.addressCity')});
}

if (!values.addressState) {
errors.addressState = this.props.translate('bankAccount.error.addressState');
errors.push({addressState: this.props.translate('bankAccount.error.addressState')});
}

if (!values.companyPhone || !ValidationUtils.isValidUSPhone(values.companyPhone, true)) {
errors.companyPhone = this.props.translate('bankAccount.error.phoneNumber');
errors.push({companyPhone: this.props.translate('bankAccount.error.phoneNumber')});
}

if (!values.website || !ValidationUtils.isValidWebsite(values.website)) {
errors.website = this.props.translate('bankAccount.error.website');
errors.push({website: this.props.translate('bankAccount.error.website')});
}

if (!values.companyTaxID || !ValidationUtils.isValidTaxID(values.companyTaxID)) {
errors.companyTaxID = this.props.translate('bankAccount.error.taxID');
errors.push({companyTaxID: this.props.translate('bankAccount.error.taxID')});
}

if (!values.incorporationType) {
errors.incorporationType = this.props.translate('bankAccount.error.companyType');
errors.push({incorporationType: this.props.translate('bankAccount.error.companyType')});
}

if (!values.incorporationDate || !ValidationUtils.isValidDate(values.incorporationDate)) {
errors.incorporationDate = this.props.translate('common.error.dateInvalid');
} else if (!values.incorporationDate || !ValidationUtils.isValidPastDate(values.incorporationDate)) {
errors.incorporationDate = this.props.translate('bankAccount.error.incorporationDateFuture');
errors.push({incorporationDate: this.props.translate('common.error.dateInvalid')});
}

if (!values.incorporationDate || !ValidationUtils.isValidPastDate(values.incorporationDate)) {
errors.push({incorporationDate: this.props.translate('bankAccount.error.incorporationDateFuture')});
}

if (!values.incorporationState) {
errors.incorporationState = this.props.translate('bankAccount.error.incorporationState');
errors.push({incorporationState: this.props.translate('bankAccount.error.incorporationState')});
}

if (!values.hasNoConnectionToCannabis) {
errors.hasNoConnectionToCannabis = this.props.translate('bankAccount.error.restrictedBusiness');
errors.push({hasNoConnectionToCannabis: this.props.translate('bankAccount.error.restrictedBusiness')});
}

return errors;
Expand Down
24 changes: 12 additions & 12 deletions src/pages/ReimbursementAccount/RequestorStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,50 +39,50 @@ class RequestorStep extends React.Component {
* @returns {Object}
*/
validate(values) {
const errors = {};
const errors = [];

if (!ValidationUtils.isRequiredFulfilled(values.firstName)) {
errors.firstName = this.props.translate('bankAccount.error.firstName');
errors.push({firstName: this.props.translate('bankAccount.error.firstName')});
}

if (!ValidationUtils.isRequiredFulfilled(values.lastName)) {
errors.lastName = this.props.translate('bankAccount.error.lastName');
errors.push({lastName: this.props.translate('bankAccount.error.lastName')});
}

if (!ValidationUtils.isRequiredFulfilled(values.dob)) {
errors.dob = this.props.translate('bankAccount.error.dob');
errors.push({dob: this.props.translate('bankAccount.error.dob')});
}

if (values.dob && !ValidationUtils.meetsAgeRequirements(values.dob)) {
errors.dob = this.props.translate('bankAccount.error.age');
errors.push({dob: this.props.translate('bankAccount.error.age')});
}

if (!ValidationUtils.isRequiredFulfilled(values.ssnLast4) || !ValidationUtils.isValidSSNLastFour(values.ssnLast4)) {
errors.ssnLast4 = this.props.translate('bankAccount.error.ssnLast4');
errors.push({ssnLast4: this.props.translate('bankAccount.error.ssnLast4')});
}

if (!ValidationUtils.isRequiredFulfilled(values.requestorAddressStreet)) {
errors.requestorAddressStreet = this.props.translate('bankAccount.error.address');
errors.push({requestorAddressStreet: this.props.translate('bankAccount.error.address')});
}

if (values.requestorAddressStreet && !ValidationUtils.isValidAddress(values.requestorAddressStreet)) {
errors.requestorAddressStreet = this.props.translate('bankAccount.error.addressStreet');
errors.push({requestorAddressStreet: this.props.translate('bankAccount.error.addressStreet')});
}

if (!ValidationUtils.isRequiredFulfilled(values.requestorAddressCity)) {
errors.requestorAddressCity = this.props.translate('bankAccount.error.addressCity');
errors.push({requestorAddressCity: this.props.translate('bankAccount.error.addressCity')});
}

if (!ValidationUtils.isRequiredFulfilled(values.requestorAddressState)) {
errors.requestorAddressState = this.props.translate('bankAccount.error.addressState');
errors.push({requestorAddressState: this.props.translate('bankAccount.error.addressState')});
}

if (!ValidationUtils.isRequiredFulfilled(values.requestorAddressZipCode) || !ValidationUtils.isValidZipCode(values.requestorAddressZipCode)) {
errors.requestorAddressZipCode = this.props.translate('bankAccount.error.zipCode');
errors.push({requestorAddressZipCode: this.props.translate('bankAccount.error.zipCode')});
}

if (!ValidationUtils.isRequiredFulfilled(values.isControllingOfficer)) {
errors.isControllingOfficer = this.props.translate('requestorStep.isControllingOfficerError');
errors.push({isControllingOfficer: this.props.translate('requestorStep.isControllingOfficerError')});
}

return errors;
Expand Down
4 changes: 2 additions & 2 deletions src/pages/ReimbursementAccount/ValidationStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@ class ValidationStep extends React.Component {
* @returns {Object}
*/
validate(values) {
const errors = {};
const errors = [];

_.each(values, (value, key) => {
const filteredValue = this.filterInput(value);
if (ValidationUtils.isRequiredFulfilled(filteredValue)) {
return;
}
errors[key] = this.props.translate('common.error.invalidAmount');
errors.push({[key]: this.props.translate('common.error.invalidAmount')});
});

return errors;
Expand Down
Loading