806 lines
28 KiB
JavaScript
806 lines
28 KiB
JavaScript
import React, {
|
|
useEffect, useMemo, useState,
|
|
} from 'react';
|
|
import { connect } from 'react-redux';
|
|
|
|
import { getConfig, snakeCaseObject } from '@edx/frontend-platform';
|
|
import { sendPageEvent } from '@edx/frontend-platform/analytics';
|
|
import {
|
|
getCountryList, getLocale, useIntl,
|
|
} from '@edx/frontend-platform/i18n';
|
|
import { Form, Spinner, StatefulButton } from '@edx/paragon';
|
|
import classNames from 'classnames';
|
|
import PropTypes from 'prop-types';
|
|
import { Helmet } from 'react-helmet';
|
|
import Skeleton from 'react-loading-skeleton';
|
|
|
|
import ConfigurableRegistrationForm from './ConfigurableRegistrationForm';
|
|
import {
|
|
backupRegistrationFormBegin,
|
|
clearRegistertionBackendError,
|
|
clearUsernameSuggestions,
|
|
fetchRealtimeValidations,
|
|
registerNewUser,
|
|
setUserPipelineDataLoaded,
|
|
} from './data/actions';
|
|
import {
|
|
COUNTRY_CODE_KEY,
|
|
COUNTRY_DISPLAY_KEY,
|
|
FIELDS,
|
|
FORM_SUBMISSION_ERROR,
|
|
TPA_AUTHENTICATION_FAILURE,
|
|
} from './data/constants';
|
|
import { registrationErrorSelector, validationsSelector } from './data/selectors';
|
|
import {
|
|
getSuggestionForInvalidEmail, validateCountryField, validateEmailAddress,
|
|
} from './data/utils';
|
|
import messages from './messages';
|
|
import RegistrationFailure from './RegistrationFailure';
|
|
import { EmailField, UsernameField } from './registrationFields';
|
|
import ThirdPartyAuth from './ThirdPartyAuth';
|
|
import {
|
|
FormGroup, InstitutionLogistration, PasswordField, RedirectLogistration, ThirdPartyAuthAlert,
|
|
} from '../common-components';
|
|
import { getThirdPartyAuthContext } from '../common-components/data/actions';
|
|
import {
|
|
fieldDescriptionSelector, optionalFieldsSelector, thirdPartyAuthContextSelector,
|
|
} from '../common-components/data/selectors';
|
|
import EnterpriseSSO from '../common-components/EnterpriseSSO';
|
|
import {
|
|
COMPLETE_STATE, DEFAULT_STATE,
|
|
INVALID_NAME_REGEX, LETTER_REGEX, NUMBER_REGEX, PENDING_STATE, REGISTER_PAGE, VALID_EMAIL_REGEX,
|
|
} from '../data/constants';
|
|
import {
|
|
getAllPossibleQueryParams, getTpaHint, getTpaProvider, isHostAvailableInQueryParams, setCookie,
|
|
} from '../data/utils';
|
|
|
|
const emailRegex = new RegExp(VALID_EMAIL_REGEX, 'i');
|
|
const urlRegex = new RegExp(INVALID_NAME_REGEX);
|
|
|
|
const RegistrationPage = (props) => {
|
|
const {
|
|
backedUpFormData,
|
|
backendCountryCode,
|
|
backendValidations,
|
|
fieldDescriptions,
|
|
handleInstitutionLogin,
|
|
institutionLogin,
|
|
optionalFields,
|
|
registrationError,
|
|
registrationErrorCode,
|
|
registrationResult,
|
|
shouldBackupState,
|
|
submitState,
|
|
thirdPartyAuthApiStatus,
|
|
thirdPartyAuthContext,
|
|
usernameSuggestions,
|
|
validationApiRateLimited,
|
|
// Actions
|
|
backupFormState,
|
|
setUserPipelineDetailsLoaded,
|
|
getRegistrationDataFromBackend,
|
|
userPipelineDataLoaded,
|
|
validateFromBackend,
|
|
clearBackendError,
|
|
} = props;
|
|
|
|
const { formatMessage } = useIntl();
|
|
const countryList = useMemo(() => getCountryList(getLocale()), []);
|
|
const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
|
|
const registrationEmbedded = isHostAvailableInQueryParams();
|
|
const { host } = queryParams;
|
|
const tpaHint = useMemo(() => getTpaHint(), []);
|
|
const flags = {
|
|
showConfigurableEdxFields: getConfig().SHOW_CONFIGURABLE_EDX_FIELDS,
|
|
showConfigurableRegistrationFields: getConfig().ENABLE_DYNAMIC_REGISTRATION_FIELDS,
|
|
showMarketingEmailOptInCheckbox: getConfig().MARKETING_EMAILS_OPT_IN,
|
|
};
|
|
|
|
const [formFields, setFormFields] = useState({ ...backedUpFormData.formFields });
|
|
const [configurableFormFields, setConfigurableFormFields] = useState({ ...backedUpFormData.configurableFormFields });
|
|
const [errors, setErrors] = useState({ ...backedUpFormData.errors });
|
|
const [emailSuggestion, setEmailSuggestion] = useState({ ...backedUpFormData.emailSuggestion });
|
|
const [autoSubmitRegisterForm, setAutoSubmitRegisterForm] = useState(false);
|
|
const [errorCode, setErrorCode] = useState({ type: '', count: 0 });
|
|
const [formStartTime, setFormStartTime] = useState(null);
|
|
const [focusedField, setFocusedField] = useState(null);
|
|
|
|
const {
|
|
providers, currentProvider, secondaryProviders, finishAuthUrl,
|
|
} = thirdPartyAuthContext;
|
|
const platformName = getConfig().SITE_NAME;
|
|
|
|
/**
|
|
* If auto submitting register form, we will check tos and honor code fields if they exist for feature parity.
|
|
*/
|
|
const checkTOSandHonorCodeFields = () => {
|
|
if (Object.keys(fieldDescriptions).includes(FIELDS.HONOR_CODE)) {
|
|
setConfigurableFormFields(prevState => ({
|
|
...prevState,
|
|
[FIELDS.HONOR_CODE]: true,
|
|
}));
|
|
}
|
|
if (Object.keys(fieldDescriptions).includes(FIELDS.TERMS_OF_SERVICE)) {
|
|
setConfigurableFormFields(prevState => ({
|
|
...prevState,
|
|
[FIELDS.TERMS_OF_SERVICE]: true,
|
|
}));
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Set the userPipelineDetails data in formFields for only first time
|
|
*/
|
|
useEffect(() => {
|
|
if (!userPipelineDataLoaded && thirdPartyAuthApiStatus === COMPLETE_STATE) {
|
|
const { autoSubmitRegForm, pipelineUserDetails, errorMessage } = thirdPartyAuthContext;
|
|
if (errorMessage) {
|
|
setErrorCode(prevState => ({ type: TPA_AUTHENTICATION_FAILURE, count: prevState.count + 1 }));
|
|
} else if (autoSubmitRegForm) {
|
|
checkTOSandHonorCodeFields();
|
|
setAutoSubmitRegisterForm(true);
|
|
}
|
|
if (pipelineUserDetails && Object.keys(pipelineUserDetails).length !== 0) {
|
|
const { name = '', username = '', email = '' } = pipelineUserDetails;
|
|
setFormFields(prevState => ({
|
|
...prevState, name, username, email,
|
|
}));
|
|
setUserPipelineDetailsLoaded(true);
|
|
}
|
|
}
|
|
}, [ // eslint-disable-line react-hooks/exhaustive-deps
|
|
thirdPartyAuthContext,
|
|
userPipelineDataLoaded,
|
|
setUserPipelineDetailsLoaded,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (!formStartTime) {
|
|
sendPageEvent('login_and_registration', 'register');
|
|
const payload = { ...queryParams, is_register_page: true };
|
|
if (tpaHint) {
|
|
payload.tpa_hint = tpaHint;
|
|
}
|
|
getRegistrationDataFromBackend(payload);
|
|
setFormStartTime(Date.now());
|
|
}
|
|
}, [formStartTime, getRegistrationDataFromBackend, queryParams, tpaHint]);
|
|
|
|
/**
|
|
* Backup the registration form in redux when register page is toggled.
|
|
*/
|
|
useEffect(() => {
|
|
if (shouldBackupState) {
|
|
backupFormState({
|
|
configurableFormFields: { ...configurableFormFields },
|
|
formFields: { ...formFields },
|
|
emailSuggestion: { ...emailSuggestion },
|
|
errors: { ...errors },
|
|
});
|
|
}
|
|
}, [shouldBackupState, configurableFormFields, formFields, errors, emailSuggestion, backupFormState]);
|
|
|
|
useEffect(() => {
|
|
if (backendValidations) {
|
|
setErrors(prevErrors => ({ ...prevErrors, ...backendValidations }));
|
|
}
|
|
}, [backendValidations]);
|
|
|
|
useEffect(() => {
|
|
if (registrationErrorCode) {
|
|
setErrorCode(prevState => ({ type: registrationErrorCode, count: prevState.count + 1 }));
|
|
}
|
|
}, [registrationErrorCode]);
|
|
|
|
useEffect(() => {
|
|
if (backendCountryCode && backendCountryCode !== configurableFormFields?.country?.countryCode) {
|
|
let countryCode = '';
|
|
let countryDisplayValue = '';
|
|
|
|
const selectedCountry = countryList.find(
|
|
(country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()),
|
|
);
|
|
if (selectedCountry) {
|
|
countryCode = selectedCountry[COUNTRY_CODE_KEY];
|
|
countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY];
|
|
}
|
|
setConfigurableFormFields(prevState => (
|
|
{
|
|
...prevState,
|
|
country: {
|
|
countryCode, displayValue: countryDisplayValue,
|
|
},
|
|
}
|
|
));
|
|
}
|
|
}, [backendCountryCode, countryList]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
/**
|
|
* We need to remove the placeholder from the field, adding a space will do that.
|
|
* This is needed because we are placing the username suggestions on top of the field.
|
|
*/
|
|
useEffect(() => {
|
|
if (usernameSuggestions.length && !formFields.username) {
|
|
setFormFields(prevState => ({ ...prevState, username: ' ' }));
|
|
}
|
|
}, [usernameSuggestions, formFields]);
|
|
|
|
useEffect(() => {
|
|
if (registrationResult.success) {
|
|
// This was added to fire social media conversion pixels through Google tag manager.
|
|
setCookie(getConfig().REGISTER_CONVERSION_COOKIE_NAME, true);
|
|
|
|
// Fire GTM event used for integration with impact.com
|
|
window.dataLayer = window.dataLayer || [];
|
|
window.dataLayer.push({
|
|
event: 'ImpactRegistrationEvent',
|
|
});
|
|
}
|
|
}, [registrationResult]);
|
|
|
|
const validateInput = (fieldName, value, payload, shouldValidateFromBackend, setError = true) => {
|
|
let fieldError = '';
|
|
let confirmEmailError = ''; // This is to handle the use case where the form contains "confirm email" field
|
|
let countryFieldCode = '';
|
|
|
|
switch (fieldName) {
|
|
case 'name':
|
|
if (!value.trim()) {
|
|
fieldError = formatMessage(messages['empty.name.field.error']);
|
|
} else 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) {
|
|
fieldError = formatMessage(messages['empty.email.field.error']);
|
|
} else 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;
|
|
} else if (shouldValidateFromBackend) {
|
|
validateFromBackend(payload);
|
|
}
|
|
setEmailSuggestion({ ...response });
|
|
|
|
if (configurableFormFields.confirm_email && value !== configurableFormFields.confirm_email) {
|
|
confirmEmailError = formatMessage(messages['email.do.not.match']);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'username':
|
|
if (!value || value.length <= 1 || value.length > 30) {
|
|
fieldError = formatMessage(messages['username.validation.message']);
|
|
} else if (!value.match(/^[a-zA-Z0-9_-]*$/i)) {
|
|
fieldError = formatMessage(messages['username.format.validation.message']);
|
|
} else if (shouldValidateFromBackend) {
|
|
validateFromBackend(payload);
|
|
}
|
|
break;
|
|
case 'password':
|
|
if (!value || !LETTER_REGEX.test(value) || !NUMBER_REGEX.test(value) || value.length < 8) {
|
|
fieldError = formatMessage(messages['password.validation.message']);
|
|
} else if (shouldValidateFromBackend) {
|
|
validateFromBackend(payload);
|
|
}
|
|
break;
|
|
case 'country':
|
|
if (flags.showConfigurableEdxFields || flags.showConfigurableRegistrationFields) {
|
|
const {
|
|
countryCode, displayValue, error,
|
|
} = validateCountryField(value.displayValue.trim(), countryList, formatMessage(messages['empty.country.field.error']));
|
|
fieldError = error;
|
|
countryFieldCode = countryCode;
|
|
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 (setError) {
|
|
setErrors(prevErrors => ({
|
|
...prevErrors,
|
|
confirm_email: flags.showConfigurableRegistrationFields ? confirmEmailError : '',
|
|
[fieldName]: fieldError,
|
|
}));
|
|
}
|
|
return { fieldError, countryFieldCode };
|
|
};
|
|
|
|
const isFormValid = (payload, focusedFieldError) => {
|
|
const fieldErrors = { ...errors };
|
|
let isValid = !focusedFieldError;
|
|
Object.keys(payload).forEach(key => {
|
|
if (!payload[key]) {
|
|
fieldErrors[key] = formatMessage(messages[`empty.${key}.field.error`]);
|
|
}
|
|
if (fieldErrors[key]) {
|
|
isValid = false;
|
|
}
|
|
});
|
|
|
|
if (flags.showConfigurableEdxFields) {
|
|
if (!configurableFormFields.country.displayValue) {
|
|
fieldErrors.country = formatMessage(messages['empty.country.field.error']);
|
|
}
|
|
if (fieldErrors.country) {
|
|
isValid = false;
|
|
}
|
|
}
|
|
|
|
if (flags.showConfigurableRegistrationFields) {
|
|
Object.keys(fieldDescriptions).forEach(key => {
|
|
if (key === 'country' && !configurableFormFields.country.displayValue) {
|
|
fieldErrors[key] = formatMessage(messages['empty.country.field.error']);
|
|
} else if (!configurableFormFields[key]) {
|
|
fieldErrors[key] = fieldDescriptions[key].error_message;
|
|
}
|
|
if (fieldErrors[key]) {
|
|
isValid = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
if (focusedField) {
|
|
fieldErrors[focusedField] = focusedFieldError;
|
|
}
|
|
setErrors({ ...fieldErrors });
|
|
return isValid;
|
|
};
|
|
|
|
const handleSuggestionClick = (event, fieldName, suggestion = '') => {
|
|
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();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
|
|
const handleEmailSuggestionClosed = () => setEmailSuggestion({ suggestion: '', type: '' });
|
|
|
|
const handleUsernameSuggestionClosed = () => props.resetUsernameSuggestions();
|
|
|
|
const handleOnChange = (event) => {
|
|
const { name } = event.target;
|
|
let value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
|
|
if (registrationError[name]) {
|
|
clearBackendError(name);
|
|
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
|
}
|
|
if (name === 'username') {
|
|
if (value.length > 30) {
|
|
return;
|
|
}
|
|
if (value.startsWith(' ')) {
|
|
value = value.trim();
|
|
}
|
|
}
|
|
|
|
setFormFields(prevState => ({ ...prevState, [name]: value }));
|
|
};
|
|
|
|
const handleOnBlur = (event) => {
|
|
const { name, value } = event.target;
|
|
if (registrationEmbedded) {
|
|
if (name === 'name') {
|
|
validateInput(
|
|
name,
|
|
value,
|
|
{ name: formFields.name, username: formFields.username, form_field_key: name },
|
|
!validationApiRateLimited,
|
|
false,
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
const payload = {
|
|
name: formFields.name,
|
|
email: formFields.email,
|
|
username: formFields.username,
|
|
password: formFields.password,
|
|
form_field_key: name,
|
|
};
|
|
|
|
setFocusedField(null);
|
|
validateInput(name, name === 'password' ? formFields.password : value, payload, !validationApiRateLimited);
|
|
};
|
|
|
|
const handleOnFocus = (event) => {
|
|
const { name, value } = event.target;
|
|
setErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
|
|
clearBackendError(name);
|
|
// Since we are removing the form errors from the focused field, we will
|
|
// need to rerun the validation for focused field on form submission.
|
|
setFocusedField(name);
|
|
|
|
if (name === 'username') {
|
|
props.resetUsernameSuggestions();
|
|
// If we added a space character to username field to display the suggestion
|
|
// remove it before user enters the input. This is to ensure user doesn't
|
|
// have a space prefixed to the username.
|
|
if (value === ' ') {
|
|
setFormFields(prevState => ({ ...prevState, [name]: '' }));
|
|
}
|
|
}
|
|
};
|
|
|
|
const registerUser = () => {
|
|
const totalRegistrationTime = (Date.now() - formStartTime) / 1000;
|
|
let payload = { ...formFields };
|
|
|
|
if (currentProvider) {
|
|
delete payload.password;
|
|
payload.social_auth_provider = currentProvider;
|
|
}
|
|
|
|
const { fieldError: focusedFieldError, countryFieldCode } = focusedField ? (
|
|
validateInput(
|
|
focusedField,
|
|
(focusedField in fieldDescriptions || ['country', 'marketingEmailsOptIn'].includes(focusedField)) ? (
|
|
configurableFormFields[focusedField]
|
|
) : formFields[focusedField],
|
|
payload,
|
|
false,
|
|
false,
|
|
)
|
|
) : '';
|
|
|
|
if (!isFormValid(payload, focusedFieldError)) {
|
|
setErrorCode(prevState => ({ type: FORM_SUBMISSION_ERROR, count: prevState.count + 1 }));
|
|
return;
|
|
}
|
|
|
|
Object.keys(configurableFormFields).forEach((fieldName) => {
|
|
if (fieldName === 'country') {
|
|
payload[fieldName] = focusedField === 'country' ? countryFieldCode : configurableFormFields[fieldName].countryCode;
|
|
} else {
|
|
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;
|
|
}
|
|
|
|
payload = snakeCaseObject(payload);
|
|
payload.totalRegistrationTime = totalRegistrationTime;
|
|
|
|
// add query params to the payload
|
|
payload = { ...payload, ...queryParams };
|
|
props.registerNewUser(payload);
|
|
};
|
|
|
|
const handleSubmit = (e) => {
|
|
e.preventDefault();
|
|
registerUser();
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (autoSubmitRegisterForm && userPipelineDataLoaded) {
|
|
registerUser();
|
|
}
|
|
}, [autoSubmitRegisterForm, userPipelineDataLoaded]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const renderForm = () => {
|
|
if (institutionLogin) {
|
|
return (
|
|
<InstitutionLogistration
|
|
secondaryProviders={secondaryProviders}
|
|
headingTitle={formatMessage(messages['register.institution.login.page.title'])}
|
|
/>
|
|
);
|
|
}
|
|
return (
|
|
<>
|
|
<Helmet>
|
|
<title>{formatMessage(messages['register.page.title'], { siteName: getConfig().SITE_NAME })}</title>
|
|
</Helmet>
|
|
<RedirectLogistration
|
|
host={host}
|
|
success={registrationResult.success}
|
|
redirectUrl={registrationResult.redirectUrl}
|
|
finishAuthUrl={finishAuthUrl}
|
|
optionalFields={optionalFields}
|
|
registrationEmbedded={registrationEmbedded}
|
|
redirectToProgressiveProfilingPage={
|
|
getConfig().ENABLE_PROGRESSIVE_PROFILING_ON_AUTHN && Object.keys(optionalFields).includes('fields')
|
|
}
|
|
/>
|
|
{autoSubmitRegisterForm && !errorCode.type ? (
|
|
<div className="mw-xs mt-5 text-center">
|
|
<Spinner animation="border" variant="primary" id="tpa-spinner" />
|
|
</div>
|
|
) : (
|
|
<div
|
|
className={classNames(
|
|
'mw-xs mt-3',
|
|
{ 'w-100 m-auto pt-4': registrationEmbedded },
|
|
)}
|
|
>
|
|
<ThirdPartyAuthAlert
|
|
currentProvider={currentProvider}
|
|
platformName={platformName}
|
|
referrer={REGISTER_PAGE}
|
|
/>
|
|
<RegistrationFailure
|
|
errorCode={errorCode.type}
|
|
failureCount={errorCode.count}
|
|
context={{ provider: currentProvider, errorMessage: thirdPartyAuthContext.errorMessage }}
|
|
/>
|
|
<Form id="registration-form" name="registration-form">
|
|
<FormGroup
|
|
name="name"
|
|
value={formFields.name}
|
|
handleChange={handleOnChange}
|
|
handleBlur={handleOnBlur}
|
|
handleFocus={handleOnFocus}
|
|
errorMessage={errors.name}
|
|
helpText={[formatMessage(messages['help.text.name'])]}
|
|
floatingLabel={formatMessage(messages['registration.fullname.label'])}
|
|
/>
|
|
<EmailField
|
|
name="email"
|
|
value={formFields.email}
|
|
handleChange={handleOnChange}
|
|
handleBlur={handleOnBlur}
|
|
handleFocus={handleOnFocus}
|
|
handleSuggestionClick={(e) => handleSuggestionClick(e, 'email')}
|
|
handleOnClose={handleEmailSuggestionClosed}
|
|
emailSuggestion={emailSuggestion}
|
|
errorMessage={errors.email}
|
|
helpText={[formatMessage(messages['help.text.email'])]}
|
|
floatingLabel={formatMessage(messages['registration.email.label'])}
|
|
/>
|
|
<UsernameField
|
|
name="username"
|
|
spellCheck="false"
|
|
value={formFields.username}
|
|
handleBlur={handleOnBlur}
|
|
handleChange={handleOnChange}
|
|
handleFocus={handleOnFocus}
|
|
handleSuggestionClick={handleSuggestionClick}
|
|
handleUsernameSuggestionClose={handleUsernameSuggestionClosed}
|
|
usernameSuggestions={usernameSuggestions}
|
|
errorMessage={errors.username}
|
|
helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]}
|
|
floatingLabel={formatMessage(messages['registration.username.label'])}
|
|
/>
|
|
{!currentProvider && (
|
|
<PasswordField
|
|
name="password"
|
|
value={formFields.password}
|
|
handleChange={handleOnChange}
|
|
handleBlur={handleOnBlur}
|
|
handleFocus={handleOnFocus}
|
|
errorMessage={errors.password}
|
|
floatingLabel={formatMessage(messages['registration.password.label'])}
|
|
/>
|
|
)}
|
|
<ConfigurableRegistrationForm
|
|
countryList={countryList}
|
|
email={formFields.email}
|
|
fieldErrors={errors}
|
|
registrationEmbedded={registrationEmbedded}
|
|
formFields={configurableFormFields}
|
|
setFieldErrors={setErrors}
|
|
setFormFields={setConfigurableFormFields}
|
|
setFocusedField={setFocusedField}
|
|
fieldDescriptions={fieldDescriptions}
|
|
/>
|
|
<StatefulButton
|
|
id="register-user"
|
|
name="register-user"
|
|
type="submit"
|
|
variant="brand"
|
|
className="register-button mt-4 mb-4"
|
|
state={submitState}
|
|
labels={{
|
|
default: formatMessage(messages['create.account.for.free.button']),
|
|
pending: '',
|
|
}}
|
|
onClick={handleSubmit}
|
|
onMouseDown={(e) => e.preventDefault()}
|
|
/>
|
|
{!registrationEmbedded && (
|
|
<ThirdPartyAuth
|
|
currentProvider={currentProvider}
|
|
providers={providers}
|
|
secondaryProviders={secondaryProviders}
|
|
handleInstitutionLogin={handleInstitutionLogin}
|
|
thirdPartyAuthApiStatus={thirdPartyAuthApiStatus}
|
|
/>
|
|
)}
|
|
</Form>
|
|
</div>
|
|
)}
|
|
|
|
</>
|
|
);
|
|
};
|
|
|
|
if (tpaHint) {
|
|
if (thirdPartyAuthApiStatus === PENDING_STATE) {
|
|
return <Skeleton height={36} />;
|
|
}
|
|
const { provider, skipHintedLogin } = getTpaProvider(tpaHint, providers, secondaryProviders);
|
|
if (skipHintedLogin) {
|
|
window.location.href = getConfig().LMS_BASE_URL + provider.registerUrl;
|
|
return null;
|
|
}
|
|
return provider ? <EnterpriseSSO provider={provider} /> : renderForm();
|
|
}
|
|
return (
|
|
renderForm()
|
|
);
|
|
};
|
|
|
|
const mapStateToProps = state => {
|
|
const registerPageState = state.register;
|
|
return {
|
|
backedUpFormData: registerPageState.registrationFormData,
|
|
backendCountryCode: registerPageState.backendCountryCode,
|
|
backendValidations: validationsSelector(state),
|
|
fieldDescriptions: fieldDescriptionSelector(state),
|
|
optionalFields: optionalFieldsSelector(state),
|
|
registrationError: registerPageState.registrationError,
|
|
registrationErrorCode: registrationErrorSelector(state),
|
|
registrationResult: registerPageState.registrationResult,
|
|
shouldBackupState: registerPageState.shouldBackupState,
|
|
userPipelineDataLoaded: registerPageState.userPipelineDataLoaded,
|
|
submitState: registerPageState.submitState,
|
|
thirdPartyAuthApiStatus: state.commonComponents.thirdPartyAuthApiStatus,
|
|
thirdPartyAuthContext: thirdPartyAuthContextSelector(state),
|
|
validationApiRateLimited: registerPageState.validationApiRateLimited,
|
|
usernameSuggestions: registerPageState.usernameSuggestions,
|
|
};
|
|
};
|
|
|
|
RegistrationPage.propTypes = {
|
|
backedUpFormData: PropTypes.shape({
|
|
configurableFormFields: PropTypes.shape({}),
|
|
formFields: PropTypes.shape({}),
|
|
errors: PropTypes.shape({}),
|
|
emailSuggestion: PropTypes.shape({}),
|
|
}),
|
|
backendCountryCode: PropTypes.string,
|
|
backendValidations: PropTypes.shape({
|
|
name: PropTypes.string,
|
|
email: PropTypes.string,
|
|
username: PropTypes.string,
|
|
password: PropTypes.string,
|
|
}),
|
|
fieldDescriptions: PropTypes.shape({}),
|
|
institutionLogin: PropTypes.bool,
|
|
optionalFields: PropTypes.shape({}),
|
|
registrationError: PropTypes.shape({}),
|
|
registrationErrorCode: PropTypes.string,
|
|
registrationResult: PropTypes.shape({
|
|
redirectUrl: PropTypes.string,
|
|
success: PropTypes.bool,
|
|
}),
|
|
shouldBackupState: PropTypes.bool,
|
|
submitState: PropTypes.string,
|
|
thirdPartyAuthApiStatus: PropTypes.string,
|
|
thirdPartyAuthContext: PropTypes.shape({
|
|
autoSubmitRegForm: PropTypes.bool,
|
|
countryCode: PropTypes.string,
|
|
currentProvider: PropTypes.string,
|
|
errorMessage: PropTypes.string,
|
|
finishAuthUrl: PropTypes.string,
|
|
pipelineUserDetails: PropTypes.shape({
|
|
email: PropTypes.string,
|
|
name: PropTypes.string,
|
|
firstName: PropTypes.string,
|
|
lastName: PropTypes.string,
|
|
username: PropTypes.string,
|
|
}),
|
|
platformName: PropTypes.string,
|
|
providers: PropTypes.arrayOf(
|
|
PropTypes.shape({}),
|
|
),
|
|
secondaryProviders: PropTypes.arrayOf(
|
|
PropTypes.shape({}),
|
|
),
|
|
}),
|
|
usernameSuggestions: PropTypes.arrayOf(PropTypes.string),
|
|
userPipelineDataLoaded: PropTypes.bool,
|
|
validationApiRateLimited: PropTypes.bool,
|
|
// Actions
|
|
backupFormState: PropTypes.func.isRequired,
|
|
clearBackendError: PropTypes.func.isRequired,
|
|
getRegistrationDataFromBackend: PropTypes.func.isRequired,
|
|
handleInstitutionLogin: PropTypes.func,
|
|
registerNewUser: PropTypes.func.isRequired,
|
|
resetUsernameSuggestions: PropTypes.func.isRequired,
|
|
setUserPipelineDetailsLoaded: PropTypes.func.isRequired,
|
|
validateFromBackend: PropTypes.func.isRequired,
|
|
};
|
|
|
|
RegistrationPage.defaultProps = {
|
|
backedUpFormData: {
|
|
configurableFormFields: {
|
|
marketingEmailsOptIn: true,
|
|
},
|
|
formFields: {
|
|
name: '', email: '', username: '', password: '',
|
|
},
|
|
errors: {
|
|
name: '', email: '', username: '', password: '',
|
|
},
|
|
emailSuggestion: {
|
|
suggestion: '', type: '',
|
|
},
|
|
},
|
|
backendCountryCode: '',
|
|
backendValidations: null,
|
|
fieldDescriptions: {},
|
|
handleInstitutionLogin: null,
|
|
institutionLogin: false,
|
|
optionalFields: {},
|
|
registrationError: {},
|
|
registrationErrorCode: '',
|
|
registrationResult: null,
|
|
shouldBackupState: false,
|
|
submitState: DEFAULT_STATE,
|
|
thirdPartyAuthApiStatus: PENDING_STATE,
|
|
thirdPartyAuthContext: {
|
|
autoSubmitRegForm: false,
|
|
countryCode: null,
|
|
currentProvider: null,
|
|
errorMessage: null,
|
|
finishAuthUrl: null,
|
|
pipelineUserDetails: null,
|
|
providers: [],
|
|
secondaryProviders: [],
|
|
},
|
|
usernameSuggestions: [],
|
|
userPipelineDataLoaded: false,
|
|
validationApiRateLimited: false,
|
|
};
|
|
|
|
export default connect(
|
|
mapStateToProps,
|
|
{
|
|
backupFormState: backupRegistrationFormBegin,
|
|
clearBackendError: clearRegistertionBackendError,
|
|
getRegistrationDataFromBackend: getThirdPartyAuthContext,
|
|
resetUsernameSuggestions: clearUsernameSuggestions,
|
|
validateFromBackend: fetchRealtimeValidations,
|
|
registerNewUser,
|
|
setUserPipelineDetailsLoaded: setUserPipelineDataLoaded,
|
|
},
|
|
)(RegistrationPage);
|