diff --git a/src/data/constants.js b/src/data/constants.js
index 2370cbebe0..61eef6478e 100644
--- a/src/data/constants.js
+++ b/src/data/constants.js
@@ -32,6 +32,7 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
+ ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63})'
+ '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$';
export const LETTER_REGEX = /[a-zA-Z]/;
+export const USERNAME_REGEX = /^[a-zA-Z0-9_-]*$/i;
export const NUMBER_REGEX = /\d/;
export const INVALID_NAME_REGEX = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi; // eslint-disable-line no-useless-escape
diff --git a/src/register/EmbeddableRegistrationPage.jsx b/src/register/EmbeddableRegistrationPage.jsx
index 3eb1cc25bb..53e005879d 100644
--- a/src/register/EmbeddableRegistrationPage.jsx
+++ b/src/register/EmbeddableRegistrationPage.jsx
@@ -25,6 +25,9 @@ import {
FORM_SUBMISSION_ERROR,
} from './data/constants';
import { registrationErrorSelector, validationsSelector } from './data/selectors';
+import {
+ emailRegex, getSuggestionForInvalidEmail, urlRegex, validateCountryField, validateEmailAddress,
+} from './data/utils';
import messages from './messages';
import RegistrationFailure from './RegistrationFailure';
import { EmailField, UsernameField } from './registrationFields';
@@ -36,7 +39,7 @@ import {
fieldDescriptionSelector,
} from '../common-components/data/selectors';
import {
- DEFAULT_STATE, REDIRECT,
+ DEFAULT_STATE, LETTER_REGEX, NUMBER_REGEX, REDIRECT, USERNAME_REGEX,
} from '../data/constants';
import {
getAllPossibleQueryParams, setCookie,
@@ -173,16 +176,76 @@ const EmbeddableRegistrationPage = (props) => {
}
}, [registrationResult, host]);
- const validateInput = (fieldName, value, payload, shouldValidateFromBackend) => {
+ const validateInput = (fieldName, value, payload, shouldValidateFromBackend, shouldSetErrors = true) => {
+ let fieldError = '';
+
switch (fieldName) {
- case 'name':
- if (value && !payload.username.trim() && shouldValidateFromBackend) {
- validateFromBackend(payload);
+ case 'name':
+ if (value && value.match(urlRegex)) {
+ fieldError = formatMessage(messages['name.validation.message']);
+ } else if (value && !payload.username.trim() && shouldValidateFromBackend) {
+ validateFromBackend(payload);
+ }
+ break;
+ case 'email':
+ if (value.length <= 2) {
+ fieldError = formatMessage(messages['email.invalid.format.error']);
+ } else {
+ const [username, domainName] = value.split('@');
+ // Check if email address is invalid. If we have a suggestion for invalid email
+ // provide that along with the error message.
+ if (!emailRegex.test(value)) {
+ fieldError = formatMessage(messages['email.invalid.format.error']);
+ setEmailSuggestion({
+ suggestion: getSuggestionForInvalidEmail(domainName, username),
+ type: 'error',
+ });
+ } else {
+ const response = validateEmailAddress(value, username, domainName);
+ if (response.hasError) {
+ fieldError = formatMessage(messages['email.invalid.format.error']);
+ delete response.hasError;
+ }
+ setEmailSuggestion({ ...response });
}
- break;
- default:
- break;
+ }
+ break;
+ case 'username':
+ if (!value.match(USERNAME_REGEX)) {
+ fieldError = formatMessage(messages['username.format.validation.message']);
+ }
+ break;
+ case 'password':
+ if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
+ fieldError = formatMessage(messages['password.validation.message']);
+ }
+ break;
+ case 'country':
+ if (flags.showConfigurableEdxFields || flags.showConfigurableRegistrationFields) {
+ const {
+ countryCode, displayValue, error,
+ } = validateCountryField(value.trim(), countryList, formatMessage(messages['empty.country.field.error']));
+ fieldError = error;
+ setConfigurableFormFields(prevState => ({ ...prevState, country: { countryCode, displayValue } }));
+ }
+ break;
+ default:
+ if (flags.showConfigurableRegistrationFields) {
+ if (!value && fieldDescriptions[fieldName]?.error_message) {
+ fieldError = fieldDescriptions[fieldName].error_message;
+ } else if (fieldName === 'confirm_email' && formFields.email && value !== formFields.email) {
+ fieldError = formatMessage(messages['email.do.not.match']);
+ }
+ }
+ break;
}
+ if (shouldSetErrors && fieldError) {
+ setErrors(prevErrors => ({
+ ...prevErrors,
+ [fieldName]: fieldError,
+ }));
+ }
+ return fieldError;
};
const isFormValid = (payload) => {
@@ -226,6 +289,10 @@ const EmbeddableRegistrationPage = (props) => {
event.preventDefault();
setErrors(prevErrors => ({ ...prevErrors, [fieldName]: '' }));
switch (fieldName) {
+ case 'email':
+ setFormFields(prevState => ({ ...prevState, email: emailSuggestion.suggestion }));
+ setEmailSuggestion({ suggestion: '', type: '' });
+ break;
case 'username':
setFormFields(prevState => ({ ...prevState, username: suggestion }));
props.resetUsernameSuggestions();
@@ -267,8 +334,12 @@ const EmbeddableRegistrationPage = (props) => {
value,
{ name: formFields.name, username: formFields.username, form_field_key: name },
!validationApiRateLimited,
+ false,
);
}
+ if (name === 'email') {
+ validateInput(name, value, null, !validationApiRateLimited, false);
+ }
};
const handleOnFocus = (event) => {
@@ -294,7 +365,6 @@ const EmbeddableRegistrationPage = (props) => {
e.preventDefault();
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
let payload = { ...formFields };
-
if (!isFormValid(payload)) {
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
return;
@@ -307,12 +377,20 @@ const EmbeddableRegistrationPage = (props) => {
payload[fieldName] = configurableFormFields[fieldName];
}
});
-
// Don't send the marketing email opt-in value if the flag is turned off
if (!flags.showMarketingEmailOptInCheckbox) {
delete payload.marketingEmailsOptIn;
}
-
+ let isValid = true;
+ Object.entries(payload).forEach(([key, value]) => {
+ if (validateInput(key, value, payload, false, true) !== '') {
+ isValid = false;
+ }
+ });
+ if (!isValid) {
+ setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
+ return;
+ }
payload = snakeCaseObject(payload);
payload.totalRegistrationTime = totalRegistrationTime;
@@ -350,6 +428,7 @@ const EmbeddableRegistrationPage = (props) => {
handleChange={handleOnChange}
handleBlur={handleOnBlur}
handleFocus={handleOnFocus}
+ handleSuggestionClick={(e) => handleSuggestionClick(e, 'email')}
handleOnClose={handleEmailSuggestionClosed}
emailSuggestion={emailSuggestion}
errorMessage={errors.email}
diff --git a/src/register/tests/EmbeddableRegistrationPage.test.jsx b/src/register/tests/EmbeddableRegistrationPage.test.jsx
index 26ae4b8919..7805fca029 100644
--- a/src/register/tests/EmbeddableRegistrationPage.test.jsx
+++ b/src/register/tests/EmbeddableRegistrationPage.test.jsx
@@ -234,6 +234,97 @@ describe('RegistrationPage', () => {
expect(registrationPage.find('input#username').prop('value')).toEqual('test-user');
});
+ it('should run username and email frontend validations', () => {
+ const payload = {
+ name: 'John Doe',
+ username: 'test@2u.com',
+ email: 'test@yopmail.test',
+ password: 'password1',
+ country: 'Pakistan',
+ honor_code: true,
+ totalRegistrationTime: 0,
+ marketing_emails_opt_in: true,
+ };
+
+ store.dispatch = jest.fn(store.dispatch);
+ const registrationPage = mount(reduxWrapper());
+ populateRequiredFields(registrationPage, payload);
+ registrationPage.find('input[name="email"]').simulate('focus');
+ registrationPage.find('input[name="email"]').simulate('blur', { target: { value: 'test@yopmail.test', name: 'email' } });
+ expect(registrationPage.find('.email-suggestion__text').exists()).toBeTruthy();
+
+ registrationPage.find('input[name="email"]').simulate('focus');
+ registrationPage.find('input[name="email"]').simulate('blur', { target: { value: 'asasasasas', name: 'email' } });
+
+ registrationPage.find('button.btn-brand').simulate('click');
+ expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
+ expect(registrationPage.find('div[feedback-for="username"]').exists()).toBeTruthy();
+ });
+ it('should run email frontend validations when random string is input', () => {
+ const payload = {
+ name: 'John Doe',
+ username: 'testh@2u.com',
+ email: 'as',
+ password: 'password1',
+ country: 'Pakistan',
+ honor_code: true,
+ totalRegistrationTime: 0,
+ marketing_emails_opt_in: true,
+ };
+
+ const registrationPage = mount(reduxWrapper());
+ populateRequiredFields(registrationPage, payload);
+
+ registrationPage.find('button.btn-brand').simulate('click');
+ expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeTruthy();
+ });
+ it('should run frontend validations for name field', () => {
+ const payload = {
+ name: 'https://localhost.com',
+ username: 'test@2u.com',
+ email: 'as',
+ password: 'password1',
+ country: 'Pakistan',
+ honor_code: true,
+ totalRegistrationTime: 0,
+ marketing_emails_opt_in: true,
+ };
+
+ const registrationPage = mount(reduxWrapper());
+ populateRequiredFields(registrationPage, payload);
+
+ registrationPage.find('button.btn-brand').simulate('click');
+ expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy();
+ });
+
+ it('should run frontend validations for password field', () => {
+ const payload = {
+ name: 'https://localhost.com',
+ username: 'test@2u.com',
+ email: 'as',
+ password: 'as',
+ country: 'Pakistan',
+ honor_code: true,
+ totalRegistrationTime: 0,
+ marketing_emails_opt_in: true,
+ };
+
+ const registrationPage = mount(reduxWrapper());
+ populateRequiredFields(registrationPage, payload);
+
+ registrationPage.find('button.btn-brand').simulate('click');
+ expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeTruthy();
+ });
+
+ it('should click on email suggestion in case suggestion is avialable', () => {
+ const registrationPage = mount(reduxWrapper());
+ registrationPage.find('input[name="email"]').simulate('focus');
+ registrationPage.find('input[name="email"]').simulate('blur', { target: { value: 'test@gmail.co', name: 'email' } });
+
+ registrationPage.find('a.email-suggestion-alert-warning').simulate('click');
+ expect(registrationPage.find('input#email').prop('value')).toEqual('test@gmail.com');
+ });
+
it('should remove extra character if username is more than 30 character long', () => {
const registrationPage = mount(reduxWrapper());
registrationPage.find('input#username').simulate('change', { target: { value: 'why_this_is_not_valid_username_', name: 'username' } });