From ee3d576a49ea0dfa79cab647992c0638761fdd70 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 18:42:03 +0100 Subject: [PATCH] Auth Refactor part 2 --- .../src/components/ForgotPassword.css | 20 ++++ .../src/components/ForgotPassword.js | 81 ++++++++++++++++ client/coral-admin/src/components/Login.css | 23 +---- client/coral-admin/src/components/Login.js | 92 ++++--------------- client/coral-admin/src/components/SignIn.css | 14 +++ client/coral-admin/src/components/SignIn.js | 76 +++++++++++++++ .../src/containers/ForgotPassword.js | 42 +++++++++ client/coral-admin/src/containers/Login.js | 34 ++----- client/coral-admin/src/containers/SignIn.js | 48 ++++++++++ client/coral-framework/hocs/index.js | 3 +- .../hocs/withForgotPassword.js | 61 ++++++++++++ .../hocs/{withLogin.js => withSignIn.js} | 10 +- 12 files changed, 381 insertions(+), 123 deletions(-) create mode 100644 client/coral-admin/src/components/ForgotPassword.css create mode 100644 client/coral-admin/src/components/ForgotPassword.js create mode 100644 client/coral-admin/src/components/SignIn.css create mode 100644 client/coral-admin/src/components/SignIn.js create mode 100644 client/coral-admin/src/containers/ForgotPassword.js create mode 100644 client/coral-admin/src/containers/SignIn.js create mode 100644 client/coral-framework/hocs/withForgotPassword.js rename client/coral-framework/hocs/{withLogin.js => withSignIn.js} (86%) diff --git a/client/coral-admin/src/components/ForgotPassword.css b/client/coral-admin/src/components/ForgotPassword.css new file mode 100644 index 000000000..beae79eea --- /dev/null +++ b/client/coral-admin/src/components/ForgotPassword.css @@ -0,0 +1,20 @@ + +.header, .cta, .success { + text-align: center; + font-size: 16px; +} + +.success { + cursor: pointer; + padding: 8px 14px; +} + +.signInLink { + color: blue; + font-weight: normal; + text-decoration: none; +} + +.signInLink:hover { + text-decoration: underline; +} diff --git a/client/coral-admin/src/components/ForgotPassword.js b/client/coral-admin/src/components/ForgotPassword.js new file mode 100644 index 000000000..1237fd7fb --- /dev/null +++ b/client/coral-admin/src/components/ForgotPassword.js @@ -0,0 +1,81 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './ForgotPassword.css'; +import { Button, TextField, Alert, Success } from 'coral-ui'; + +class ForgotPassword extends React.Component { + constructor(props) { + super(props); + } + + handleEmailChange = e => this.props.onEmailChange(e.target.value); + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + handleSignInLink = e => { + e.preventDefault(); + this.props.onSignInLink(); + }; + + renderSuccess() { + return ( +
+ {this.props.success}{' '} + + Sign in + + +
+ ); + } + + renderForm() { + const { email, errorMessage } = this.props; + return ( +
+ {errorMessage && {errorMessage}} + + +

+ Go back to{' '} + + Sign In + + . +

+ + ); + } + + render() { + return this.props.success ? this.renderSuccess() : this.renderForm(); + } +} + +ForgotPassword.propTypes = { + success: PropTypes.bool.isRequired, + email: PropTypes.string.isRequired, + onEmailChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + onSignInLink: PropTypes.func.isRequired, +}; + +export default ForgotPassword; diff --git a/client/coral-admin/src/components/Login.css b/client/coral-admin/src/components/Login.css index 9977f858a..49f03a11f 100644 --- a/client/coral-admin/src/components/Login.css +++ b/client/coral-admin/src/components/Login.css @@ -1,37 +1,18 @@ .layout { - max-width: 800px; - margin: 0 auto; -} - -.loginLayout { max-width: 400px; margin: 0 auto; } -.loginHeader, .loginCTA, .forgotPasswordCTA, .passwordRequestSuccess { +.header, .cta { text-align: center; font-size: 16px; } -.forgotPasswordLink, .signInLink { - color: blue; - font-weight: normal; - text-decoration: none; -} - -.forgotPasswordLink:hover, .signInLink:hover { - text-decoration: underline; -} - .layout h1 { font-size: 40px; } -.loginHeader { +.header { font-size: 30px; } -.passwordRequestSuccess { - cursor: pointer; - padding: 8px 14px; -} diff --git a/client/coral-admin/src/components/Login.js b/client/coral-admin/src/components/Login.js index 66ab81e78..cc5ec9436 100644 --- a/client/coral-admin/src/components/Login.js +++ b/client/coral-admin/src/components/Login.js @@ -1,89 +1,37 @@ -import React from 'react'; +import React, { Component } from 'react'; +import SignIn from '../containers/SignIn'; +import ForgotPassword from '../containers/ForgotPassword'; import PropTypes from 'prop-types'; -import Layout from 'coral-admin/src/components/Layout'; import styles from './Login.css'; -import { Button, TextField, Alert } from 'coral-ui'; +import Layout from 'coral-admin/src/components/Layout'; import cn from 'classnames'; -class AdminLogin extends React.Component { - constructor(props) { - super(props); +class LoginContainer extends Component { + renderForm() { + return this.props.forgotPassword ? ( + + ) : ( + + ); } - handleForgotPassword = e => { - e.preventDefault(); - this.props.onForgotPassword(); - }; - handleEmailChange = e => this.props.onEmailChange(e.target.value); - handlePasswordChange = e => this.props.onPasswordChange(e.target.value); - - handleSubmit = e => { - e.preventDefault(); - this.props.onSubmit(); - }; - render() { - const { email, password, errorMessage } = this.props; return ( -
-

Team sign in

-

- Sign in to interact with your community. -

-
- {errorMessage && {errorMessage}} - - -
- -

- Forgot your password?{' '} - - Request a new one. - -

- +
+

Team sign in

+

Sign in to interact with your community.

+ {this.renderForm()}
); } } -AdminLogin.propTypes = { - email: PropTypes.string, - password: PropTypes.string, - onEmailChange: PropTypes.func, - onPasswordChange: PropTypes.func, - onForgotPassword: PropTypes.func, - onSubmit: PropTypes.func, - errorMessage: PropTypes.string, - requireRecaptcha: PropTypes.string, +LoginContainer.propTypes = { + forgotPassword: PropTypes.bool.isRequired, + onForgotPasswordLink: PropTypes.func.isRequired, + onSignInLink: PropTypes.func.isRequired, }; -export default AdminLogin; +export default LoginContainer; diff --git a/client/coral-admin/src/components/SignIn.css b/client/coral-admin/src/components/SignIn.css new file mode 100644 index 000000000..88d8a452c --- /dev/null +++ b/client/coral-admin/src/components/SignIn.css @@ -0,0 +1,14 @@ +.forgotPasswordCTA { + text-align: center; + font-size: 16px; +} + +.forgotPasswordLink:hover { + text-decoration: underline; +} + +.forgotPasswordLink { + color: blue; + font-weight: normal; + text-decoration: none; +} diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js new file mode 100644 index 000000000..71f14d12a --- /dev/null +++ b/client/coral-admin/src/components/SignIn.js @@ -0,0 +1,76 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './SignIn.css'; +import { Button, TextField, Alert } from 'coral-ui'; + +class SignIn extends React.Component { + constructor(props) { + super(props); + } + + handleForgotPasswordLink = e => { + e.preventDefault(); + this.props.onForgotPasswordLink(); + }; + handleEmailChange = e => this.props.onEmailChange(e.target.value); + handlePasswordChange = e => this.props.onPasswordChange(e.target.value); + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + render() { + const { email, password, errorMessage } = this.props; + return ( +
+ {errorMessage && {errorMessage}} + + +
+ +

+ Forgot your password?{' '} + + Request a new one. + +

+ + ); + } +} + +SignIn.propTypes = { + email: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + onEmailChange: PropTypes.func.isRequired, + onPasswordChange: PropTypes.func.isRequired, + onForgotPasswordLink: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + requireRecaptcha: PropTypes.bool.isRequired, +}; + +export default SignIn; diff --git a/client/coral-admin/src/containers/ForgotPassword.js b/client/coral-admin/src/containers/ForgotPassword.js new file mode 100644 index 000000000..4de51fa1e --- /dev/null +++ b/client/coral-admin/src/containers/ForgotPassword.js @@ -0,0 +1,42 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withForgotPassword } from 'coral-framework/hocs'; +import { compose } from 'recompose'; +import ForgotPassword from '../components/ForgotPassword'; + +class ForgotPasswordContainer extends Component { + state = { + email: '', + password: '', + }; + + handleSubmit = () => { + this.props.forgotPassword(this.state.email); + }; + + handleEmailChange = email => { + this.setState({ email }); + }; + + render() { + return ( + + ); + } +} + +ForgotPasswordContainer.propTypes = { + success: PropTypes.bool.isRequired, + forgotPassword: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + onSignInLink: PropTypes.func.isRequired, +}; + +export default compose(withForgotPassword)(ForgotPasswordContainer); diff --git a/client/coral-admin/src/containers/Login.js b/client/coral-admin/src/containers/Login.js index 3d32a2af2..08558bc5c 100644 --- a/client/coral-admin/src/containers/Login.js +++ b/client/coral-admin/src/containers/Login.js @@ -1,44 +1,30 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import { withLogin } from 'coral-framework/hocs'; -import { compose } from 'recompose'; import Login from '../components/Login'; class LoginContainer extends Component { state = { - email: '', - password: '', + forgotPassword: false, }; - handleSubmit = () => { - this.props.login(this.state.email, this.state.password); + switchToForgotPassword = () => { + this.setState({ forgotPassword: true }); }; - handleEmailChange = email => { - this.setState({ email }); - }; - - handlePasswordChange = password => { - this.setState({ password }); + switchToSignIn = () => { + this.setState({ forgotPassword: false }); }; render() { return ( ); } } -LoginContainer.propTypes = { - login: PropTypes.func, - errorMessage: PropTypes.string, -}; +LoginContainer.propTypes = {}; -export default compose(withLogin)(LoginContainer); +export default LoginContainer; diff --git a/client/coral-admin/src/containers/SignIn.js b/client/coral-admin/src/containers/SignIn.js new file mode 100644 index 000000000..ab7886c3a --- /dev/null +++ b/client/coral-admin/src/containers/SignIn.js @@ -0,0 +1,48 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withSignIn } from 'coral-framework/hocs'; +import { compose } from 'recompose'; +import SignIn from '../components/SignIn'; + +class SignInContainer extends Component { + state = { + email: '', + password: '', + }; + + handleSubmit = () => { + this.props.signIn(this.state.email, this.state.password); + }; + + handleEmailChange = email => { + this.setState({ email }); + }; + + handlePasswordChange = password => { + this.setState({ password }); + }; + + render() { + return ( + + ); + } +} + +SignInContainer.propTypes = { + signIn: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + onForgotPasswordLink: PropTypes.func.isRequired, + requireRecaptcha: PropTypes.bool.isRequired, +}; + +export default compose(withSignIn)(SignInContainer); diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index c871306ae..cfc41676b 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -6,4 +6,5 @@ export { default as withEmit } from './withEmit'; export { default as excludeIf } from './excludeIf'; export { default as connect } from './connect'; export { default as withMergedSettings } from './withMergedSettings'; -export { default as withLogin } from './withLogin'; +export { default as withSignIn } from './withSignIn'; +export { default as withForgotPassword } from './withForgotPassword'; diff --git a/client/coral-framework/hocs/withForgotPassword.js b/client/coral-framework/hocs/withForgotPassword.js new file mode 100644 index 000000000..c8b42b8c5 --- /dev/null +++ b/client/coral-framework/hocs/withForgotPassword.js @@ -0,0 +1,61 @@ +import React from 'react'; +import hoistStatics from 'recompose/hoistStatics'; +import PropTypes from 'prop-types'; +import { translateError } from '../utils'; + +/** + * WithForgotPassword provides properties `forgotPasssword`, `loading`, `errorMessage`, `success`. + */ +export default hoistStatics(WrappedComponent => { + class WithForgotPassword extends React.Component { + static contextTypes = { + store: PropTypes.object, + rest: PropTypes.func, + }; + + state = { + error: null, + loading: false, + success: false, + }; + + forgotPassword = email => { + const { rest } = this.context; + const redirectUri = location.href; + this.setState({ loading: true, error: null, success: false }); + + rest('/account/password/reset', { + method: 'POST', + body: { email, loc: redirectUri }, + }) + .then(() => { + this.setState({ loading: false, error: null, success: true }); + }) + .catch(error => { + console.error(error); + this.setState({ loading: false, error }); + }); + }; + + getErrorMessage() { + if (!this.state.error) { + return ''; + } + return translateError(this.state.error); + } + + render() { + return ( + + ); + } + } + + return WithForgotPassword; +}); diff --git a/client/coral-framework/hocs/withLogin.js b/client/coral-framework/hocs/withSignIn.js similarity index 86% rename from client/coral-framework/hocs/withLogin.js rename to client/coral-framework/hocs/withSignIn.js index 56586b7af..a725f01e9 100644 --- a/client/coral-framework/hocs/withLogin.js +++ b/client/coral-framework/hocs/withSignIn.js @@ -6,10 +6,10 @@ import { translateError } from '../utils'; import { t } from '../services/i18n'; /** - * WithLogin provides properties `login`, `loading` and `errorMessage`, `requireRecaptcha`. + * WithSignIn provides properties `signIn`, `loading` and `errorMessage`, `requireRecaptcha`. */ export default hoistStatics(WrappedComponent => { - class WithLogin extends React.Component { + class WithSignIn extends React.Component { static contextTypes = { store: PropTypes.object, rest: PropTypes.func, @@ -20,7 +20,7 @@ export default hoistStatics(WrappedComponent => { loading: false, }; - login = (email, password, recaptchaResponse) => { + signIn = (email, password, recaptchaResponse) => { const { store, rest } = this.context; const params = { method: 'POST', @@ -62,7 +62,7 @@ export default hoistStatics(WrappedComponent => { return ( { } } - return WithLogin; + return WithSignIn; });