Compare commits

...

2 Commits

Author SHA1 Message Date
Attiya Ishaque
25909563a4 fix: TPA data not auto-populated (#1156) 2024-02-14 16:18:10 +05:00
Attiya Ishaque
94e823663e feat: split full name into first name and last name (#1131) 2024-02-14 16:18:10 +05:00
9 changed files with 156 additions and 76 deletions

View File

@@ -27,21 +27,23 @@ const NameField = (props) => {
const { const {
handleErrorChange, handleErrorChange,
shouldFetchUsernameSuggestions, shouldFetchUsernameSuggestions,
name,
fullName,
} = props; } = props;
const handleOnBlur = (e) => { const handleOnBlur = (e) => {
const { value } = e.target; const { value } = e.target;
const fieldError = validateName(value, formatMessage); const fieldError = validateName(value, name, formatMessage);
if (fieldError) { if (fieldError) {
handleErrorChange('name', fieldError); handleErrorChange(name, fieldError);
} else if (shouldFetchUsernameSuggestions && !validationApiRateLimited) { } else if (shouldFetchUsernameSuggestions && !validationApiRateLimited) {
dispatch(fetchRealtimeValidations({ name: value })); dispatch(fetchRealtimeValidations({ name: fullName.trim() }));
} }
}; };
const handleOnFocus = () => { const handleOnFocus = () => {
handleErrorChange('name', ''); handleErrorChange(name, '');
dispatch(clearRegistrationBackendError('name')); dispatch(clearRegistrationBackendError(name));
}; };
return ( return (
@@ -56,6 +58,7 @@ const NameField = (props) => {
NameField.defaultProps = { NameField.defaultProps = {
errorMessage: '', errorMessage: '',
shouldFetchUsernameSuggestions: false, shouldFetchUsernameSuggestions: false,
fullName: '',
}; };
NameField.propTypes = { NameField.propTypes = {
@@ -64,6 +67,8 @@ NameField.propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
handleChange: PropTypes.func.isRequired, handleChange: PropTypes.func.isRequired,
handleErrorChange: PropTypes.func.isRequired, handleErrorChange: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
fullName: PropTypes.string,
}; };
export default NameField; export default NameField;

View File

@@ -51,7 +51,7 @@ describe('NameField', () => {
beforeEach(() => { beforeEach(() => {
store = mockStore(initialState); store = mockStore(initialState);
props = { props = {
name: 'name', name: '',
value: '', value: '',
errorMessage: '', errorMessage: '',
handleChange: jest.fn(), handleChange: jest.fn(),
@@ -66,43 +66,44 @@ describe('NameField', () => {
}); });
describe('Test Name Field', () => { describe('Test Name Field', () => {
const fieldValidation = { name: 'Enter your full name' }; it('should run first name field validation when onBlur is fired', () => {
props.name = 'firstName';
it('should run name field validation when onBlur is fired', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const nameInput = container.querySelector('input#name'); const firstNameInput = container.querySelector('input#firstName');
fireEvent.blur(nameInput, { target: { value: '', name: 'name' } }); fireEvent.blur(firstNameInput, { target: { value: '', name: 'firstName' } });
expect(props.handleErrorChange).toHaveBeenCalledTimes(1); expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith( expect(props.handleErrorChange).toHaveBeenCalledWith(
'name', 'firstName',
fieldValidation.name, 'Enter your first name',
); );
}); });
it('should update errors for frontend validations', () => { it('should update first name field error for frontend validations', () => {
props.name = 'firstName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const nameInput = container.querySelector('input#name'); const firstNameInput = container.querySelector('input#firstName');
fireEvent.blur(nameInput, { target: { value: 'https://invalid-name.com', name: 'name' } }); fireEvent.blur(firstNameInput, { target: { value: 'https://invalid-name.com', name: 'firstName' } });
expect(props.handleErrorChange).toHaveBeenCalledTimes(1); expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith( expect(props.handleErrorChange).toHaveBeenCalledWith(
'name', 'firstName',
'Enter a valid name', 'Enter a valid first name',
); );
}); });
it('should clear error on focus', () => { it('should clear first name error on focus', () => {
props.name = 'firstName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const nameInput = container.querySelector('input#name'); const firstNameInput = container.querySelector('input#firstName');
fireEvent.focus(nameInput, { target: { value: '', name: 'name' } }); fireEvent.focus(firstNameInput, { target: { value: '', name: 'firstName' } });
expect(props.handleErrorChange).toHaveBeenCalledTimes(1); expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith( expect(props.handleErrorChange).toHaveBeenCalledWith(
'name', 'firstName',
'', '',
); );
}); });
@@ -112,14 +113,16 @@ describe('NameField', () => {
props = { props = {
...props, ...props,
shouldFetchUsernameSuggestions: true, shouldFetchUsernameSuggestions: true,
fullName: 'test test',
}; };
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const nameInput = container.querySelector('input#name'); const lastNameInput = container.querySelector('input#lastName');
// Enter a valid name so that frontend validations are passed // Enter a valid name so that frontend validations are passed
fireEvent.blur(nameInput, { target: { value: 'test', name: 'name' } }); fireEvent.blur(lastNameInput, { target: { value: 'test', name: 'lastName' } });
expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ name: 'test' })); expect(store.dispatch).toHaveBeenCalledWith(fetchRealtimeValidations({ name: props.fullName }));
}); });
it('should clear the registration validation error on focus on field', () => { it('should clear the registration validation error on focus on field', () => {
@@ -134,14 +137,43 @@ describe('NameField', () => {
}, },
}); });
props.name = 'lastName';
store.dispatch = jest.fn(store.dispatch); store.dispatch = jest.fn(store.dispatch);
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const nameInput = container.querySelector('input#name'); const lastNameInput = container.querySelector('input#lastName');
fireEvent.focus(nameInput, { target: { value: 'test', name: 'name' } }); fireEvent.focus(lastNameInput, { target: { value: 'test', name: 'lastName' } });
expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('name')); expect(store.dispatch).toHaveBeenCalledWith(clearRegistrationBackendError('lastName'));
});
it('should run last name field validation when onBlur is fired', () => {
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const lastNameInput = container.querySelector('input#lastName');
fireEvent.blur(lastNameInput, { target: { value: '', name: 'lastName' } });
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'lastName',
'Enter your last name',
);
});
it('should update last name field error for frontend validation', () => {
props.name = 'lastName';
const { container } = render(routerWrapper(reduxWrapper(<IntlNameField {...props} />)));
const lastNameInput = container.querySelector('input#lastName');
fireEvent.blur(lastNameInput, { target: { value: 'https://invalid-name.com', name: 'lastName' } });
expect(props.handleErrorChange).toHaveBeenCalledTimes(1);
expect(props.handleErrorChange).toHaveBeenCalledWith(
'lastName',
'Enter a valid last name',
);
}); });
}); });
}); });

View File

@@ -9,12 +9,16 @@ export const HTML_REGEX = /<|>/u;
// regex from backend // regex from backend
export const INVALID_NAME_REGEX = /https?:\/\/(?:[-\w.]|(?:%[\da-fA-F]{2}))*/g; export const INVALID_NAME_REGEX = /https?:\/\/(?:[-\w.]|(?:%[\da-fA-F]{2}))*/g;
const validateName = (value, formatMessage) => { const validateName = (value, fieldName, formatMessage) => {
let fieldError; let fieldError;
if (!value.trim()) { if (!value.trim()) {
fieldError = formatMessage(messages['empty.name.field.error']); fieldError = fieldName === 'lastName'
? formatMessage(messages['empty.lastName.field.error'])
: formatMessage(messages['empty.firstName.field.error']);
} else if (URL_REGEX.test(value) || HTML_REGEX.test(value) || INVALID_NAME_REGEX.test(value)) { } else if (URL_REGEX.test(value) || HTML_REGEX.test(value) || INVALID_NAME_REGEX.test(value)) {
fieldError = formatMessage(messages['name.validation.message']); fieldError = fieldName === 'lastName'
? formatMessage(messages['lastName.validation.message'])
: formatMessage(messages['firstName.validation.message']);
} }
return fieldError; return fieldError;
}; };

View File

@@ -119,9 +119,11 @@ const RegistrationPage = (props) => {
setErrorCode(prevState => ({ type: TPA_AUTHENTICATION_FAILURE, count: prevState.count + 1 })); setErrorCode(prevState => ({ type: TPA_AUTHENTICATION_FAILURE, count: prevState.count + 1 }));
} }
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) { if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) {
const { name = '', username = '', email = '' } = pipelineUserDetails; const {
firstName = '', lastName = '', username = '', email = '',
} = pipelineUserDetails;
setFormFields(prevState => ({ setFormFields(prevState => ({
...prevState, name, username, email, ...prevState, firstName, lastName, username, email,
})); }));
dispatch(setUserPipelineDataLoaded(true)); dispatch(setUserPipelineDataLoaded(true));
} }
@@ -310,14 +312,22 @@ const RegistrationPage = (props) => {
/> />
<Form id="registration-form" name="registration-form"> <Form id="registration-form" name="registration-form">
<NameField <NameField
name="name" name="firstName"
value={formFields.name} value={formFields.firstName}
shouldFetchUsernameSuggestions={!formFields.username.trim()}
handleChange={handleOnChange} handleChange={handleOnChange}
handleErrorChange={handleErrorChange} handleErrorChange={handleErrorChange}
errorMessage={errors.name} errorMessage={errors.firstName}
helpText={[formatMessage(messages['help.text.name'])]} floatingLabel={formatMessage(messages['registration.firstName.label'])}
floatingLabel={formatMessage(messages['registration.fullname.label'])} />
<NameField
name="lastName"
value={formFields.lastName}
shouldFetchUsernameSuggestions={!formFields.username.trim()}
fullName={`${formFields.firstName} ${formFields.lastName}`}
handleChange={handleOnChange}
handleErrorChange={handleErrorChange}
errorMessage={errors.lastName}
floatingLabel={formatMessage(messages['registration.lastName.label'])}
/> />
<EmailField <EmailField
name="email" name="email"

View File

@@ -64,13 +64,13 @@ describe('RegistrationPage', () => {
marketingEmailsOptIn: true, marketingEmailsOptIn: true,
}, },
formFields: { formFields: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
emailSuggestion: { emailSuggestion: {
suggestion: '', type: '', suggestion: '', type: '',
}, },
errors: { errors: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
}; };
@@ -134,7 +134,8 @@ describe('RegistrationPage', () => {
}); });
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => { const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } }); fireEvent.change(getByLabelText('First name'), { target: { value: payload.first_name, name: 'firstName' } });
fireEvent.change(getByLabelText('Last name'), { target: { value: payload.last_name, name: 'lastName' } });
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } }); fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } }); fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
@@ -152,7 +153,8 @@ describe('RegistrationPage', () => {
}); });
const emptyFieldValidation = { const emptyFieldValidation = {
name: 'Enter your full name', firstName: 'Enter your first name',
lastName: 'Enter your last name',
username: 'Username must be between 2 and 30 characters', username: 'Username must be between 2 and 30 characters',
email: 'Enter your email', email: 'Enter your email',
password: 'Password criteria has not been met', password: 'Password criteria has not been met',
@@ -169,7 +171,8 @@ describe('RegistrationPage', () => {
window.location = { href: getConfig().BASE_URL, search: '?next=/course/demo-course-url' }; window.location = { href: getConfig().BASE_URL, search: '?next=/course/demo-course-url' };
const payload = { const payload = {
name: 'John Doe', first_name: 'John',
last_name: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@gmail.com', email: 'john.doe@gmail.com',
password: 'password1', password: 'password1',
@@ -192,7 +195,8 @@ describe('RegistrationPage', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0); jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const formPayload = { const formPayload = {
name: 'John Doe', first_name: 'John',
last_name: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
country: 'Pakistan', country: 'Pakistan',
@@ -228,7 +232,8 @@ describe('RegistrationPage', () => {
jest.spyOn(global.Date, 'now').mockImplementation(() => 0); jest.spyOn(global.Date, 'now').mockImplementation(() => 0);
const payload = { const payload = {
name: 'John Doe', first_name: 'John',
last_name: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@gmail.com', email: 'john.doe@gmail.com',
password: 'password1', password: 'password1',
@@ -579,7 +584,8 @@ describe('RegistrationPage', () => {
registrationFormData: { registrationFormData: {
...registrationFormData, ...registrationFormData,
formFields: { formFields: {
name: 'John Doe', firstName: 'John',
lastName: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@yopmail.com', email: 'john.doe@yopmail.com',
password: 'password1', password: 'password1',
@@ -593,13 +599,15 @@ describe('RegistrationPage', () => {
const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))); const { container } = render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
const fullNameInput = container.querySelector('input#name'); const firstNameInput = container.querySelector('input#firstName');
const lastNameInput = container.querySelector('input#lastName');
const usernameInput = container.querySelector('input#username'); const usernameInput = container.querySelector('input#username');
const emailInput = container.querySelector('input#email'); const emailInput = container.querySelector('input#email');
const passwordInput = container.querySelector('input#password'); const passwordInput = container.querySelector('input#password');
const emailSuggestion = container.querySelector('.email-suggestion-alert-warning'); const emailSuggestion = container.querySelector('.email-suggestion-alert-warning');
expect(fullNameInput.value).toEqual('John Doe'); expect(firstNameInput.value).toEqual('John');
expect(lastNameInput.value).toEqual('Doe');
expect(usernameInput.value).toEqual('john_doe'); expect(usernameInput.value).toEqual('john_doe');
expect(emailInput.value).toEqual('john.doe@yopmail.com'); expect(emailInput.value).toEqual('john.doe@yopmail.com');
expect(passwordInput.value).toEqual('password1'); expect(passwordInput.value).toEqual('password1');
@@ -720,7 +728,8 @@ describe('RegistrationPage', () => {
thirdPartyAuthContext: { thirdPartyAuthContext: {
...initialState.commonComponents.thirdPartyAuthContext, ...initialState.commonComponents.thirdPartyAuthContext,
pipelineUserDetails: { pipelineUserDetails: {
name: 'John Doe', firstName: 'John',
lastName: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
}, },
@@ -751,7 +760,8 @@ describe('RegistrationPage', () => {
registrationFormData: { registrationFormData: {
...registrationFormData, ...registrationFormData,
formFields: { formFields: {
name: 'John Doe', firstName: 'John',
lastName: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
}, },
@@ -771,7 +781,8 @@ describe('RegistrationPage', () => {
...initialState.commonComponents.thirdPartyAuthContext, ...initialState.commonComponents.thirdPartyAuthContext,
currentProvider: 'Apple', currentProvider: 'Apple',
pipelineUserDetails: { pipelineUserDetails: {
name: 'John Doe', firstName: 'John',
lastName: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
}, },
@@ -783,7 +794,8 @@ describe('RegistrationPage', () => {
render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />))); render(routerWrapper(reduxWrapper(<IntlRegistrationPage {...props} />)));
expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({ expect(store.dispatch).toHaveBeenCalledWith(registerNewUser({
name: 'John Doe', first_name: 'John',
last_name: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
country: 'PK', country: 'PK',

View File

@@ -56,13 +56,13 @@ describe('ConfigurableRegistrationForm', () => {
marketingEmailsOptIn: true, marketingEmailsOptIn: true,
}, },
formFields: { formFields: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
emailSuggestion: { emailSuggestion: {
suggestion: '', type: '', suggestion: '', type: '',
}, },
errors: { errors: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
}; };
@@ -128,7 +128,8 @@ describe('ConfigurableRegistrationForm', () => {
}); });
const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => { const populateRequiredFields = (getByLabelText, payload, isThirdPartyAuth = false) => {
fireEvent.change(getByLabelText('Full name'), { target: { value: payload.name, name: 'name' } }); fireEvent.change(getByLabelText('First name'), { target: { value: payload.first_name, name: 'firstName' } });
fireEvent.change(getByLabelText('Last name'), { target: { value: payload.last_name, name: 'lastName' } });
fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } }); fireEvent.change(getByLabelText('Public username'), { target: { value: payload.username, name: 'username' } });
fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } }); fireEvent.change(getByLabelText('Email'), { target: { value: payload.email, name: 'email' } });
@@ -238,7 +239,8 @@ describe('ConfigurableRegistrationForm', () => {
}); });
const payload = { const payload = {
name: 'John Doe', first_name: 'John',
last_name: 'Doe',
username: 'john_doe', username: 'john_doe',
email: 'john.doe@example.com', email: 'john.doe@example.com',
password: 'password1', password: 'password1',

View File

@@ -22,13 +22,13 @@ export const defaultState = {
marketingEmailsOptIn: true, marketingEmailsOptIn: true,
}, },
formFields: { formFields: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
emailSuggestion: { emailSuggestion: {
suggestion: '', type: '', suggestion: '', type: '',
}, },
errors: { errors: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
}, },
validations: null, validations: null,

View File

@@ -22,13 +22,13 @@ describe('Registration Reducer Tests', () => {
marketingEmailsOptIn: true, marketingEmailsOptIn: true,
}, },
formFields: { formFields: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
emailSuggestion: { emailSuggestion: {
suggestion: '', type: '', suggestion: '', type: '',
}, },
errors: { errors: {
name: '', email: '', username: '', password: '', firstName: '', lastName: '', email: '', username: '', password: '',
}, },
}, },
validations: null, validations: null,

View File

@@ -7,10 +7,15 @@ const messages = defineMessages({
description: 'register page title', description: 'register page title',
}, },
// Field labels // Field labels
'registration.fullname.label': { 'registration.firstName.label': {
id: 'registration.fullname.label', id: 'registration.firstName.label',
defaultMessage: 'Full name', defaultMessage: 'First name',
description: 'Label that appears above fullname field', description: 'Label that appears above first name field',
},
'registration.lastName.label': {
id: 'registration.lastName.label',
defaultMessage: 'Last name',
description: 'Label that appears above last name field',
}, },
'registration.email.label': { 'registration.email.label': {
id: 'registration.email.label', id: 'registration.email.label',
@@ -38,10 +43,10 @@ const messages = defineMessages({
description: 'Text for opt in option on register page.', description: 'Text for opt in option on register page.',
}, },
// Help text // Help text
'help.text.name': { 'help.text.firstName': {
id: 'help.text.name', id: 'help.text.firstName',
defaultMessage: 'This name will be used by any certificates that you earn.', defaultMessage: 'This name will be used by any certificates that you earn.',
description: 'Help text for fullname field on registration page', description: 'Help text for first name field on registration page',
}, },
'help.text.username.1': { 'help.text.username.1': {
id: 'help.text.username.1', id: 'help.text.username.1',
@@ -76,10 +81,15 @@ const messages = defineMessages({
description: 'Heading of institution page', description: 'Heading of institution page',
}, },
// Validation messages // Validation messages
'empty.name.field.error': { 'empty.firstName.field.error': {
id: 'empty.name.field.error', id: 'empty.firstName.field.error',
defaultMessage: 'Enter your full name', defaultMessage: 'Enter your first name',
description: 'Error message for empty fullname field', description: 'Error message for empty first name field',
},
'empty.lastName.field.error': {
id: 'empty.lastName.field.error',
defaultMessage: 'Enter your last name',
description: 'Error message for empty last name field',
}, },
'empty.email.field.error': { 'empty.email.field.error': {
id: 'empty.email.field.error', id: 'empty.email.field.error',
@@ -121,10 +131,15 @@ const messages = defineMessages({
defaultMessage: 'Username must be between 2 and 30 characters', defaultMessage: 'Username must be between 2 and 30 characters',
description: 'Error message for empty username field', description: 'Error message for empty username field',
}, },
'name.validation.message': { 'firstName.validation.message': {
id: 'name.validation.message', id: 'firstName.validation.message',
defaultMessage: 'Enter a valid name', defaultMessage: 'Enter a valid first name',
description: 'Validation message that appears when fullname contain URL', description: 'Validation message that appears when first name contain URL',
},
'lastName.validation.message': {
id: 'lastName.validation.message',
defaultMessage: 'Enter a valid last name',
description: 'Validation message that appears when last name contain URL',
}, },
'password.validation.message': { 'password.validation.message': {
id: 'password.validation.message', id: 'password.validation.message',