diff --git a/src/logistration/LoginPage.jsx b/src/logistration/LoginPage.jsx index 2da45495..821349a2 100644 --- a/src/logistration/LoginPage.jsx +++ b/src/logistration/LoginPage.jsx @@ -130,7 +130,7 @@ class LoginPage extends React.Component { redirectUrl={this.props.loginResult.redirectUrl} finishAuthUrl={finishAuthUrl} /> -
+
{currentProvider && ( diff --git a/src/logistration/RegistrationPage.jsx b/src/logistration/RegistrationPage.jsx index cdd47139..37116553 100644 --- a/src/logistration/RegistrationPage.jsx +++ b/src/logistration/RegistrationPage.jsx @@ -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 ( ); } + return ( <> -
+
{this.props.registrationError ? : null} -
- -

Start learning now!

-
-
- Create an account using - - - - - or create a new one here + )} +
+ {intl.formatMessage(messages['logistration.already.have.an.edx.account'])} + {intl.formatMessage(messages['logistration.sign.in.hyperlink'])}
- + {(providers.length || secondaryProviders.length) && !currentProvider ? ( +
+ + {intl.formatMessage(messages['logistration.create.an.account.using'])} + +
+ +
+ + + {intl.formatMessage(messages['logistration.create.a.new.one.here'])} + +
+ ) : null}
this.handleOnChange(e)} required /> @@ -283,34 +298,33 @@ class RegistrationPage extends React.Component { -
- Already have an edX account? - Sign in. -
); } } + 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, }; }; diff --git a/src/logistration/SocialAuthProviders.jsx b/src/logistration/SocialAuthProviders.jsx index 590df3c5..148cb56d 100644 --- a/src/logistration/SocialAuthProviders.jsx +++ b/src/logistration/SocialAuthProviders.jsx @@ -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, diff --git a/src/logistration/ThirdPartyAuthAlert.jsx b/src/logistration/ThirdPartyAuthAlert.jsx index 59c1e95e..f6f750f8 100644 --- a/src/logistration/ThirdPartyAuthAlert.jsx +++ b/src/logistration/ThirdPartyAuthAlert.jsx @@ -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 = ( { }; ThirdPartyAuthAlert.defaultProps = { - referrer: 'login', + referrer: LOGIN_PAGE, }; ThirdPartyAuthAlert.propTypes = { diff --git a/src/logistration/_style.scss b/src/logistration/_style.scss index 393d337c..e0ce7463 100644 --- a/src/logistration/_style.scss +++ b/src/logistration/_style.scss @@ -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; diff --git a/src/logistration/messages.jsx b/src/logistration/messages.jsx index 7e5905eb..bb428dec 100644 --- a/src/logistration/messages.jsx +++ b/src/logistration/messages.jsx @@ -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', diff --git a/src/logistration/tests/RegistrationPage.test.jsx b/src/logistration/tests/RegistrationPage.test.jsx index 222dc1ec..40716492 100644 --- a/src/logistration/tests/RegistrationPage.test.jsx +++ b/src/logistration/tests/RegistrationPage.test.jsx @@ -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()).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()); + 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()); + + 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()); + 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: [ { diff --git a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap index e2d70143..facf4090 100644 --- a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap +++ b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap @@ -2,7 +2,7 @@ exports[`LoginPage should match TPA provider snapshot 1`] = `
- -

- Start learning now! -

+ Sign in. +
Create an account using - - - + + + +
@@ -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`] = ` /> Enter your full name. @@ -1528,15 +1466,21 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = ` +
+`; + +exports[`./RegistrationPage.js should match default section snapshot 1`] = ` +
Already have an edX account? @@ -1547,17 +1491,1428 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = ` Sign in.
+
+
+ + + + Enter a valid email address that contains at least 3 characters. + +
+
+ + + + Enter your full name. + +
+
+ + + + Username must be between 2 and 30 characters long. + +
+
+ + + + This password is too short. It must contain at least 8 characters. This password must contain at least 1 number. + +
+
+ + + + Select your country or region of residence. + +
+ + By creating an account, you agree to the + + Terms of Service and Honor Code + + and you acknowledge that edX and each Member process your personal data in accordance with the + + Privacy Policy + + . + + +
`; exports[`./RegistrationPage.js should show error message on 409 1`] = `
- -

- Start learning now! -

-
-
- - Create an account using + + Already have an edX account? - - - - - or create a new one here - + Sign in. +
Enter your full name. @@ -3116,24 +4374,11 @@ exports[`./RegistrationPage.js should show error message on 409 1`] = `
-
- - Already have an edX account? - - - Sign in. - -
`;