Skip to content

Commit

Permalink
Merge pull request #20228 from fedirjh/Form-validation-translate-enha…
Browse files Browse the repository at this point in the history
…ncements

[Refactor] Form validation translated error
  • Loading branch information
luacmartins authored Jun 14, 2023
2 parents b66ad24 + 7d294d9 commit 37537ff
Show file tree
Hide file tree
Showing 34 changed files with 179 additions and 179 deletions.
10 changes: 5 additions & 5 deletions contributingGuides/FORMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,10 @@ Here's an example for a form that has two inputs, `routingNumber` and `accountNu
function validate(values) {
const errors = {};
if (!values.routingNumber) {
errors.routingNumber = props.translate(CONST.ERRORS.ROUTING_NUMBER);
errors.routingNumber = CONST.ERRORS.ROUTING_NUMBER;
}
if (!values.accountNumber) {
errors.accountNumber = props.translate(CONST.ERRORS.ACCOUNT_NUMBER);
errors.accountNumber = CONST.ERRORS.ACCOUNT_NUMBER;
}
return errors;
}
Expand All @@ -130,15 +130,15 @@ function validate(values) {
let errors = {};

if (!ValidationUtils.isValidDisplayName(values.firstName)) {
errors = ErrorUtils.addErrorMessage(errors, 'firstName', props.translate('personalDetails.error.hasInvalidCharacter'));
errors = ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.hasInvalidCharacter');
}

if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_FIRST_NAMES)) {
errors = ErrorUtils.addErrorMessage(errors, 'firstName', props.translate('personalDetails.error.containsReservedWord'));
errors = ErrorUtils.addErrorMessage(errors, 'firstName', 'personalDetails.error.containsReservedWord');
}

if (!ValidationUtils.isValidDisplayName(values.lastName)) {
errors.lastName = props.translate('personalDetails.error.hasInvalidCharacter');
errors.lastName = 'personalDetails.error.hasInvalidCharacter';
}

return errors;
Expand Down
2 changes: 1 addition & 1 deletion src/components/DotIndicatorMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const propTypes = {
* timestamp: 'message',
* }
*/
messages: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.array, PropTypes.string])),
messages: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))])),

// The type of message, 'error' shows a red dot, 'success' shows a green dot
type: PropTypes.oneOf(['error', 'success']).isRequired,
Expand Down
6 changes: 3 additions & 3 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ function Form(props) {
const inputRefs = useRef({});
const touchedInputs = useRef({});

const {validate, translate, onSubmit, children} = props;
const {validate, onSubmit, children} = props;

/**
* @param {Object} values - An object containing the value of each inputID, e.g. {inputID1: value1, inputID2: value2}
Expand Down Expand Up @@ -127,7 +127,7 @@ function Form(props) {
}

// Add a validation error here because it is a string value that contains HTML characters
validationErrors[inputID] = translate('common.error.invalidCharacter');
validationErrors[inputID] = 'common.error.invalidCharacter';
});

if (!_.isObject(validationErrors)) {
Expand All @@ -142,7 +142,7 @@ function Form(props) {

return touchedInputErrors;
},
[errors, touchedInputs, props.formID, validate, translate],
[errors, touchedInputs, props.formID, validate],
);

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/FormHelpMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import * as Localize from '../libs/Localize';

const propTypes = {
/** Error or hint text. Ignored when children is not empty */
message: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
message: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]),

/** Children to render next to dot indicator */
children: PropTypes.node,
Expand Down
2 changes: 1 addition & 1 deletion src/components/RoomNameInput/roomNameInputPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const propTypes = {
disabled: PropTypes.bool,

/** Error text to show */
errorText: PropTypes.string,
errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]),

...withLocalizePropTypes,

Expand Down
2 changes: 1 addition & 1 deletion src/components/TextInput/baseTextInputPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const propTypes = {
placeholder: PropTypes.string,

/** Error text to display */
errorText: PropTypes.string,
errorText: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.object]))]),

/** Icon to display in right side of text input */
icon: PropTypes.func,
Expand Down
14 changes: 10 additions & 4 deletions src/libs/ErrorUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'underscore';
import lodashGet from 'lodash/get';
import CONST from '../CONST';
import DateUtils from './DateUtils';
import * as Localize from './Localize';

/**
* @param {Object} response
Expand Down Expand Up @@ -91,18 +92,23 @@ function getLatestErrorField(onyxData, fieldName) {
* Method used to generate error message for given inputID
* @param {Object} errors - An object containing current errors in the form
* @param {String} inputID
* @param {String} message - Message to assign to the inputID errors
* @param {String|Array} message - Message to assign to the inputID errors
*
*/
function addErrorMessage(errors, inputID, message) {
const errorList = errors;
if (!message || !inputID) {
return;
}

const errorList = errors;
const translatedMessage = Localize.translateIfPhraseKey(message);

if (_.isEmpty(errorList[inputID])) {
errorList[inputID] = message;
errorList[inputID] = [translatedMessage, {isTranslated: true}];
} else if (_.isString(errorList[inputID])) {
errorList[inputID] = [`${errorList[inputID]}\n${translatedMessage}`, {isTranslated: true}];
} else {
errorList[inputID] = `${errorList[inputID]}\n${message}`;
errorList[inputID][0] = `${errorList[inputID][0]}\n${translatedMessage}`;
}
}

Expand Down
11 changes: 11 additions & 0 deletions src/libs/Localize/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,20 @@ function translateLocal(phrase, variables) {
* @returns {String}
*/
function translateIfPhraseKey(message) {
if (_.isEmpty(message)) {
return '';
}

try {
// check if error message has a variable parameter
const [phrase, variables] = _.isArray(message) ? message : [message];

// This condition checks if the error is already translated. For example, if there are multiple errors per input, we handle translation in ErrorUtils.addErrorMessage due to the inability to concatenate error keys.

if (variables && variables.isTranslated) {
return phrase;
}

return translateLocal(phrase, variables);
} catch (error) {
return message;
Expand Down
9 changes: 4 additions & 5 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {parsePhoneNumber} from 'awesome-phonenumber';
import CONST from '../CONST';
import * as CardUtils from './CardUtils';
import * as LoginUtils from './LoginUtils';
import * as Localize from './Localize';

/**
* Implements the Luhn Algorithm, a checksum formula used to validate credit card
Expand Down Expand Up @@ -225,22 +224,22 @@ function meetsMaximumAgeRequirement(date) {
* @param {String} date
* @param {Number} minimumAge
* @param {Number} maximumAge
* @returns {String}
* @returns {String|Array}
*/
function getAgeRequirementError(date, minimumAge, maximumAge) {
const recentDate = moment().startOf('day').subtract(minimumAge, 'years');
const longAgoDate = moment().startOf('day').subtract(maximumAge, 'years');
const testDate = moment(date);
if (!testDate.isValid()) {
return Localize.translateLocal('common.error.dateInvalid');
return 'common.error.dateInvalid';
}
if (testDate.isBetween(longAgoDate, recentDate, undefined, '[]')) {
return '';
}
if (testDate.isSameOrAfter(recentDate)) {
return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeBefore', {dateString: recentDate.format(CONST.DATE.MOMENT_FORMAT_STRING)});
return ['privatePersonalDetails.error.dateShouldBeBefore', {dateString: recentDate.format(CONST.DATE.MOMENT_FORMAT_STRING)}];
}
return Localize.translateLocal('privatePersonalDetails.error.dateShouldBeAfter', {dateString: longAgoDate.format(CONST.DATE.MOMENT_FORMAT_STRING)});
return ['privatePersonalDetails.error.dateShouldBeAfter', {dateString: longAgoDate.format(CONST.DATE.MOMENT_FORMAT_STRING)}];
}

/**
Expand Down
22 changes: 11 additions & 11 deletions src/pages/EnablePayments/AdditionalDetailsStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,47 +123,47 @@ class AdditionalDetailsStep extends React.Component {
const errors = {};

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

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

if (!ValidationUtils.isValidPastDate(values[INPUT_IDS.DOB]) || !ValidationUtils.meetsMaximumAgeRequirement(values[INPUT_IDS.DOB])) {
ErrorUtils.addErrorMessage(errors, INPUT_IDS.DOB, this.props.translate(this.errorTranslationKeys.dob));
ErrorUtils.addErrorMessage(errors, INPUT_IDS.DOB, this.errorTranslationKeys.dob);
} else if (!ValidationUtils.meetsMinimumAgeRequirement(values[INPUT_IDS.DOB])) {
ErrorUtils.addErrorMessage(errors, INPUT_IDS.DOB, this.props.translate(this.errorTranslationKeys.age));
ErrorUtils.addErrorMessage(errors, INPUT_IDS.DOB, 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[INPUT_IDS.ADDRESS.street] = 'bankAccount.error.addressStreet';
}

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

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

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

if (!ValidationUtils.isValidUSPhone(values[INPUT_IDS.PHONE_NUMBER], true)) {
errors[INPUT_IDS.PHONE_NUMBER] = this.props.translate(this.errorTranslationKeys.phoneNumber);
errors[INPUT_IDS.PHONE_NUMBER] = 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[INPUT_IDS.SSN] = this.errorTranslationKeys.ssnFull9;
}
} else if (!ValidationUtils.isValidSSNLastFour(values[INPUT_IDS.SSN])) {
errors[INPUT_IDS.SSN] = this.props.translate(this.errorTranslationKeys.ssn);
errors[INPUT_IDS.SSN] = this.errorTranslationKeys.ssn;
}

return errors;
Expand Down
16 changes: 8 additions & 8 deletions src/pages/ReimbursementAccount/ACHContractStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,38 +51,38 @@ function ACHContractStep(props) {
_.each(requiredFields, (inputKey) => {
if (!ValidationUtils.isRequiredFulfilled(values[`beneficialOwner_${ownerKey}_${inputKey}`])) {
const errorKey = errorKeys[inputKey] || inputKey;
errors[`beneficialOwner_${ownerKey}_${inputKey}`] = props.translate(`bankAccount.error.${errorKey}`);
errors[`beneficialOwner_${ownerKey}_${inputKey}`] = `bankAccount.error.${errorKey}`;
}
});

if (values[`beneficialOwner_${ownerKey}_dob`]) {
if (!ValidationUtils.meetsMinimumAgeRequirement(values[`beneficialOwner_${ownerKey}_dob`])) {
errors[`beneficialOwner_${ownerKey}_dob`] = props.translate('bankAccount.error.age');
errors[`beneficialOwner_${ownerKey}_dob`] = 'bankAccount.error.age';
} else if (!ValidationUtils.meetsMaximumAgeRequirement(values[`beneficialOwner_${ownerKey}_dob`])) {
errors[`beneficialOwner_${ownerKey}_dob`] = props.translate('bankAccount.error.dob');
errors[`beneficialOwner_${ownerKey}_dob`] = 'bankAccount.error.dob';
}
}

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

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

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

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

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

return errors;
Expand Down
6 changes: 3 additions & 3 deletions src/pages/ReimbursementAccount/BankAccountManualStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ class BankAccountManualStep extends React.Component {
!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.accountNumber = '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.routingNumber = 'bankAccount.error.routingNumber';
}
if (!values.acceptTerms) {
errorFields.acceptTerms = this.props.translate('common.error.acceptTerms');
errorFields.acceptTerms = '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 @@ -49,7 +49,7 @@ class BankAccountPlaidStep extends React.Component {
validate(values) {
const errorFields = {};
if (!values.acceptTerms) {
errorFields.acceptTerms = this.props.translate('common.error.acceptTerms');
errorFields.acceptTerms = 'common.error.acceptTerms';
}

return errorFields;
Expand Down
26 changes: 13 additions & 13 deletions src/pages/ReimbursementAccount/CompanyStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,53 +83,53 @@ class CompanyStep extends React.Component {
const errors = {};

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

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

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

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

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

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

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

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

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

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

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

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

return errors;
Expand Down
Loading

0 comments on commit 37537ff

Please sign in to comment.