Add SSO to register page (#32)

This commit is contained in:
Zainab Amir
2020-11-25 14:39:12 +05:00
committed by GitHub
parent c4c8dd6109
commit 370cf08763
9 changed files with 1683 additions and 295 deletions

View File

@@ -130,7 +130,7 @@ class LoginPage extends React.Component {
redirectUrl={this.props.loginResult.redirectUrl}
finishAuthUrl={finishAuthUrl}
/>
<div className="d-flex justify-content-center logistration-container">
<div className="d-flex justify-content-center login-container">
<div className="d-flex flex-column" style={{ width: '400px' }}>
{currentProvider
&& (

View File

@@ -4,21 +4,17 @@ import PropTypes from 'prop-types';
import {
Button, Input, ValidationFormGroup,
} from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFacebookF, faGoogle, faMicrosoft } from '@fortawesome/free-brands-svg-icons';
import { faGraduationCap } from '@fortawesome/free-solid-svg-icons';
import {
getLocale,
getCountryList,
injectIntl,
intlShape,
getLocale, getCountryList, injectIntl, intlShape,
} from '@edx/frontend-platform/i18n';
import { getThirdPartyAuthContext, registerNewUser } from './data/actions';
import { registrationRequestSelector, thirdPartyAuthContextSelector } from './data/selectors';
import { DEFAULT_REDIRECT_URL } from '../data/constants';
import RedirectLogistration from './RedirectLogistration';
import RegistrationFailure from './RegistrationFailure';
import { DEFAULT_REDIRECT_URL, LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
import SocialAuthProviders from './SocialAuthProviders';
import ThirdPartyAuthAlert from './ThirdPartyAuthAlert';
import InstitutionLogistration, { RenderInstitutionButton } from './InstitutionLogistration';
import messages from './messages';
@@ -28,13 +24,13 @@ class RegistrationPage extends React.Component {
this.state = {
email: '',
name: '',
fullname: '',
username: '',
password: '',
country: '',
errors: {
email: '',
name: '',
fullname: '',
username: '',
password: '',
country: '',
@@ -68,7 +64,7 @@ class RegistrationPage extends React.Component {
email: this.state.email,
username: this.state.username,
password: this.state.password,
name: this.state.name,
name: this.state.fullname,
honor_code: true,
country: this.state.country,
};
@@ -112,9 +108,9 @@ class RegistrationPage extends React.Component {
emailValid = value.match(/^([\w.%+-]+)@([\w-]+\.)+([\w]{2,})$/i);
errors.email = emailValid ? '' : null;
break;
case 'name':
case 'fullname':
nameValid = value.length >= 1;
errors.name = nameValid ? '' : null;
errors.fullname = nameValid ? '' : null;
break;
case 'username':
usernameValid = value.length >= 2 && value.length <= 30;
@@ -163,41 +159,60 @@ class RegistrationPage extends React.Component {
}
render() {
const { intl } = this.props;
const {
currentProvider, finishAuthUrl, providers, secondaryProviders,
} = this.props.thirdPartyAuthContext;
if (this.state.institutionLogin) {
return (
<InstitutionLogistration
onSubmitHandler={this.handleInstitutionLogin}
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
headingTitle={this.props.intl.formatMessage(messages['logistration.register.institution.login.page.title'])}
buttonTitle={this.props.intl.formatMessage(messages['logistration.register.institution.login.page.back.button'])}
headingTitle={intl.formatMessage(messages['logistration.register.institution.login.page.title'])}
buttonTitle={intl.formatMessage(messages['logistration.register.institution.login.page.back.button'])}
/>
);
}
return (
<>
<RedirectLogistration
success={this.props.registrationResult.success}
redirectUrl={this.props.registrationResult.redirectUrl}
finishAuthUrl={finishAuthUrl}
/>
<div className="logistration-container d-flex flex-column align-items-center mx-auto" style={{ width: '30rem' }}>
<div className="register-container mx-auto">
{this.props.registrationError ? <RegistrationFailure errors={this.props.registrationError} /> : null}
<div className="mb-4">
<FontAwesomeIcon className="d-block mx-auto fa-2x" icon={faGraduationCap} />
<h4 className="d-block mx-auto">Start learning now!</h4>
</div>
<div className="d-block mb-4">
<span className="d-block mx-auto mb-4 section-heading-line">Create an account using</span>
<button type="button" className="btn-social facebook"><FontAwesomeIcon className="mr-2" icon={faFacebookF} />Facebook</button>
<button type="button" className="btn-social google"><FontAwesomeIcon className="mr-2" icon={faGoogle} />Google</button>
<button type="button" className="btn-social microsoft mb-3"><FontAwesomeIcon className="mr-2" icon={faMicrosoft} />Microsoft</button>
<RenderInstitutionButton
onSubmitHandler={this.handleInstitutionLogin}
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
buttonTitle={this.props.intl.formatMessage(messages['logistration.register.institution.login.button'])}
{currentProvider && (
<ThirdPartyAuthAlert
currentProvider={currentProvider}
platformName={this.props.thirdPartyAuthContext.platformName}
referrer="register"
/>
<span className="d-block mx-auto text-center mt-4 section-heading-line">or create a new one here</span>
)}
<div className="text-left">
<span>{intl.formatMessage(messages['logistration.already.have.an.edx.account'])}</span>
<a href={LOGIN_PAGE}>{intl.formatMessage(messages['logistration.sign.in.hyperlink'])}</a>
</div>
{(providers.length || secondaryProviders.length) && !currentProvider ? (
<div className="d-block mb-4 mt-4">
<span className="d-block mx-auto mb-4 section-heading-line">
{intl.formatMessage(messages['logistration.create.an.account.using'])}
</span>
<div className="row tpa-container">
<SocialAuthProviders socialAuthProviders={providers} referrer={REGISTER_PAGE} />
</div>
<RenderInstitutionButton
onSubmitHandler={this.handleInstitutionLogin}
secondaryProviders={this.props.thirdPartyAuthContext.secondaryProviders}
buttonTitle={intl.formatMessage(messages['logistration.register.institution.login.button'])}
/>
<span className="d-block mx-auto text-center mt-4 section-heading-line">
{intl.formatMessage(messages['logistration.create.a.new.one.here'])}
</span>
</div>
) : null}
<form className="mb-4 mx-auto form-group">
<ValidationFormGroup
for="email"
@@ -216,17 +231,17 @@ class RegistrationPage extends React.Component {
/>
</ValidationFormGroup>
<ValidationFormGroup
for="name"
invalid={this.state.errors.name !== ''}
for="fullname"
invalid={this.state.errors.fullname !== ''}
invalidMessage="Enter your full name."
>
<label htmlFor="registrationName" className="h6 pt-3">Full Name (required)</label>
<Input
name="name"
name="fullname"
id="registrationName"
type="text"
placeholder="Name"
value={this.state.name}
value={this.state.fullname}
onChange={e => this.handleOnChange(e)}
required
/>
@@ -283,34 +298,33 @@ class RegistrationPage extends React.Component {
<Button
className="btn-primary mt-4 submit"
onClick={this.handleSubmit}
inputRef={(input) => {
this.button = input;
}}
>
Create Account
</Button>
</form>
<div className="text-center mb-2 pt-2">
<span>Already have an edX account?</span>
<a href="/login"> Sign in.</a>
</div>
</div>
</>
);
}
}
RegistrationPage.defaultProps = {
registrationResult: null,
registerNewUser: null,
registrationError: null,
thirdPartyAuthContext: {},
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
providers: [],
secondaryProviders: [],
},
};
RegistrationPage.propTypes = {
intl: intlShape.isRequired,
registerNewUser: PropTypes.func,
getThirdPartyAuthContext: PropTypes.func.isRequired,
registerNewUser: PropTypes.func,
registrationResult: PropTypes.shape({
redirectUrl: PropTypes.string,
success: PropTypes.bool,
@@ -321,6 +335,7 @@ RegistrationPage.propTypes = {
}),
thirdPartyAuthContext: PropTypes.shape({
currentProvider: PropTypes.string,
platformName: PropTypes.string,
providers: PropTypes.array,
secondaryProviders: PropTypes.array,
finishAuthUrl: PropTypes.string,
@@ -339,8 +354,8 @@ const mapStateToProps = state => {
const thirdPartyAuthContext = thirdPartyAuthContextSelector(state);
return {
registrationResult,
thirdPartyAuthContext,
registrationError: state.logistration.registrationError,
thirdPartyAuthContext,
};
};

View File

@@ -5,11 +5,11 @@ import { getConfig } from '@edx/frontend-platform';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSignInAlt } from '@fortawesome/free-solid-svg-icons';
import { SUPPORTED_ICON_CLASSES } from '../data/constants';
import { LOGIN_PAGE, SUPPORTED_ICON_CLASSES } from '../data/constants';
function SocialAuthProviders(props) {
const { socialAuthProviders } = props;
const { referrer, socialAuthProviders } = props;
function handleSubmit(e) {
e.preventDefault();
@@ -24,7 +24,7 @@ function SocialAuthProviders(props) {
key={provider.id}
type="button"
className={`btn-social btn-${provider.id}`}
data-provider-url={`${provider.loginUrl}`}
data-provider-url={referrer === LOGIN_PAGE ? provider.loginUrl : provider.registerUrl}
onClick={handleSubmit}
>
{provider.iconImage ? (
@@ -53,10 +53,12 @@ function SocialAuthProviders(props) {
}
SocialAuthProviders.defaultProps = {
referrer: LOGIN_PAGE,
socialAuthProviders: [],
};
SocialAuthProviders.propTypes = {
referrer: PropTypes.string,
socialAuthProviders: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,

View File

@@ -3,12 +3,13 @@ import PropTypes from 'prop-types';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Alert } from '@edx/paragon';
import { LOGIN_PAGE } from '../data/constants';
const ThirdPartyAuthAlert = (props) => {
const { currentProvider, referrer, platformName } = props;
let message;
if (referrer === 'login') {
if (referrer === LOGIN_PAGE) {
message = (
<FormattedMessage
id="login.third.party.auth.account.not.linked.message"
@@ -32,7 +33,7 @@ const ThirdPartyAuthAlert = (props) => {
};
ThirdPartyAuthAlert.defaultProps = {
referrer: 'login',
referrer: LOGIN_PAGE,
};
ThirdPartyAuthAlert.propTypes = {

View File

@@ -2,6 +2,7 @@
// #COLORS
// ----------------------------
$link-blue: #23419f;
$font-blue: #126f9a;
$white: #FFFFFF;
// social platforms
@@ -14,11 +15,21 @@ $microsoft-focus-black: #000;
$apple-black: #000000;
$apple-focus-black: $apple-black;
.logistration-container {
.font-color {
color: $font-blue;
}
.login-container {
margin: 4rem;
line-height: 1.5;
}
.register-container {
@extend .login-container;
width: 30rem;
}
.logistration-header {
border-bottom: 1px solid #e7e7e7;
height: 3.75rem;
@@ -45,13 +56,13 @@ $apple-focus-black: $apple-black;
font-size: 14px;
background-color: $white;
border: 1px solid #126f9a;
border: 1px solid $font-blue;
width: 130px;
height: 36px;
color: #126f9a;
color: $font-blue;
.icon-image {
background-color: #126f9a;
background-color: $font-blue;
max-height: 1.4em;
max-width: 1.4em;
}
@@ -64,7 +75,7 @@ $apple-focus-black: $apple-black;
}
.font-container {
background-color: #126f9a;
background-color: $font-blue;
color: $white;
font-size: 11px;

View File

@@ -22,6 +22,26 @@ const messages = defineMessages({
defaultMessage: 'Forgot password?',
description: 'Forgot password link',
},
'logistration.already.have.an.edx.account': {
id: 'logistration.already.have.an.edx.account',
defaultMessage: 'Already have an edX account?',
description: 'A message on registration page asking the user if he already has an edX account',
},
'logistration.sign.in.hyperlink': {
id: 'logistration.sign.in.hyperlink',
defaultMessage: ' Sign in.',
description: 'Text for the hyperlink that takes user to login page',
},
'logistration.create.an.account.using': {
id: 'logistration.create.an.account.using',
defaultMessage: 'Create an account using',
description: 'A message that appears before social auth buttons',
},
'logistration.create.a.new.one.here': {
id: 'logistration.create.a.new.one.here',
defaultMessage: 'or create a new one here',
description: 'Text that appears after social auth buttons and before the registration form',
},
'logistration.other.sign.in.issues': {
id: 'logistration.other.sign.in.issues',
defaultMessage: 'Other sign-in issues',

View File

@@ -3,6 +3,7 @@ import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureStore from 'redux-mock-store';
import { mount } from 'enzyme';
import { getConfig } from '@edx/frontend-platform';
import { IntlProvider, injectIntl, configure } from '@edx/frontend-platform/i18n';
import RegistrationPage from '../RegistrationPage';
@@ -16,13 +17,26 @@ describe('./RegistrationPage.js', () => {
const initialState = {
logistration: {
registrationResult: { success: false, redirectUrl: '' },
thirdPartyAuthContext: { secondaryProviders: [] },
thirdPartyAuthContext: {
currentProvider: null,
finishAuthUrl: null,
providers: [],
secondaryProviders: [],
},
},
};
let props = {};
let store = {};
const appleProvider = {
id: 'oa2-apple-id',
name: 'Apple',
iconClass: null,
iconImage: 'https://edx.devstack.lms/logo.png',
loginUrl: '/auth/login/apple-id/?auth_entry=login&next=/dashboard',
};
const secondaryProviders = {
id: 'saml-test',
name: 'Test University',
@@ -64,10 +78,25 @@ describe('./RegistrationPage.js', () => {
expect(tree.toJSON()).toMatchSnapshot();
});
it('should match TPA provider snapshot', () => {
store = mockStore({
...initialState,
logistration: {
...initialState.logistration,
thirdPartyAuthContext: {
providers: [appleProvider],
},
},
});
const tree = renderer.create(reduxWrapper(<IntlRegistrationPage {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
it('should match url after redirection', () => {
const dasboardUrl = 'http://test.com/testing-dashboard/';
store = mockStore({
...store,
...initialState,
logistration: {
...initialState.logistration,
registrationResult: {
@@ -113,6 +142,75 @@ describe('./RegistrationPage.js', () => {
expect(root.text().includes('Test University')).toBe(true);
});
it('should match url after TPA redirection', () => {
const authCompleteUrl = '/auth/complete/google-oauth2/';
store = mockStore({
...initialState,
logistration: {
...initialState.logistration,
registrationResult: {
success: true,
redirectUrl: '',
},
thirdPartyAuthContext: {
...initialState.logistration.thirdPartyAuthContext,
finishAuthUrl: authCompleteUrl,
},
},
});
delete window.location;
window.location = { href: '' };
renderer.create(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + authCompleteUrl);
});
it('should redirect to social auth provider url', () => {
const registerUrl = '/auth/login/apple-id/?auth_entry=register&next=/dashboard';
store = mockStore({
...initialState,
logistration: {
...initialState.logistration,
thirdPartyAuthContext: {
providers: [{
...appleProvider,
registerUrl,
}],
},
},
});
delete window.location;
window.location = { href: '' };
const loginPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
loginPage.find('button#oa2-apple-id').simulate('click');
expect(window.location.href).toBe(getConfig().LMS_BASE_URL + registerUrl);
});
it('should match third party auth alert', () => {
store = mockStore({
...initialState,
logistration: {
...initialState.logistration,
thirdPartyAuthContext: {
...initialState.logistration.thirdPartyAuthContext,
currentProvider: 'Apple',
platformName: 'edX',
},
},
});
const expectedMessage = 'You\'ve successfully signed into Apple. We just need a little more information before '
+ 'you start learning with edX.';
const loginPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(loginPage.find('#tpa-alert').find('span').text()).toEqual(expectedMessage);
});
it('should show error message on 409', () => {
const windowSpy = jest.spyOn(global, 'window', 'get');
windowSpy.mockImplementation(() => ({
@@ -122,13 +220,9 @@ describe('./RegistrationPage.js', () => {
}));
store = mockStore({
...store,
...initialState,
logistration: {
...store.logistration,
registrationResult: {
success: false,
redirectUrl: '',
},
...initialState.logistration,
registrationError: {
email: [
{

View File

@@ -2,7 +2,7 @@
exports[`LoginPage should match TPA provider snapshot 1`] = `
<div
className="d-flex justify-content-center logistration-container"
className="d-flex justify-content-center login-container"
>
<div
className="d-flex flex-column"
@@ -207,7 +207,7 @@ exports[`LoginPage should match TPA provider snapshot 1`] = `
exports[`LoginPage should match default section snapshot 1`] = `
<div
className="d-flex justify-content-center logistration-container"
className="d-flex justify-content-center login-container"
>
<div
className="d-flex flex-column"
@@ -378,7 +378,7 @@ exports[`LoginPage should match default section snapshot 1`] = `
exports[`LoginPage should match forget password alert message snapshot 1`] = `
<div
className="d-flex justify-content-center logistration-container"
className="d-flex justify-content-center login-container"
>
<div
className="d-flex flex-column"
@@ -549,7 +549,7 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `
exports[`LoginPage should show error message on 400 1`] = `
<div
className="d-flex justify-content-center logistration-container"
className="d-flex justify-content-center login-container"
>
<div
className="d-flex flex-column"
@@ -743,7 +743,7 @@ exports[`LoginPage should show error message on 400 1`] = `
exports[`LoginPage should show error message on 400 on receiving link 1`] = `
<div
className="d-flex justify-content-center logistration-container"
className="d-flex justify-content-center login-container"
>
<div
className="d-flex flex-column"

View File

@@ -1,117 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`./RegistrationPage.js should match default section snapshot 1`] = `
exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
<div
className="logistration-container d-flex flex-column align-items-center mx-auto"
style={
Object {
"width": "30rem",
}
}
className="register-container mx-auto"
>
<div
className="mb-4"
className="text-left"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-graduation-cap fa-w-20 d-block mx-auto fa-2x"
data-icon="graduation-cap"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 640 512"
xmlns="http://www.w3.org/2000/svg"
<span>
Already have an edX account?
</span>
<a
href="/login"
>
<path
d="M622.34 153.2L343.4 67.5c-15.2-4.67-31.6-4.67-46.79 0L17.66 153.2c-23.54 7.23-23.54 38.36 0 45.59l48.63 14.94c-10.67 13.19-17.23 29.28-17.88 46.9C38.78 266.15 32 276.11 32 288c0 10.78 5.68 19.85 13.86 25.65L20.33 428.53C18.11 438.52 25.71 448 35.94 448h56.11c10.24 0 17.84-9.48 15.62-19.47L82.14 313.65C90.32 307.85 96 298.78 96 288c0-11.57-6.47-21.25-15.66-26.87.76-15.02 8.44-28.3 20.69-36.72L296.6 284.5c9.06 2.78 26.44 6.25 46.79 0l278.95-85.7c23.55-7.24 23.55-38.36 0-45.6zM352.79 315.09c-28.53 8.76-52.84 3.92-65.59 0l-145.02-44.55L128 384c0 35.35 85.96 64 192 64s192-28.65 192-64l-14.18-113.47-145.03 44.56z"
fill="currentColor"
style={Object {}}
/>
</svg>
<h4
className="d-block mx-auto"
>
Start learning now!
</h4>
Sign in.
</a>
</div>
<div
className="d-block mb-4"
className="d-block mb-4 mt-4"
>
<span
className="d-block mx-auto mb-4 section-heading-line"
>
Create an account using
</span>
<button
className="btn-social facebook"
type="button"
<div
className="row tpa-container"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-facebook-f fa-w-10 mr-2"
data-icon="facebook-f"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
<button
className="btn-social btn-oa2-apple-id"
id="oa2-apple-id"
onClick={[Function]}
type="button"
>
<path
d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"
fill="currentColor"
style={Object {}}
/>
</svg>
Facebook
</button>
<button
className="btn-social google"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-google fa-w-16 mr-2"
data-icon="google"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 488 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Google
</button>
<button
className="btn-social microsoft mb-3"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-microsoft fa-w-14 mr-2"
data-icon="microsoft"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 32h214.6v214.6H0V32zm233.4 0H448v214.6H233.4V32zM0 265.4h214.6V480H0V265.4zm233.4 0H448V480H233.4V265.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Microsoft
</button>
<div
aria-hidden="true"
>
<img
alt="icon Apple"
className="icon-image"
src="https://edx.devstack.lms/logo.png"
/>
</div>
<span
aria-hidden="true"
className="pl-2"
>
Apple
</span>
</button>
</div>
<span
className="d-block mx-auto text-center mt-4 section-heading-line"
>
@@ -161,7 +99,7 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
aria-describedby=""
className="form-control"
id="registrationName"
name="name"
name="fullname"
onChange={[Function]}
placeholder="Name"
required={true}
@@ -170,7 +108,7 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
/>
<strong
className="invalid-feedback"
id="name-invalid-feedback"
id="fullname-invalid-feedback"
>
Enter your full name.
</strong>
@@ -1528,15 +1466,21 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
<button
className="btn-primary mt-4 submit btn btn-primary"
disabled={false}
inputRef={[Function]}
onClick={[Function]}
type="button"
>
Create Account
</button>
</form>
</div>
`;
exports[`./RegistrationPage.js should match default section snapshot 1`] = `
<div
className="register-container mx-auto"
>
<div
className="text-center mb-2 pt-2"
className="text-left"
>
<span>
Already have an edX account?
@@ -1547,17 +1491,1428 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
Sign in.
</a>
</div>
<form
className="mb-4 mx-auto form-group"
>
<div
className="form-group"
>
<label
className="h6 pt-3"
htmlFor="registrationEmail"
>
Email (required)
</label>
<input
aria-describedby=""
className="form-control"
id="registrationEmail"
name="email"
onChange={[Function]}
placeholder="username@domain.com"
required={true}
type="email"
value=""
/>
<strong
className="invalid-feedback"
id="email-invalid-feedback"
>
Enter a valid email address that contains at least 3 characters.
</strong>
</div>
<div
className="form-group"
>
<label
className="h6 pt-3"
htmlFor="registrationName"
>
Full Name (required)
</label>
<input
aria-describedby=""
className="form-control"
id="registrationName"
name="fullname"
onChange={[Function]}
placeholder="Name"
required={true}
type="text"
value=""
/>
<strong
className="invalid-feedback"
id="fullname-invalid-feedback"
>
Enter your full name.
</strong>
</div>
<div
className="form-group"
>
<label
className="h6 pt-3"
htmlFor="registrationUsername"
>
Public Username (required)
</label>
<input
aria-describedby=""
className="form-control"
id="registrationUsername"
name="username"
onChange={[Function]}
placeholder="Username"
required={true}
type="text"
value=""
/>
<strong
className="invalid-feedback"
id="username-invalid-feedback"
>
Username must be between 2 and 30 characters long.
</strong>
</div>
<div
className="form-group"
>
<label
className="h6 pt-3"
htmlFor="registrationPassword"
>
Password (required)
</label>
<input
aria-describedby=""
className="form-control"
id="registrationPassword"
name="password"
onChange={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<strong
className="invalid-feedback"
id="password-invalid-feedback"
>
This password is too short. It must contain at least 8 characters. This password must contain at least 1 number.
</strong>
</div>
<div
className="form-group"
>
<label
className="h6 pt-3"
htmlFor="registrationCountry"
>
Country (required)
</label>
<select
aria-describedby=""
className="form-control"
name="country"
onChange={[Function]}
placeholder="Country or Region of Residence"
required={true}
value=""
>
<option
value=""
>
Country or Region of Residence (required)
</option>
<option
value="AF"
>
Afghanistan
</option>
<option
value="AL"
>
Albania
</option>
<option
value="DZ"
>
Algeria
</option>
<option
value="AS"
>
American Samoa
</option>
<option
value="AD"
>
Andorra
</option>
<option
value="AO"
>
Angola
</option>
<option
value="AI"
>
Anguilla
</option>
<option
value="AQ"
>
Antarctica
</option>
<option
value="AG"
>
Antigua and Barbuda
</option>
<option
value="AR"
>
Argentina
</option>
<option
value="AM"
>
Armenia
</option>
<option
value="AW"
>
Aruba
</option>
<option
value="AU"
>
Australia
</option>
<option
value="AT"
>
Austria
</option>
<option
value="AZ"
>
Azerbaijan
</option>
<option
value="BS"
>
Bahamas
</option>
<option
value="BH"
>
Bahrain
</option>
<option
value="BD"
>
Bangladesh
</option>
<option
value="BB"
>
Barbados
</option>
<option
value="BY"
>
Belarus
</option>
<option
value="BE"
>
Belgium
</option>
<option
value="BZ"
>
Belize
</option>
<option
value="BJ"
>
Benin
</option>
<option
value="BM"
>
Bermuda
</option>
<option
value="BT"
>
Bhutan
</option>
<option
value="BO"
>
Bolivia
</option>
<option
value="BA"
>
Bosnia and Herzegovina
</option>
<option
value="BW"
>
Botswana
</option>
<option
value="BV"
>
Bouvet Island
</option>
<option
value="BR"
>
Brazil
</option>
<option
value="IO"
>
British Indian Ocean Territory
</option>
<option
value="BN"
>
Brunei Darussalam
</option>
<option
value="BG"
>
Bulgaria
</option>
<option
value="BF"
>
Burkina Faso
</option>
<option
value="BI"
>
Burundi
</option>
<option
value="KH"
>
Cambodia
</option>
<option
value="CM"
>
Cameroon
</option>
<option
value="CA"
>
Canada
</option>
<option
value="CV"
>
Cape Verde
</option>
<option
value="KY"
>
Cayman Islands
</option>
<option
value="CF"
>
Central African Republic
</option>
<option
value="TD"
>
Chad
</option>
<option
value="CL"
>
Chile
</option>
<option
value="CN"
>
China
</option>
<option
value="CX"
>
Christmas Island
</option>
<option
value="CC"
>
Cocos (Keeling) Islands
</option>
<option
value="CO"
>
Colombia
</option>
<option
value="KM"
>
Comoros
</option>
<option
value="CG"
>
Congo
</option>
<option
value="CD"
>
Congo, the Democratic Republic of the
</option>
<option
value="CK"
>
Cook Islands
</option>
<option
value="CR"
>
Costa Rica
</option>
<option
value="CI"
>
Cote D'Ivoire
</option>
<option
value="HR"
>
Croatia
</option>
<option
value="CU"
>
Cuba
</option>
<option
value="CY"
>
Cyprus
</option>
<option
value="CZ"
>
Czech Republic
</option>
<option
value="DK"
>
Denmark
</option>
<option
value="DJ"
>
Djibouti
</option>
<option
value="DM"
>
Dominica
</option>
<option
value="DO"
>
Dominican Republic
</option>
<option
value="EC"
>
Ecuador
</option>
<option
value="EG"
>
Egypt
</option>
<option
value="SV"
>
El Salvador
</option>
<option
value="GQ"
>
Equatorial Guinea
</option>
<option
value="ER"
>
Eritrea
</option>
<option
value="EE"
>
Estonia
</option>
<option
value="ET"
>
Ethiopia
</option>
<option
value="FK"
>
Falkland Islands (Malvinas)
</option>
<option
value="FO"
>
Faroe Islands
</option>
<option
value="FJ"
>
Fiji
</option>
<option
value="FI"
>
Finland
</option>
<option
value="FR"
>
France
</option>
<option
value="GF"
>
French Guiana
</option>
<option
value="PF"
>
French Polynesia
</option>
<option
value="TF"
>
French Southern Territories
</option>
<option
value="GA"
>
Gabon
</option>
<option
value="GM"
>
Gambia
</option>
<option
value="GE"
>
Georgia
</option>
<option
value="DE"
>
Germany
</option>
<option
value="GH"
>
Ghana
</option>
<option
value="GI"
>
Gibraltar
</option>
<option
value="GR"
>
Greece
</option>
<option
value="GL"
>
Greenland
</option>
<option
value="GD"
>
Grenada
</option>
<option
value="GP"
>
Guadeloupe
</option>
<option
value="GU"
>
Guam
</option>
<option
value="GT"
>
Guatemala
</option>
<option
value="GN"
>
Guinea
</option>
<option
value="GW"
>
Guinea-Bissau
</option>
<option
value="GY"
>
Guyana
</option>
<option
value="HT"
>
Haiti
</option>
<option
value="HM"
>
Heard Island and Mcdonald Islands
</option>
<option
value="VA"
>
Holy See (Vatican City State)
</option>
<option
value="HN"
>
Honduras
</option>
<option
value="HK"
>
Hong Kong
</option>
<option
value="HU"
>
Hungary
</option>
<option
value="IS"
>
Iceland
</option>
<option
value="IN"
>
India
</option>
<option
value="ID"
>
Indonesia
</option>
<option
value="IR"
>
Iran, Islamic Republic of
</option>
<option
value="IQ"
>
Iraq
</option>
<option
value="IE"
>
Ireland
</option>
<option
value="IL"
>
Israel
</option>
<option
value="IT"
>
Italy
</option>
<option
value="JM"
>
Jamaica
</option>
<option
value="JP"
>
Japan
</option>
<option
value="JO"
>
Jordan
</option>
<option
value="KZ"
>
Kazakhstan
</option>
<option
value="KE"
>
Kenya
</option>
<option
value="KI"
>
Kiribati
</option>
<option
value="KP"
>
North Korea
</option>
<option
value="KR"
>
South Korea
</option>
<option
value="KW"
>
Kuwait
</option>
<option
value="KG"
>
Kyrgyzstan
</option>
<option
value="LA"
>
Lao People's Democratic Republic
</option>
<option
value="LV"
>
Latvia
</option>
<option
value="LB"
>
Lebanon
</option>
<option
value="LS"
>
Lesotho
</option>
<option
value="LR"
>
Liberia
</option>
<option
value="LY"
>
Libya
</option>
<option
value="LI"
>
Liechtenstein
</option>
<option
value="LT"
>
Lithuania
</option>
<option
value="LU"
>
Luxembourg
</option>
<option
value="MO"
>
Macao
</option>
<option
value="MG"
>
Madagascar
</option>
<option
value="MW"
>
Malawi
</option>
<option
value="MY"
>
Malaysia
</option>
<option
value="MV"
>
Maldives
</option>
<option
value="ML"
>
Mali
</option>
<option
value="MT"
>
Malta
</option>
<option
value="MH"
>
Marshall Islands
</option>
<option
value="MQ"
>
Martinique
</option>
<option
value="MR"
>
Mauritania
</option>
<option
value="MU"
>
Mauritius
</option>
<option
value="YT"
>
Mayotte
</option>
<option
value="MX"
>
Mexico
</option>
<option
value="FM"
>
Micronesia, Federated States of
</option>
<option
value="MD"
>
Moldova, Republic of
</option>
<option
value="MC"
>
Monaco
</option>
<option
value="MN"
>
Mongolia
</option>
<option
value="MS"
>
Montserrat
</option>
<option
value="MA"
>
Morocco
</option>
<option
value="MZ"
>
Mozambique
</option>
<option
value="MM"
>
Myanmar
</option>
<option
value="NA"
>
Namibia
</option>
<option
value="NR"
>
Nauru
</option>
<option
value="NP"
>
Nepal
</option>
<option
value="NL"
>
Netherlands
</option>
<option
value="NC"
>
New Caledonia
</option>
<option
value="NZ"
>
New Zealand
</option>
<option
value="NI"
>
Nicaragua
</option>
<option
value="NE"
>
Niger
</option>
<option
value="NG"
>
Nigeria
</option>
<option
value="NU"
>
Niue
</option>
<option
value="NF"
>
Norfolk Island
</option>
<option
value="MK"
>
North Macedonia, Republic of
</option>
<option
value="MP"
>
Northern Mariana Islands
</option>
<option
value="NO"
>
Norway
</option>
<option
value="OM"
>
Oman
</option>
<option
value="PK"
>
Pakistan
</option>
<option
value="PW"
>
Palau
</option>
<option
value="PS"
>
Palestinian Territory, Occupied
</option>
<option
value="PA"
>
Panama
</option>
<option
value="PG"
>
Papua New Guinea
</option>
<option
value="PY"
>
Paraguay
</option>
<option
value="PE"
>
Peru
</option>
<option
value="PH"
>
Philippines
</option>
<option
value="PN"
>
Pitcairn
</option>
<option
value="PL"
>
Poland
</option>
<option
value="PT"
>
Portugal
</option>
<option
value="PR"
>
Puerto Rico
</option>
<option
value="QA"
>
Qatar
</option>
<option
value="RE"
>
Reunion
</option>
<option
value="RO"
>
Romania
</option>
<option
value="RU"
>
Russian Federation
</option>
<option
value="RW"
>
Rwanda
</option>
<option
value="SH"
>
Saint Helena
</option>
<option
value="KN"
>
Saint Kitts and Nevis
</option>
<option
value="LC"
>
Saint Lucia
</option>
<option
value="PM"
>
Saint Pierre and Miquelon
</option>
<option
value="VC"
>
Saint Vincent and the Grenadines
</option>
<option
value="WS"
>
Samoa
</option>
<option
value="SM"
>
San Marino
</option>
<option
value="ST"
>
Sao Tome and Principe
</option>
<option
value="SA"
>
Saudi Arabia
</option>
<option
value="SN"
>
Senegal
</option>
<option
value="SC"
>
Seychelles
</option>
<option
value="SL"
>
Sierra Leone
</option>
<option
value="SG"
>
Singapore
</option>
<option
value="SK"
>
Slovakia
</option>
<option
value="SI"
>
Slovenia
</option>
<option
value="SB"
>
Solomon Islands
</option>
<option
value="SO"
>
Somalia
</option>
<option
value="ZA"
>
South Africa
</option>
<option
value="GS"
>
South Georgia and the South Sandwich Islands
</option>
<option
value="ES"
>
Spain
</option>
<option
value="LK"
>
Sri Lanka
</option>
<option
value="SD"
>
Sudan
</option>
<option
value="SR"
>
Suriname
</option>
<option
value="SJ"
>
Svalbard and Jan Mayen
</option>
<option
value="SZ"
>
Swaziland
</option>
<option
value="SE"
>
Sweden
</option>
<option
value="CH"
>
Switzerland
</option>
<option
value="SY"
>
Syrian Arab Republic
</option>
<option
value="TW"
>
Taiwan
</option>
<option
value="TJ"
>
Tajikistan
</option>
<option
value="TZ"
>
Tanzania, United Republic of
</option>
<option
value="TH"
>
Thailand
</option>
<option
value="TL"
>
Timor-Leste
</option>
<option
value="TG"
>
Togo
</option>
<option
value="TK"
>
Tokelau
</option>
<option
value="TO"
>
Tonga
</option>
<option
value="TT"
>
Trinidad and Tobago
</option>
<option
value="TN"
>
Tunisia
</option>
<option
value="TR"
>
Turkey
</option>
<option
value="TM"
>
Turkmenistan
</option>
<option
value="TC"
>
Turks and Caicos Islands
</option>
<option
value="TV"
>
Tuvalu
</option>
<option
value="UG"
>
Uganda
</option>
<option
value="UA"
>
Ukraine
</option>
<option
value="AE"
>
United Arab Emirates
</option>
<option
value="GB"
>
United Kingdom
</option>
<option
value="US"
>
United States of America
</option>
<option
value="UM"
>
United States Minor Outlying Islands
</option>
<option
value="UY"
>
Uruguay
</option>
<option
value="UZ"
>
Uzbekistan
</option>
<option
value="VU"
>
Vanuatu
</option>
<option
value="VE"
>
Venezuela
</option>
<option
value="VN"
>
Viet Nam
</option>
<option
value="VG"
>
Virgin Islands, British
</option>
<option
value="VI"
>
Virgin Islands, U.S.
</option>
<option
value="WF"
>
Wallis and Futuna
</option>
<option
value="EH"
>
Western Sahara
</option>
<option
value="YE"
>
Yemen
</option>
<option
value="ZM"
>
Zambia
</option>
<option
value="ZW"
>
Zimbabwe
</option>
<option
value="AX"
>
Åland Islands
</option>
<option
value="BQ"
>
Bonaire, Sint Eustatius and Saba
</option>
<option
value="CW"
>
Curaçao
</option>
<option
value="GG"
>
Guernsey
</option>
<option
value="IM"
>
Isle of Man
</option>
<option
value="JE"
>
Jersey
</option>
<option
value="ME"
>
Montenegro
</option>
<option
value="BL"
>
Saint Barthélemy
</option>
<option
value="MF"
>
Saint Martin (French part)
</option>
<option
value="RS"
>
Serbia
</option>
<option
value="SX"
>
Sint Maarten (Dutch part)
</option>
<option
value="SS"
>
South Sudan
</option>
<option
value="XK"
>
Kosovo
</option>
</select>
<strong
className="invalid-feedback"
id="country-invalid-feedback"
>
Select your country or region of residence.
</strong>
</div>
<span>
By creating an account, you agree to the
<a
href="https://www.edx.org/edx-terms-service"
>
Terms of Service and Honor Code
</a>
and you acknowledge that edX and each Member process your personal data in accordance with the
<a
href="https://www.edx.org/edx-privacy-policy"
>
Privacy Policy
</a>
.
</span>
<button
className="btn-primary mt-4 submit btn btn-primary"
disabled={false}
onClick={[Function]}
type="button"
>
Create Account
</button>
</form>
</div>
`;
exports[`./RegistrationPage.js should show error message on 409 1`] = `
<div
className="logistration-container d-flex flex-column align-items-center mx-auto"
style={
Object {
"width": "30rem",
}
}
className="register-container mx-auto"
>
<div
className="fade alert alert-danger show"
@@ -1598,113 +2953,16 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
</div>
</div>
<div
className="mb-4"
className="text-left"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-graduation-cap fa-w-20 d-block mx-auto fa-2x"
data-icon="graduation-cap"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 640 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M622.34 153.2L343.4 67.5c-15.2-4.67-31.6-4.67-46.79 0L17.66 153.2c-23.54 7.23-23.54 38.36 0 45.59l48.63 14.94c-10.67 13.19-17.23 29.28-17.88 46.9C38.78 266.15 32 276.11 32 288c0 10.78 5.68 19.85 13.86 25.65L20.33 428.53C18.11 438.52 25.71 448 35.94 448h56.11c10.24 0 17.84-9.48 15.62-19.47L82.14 313.65C90.32 307.85 96 298.78 96 288c0-11.57-6.47-21.25-15.66-26.87.76-15.02 8.44-28.3 20.69-36.72L296.6 284.5c9.06 2.78 26.44 6.25 46.79 0l278.95-85.7c23.55-7.24 23.55-38.36 0-45.6zM352.79 315.09c-28.53 8.76-52.84 3.92-65.59 0l-145.02-44.55L128 384c0 35.35 85.96 64 192 64s192-28.65 192-64l-14.18-113.47-145.03 44.56z"
fill="currentColor"
style={Object {}}
/>
</svg>
<h4
className="d-block mx-auto"
>
Start learning now!
</h4>
</div>
<div
className="d-block mb-4"
>
<span
className="d-block mx-auto mb-4 section-heading-line"
>
Create an account using
<span>
Already have an edX account?
</span>
<button
className="btn-social facebook"
type="button"
<a
href="/login"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-facebook-f fa-w-10 mr-2"
data-icon="facebook-f"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"
fill="currentColor"
style={Object {}}
/>
</svg>
Facebook
</button>
<button
className="btn-social google"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-google fa-w-16 mr-2"
data-icon="google"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 488 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Google
</button>
<button
className="btn-social microsoft mb-3"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-microsoft fa-w-14 mr-2"
data-icon="microsoft"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 32h214.6v214.6H0V32zm233.4 0H448v214.6H233.4V32zM0 265.4h214.6V480H0V265.4zm233.4 0H448V480H233.4V265.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Microsoft
</button>
<span
className="d-block mx-auto text-center mt-4 section-heading-line"
>
or create a new one here
</span>
Sign in.
</a>
</div>
<form
className="mb-4 mx-auto form-group"
@@ -1749,7 +3007,7 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
aria-describedby=""
className="form-control"
id="registrationName"
name="name"
name="fullname"
onChange={[Function]}
placeholder="Name"
required={true}
@@ -1758,7 +3016,7 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
/>
<strong
className="invalid-feedback"
id="name-invalid-feedback"
id="fullname-invalid-feedback"
>
Enter your full name.
</strong>
@@ -3116,24 +4374,11 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
<button
className="btn-primary mt-4 submit btn btn-primary"
disabled={false}
inputRef={[Function]}
onClick={[Function]}
type="button"
>
Create Account
</button>
</form>
<div
className="text-center mb-2 pt-2"
>
<span>
Already have an edX account?
</span>
<a
href="/login"
>
Sign in.
</a>
</div>
</div>
`;