diff --git a/.env.development b/.env.development
index c24029d8..a1691ec6 100644
--- a/.env.development
+++ b/.env.development
@@ -20,7 +20,7 @@ SEGMENT_KEY=null
SITE_NAME='edX'
USER_INFO_COOKIE_NAME='edx-user-info'
AUTHN_MINIMAL_HEADER=true
-LOGIN_ISSUE_SUPPORT_LINK=''
+LOGIN_ISSUE_SUPPORT_LINK='/login-issue-support-url'
TOS_AND_HONOR_CODE='http://localhost:18000/honor'
PRIVACY_POLICY='http://localhost:18000/privacy'
REGISTRATION_OPTIONAL_FIELDS=''
diff --git a/src/common-components/AlertDismissible.jsx b/src/common-components/AlertDismissible.jsx
deleted file mode 100644
index 5504085e..00000000
--- a/src/common-components/AlertDismissible.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React, { useState } from 'react';
-import { Alert } from '@edx/paragon';
-import { Info } from '@edx/paragon/icons';
-import { injectIntl } from '@edx/frontend-platform/i18n';
-import PropTypes from 'prop-types';
-
-function AlertDismissible(props) {
- const [show, setShow] = useState(true);
-
- return show ? (
- setShow(false)} dismissible className="pb-1 pt-1" icon={Info}>
- {props.msg}
-
- ) : null;
-}
-
-AlertDismissible.propTypes = {
- variant: PropTypes.string.isRequired,
- msg: PropTypes.string.isRequired,
-};
-
-export default injectIntl(AlertDismissible);
diff --git a/src/common-components/FormGroup.jsx b/src/common-components/FormGroup.jsx
index 172e0652..9ad410c0 100644
--- a/src/common-components/FormGroup.jsx
+++ b/src/common-components/FormGroup.jsx
@@ -1,10 +1,9 @@
import React, { useState } from 'react';
import {
- Form, Hyperlink, TransitionReplace,
+ Form, TransitionReplace,
} from '@edx/paragon';
import PropTypes from 'prop-types';
-import AlertDismissible from './AlertDismissible';
const FormGroup = (props) => {
const [hasFocus, setHasFocus] = useState(false);
@@ -56,10 +55,6 @@ const FormGroup = (props) => {
{props.errorMessage !== '' && (
{props.errorMessage}
)}
-
- {props.suggestedTopLevelDomain ? : null}
-
- {props.suggestedServiceLevelDomain ? {props.suggestedServiceLevelDomain.split(':')[0]}: {props.suggestedServiceLevelDomain.split(':')[1]} : null}
{props.children}
);
@@ -69,8 +64,6 @@ FormGroup.defaultProps = {
as: 'input',
errorMessage: '',
borderClass: '',
- suggestedTopLevelDomain: '',
- suggestedServiceLevelDomain: '',
autoComplete: null,
handleBlur: null,
handleChange: () => {},
@@ -87,8 +80,6 @@ FormGroup.propTypes = {
as: PropTypes.string,
errorMessage: PropTypes.string,
borderClass: PropTypes.string,
- suggestedTopLevelDomain: PropTypes.string,
- suggestedServiceLevelDomain: PropTypes.string,
autoComplete: PropTypes.string,
floatingLabel: PropTypes.string.isRequired,
handleBlur: PropTypes.func,
diff --git a/src/common-components/Logistration.jsx b/src/common-components/Logistration.jsx
index f80f50ed..48b10128 100644
--- a/src/common-components/Logistration.jsx
+++ b/src/common-components/Logistration.jsx
@@ -27,6 +27,11 @@ const Logistration = (props) => {
setInstitutionLogin(!institutionLogin);
};
+ const handleOnSelect = (tabKey) => {
+ sendTrackEvent(`edx.bi.${tabKey.replace('/', '')}_form.toggled`, { category: 'user-engagement' });
+ setKey(tabKey);
+ };
+
return (
{institutionLogin
@@ -48,7 +53,7 @@ const Logistration = (props) => {
: (
<>
{!tpa && (
-
setKey(k)}>
+
diff --git a/src/forgot-password/ForgotPasswordPage.jsx b/src/forgot-password/ForgotPasswordPage.jsx
index 8ba02ebc..a0eec177 100644
--- a/src/forgot-password/ForgotPasswordPage.jsx
+++ b/src/forgot-password/ForgotPasswordPage.jsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { Formik } from 'formik';
import PropTypes from 'prop-types';
@@ -7,7 +7,7 @@ import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
-import { sendPageEvent } from '@edx/frontend-platform/analytics';
+import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form, StatefulButton, Hyperlink } from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
@@ -29,6 +29,11 @@ const ForgotPasswordPage = (props) => {
const regex = new RegExp(VALID_EMAIL_REGEX, 'i');
const [validationError, setValidationError] = useState('');
+ useEffect(() => {
+ sendPageEvent('login_and_registration', 'reset');
+ sendTrackEvent('edx.bi.password_reset_form.viewed', { category: 'user-engagement' });
+ }, []);
+
const getValidationMessage = (email) => {
let error = '';
@@ -42,8 +47,6 @@ const ForgotPasswordPage = (props) => {
return error;
};
- sendPageEvent('login_and_registration', 'reset');
-
return (
diff --git a/src/login/LoginHelpLinks.jsx b/src/login/LoginHelpLinks.jsx
deleted file mode 100644
index bba14ed5..00000000
--- a/src/login/LoginHelpLinks.jsx
+++ /dev/null
@@ -1,94 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { sendTrackEvent } from '@edx/frontend-platform/analytics';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import { getConfig } from '@edx/frontend-platform';
-import { Hyperlink } from '@edx/paragon';
-
-import SwitchContent from '../common-components/SwitchContent';
-import {
- LOGIN_PAGE,
- REGISTER_PAGE,
- RESET_PAGE,
-} from '../data/constants';
-import messages from './messages';
-import { updatePathWithQueryParams } from '../data/utils';
-
-const LoginHelpLinks = (props) => {
- const { intl, page } = props;
- const [showLoginHelp, setShowLoginHelpValue] = useState(false);
-
- const toggleLoginHelp = (e) => {
- e.preventDefault();
- setShowLoginHelpValue(!showLoginHelp);
- };
-
- const handleForgotPasswordLinkClickEvent = () => {
- sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
- };
-
- const forgotPasswordLink = () => (
-
- {intl.formatMessage(messages['forgot.password.link'])}
-
- );
-
- const signUpLink = () => (
-
- {intl.formatMessage(messages['register.link'])}
-
- );
-
- const loginIssueSupportURL = (config) => (config.LOGIN_ISSUE_SUPPORT_LINK
- ? (
-
- {intl.formatMessage(messages['other.sign.in.issues'])}
-
- )
- : null);
-
- const getHelpButtonMessage = () => {
- let mid = 'need.other.help.signing.in.collapsible.menu';
- if (page === LOGIN_PAGE) {
- mid = 'need.help.signing.in.collapsible.menu';
- }
-
- return intl.formatMessage(messages[mid]);
- };
-
- const renderLoginHelp = () => (
-
- { page === LOGIN_PAGE ? forgotPasswordLink() : signUpLink() }
- { loginIssueSupportURL(getConfig()) }
-
- );
-
- return (
- <>
-
- >,
- }}
- />
- >
- );
-};
-
-LoginHelpLinks.propTypes = {
- intl: intlShape.isRequired,
- page: PropTypes.string.isRequired,
-};
-
-export default injectIntl(LoginHelpLinks);
diff --git a/src/login/LoginPage.jsx b/src/login/LoginPage.jsx
index 83ef2f2d..fddeb3c9 100644
--- a/src/login/LoginPage.jsx
+++ b/src/login/LoginPage.jsx
@@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { getConfig } from '@edx/frontend-platform';
-import { sendPageEvent } from '@edx/frontend-platform/analytics';
+import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Form, Hyperlink, Icon, StatefulButton,
@@ -101,6 +101,10 @@ class LoginPage extends React.Component {
this.setState({ errors });
}
+ handleForgotPasswordLinkClickEvent = () => {
+ sendTrackEvent('edx.bi.password-reset_form.toggled', { category: 'user-engagement' });
+ };
+
validateEmail(email) {
const { errors } = this.state;
@@ -242,7 +246,12 @@ class LoginPage extends React.Component {
onClick={this.handleSubmit}
onMouseDown={(e) => e.preventDefault()}
/>
-
+
{intl.formatMessage(messages['forgot.password'])}
{this.renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl)}
diff --git a/src/login/tests/LoginHelpLinks.test.jsx b/src/login/tests/LoginHelpLinks.test.jsx
deleted file mode 100644
index e975aa1c..00000000
--- a/src/login/tests/LoginHelpLinks.test.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import { IntlProvider } from '@edx/frontend-platform/i18n';
-import * as analytics from '@edx/frontend-platform/analytics';
-import { mount } from 'enzyme';
-
-import LoginHelpLinks from '../LoginHelpLinks';
-import { LOGIN_PAGE } from '../../data/constants';
-
-const otherSignInIssues = 'https://login-issue-support-url.com';
-
-jest.mock('@edx/frontend-platform', () => ({
- getConfig: jest.fn().mockReturnValue({ LOGIN_ISSUE_SUPPORT_LINK: otherSignInIssues }),
-}));
-
-jest.mock('@edx/frontend-platform/analytics');
-analytics.sendTrackEvent = jest.fn();
-
-describe('LoginHelpLinks', () => {
- let props = {};
-
- const reduxWrapper = children => (
-
- {children}
-
- );
-
- it('renders help links on button click', () => {
- props = {
- ...props,
- page: LOGIN_PAGE,
- };
- const loginHelpLinks = mount(reduxWrapper());
-
- expect(loginHelpLinks.find('.login-help').length).toBe(0);
- loginHelpLinks.find('button').first().simulate('click');
- expect(loginHelpLinks.find('.login-help').length).toBe(1);
- });
-
- it('should display login page help links', () => {
- props = {
- ...props,
- page: LOGIN_PAGE,
- };
-
- const wrapper = mount(reduxWrapper());
- wrapper.find('button').first().simulate('click');
-
- const loginHelpLinks = wrapper.find('a');
-
- expect(loginHelpLinks.at(0).prop('href')).toEqual('/reset');
- expect(loginHelpLinks.at(1).prop('href')).toEqual(otherSignInIssues);
- });
-
- it('should display forget password page help links', () => {
- props = {
- ...props,
- page: 'forget-password',
- };
-
- const wrapper = mount(reduxWrapper());
- wrapper.find('button').first().simulate('click');
-
- const loginHelpLinks = wrapper.find('a');
-
- expect(loginHelpLinks.at(0).prop('href')).toEqual('/register');
- expect(loginHelpLinks.at(1).prop('href')).toEqual(otherSignInIssues);
- });
-});
diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx
index 0df1fd69..45eca362 100644
--- a/src/register/RegistrationPage.jsx
+++ b/src/register/RegistrationPage.jsx
@@ -12,8 +12,9 @@ import {
injectIntl, intlShape, getCountryList, getLocale, FormattedMessage,
} from '@edx/frontend-platform/i18n';
import {
- Form, Hyperlink, StatefulButton,
+ Alert, Form, Hyperlink, StatefulButton,
} from '@edx/paragon';
+import { Error } from '@edx/paragon/icons';
import { closest } from 'fastest-levenshtein';
import {
@@ -48,8 +49,8 @@ class RegistrationPage extends React.Component {
constructor(props, context) {
super(props, context);
sendPageEvent('login_and_registration', 'register');
-
const optionalFields = getConfig().REGISTRATION_OPTIONAL_FIELDS ? getConfig().REGISTRATION_OPTIONAL_FIELDS.split(',') : [];
+ this.handleOnClose = this.handleOnClose.bind(this);
this.queryParams = getAllPossibleQueryParam();
this.tpaHint = getTpaHint();
@@ -73,6 +74,7 @@ class RegistrationPage extends React.Component {
showOptionalField: false,
startTime: Date.now(),
optimizelyExperimentName: '',
+ skipEmailValidation: false,
};
}
@@ -220,7 +222,7 @@ class RegistrationPage extends React.Component {
handleOnFocus = (e) => {
const { errors } = this.state;
errors[e.target.name] = '';
- this.setState({ errors });
+ this.setState({ errors, skipEmailValidation: false });
}
handleSuggestionClick = (suggestion) => {
@@ -249,12 +251,12 @@ class RegistrationPage extends React.Component {
errors.email = intl.formatMessage(messages['empty.email.field.error']);
} else if (value.length <= 2 || !emailRegex.test(value)) {
errors.email = intl.formatMessage(messages['email.invalid.format.error']);
- } else if (emailRegex.test(value)) {
+ } else if (emailRegex.test(value) && !this.state.skipEmailValidation) {
errors.email = '';
let emailLexemes = value.split('@');
let domainLexemes = emailLexemes[1].split('.');
- const serviceProvider = domainLexemes[0];
- const topLevelDomain = domainLexemes[1];
+ const serviceProvider = domainLexemes.slice(-2)[0];
+ const topLevelDomain = domainLexemes.slice(-1)[0];
if (DEFAULT_TOP_LEVEL_DOMAINS.indexOf(topLevelDomain) < 0) {
let suggestedTld = closest(topLevelDomain, DEFAULT_TOP_LEVEL_DOMAINS);
@@ -265,6 +267,7 @@ class RegistrationPage extends React.Component {
suggestedTopLevelDomain: suggestedTld,
suggestedServiceLevelDomain: '',
borderClass: '',
+ skipEmailValidation: false,
});
break;
} else {
@@ -336,6 +339,27 @@ class RegistrationPage extends React.Component {
return errors;
}
+ handleOnClose() {
+ const { errors } = this.state;
+ errors.email = '';
+ this.setState({ errors, suggestedTopLevelDomain: '', skipEmailValidation: true });
+ }
+
+ renderEmailFeedback() {
+ if (this.state.suggestedTopLevelDomain) {
+ return (
+
+ {this.state.suggestedTopLevelDomain}
+
+ );
+ }
+ if (this.state.suggestedServiceLevelDomain) {
+ return {this.state.suggestedServiceLevelDomain};
+ }
+
+ return null;
+ }
+
renderThirdPartyAuth(providers, secondaryProviders, currentProvider, thirdPartyAuthApiStatus, intl) {
const isInstitutionAuthActive = !!secondaryProviders.length && !currentProvider;
const isSocialAuthActive = !!providers.length && !currentProvider;
@@ -475,9 +499,9 @@ class RegistrationPage extends React.Component {
helpText={[intl.formatMessage(messages['help.text.email'])]}
floatingLabel={intl.formatMessage(messages['registration.email.label'])}
borderClass={this.state.borderClass}
- suggestedTopLevelDomain={this.state.suggestedTopLevelDomain}
- suggestedServiceLevelDomain={this.state.suggestedServiceLevelDomain}
- />
+ >
+ {this.renderEmailFeedback()}
+
{!currentProvider && (