From a9da4c634864341062b97fbb0cebcaec29d9efdd Mon Sep 17 00:00:00 2001 From: Waheed Ahmed Date: Fri, 11 Jun 2021 18:05:40 +0500 Subject: [PATCH] fix: email validation and analytic events (#333) Fixed email validation logic and clear error state on close. Also fixed analytic events and removed unused components. VAN-538, VAN-568 --- .env.development | 2 +- src/common-components/AlertDismissible.jsx | 22 ----- src/common-components/FormGroup.jsx | 11 +-- src/common-components/Logistration.jsx | 7 +- src/forgot-password/ForgotPasswordPage.jsx | 11 ++- src/login/LoginHelpLinks.jsx | 94 ---------------------- src/login/LoginPage.jsx | 13 ++- src/login/tests/LoginHelpLinks.test.jsx | 68 ---------------- src/register/RegistrationPage.jsx | 42 +++++++--- 9 files changed, 59 insertions(+), 211 deletions(-) delete mode 100644 src/common-components/AlertDismissible.jsx delete mode 100644 src/login/LoginHelpLinks.jsx delete mode 100644 src/login/tests/LoginHelpLinks.test.jsx 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 && (