Merge pull request #13 from edx/adeel/van_105_adding_error_handling_for_login_page

Adds error handling to Login page.
This commit is contained in:
adeel khan
2020-11-09 16:57:03 +05:00
committed by GitHub
7 changed files with 668 additions and 4 deletions

View File

@@ -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 href=["']([^"']*).*>([^<]+)<\/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}
<Hyperlink destination={args.link}>
{args.linkText}
</Hyperlink>
{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 (
<li key={error}>
<Link // eslint-disable-line jsx-a11y/anchor-is-valid
beforeLink={beforeLink}
link={link}
linkText={linkText}
afterLink={afterLink}
/>
</li>
);
}
return <li key={error}>{error}</li>;
});
return (
<Alert variant="danger">
<div>
<h4 style={{ color: '#a0050e' }}>
<FormattedMessage
id="logistration.login.failure.header.title"
defaultMessage="We couldn't sign you in."
description="login failure header message."
/>
</h4>
<ul>{errorList}</ul>
</div>
</Alert>
);
};
LoginFailureMessage.defaultProps = {
errors: '',
};
LoginFailureMessage.propTypes = {
errors: PropTypes.string,
};
export default LoginFailureMessage;

View File

@@ -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 {
/>
<div className="d-flex justify-content-center logistration-container">
<div className="d-flex flex-column" style={{ width: '400px' }}>
{this.props.loginError ? <LoginFailureMessage errors={this.props.loginError} /> : null}
{this.props.forgotPassword.status === 'complete' ? <ConfirmationAlert email={this.props.forgotPassword.email} /> : null}
<div className="d-flex flex-row">
<p>
@@ -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(

View File

@@ -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

View File

@@ -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 {

View File

@@ -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));
}
}
}

View File

@@ -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(<IntlLoginPage {...props} />));
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(<IntlLoginPage {...props} />)).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 <a href="/reset">here</a> before you try again.\n',
},
});
const tree = renderer.create(reduxWrapper(<IntlLoginPage {...props} />)).toJSON();
expect(tree).toMatchSnapshot();
});
});

View File

@@ -489,3 +489,547 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `
</div>
</div>
`;
exports[`LoginPage should show error message on 400 1`] = `
<div
className="d-flex justify-content-center logistration-container"
>
<div
className="d-flex flex-column"
style={
Object {
"width": "400px",
}
}
>
<div
className="fade alert alert-danger show"
role="alert"
>
<div>
<h4
style={
Object {
"color": "#a0050e",
}
}
>
<span>
We couldn't sign you in.
</span>
</h4>
<ul>
<li>
Email or password is incorrect.
</li>
</ul>
</div>
</div>
<div
className="d-flex flex-row"
>
<p>
First time here?
<a
className="ml-1"
href="/register"
>
Create an Account.
</a>
</p>
</div>
<form
className="m-0"
>
<div
className="form-group"
>
<h3
className="text-center mt-3"
>
Sign In
</h3>
<div
className="d-flex flex-column align-items-start"
>
<div
className="form-group"
>
<label
className="h6 mr-1"
htmlFor="loginEmail"
>
Email
</label>
<input
aria-describedby=""
className="form-control"
id="loginEmail"
name="email"
onChange={[Function]}
placeholder="username@domain.com"
style={
Object {
"width": "400px",
}
}
type="email"
value=""
/>
<strong
className="invalid-feedback"
id="email-invalid-feedback"
>
The email address you've provided isn't formatted correctly.
</strong>
</div>
</div>
<p
className="mb-4"
>
The email address you used to register with edX.
</p>
<div
className="d-flex flex-column align-items-start"
>
<div
className="form-group mb-0"
>
<label
className="h6 mr-1"
htmlFor="loginPassword"
>
Password
</label>
<input
aria-describedby=""
className="form-control"
id="loginPassword"
name="password"
onChange={[Function]}
style={
Object {
"width": "400px",
}
}
type="password"
value=""
/>
<strong
className="invalid-feedback"
id="password-invalid-feedback"
>
Please enter your password.
</strong>
</div>
</div>
<button
className="mt-0 field-link btn btn-primary"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-caret-right fa-w-6 mr-1"
data-icon="caret-right"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 192 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
fill="currentColor"
style={Object {}}
/>
</svg>
Need help signing in?
</button>
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
/>
</div>
</div>
<button
className="btn-primary submit btn btn-primary"
disabled={false}
onClick={[Function]}
type="button"
>
Sign in
</button>
</form>
<div
className="section-heading-line mb-4"
>
<h4>
or sign in with
</h4>
</div>
<div
className="row text-center d-block mb-4"
>
<button
className="btn-social facebook"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-facebook-f fa-w-10 mr-2"
data-icon="facebook-f"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"
fill="currentColor"
style={Object {}}
/>
</svg>
Facebook
</button>
<button
className="btn-social google"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-google fa-w-16 mr-2"
data-icon="google"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 488 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Google
</button>
<button
className="btn-social microsoft"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-microsoft fa-w-14 mr-2"
data-icon="microsoft"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 32h214.6v214.6H0V32zm233.4 0H448v214.6H233.4V32zM0 265.4h214.6V480H0V265.4zm233.4 0H448V480H233.4V265.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Microsoft
</button>
</div>
</div>
</div>
`;
exports[`LoginPage should show error message on 400 on receiving link 1`] = `
<div
className="d-flex justify-content-center logistration-container"
>
<div
className="d-flex flex-column"
style={
Object {
"width": "400px",
}
}
>
<div
className="fade alert alert-danger show"
role="alert"
>
<div>
<h4
style={
Object {
"color": "#a0050e",
}
}
>
<span>
We couldn't sign you in.
</span>
</h4>
<ul>
<li>
To be on the safe side, you can reset your password
<a
href="/reset"
onClick={[Function]}
target="_self"
>
here
</a>
before you try again.
</li>
</ul>
</div>
</div>
<div
className="d-flex flex-row"
>
<p>
First time here?
<a
className="ml-1"
href="/register"
>
Create an Account.
</a>
</p>
</div>
<form
className="m-0"
>
<div
className="form-group"
>
<h3
className="text-center mt-3"
>
Sign In
</h3>
<div
className="d-flex flex-column align-items-start"
>
<div
className="form-group"
>
<label
className="h6 mr-1"
htmlFor="loginEmail"
>
Email
</label>
<input
aria-describedby=""
className="form-control"
id="loginEmail"
name="email"
onChange={[Function]}
placeholder="username@domain.com"
style={
Object {
"width": "400px",
}
}
type="email"
value=""
/>
<strong
className="invalid-feedback"
id="email-invalid-feedback"
>
The email address you've provided isn't formatted correctly.
</strong>
</div>
</div>
<p
className="mb-4"
>
The email address you used to register with edX.
</p>
<div
className="d-flex flex-column align-items-start"
>
<div
className="form-group mb-0"
>
<label
className="h6 mr-1"
htmlFor="loginPassword"
>
Password
</label>
<input
aria-describedby=""
className="form-control"
id="loginPassword"
name="password"
onChange={[Function]}
style={
Object {
"width": "400px",
}
}
type="password"
value=""
/>
<strong
className="invalid-feedback"
id="password-invalid-feedback"
>
Please enter your password.
</strong>
</div>
</div>
<button
className="mt-0 field-link btn btn-primary"
disabled={false}
onClick={[Function]}
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-caret-right fa-w-6 mr-1"
data-icon="caret-right"
data-prefix="fas"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 192 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 384.662V127.338c0-17.818 21.543-26.741 34.142-14.142l128.662 128.662c7.81 7.81 7.81 20.474 0 28.284L34.142 398.804C21.543 411.404 0 402.48 0 384.662z"
fill="currentColor"
style={Object {}}
/>
</svg>
Need help signing in?
</button>
<div
className="pgn-transition-replace-group position-relative"
style={
Object {
"height": null,
}
}
>
<div
style={
Object {
"padding": ".1px 0",
}
}
/>
</div>
</div>
<button
className="btn-primary submit btn btn-primary"
disabled={false}
onClick={[Function]}
type="button"
>
Sign in
</button>
</form>
<div
className="section-heading-line mb-4"
>
<h4>
or sign in with
</h4>
</div>
<div
className="row text-center d-block mb-4"
>
<button
className="btn-social facebook"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-facebook-f fa-w-10 mr-2"
data-icon="facebook-f"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 320 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"
fill="currentColor"
style={Object {}}
/>
</svg>
Facebook
</button>
<button
className="btn-social google"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-google fa-w-16 mr-2"
data-icon="google"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 488 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Google
</button>
<button
className="btn-social microsoft"
type="button"
>
<svg
aria-hidden="true"
className="svg-inline--fa fa-microsoft fa-w-14 mr-2"
data-icon="microsoft"
data-prefix="fab"
focusable="false"
role="img"
style={Object {}}
viewBox="0 0 448 512"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 32h214.6v214.6H0V32zm233.4 0H448v214.6H233.4V32zM0 265.4h214.6V480H0V265.4zm233.4 0H448V480H233.4V265.4z"
fill="currentColor"
style={Object {}}
/>
</svg>
Microsoft
</button>
</div>
</div>
</div>
`;