Skip to content

Commit

Permalink
Merge pull request #14873 from Expensify/reserved-first-names
Browse files Browse the repository at this point in the history
Update display name front-end validation to match back-end
  • Loading branch information
puneetlath authored Feb 14, 2023
2 parents 137b947 + b31fb63 commit 023e59e
Show file tree
Hide file tree
Showing 7 changed files with 48 additions and 133 deletions.
5 changes: 5 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ const CONST = {
DEFAULT_AVATAR_COUNT: 24,
OLD_DEFAULT_AVATAR_COUNT: 8,

DISPLAY_NAME: {
MAX_LENGTH: 50,
RESERVED_FIRST_NAMES: ['Expensify', 'Concierge'],
},

// Sizes needed for report empty state background image handling
EMPTY_STATE_BACKGROUND: {
SMALL_SCREEN: {
Expand Down
10 changes: 4 additions & 6 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,9 +600,10 @@ export default {
},
personalDetails: {
error: {
firstNameLength: 'First name shouldn\'t be longer than 50 characters',
lastNameLength: 'Last name shouldn\'t be longer than 50 characters',
hasInvalidCharacter: ({invalidCharacter}) => `Please remove the ${invalidCharacter} from the name field.`,
firstNameLength: `First name cannot be longer than ${CONST.DISPLAY_NAME.MAX_LENGTH} characters`,
lastNameLength: `Last name cannot be longer than ${CONST.DISPLAY_NAME.MAX_LENGTH} characters`,
containsReservedWord: 'First name cannot contain the words Expensify or Concierge',
hasInvalidCharacter: 'Name cannot contain a comma or semicolon',
},
},
privatePersonalDetails: {
Expand All @@ -613,7 +614,6 @@ export default {
legalLastName: 'Legal last name',
homeAddress: 'Home address',
error: {
hasInvalidCharacter: ({invalidCharacter}) => `Please remove the ${invalidCharacter} from the field above.`,
dateShouldBeBefore: ({dateString}) => `Date should be before ${dateString}.`,
dateShouldBeAfter: ({dateString}) => `Date should be after ${dateString}.`,
},
Expand Down Expand Up @@ -1067,8 +1067,6 @@ export default {
phoneNumberExtension: 'Please enter a valid phone extension number',
firstName: 'Please provide your first name',
lastName: 'Please provide your last name',
firstNameLength: 'First name shouldn\'t be longer than 50 characters',
lastNameLength: 'Last name shouldn\'t be longer than 50 characters',
},
},
requestCallConfirmationScreen: {
Expand Down
10 changes: 4 additions & 6 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -600,9 +600,10 @@ export default {
},
personalDetails: {
error: {
firstNameLength: 'El nombre no debe tener más de 50 caracteres',
lastNameLength: 'Los apellidos no pueden tener más de 50 caracteres',
hasInvalidCharacter: ({invalidCharacter}) => `Por favor elimina ${invalidCharacter} del campo nombre.`,
firstNameLength: `El nombre no puede tener más de ${CONST.DISPLAY_NAME.MAX_LENGTH} caracteres`,
lastNameLength: `El apellido no puede tener más de ${CONST.DISPLAY_NAME.MAX_LENGTH} caracteres`,
containsReservedWord: 'El nombre no puede contener las palabras Expensify o Concierge',
hasInvalidCharacter: 'El nombre no puede contener una coma o un punto y coma',
},
},
privatePersonalDetails: {
Expand All @@ -613,7 +614,6 @@ export default {
legalLastName: 'Apellidos legales',
homeAddress: 'Domicilio',
error: {
hasInvalidCharacter: ({invalidCharacter}) => `Por favor elimina ${invalidCharacter}`,
dateShouldBeBefore: ({dateString}) => `La fecha debe ser anterior a ${dateString}.`,
dateShouldBeAfter: ({dateString}) => `La fecha debe ser posterior a ${dateString}.`,
},
Expand Down Expand Up @@ -1069,8 +1069,6 @@ export default {
phoneNumberExtension: 'Por favor, introduce una extensión telefónica válida',
firstName: 'Por favor, ingresa tu nombre',
lastName: 'Por favor, ingresa tus apellidos',
firstNameLength: 'El nombre no debe tener más de 50 caracteres',
lastNameLength: 'Los apellidos no deben tener más de 50 caracteres',
},
},
requestCallConfirmationScreen: {
Expand Down
50 changes: 14 additions & 36 deletions src/libs/ValidationUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -357,46 +357,25 @@ function isValidRoutingNumber(number) {
}

/**
* Checks if each string in array is of valid length and then returns true
* for each string which exceeds the limit.
* Checks that the provided name doesn't contain any commas or semicolons
*
* @param {Number} maxLength
* @param {String[]} valuesToBeValidated
* @returns {Boolean[]}
*/
function doesFailCharacterLimit(maxLength, valuesToBeValidated) {
return _.map(valuesToBeValidated, value => value && value.length > maxLength);
}

/**
* Checks if each string in array is of valid length and then returns true
* for each string which exceeds the limit. The function trims the passed values.
*
* @param {Number} maxLength
* @param {String[]} valuesToBeValidated
* @returns {Boolean[]}
* @param {String} name
* @returns {Boolean}
*/
function doesFailCharacterLimitAfterTrim(maxLength, valuesToBeValidated) {
return _.map(valuesToBeValidated, value => value && value.trim().length > maxLength);
function isValidDisplayName(name) {
return !name.includes(',') && !name.includes(';');
}

/**
* Checks if input value includes comma or semicolon which are not accepted
* Checks if the provided string includes any of the provided reserved words
*
* @param {String[]} valuesToBeValidated
* @returns {String[]}
* @param {String} value
* @param {String[]} reservedWords
* @returns {Boolean}
*/
function findInvalidSymbols(valuesToBeValidated) {
return _.map(valuesToBeValidated, (value) => {
if (!value) {
return '';
}
let inValidSymbol = value.replace(/[,]+/g, '') !== value ? Localize.translateLocal('common.comma') : '';
if (_.isEmpty(inValidSymbol)) {
inValidSymbol = value.replace(/[;]+/g, '') !== value ? Localize.translateLocal('common.semicolon') : '';
}
return inValidSymbol;
});
function doesContainReservedWord(value, reservedWords) {
const valueToCheck = value.trim().toLowerCase();
return _.some(reservedWords, reservedWord => valueToCheck.includes(reservedWord.toLowerCase()));
}

/**
Expand Down Expand Up @@ -473,12 +452,11 @@ export {
isValidRoutingNumber,
isValidSSNLastFour,
isValidSSNFullNine,
doesFailCharacterLimit,
doesFailCharacterLimitAfterTrim,
isReservedRoomName,
isExistingRoomName,
isValidRoomName,
isValidTaxID,
isValidValidateCode,
findInvalidSymbols,
isValidDisplayName,
doesContainReservedWord,
};
12 changes: 2 additions & 10 deletions src/pages/RequestCallPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,16 +219,6 @@ class RequestCallPage extends Component {
errors.lastName = this.props.translate('requestCallPage.error.lastName');
}

const [firstNameLengthError, lastNameLengthError] = ValidationUtils.doesFailCharacterLimit(50, [values.firstName, values.lastName]);

if (firstNameLengthError) {
errors.firstName = this.props.translate('requestCallPage.error.firstNameLength');
}

if (lastNameLengthError) {
errors.lastName = this.props.translate('requestCallPage.error.lastNameLength');
}

const phoneNumber = LoginUtils.getPhoneNumberWithoutSpecialChars(values.phoneNumber);
if (_.isEmpty(values.phoneNumber.trim()) || !Str.isValidPhone(phoneNumber)) {
errors.phoneNumber = this.props.translate('common.error.phoneNumber');
Expand Down Expand Up @@ -285,6 +275,7 @@ class RequestCallPage extends Component {
name="fname"
placeholder={this.props.translate('profilePage.john')}
containerStyles={[styles.mt4]}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
<TextInput
inputID="lastName"
Expand All @@ -293,6 +284,7 @@ class RequestCallPage extends Component {
name="lname"
placeholder={this.props.translate('profilePage.doe')}
containerStyles={[styles.mt4]}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
<TextInput
inputID="phoneNumber"
Expand Down
60 changes: 11 additions & 49 deletions src/pages/settings/Profile/DisplayNamePage.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import lodashGet from 'lodash/get';
import _ from 'underscore';
import React, {Component} from 'react';
import {View} from 'react-native';
import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes, withCurrentUserPersonalDetailsDefaultProps} from '../../../components/withCurrentUserPersonalDetails';
import ScreenWrapper from '../../../components/ScreenWrapper';
import HeaderWithCloseButton from '../../../components/HeaderWithCloseButton';
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import * as Localize from '../../../libs/Localize';
import ROUTES from '../../../ROUTES';
import Form from '../../../components/Form';
import ONYXKEYS from '../../../ONYXKEYS';
Expand Down Expand Up @@ -58,59 +56,21 @@ class DisplayNamePage extends Component {
validate(values) {
const errors = {};

// Check for invalid characters in first and last name
const [firstNameInvalidCharacter, lastNameInvalidCharacter] = ValidationUtils.findInvalidSymbols(
[values.firstName, values.lastName],
);
this.assignError(
errors,
'firstName',
!_.isEmpty(firstNameInvalidCharacter),
Localize.translateLocal(
'personalDetails.error.hasInvalidCharacter',
{invalidCharacter: firstNameInvalidCharacter},
),
);
this.assignError(
errors,
'lastName',
!_.isEmpty(lastNameInvalidCharacter),
Localize.translateLocal(
'personalDetails.error.hasInvalidCharacter',
{invalidCharacter: lastNameInvalidCharacter},
),
);
if (!_.isEmpty(errors)) {
return errors;
// First we validate the first name field
if (!ValidationUtils.isValidDisplayName(values.firstName)) {
errors.firstName = this.props.translate('personalDetails.error.hasInvalidCharacter');
} else if (ValidationUtils.doesContainReservedWord(values.firstName, CONST.DISPLAY_NAME.RESERVED_FIRST_NAMES)) {
errors.firstName = this.props.translate('personalDetails.error.containsReservedWord');
}

// Check the character limit for first and last name
const characterLimitError = Localize.translateLocal('common.error.characterLimit', {limit: CONST.FORM_CHARACTER_LIMIT});
const [hasFirstNameError, hasLastNameError] = ValidationUtils.doesFailCharacterLimitAfterTrim(
CONST.FORM_CHARACTER_LIMIT,
[values.firstName, values.lastName],
);
this.assignError(errors, 'firstName', hasFirstNameError, characterLimitError);
this.assignError(errors, 'lastName', hasLastNameError, characterLimitError);
// Then we validate the last name field
if (!ValidationUtils.isValidDisplayName(values.lastName)) {
errors.lastName = this.props.translate('personalDetails.error.hasInvalidCharacter');
}

return errors;
}

/**
* @param {Object} errors
* @param {String} errorKey
* @param {Boolean} hasError
* @param {String} errorCopy
* @returns {Object} - An object containing the errors for each inputID
*/
assignError(errors, errorKey, hasError, errorCopy) {
const validateErrors = errors;
if (hasError) {
validateErrors[errorKey] = errorCopy;
}
return validateErrors;
}

render() {
const currentUserDetails = this.props.currentUserPersonalDetails || {};

Expand Down Expand Up @@ -140,6 +100,7 @@ class DisplayNamePage extends Component {
label={this.props.translate('common.firstName')}
defaultValue={lodashGet(currentUserDetails, 'firstName', '')}
placeholder={this.props.translate('displayNamePage.john')}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
</View>
<View>
Expand All @@ -149,6 +110,7 @@ class DisplayNamePage extends Component {
label={this.props.translate('common.lastName')}
defaultValue={lodashGet(currentUserDetails, 'lastName', '')}
placeholder={this.props.translate('displayNamePage.doe')}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
</View>
</Form>
Expand Down
34 changes: 8 additions & 26 deletions src/pages/settings/Profile/PersonalDetails/LegalNamePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {withOnyx} from 'react-native-onyx';
import ScreenWrapper from '../../../../components/ScreenWrapper';
import HeaderWithCloseButton from '../../../../components/HeaderWithCloseButton';
import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize';
import * as Localize from '../../../../libs/Localize';
import ROUTES from '../../../../ROUTES';
import Form from '../../../../components/Form';
import ONYXKEYS from '../../../../ONYXKEYS';
Expand Down Expand Up @@ -67,35 +66,16 @@ class LegalNamePage extends Component {
validate(values) {
const errors = {};

// Check for invalid characters in legal first and last name
const [invalidLegalFirstNameCharacter, invalidLegalLastNameCharacter] = ValidationUtils.findInvalidSymbols(
[values.legalFirstName, values.legalLastName],
);
const [hasLegalFirstNameLengthError, hasLegalLastNameLengthError] = ValidationUtils.doesFailCharacterLimitAfterTrim(
CONST.LEGAL_NAMES_CHARACTER_LIMIT,
[values.legalFirstName, values.legalLastName],
);

if (!_.isEmpty(invalidLegalFirstNameCharacter)) {
errors.legalFirstName = Localize.translateLocal(
'privatePersonalDetails.error.hasInvalidCharacter',
{invalidCharacter: invalidLegalFirstNameCharacter},
);
if (!ValidationUtils.isValidDisplayName(values.legalFirstName)) {
errors.legalFirstName = this.props.translate('personalDetails.error.hasInvalidCharacter');
} else if (_.isEmpty(values.legalFirstName)) {
errors.legalFirstName = Localize.translateLocal('common.error.fieldRequired');
} else if (hasLegalFirstNameLengthError) {
errors.legalFirstName = Localize.translateLocal('common.error.characterLimit', {limit: CONST.LEGAL_NAMES_CHARACTER_LIMIT});
errors.legalFirstName = this.props.translate('common.error.fieldRequired');
}

if (!_.isEmpty(invalidLegalLastNameCharacter)) {
errors.legalLastName = Localize.translateLocal(
'privatePersonalDetails.error.hasInvalidCharacter',
{invalidCharacter: invalidLegalLastNameCharacter},
);
if (!ValidationUtils.isValidDisplayName(values.legalLastName)) {
errors.legalLastName = this.props.translate('personalDetails.error.hasInvalidCharacter');
} else if (_.isEmpty(values.legalLastName)) {
errors.legalLastName = Localize.translateLocal('common.error.fieldRequired');
} else if (hasLegalLastNameLengthError) {
errors.legalLastName = Localize.translateLocal('common.error.characterLimit', {limit: CONST.LEGAL_NAMES_CHARACTER_LIMIT});
errors.legalLastName = this.props.translate('common.error.fieldRequired');
}

return errors;
Expand Down Expand Up @@ -126,6 +106,7 @@ class LegalNamePage extends Component {
name="lfname"
label={this.props.translate('privatePersonalDetails.legalFirstName')}
defaultValue={privateDetails.legalFirstName || ''}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
</View>
<View>
Expand All @@ -134,6 +115,7 @@ class LegalNamePage extends Component {
name="llname"
label={this.props.translate('privatePersonalDetails.legalLastName')}
defaultValue={privateDetails.legalLastName || ''}
maxLength={CONST.DISPLAY_NAME.MAX_LENGTH}
/>
</View>
</Form>
Expand Down

0 comments on commit 023e59e

Please sign in to comment.