VAN-408 - Redesign login/register SSO-SAML (#270)
* VAN-408 - Redesign login/register SSO-SAML * VAN-408 - Moved back button to Logistration component * update sign-in messages (#272) * VAN-408 - Added back the third party provider check * VAN-408 - Updated DISABLE_ENTERPRISE_LOGIN value to empty string by default * VAN-408 - Updated event tracking for institution login on register and login pages * VAN-408 - Updated event tracking for institution login on register and login pages * VAN-408 - Fixed Logistration event name test * VAN-408 - Merged redesign-master and resolved tpa conflicts Co-authored-by: Uzair Rasheed <uzairr@yahoo.com>
This commit is contained in:
committed by
Waheed Ahmed
parent
a29360fb66
commit
b9f97a1dff
@@ -2,68 +2,64 @@ import React from 'react';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button, Hyperlink } from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Button, Hyperlink, Icon } from '@edx/paragon';
|
||||
import { Institution } from '@edx/paragon/icons';
|
||||
import messages from './messages';
|
||||
|
||||
export const RenderInstitutionButton = props => {
|
||||
const { onSubmitHandler, secondaryProviders, buttonTitle } = props;
|
||||
if (secondaryProviders !== undefined && secondaryProviders.length > 0) {
|
||||
return (
|
||||
<Button
|
||||
className="w-auto mb-3"
|
||||
block
|
||||
variant="outline-primary"
|
||||
onClick={onSubmitHandler}
|
||||
>
|
||||
{buttonTitle}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
const { onSubmitHandler, buttonTitle } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="btn btn-link btn-sm text-body p-0 mb-4"
|
||||
variant="link"
|
||||
data-event-name="institution_login"
|
||||
onClick={onSubmitHandler}
|
||||
>
|
||||
<Icon src={Institution} className="institute-icon" />
|
||||
{buttonTitle}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const InstitutionLogistration = props => {
|
||||
const lmsBaseUrl = getConfig().LMS_BASE_URL;
|
||||
const {
|
||||
intl,
|
||||
onSubmitHandler,
|
||||
secondaryProviders,
|
||||
headingTitle,
|
||||
buttonTitle,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="d-flex justify-content-center m-4">
|
||||
<div className="d-flex justify-content-left mb-4 mt-4">
|
||||
<div className="flex-column">
|
||||
<div className="mt-3">
|
||||
<FontAwesomeIcon className="mr-2" icon={faChevronLeft} />
|
||||
<Hyperlink
|
||||
destination=""
|
||||
onClick={onSubmitHandler}
|
||||
>
|
||||
{buttonTitle}
|
||||
</Hyperlink>
|
||||
</div>
|
||||
<h1 className="mt-3 mb-4 font-weight-normal h3">
|
||||
<h4 className="mb-2 font-weight-bold institute-heading">
|
||||
{headingTitle}
|
||||
</h1>
|
||||
</h4>
|
||||
<p className="mb-2">
|
||||
{intl.formatMessage(messages['institution.login.page.sub.heading'])}
|
||||
</p>
|
||||
<div className="mb-2 ml-2">
|
||||
<ul>
|
||||
{secondaryProviders.map(provider => (
|
||||
<li key={provider}>
|
||||
<Hyperlink destination={lmsBaseUrl + provider.loginUrl}>{provider.name}</Hyperlink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<table className="pgn__data-table table-striped table-borderless">
|
||||
<tbody>
|
||||
{secondaryProviders.map(provider => (
|
||||
<tr key={provider} className="pgn__data-table-row">
|
||||
<td>
|
||||
<Hyperlink
|
||||
className="btn nav-item p-0 mb-1"
|
||||
destination={lmsBaseUrl + provider.loginUrl}
|
||||
>
|
||||
{provider.name}
|
||||
</Hyperlink>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -73,16 +69,17 @@ const LogistrationDefaultProps = {
|
||||
buttonTitle: '',
|
||||
};
|
||||
const LogistrationProps = {
|
||||
onSubmitHandler: PropTypes.func.isRequired,
|
||||
secondaryProviders: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string.isRequried,
|
||||
loginUrl: PropTypes.string.isRequired,
|
||||
})),
|
||||
buttonTitle: PropTypes.string,
|
||||
};
|
||||
|
||||
RenderInstitutionButton.propTypes = {
|
||||
...LogistrationProps,
|
||||
buttonTitle: PropTypes.string,
|
||||
onSubmitHandler: PropTypes.func.isRequired,
|
||||
|
||||
};
|
||||
RenderInstitutionButton.defaultProps = {
|
||||
...LogistrationDefaultProps,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Button } from '@edx/paragon';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import messages from './messages';
|
||||
import { LOGIN_PAGE, REGISTER_PAGE } from '../data/constants';
|
||||
@@ -13,22 +17,51 @@ import { RegistrationPage } from '../register';
|
||||
const Logistration = (props) => {
|
||||
const { intl, selectedPage } = props;
|
||||
const tpa = getTpaHint();
|
||||
const [institutionLogin, setInstitutionLogin] = useState(false);
|
||||
|
||||
const handleInstitutionLogin = (e) => {
|
||||
sendTrackEvent('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
sendPageEvent('login_and_registration', e.target.dataset.eventName);
|
||||
|
||||
setInstitutionLogin(!institutionLogin);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!tpa
|
||||
&& (
|
||||
<span className="nav nav-tabs">
|
||||
<Link className={`nav-item nav-link ${selectedPage === REGISTER_PAGE ? 'active' : ''}`} to={updatePathWithQueryParams(REGISTER_PAGE)}>
|
||||
{intl.formatMessage(messages['logistration.register'])}
|
||||
</Link>
|
||||
<Link className={`nav-item nav-link ${selectedPage === LOGIN_PAGE ? 'active' : ''}`} to={updatePathWithQueryParams(LOGIN_PAGE)}>
|
||||
{intl.formatMessage(messages['logistration.sign.in'])}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
{institutionLogin
|
||||
? (
|
||||
<span className="nav nav-tabs mt-2 pb-2">
|
||||
<FontAwesomeIcon className="mr-2 mt-1 ml-3" icon={faChevronLeft} />
|
||||
<Button
|
||||
variant="link"
|
||||
className="nav-item p-0 mb-1 logistration-button"
|
||||
data-event-name={selectedPage === LOGIN_PAGE ? 'login' : 'register'}
|
||||
onClick={handleInstitutionLogin}
|
||||
>
|
||||
{selectedPage === LOGIN_PAGE
|
||||
? intl.formatMessage(messages['logistration.sign.in'])
|
||||
: intl.formatMessage(messages['logistration.register'])}
|
||||
</Button>
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{!tpa && (
|
||||
<span className="nav nav-tabs">
|
||||
<Link className={`nav-item nav-link ${selectedPage === REGISTER_PAGE ? 'active' : ''}`} to={updatePathWithQueryParams(REGISTER_PAGE)}>
|
||||
{intl.formatMessage(messages['logistration.register'])}
|
||||
</Link>
|
||||
<Link className={`nav-item nav-link ${selectedPage === LOGIN_PAGE ? 'active' : ''}`} to={updatePathWithQueryParams(LOGIN_PAGE)}>
|
||||
{intl.formatMessage(messages['logistration.sign.in'])}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div id="main-content" className="main-content">
|
||||
{selectedPage === LOGIN_PAGE ? <LoginPage /> : <RegistrationPage />}
|
||||
{selectedPage === LOGIN_PAGE
|
||||
? <LoginPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />
|
||||
: <RegistrationPage institutionLogin={institutionLogin} handleInstitutionLogin={handleInstitutionLogin} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
const messages = defineMessages({
|
||||
'institution.login.page.sub.heading': {
|
||||
id: 'institution.login.page.sub.heading',
|
||||
defaultMessage: 'Choose your institution from the list below:',
|
||||
defaultMessage: 'Choose your institution from the list below',
|
||||
description: 'Heading of the institutions list',
|
||||
},
|
||||
// Confirmation Alert Message
|
||||
|
||||
@@ -4,11 +4,13 @@ import configureStore from 'redux-mock-store';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { getConfig, mergeConfig } from '@edx/frontend-platform';
|
||||
import * as analytics from '@edx/frontend-platform/analytics';
|
||||
import { configure, injectIntl, IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import Logistration from '../Logistration';
|
||||
import { LOGIN_PAGE } from '../../data/constants';
|
||||
import { RenderInstitutionButton } from '../InstitutionLogistration';
|
||||
import { COMPLETE_STATE, LOGIN_PAGE } from '../../data/constants';
|
||||
|
||||
jest.mock('@edx/frontend-platform/analytics');
|
||||
analytics.sendPageEvent = jest.fn();
|
||||
@@ -19,6 +21,13 @@ const IntlLogistration = injectIntl(Logistration);
|
||||
describe('Logistration', () => {
|
||||
let store = {};
|
||||
|
||||
const secondaryProviders = {
|
||||
id: 'saml-test',
|
||||
name: 'Test University',
|
||||
loginUrl: '/dummy-auth',
|
||||
registerUrl: '/dummy_auth',
|
||||
};
|
||||
|
||||
const reduxWrapper = children => (
|
||||
<IntlProvider locale="en">
|
||||
<MemoryRouter>
|
||||
@@ -66,4 +75,102 @@ describe('Logistration', () => {
|
||||
|
||||
expect(logistration.find('#main-content').find('LoginPage').exists()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display institution login option when secondary providers are present', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: 'true',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
login: {
|
||||
loginResult: { success: false, redirectUrl: '' },
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
expect(logistration.text().includes('Institution/campus credentials')).toBe(true);
|
||||
|
||||
// on clicking "Institution/campus credentials" button, it should display institution login page
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
expect(logistration.text().includes('Test University')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('send tracking and page events when institutional login button is clicked', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: 'true',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
login: {
|
||||
loginResult: { success: false, redirectUrl: '' },
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
const props = { selectedPage: LOGIN_PAGE };
|
||||
const logistration = mount(reduxWrapper(<IntlLogistration {...props} />));
|
||||
logistration.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
|
||||
expect(analytics.sendTrackEvent).toHaveBeenCalledWith('edx.bi.institution_login_form.toggled', { category: 'user-engagement' });
|
||||
expect(analytics.sendPageEvent).toHaveBeenCalledWith('login_and_registration', 'institution_login');
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not display institution register button', () => {
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: 'true',
|
||||
});
|
||||
|
||||
store = mockStore({
|
||||
register: {
|
||||
registrationResult: { success: false, redirectUrl: '' },
|
||||
registrationError: {},
|
||||
},
|
||||
commonComponents: {
|
||||
thirdPartyAuthContext: {
|
||||
currentProvider: null,
|
||||
finishAuthUrl: null,
|
||||
providers: [],
|
||||
secondaryProviders: [secondaryProviders],
|
||||
},
|
||||
thirdPartyAuthApiStatus: COMPLETE_STATE,
|
||||
},
|
||||
});
|
||||
|
||||
delete window.location;
|
||||
window.location = { href: getConfig().BASE_URL };
|
||||
|
||||
const root = mount(reduxWrapper(<IntlLogistration />));
|
||||
root.find(RenderInstitutionButton).simulate('click', { institutionLogin: true });
|
||||
expect(root.text().includes('Test University')).toBe(true);
|
||||
|
||||
mergeConfig({
|
||||
DISABLE_ENTERPRISE_LOGIN: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user