diff --git a/src/logistration/LoginFailure.jsx b/src/logistration/LoginFailure.jsx new file mode 100644 index 00000000..f1d4d393 --- /dev/null +++ b/src/logistration/LoginFailure.jsx @@ -0,0 +1,70 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormattedMessage } from '@edx/frontend-platform/i18n'; + +import { Alert, Hyperlink } from '@edx/paragon'; + +const processLink = (link) => { + let matches; + link.replace(/(.*)([^<]+)<\/a>(.*)/g, function () { // eslint-disable-line func-names + matches = Array.prototype.slice.call(arguments, 1, 5); // eslint-disable-line prefer-rest-params + }); + return matches; +}; + +const LoginFailureMessage = (props) => { + const Link = (args) => ( + <> + {args.beforeLink} + + {args.linkText} + + {args.afterLink} + + ); + + const errorMessage = props.errors; + let errorList = errorMessage.trim().split('\n'); + errorList = errorList.map((error) => { + let matches; + if (error.includes('a href')) { + matches = processLink(error); + const [beforeLink, link, linkText, afterLink] = matches; + return ( +
  • + +
  • + ); + } + return
  • {error}
  • ; + }); + + return ( + +
    +

    + +

    +
      {errorList}
    +
    +
    + ); +}; + +LoginFailureMessage.defaultProps = { + errors: '', +}; +LoginFailureMessage.propTypes = { + errors: PropTypes.string, +}; + +export default LoginFailureMessage; diff --git a/src/logistration/LoginPage.jsx b/src/logistration/LoginPage.jsx index 25d0e60b..e18dd10e 100644 --- a/src/logistration/LoginPage.jsx +++ b/src/logistration/LoginPage.jsx @@ -13,6 +13,8 @@ import { DEFAULT_REDIRECT_URL } from './data/constants'; import { loginRequestSelector, thirdPartyAuthContextSelector } from './data/selectors'; import LoginHelpLinks from './LoginHelpLinks'; import RedirectLogistration from './RedirectLogistration'; +import LoginFailureMessage from './LoginFailure'; + class LoginPage extends React.Component { constructor(props, context) { @@ -110,6 +112,7 @@ class LoginPage extends React.Component { />
    + {this.props.loginError ? : null} {this.props.forgotPassword.status === 'complete' ? : null}

    @@ -185,6 +188,7 @@ LoginPage.defaultProps = { loginResult: null, forgotPassword: null, thirdPartyAuthContext: null, + loginError: null, }; LoginPage.propTypes = { @@ -211,13 +215,19 @@ LoginPage.propTypes = { username: PropTypes.string, }), }), + loginError: PropTypes.string, }; const mapStateToProps = state => { const forgotPassword = forgotPasswordResultSelector(state); const loginResult = loginRequestSelector(state); const thirdPartyAuthContext = thirdPartyAuthContextSelector(state); - return { forgotPassword, loginResult, thirdPartyAuthContext }; + return { + forgotPassword, + loginResult, + thirdPartyAuthContext, + loginError: state.logistration.loginError, + }; }; export default connect( diff --git a/src/logistration/data/actions.js b/src/logistration/data/actions.js index ccc7a413..18813d53 100644 --- a/src/logistration/data/actions.js +++ b/src/logistration/data/actions.js @@ -41,8 +41,9 @@ export const loginRequestSuccess = (redirectUrl, success) => ({ payload: { redirectUrl, success }, }); -export const loginRequestFailure = () => ({ +export const loginRequestFailure = (loginError) => ({ type: LOGIN_REQUEST.FAILURE, + payload: { loginError }, }); // Third party auth context diff --git a/src/logistration/data/reducers.js b/src/logistration/data/reducers.js index 162aa8f6..7abc5284 100644 --- a/src/logistration/data/reducers.js +++ b/src/logistration/data/reducers.js @@ -8,6 +8,7 @@ export const defaultState = { registrationResult: {}, loginResult: {}, registrationError: null, + loginError: null, }; const reducer = (state = defaultState, action) => { @@ -39,6 +40,7 @@ const reducer = (state = defaultState, action) => { case LOGIN_REQUEST.FAILURE: return { ...state, + loginError: action.payload.loginError, }; case THIRD_PARTY_AUTH_CONTEXT.BEGIN: return { diff --git a/src/logistration/data/sagas.js b/src/logistration/data/sagas.js index 56eee0f4..fedd9b7f 100644 --- a/src/logistration/data/sagas.js +++ b/src/logistration/data/sagas.js @@ -49,8 +49,10 @@ export function* handleLoginRequest(action) { success, )); } catch (e) { - yield put(loginRequestFailure()); - throw e; + const statusCodes = [400]; + if (e.response && statusCodes.includes(e.response.status)) { + yield put(loginRequestFailure(e.response.data.value)); + } } } diff --git a/src/logistration/tests/LoginPage.test.jsx b/src/logistration/tests/LoginPage.test.jsx index c9823ce6..1745327f 100644 --- a/src/logistration/tests/LoginPage.test.jsx +++ b/src/logistration/tests/LoginPage.test.jsx @@ -15,6 +15,7 @@ describe('LoginPage', () => { logistration: { forgotPassword: { status: null }, loginResult: { success: false, redirectUrl: '' }, + response_error: null, }, }; @@ -95,4 +96,38 @@ describe('LoginPage', () => { mount(reduxWrapper()); expect(spy).toHaveBeenCalled(); }); + + it('should show error message on 400', () => { + store = mockStore({ + ...store, + logistration: { + ...store.logistration, + loginResult: { + success: false, + redirectUrl: '', + }, + loginError: 'Email or password is incorrect.', + }, + }); + + const tree = renderer.create(reduxWrapper()).toJSON(); + expect(tree).toMatchSnapshot(); + }); + + it('should show error message on 400 on receiving link', () => { + store = mockStore({ + ...store, + logistration: { + ...store.logistration, + loginResult: { + success: false, + redirectUrl: '', + }, + loginError: 'To be on the safe side, you can reset your password here before you try again.\n', + }, + }); + + const tree = renderer.create(reduxWrapper()).toJSON(); + expect(tree).toMatchSnapshot(); + }); }); diff --git a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap index 6322995b..86d7bcd2 100644 --- a/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap +++ b/src/logistration/tests/__snapshots__/LoginPage.test.jsx.snap @@ -489,3 +489,547 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `

    `; + +exports[`LoginPage should show error message on 400 1`] = ` +
    +
    +
    +
    +

    + + We couldn't sign you in. + +

    +
      +
    • + Email or password is incorrect. +
    • +
    +
    +
    +
    +

    + First time here? + + Create an Account. + +

    +
    +
    +
    +

    + Sign In +

    +
    +
    + + + + The email address you've provided isn't formatted correctly. + +
    +
    +

    + The email address you used to register with edX. +

    +
    +
    + + + + Please enter your password. + +
    +
    + +
    +
    +
    +
    + + +
    +

    + or sign in with +

    +
    +
    + + + +
    +
    +
    +`; + +exports[`LoginPage should show error message on 400 on receiving link 1`] = ` +
    +
    +
    +
    +

    + + We couldn't sign you in. + +

    +
      +
    • + To be on the safe side, you can reset your password + + here + + before you try again. +
    • +
    +
    +
    +
    +

    + First time here? + + Create an Account. + +

    +
    +
    +
    +

    + Sign In +

    +
    +
    + + + + The email address you've provided isn't formatted correctly. + +
    +
    +

    + The email address you used to register with edX. +

    +
    +
    + + + + Please enter your password. + +
    +
    + +
    +
    +
    +
    + + +
    +

    + or sign in with +

    +
    +
    + + + +
    +
    +
    +`;