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:
70
src/logistration/LoginFailure.jsx
Normal file
70
src/logistration/LoginFailure.jsx
Normal 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;
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user