diff --git a/client/coral-admin/src/components/ForgotPassword.js b/client/coral-admin/src/components/ForgotPassword.js index 3bd9804f5..ad90c5d6a 100644 --- a/client/coral-admin/src/components/ForgotPassword.js +++ b/client/coral-admin/src/components/ForgotPassword.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './ForgotPassword.css'; import { Button, TextField, Alert, Success } from 'coral-ui'; +import t from 'coral-framework/services/i18n'; class ForgotPassword extends React.Component { constructor(props) { @@ -23,8 +24,7 @@ class ForgotPassword extends React.Component { renderSuccess() { return (
- If you have a registered account, a password reset link was sent to that - email.{' '} + {t('password_reset.mail_sent')}{' '} { diff --git a/locales/en.yml b/locales/en.yml index 14c29e3f6..b19fb6ded 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -23,6 +23,7 @@ en: click_to_confirm: "Click below to confirm your email address" confirm: "Confirm" password_reset: + mail_sent: 'If you have a registered account, a password reset link was sent to that email' set_new_password: "Change Your Password" new_password: "New Password" new_password_help: "Password must be at least 8 characters" diff --git a/plugins/talk-plugin-auth/client/actions.js b/plugins/talk-plugin-auth/client/actions.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 0c70cb112..5ed18d6d0 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -2,8 +2,10 @@ import UserBox from './stream/containers/UserBox'; import SignInButton from './stream/containers/SignInButton'; import translations from './translations.yml'; import Login from './login/containers/Main'; +import reducer from './login/reducer'; export default { + reducer, translations, slots: { stream: [UserBox, SignInButton], diff --git a/plugins/talk-plugin-auth/client/login/actions.js b/plugins/talk-plugin-auth/client/login/actions.js new file mode 100644 index 000000000..593556507 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/actions.js @@ -0,0 +1,16 @@ +import * as actions from './constants'; + +export const setView = view => ({ + type: actions.SET_VIEW, + view, +}); + +export const setEmail = email => ({ + type: actions.SET_EMAIL, + email, +}); + +export const setPassword = password => ({ + type: actions.SET_PASSWORD, + password, +}); diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.css b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.css new file mode 100644 index 000000000..ca00726c4 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.css @@ -0,0 +1,42 @@ +.header { + margin-bottom: 20px; +} + +.header h1, .separator h1{ + text-align: center; + font-size: 1.2em; +} + +.button { + margin-top: 10px; + background-color: #2a2a2a; +} +.footer { + margin: 20px auto 10px; + text-align: center; +} + +.footer span { + display: block; + margin-bottom: 5px; +} + +.footer a { + color: #2c69b6; + cursor: pointer; + margin: 0 5px; +} + +.success { + border: 1px solid green; + background-color: lightgreen; + padding: 10px; +} + +.failure { + border: 1px solid orange; + background-color: 1px solid coral; + padding: 10px; +} + + diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js new file mode 100644 index 000000000..84daa04b7 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js @@ -0,0 +1,79 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './ForgotPassword.css'; +import { Button, TextField } from 'plugin-api/beta/client/components/ui'; +import t from 'coral-framework/services/i18n'; + +class ForgotPassword extends React.Component { + state = { value: '' }; + + handleSignUpLink = e => { + e.preventDefault(); + this.props.onSignUpLink(); + }; + handleSignInLink = e => { + e.preventDefault(); + this.props.onSignInLink(); + }; + handleEmailChange = e => this.props.onEmailChange(e.target.value); + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + render() { + const { email, errorMessage, success } = this.props; + + return ( +
+
+

{t('sign_in.recover_password')}

+
+
+
+ +
+ + {success ? ( +

{t('password_reset.mail_sent')}

+ ) : null} + {errorMessage ? ( +

{errorMessage}

+ ) : null} +
+
+ + {t('sign_in.need_an_account')}{' '} + {t('sign_in.register')} + + + {t('sign_in.already_have_an_account')}{' '} + {t('sign_in.sign_in')} + +
+
+ ); + } +} + +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, + onSignUpLink: PropTypes.func.isRequired, +}; + +export default ForgotPassword; diff --git a/plugins/talk-plugin-auth/client/login/components/Main.css b/plugins/talk-plugin-auth/client/login/components/Main.css index 9d73e7403..eb48d61c1 100644 --- a/plugins/talk-plugin-auth/client/login/components/Main.css +++ b/plugins/talk-plugin-auth/client/login/components/Main.css @@ -6,3 +6,19 @@ font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; font-size: 14px; } + +.close { + font-size: 20px; + line-height: 14px; + top: 10px; + right: 10px; + position: absolute; + display: block; + font-weight: bold; + color: #363636; + cursor: pointer; +} + +.close:hover { + color: #6b6b6b; +} diff --git a/plugins/talk-plugin-auth/client/login/components/Main.js b/plugins/talk-plugin-auth/client/login/components/Main.js index e74a38b66..1aed0f229 100644 --- a/plugins/talk-plugin-auth/client/login/components/Main.js +++ b/plugins/talk-plugin-auth/client/login/components/Main.js @@ -1,13 +1,26 @@ import React from 'react'; import { Dialog } from 'plugin-api/beta/client/components/ui'; import styles from './Main.css'; - +import PropTypes from 'prop-types'; import SignIn from '../containers/SignIn'; +import ForgotPassword from '../containers/ForgotPassword'; +import * as views from '../enums/views'; -const SignDialog = () => ( +const Main = ({ view, onResetView }) => ( - + {view !== views.SIGN_IN && ( + + × + + )} + {view === views.SIGN_IN && } + {view === views.FORGOT_PASSWORD && } ); -export default SignDialog; +Main.propTypes = { + view: PropTypes.string.isRequired, + onResetView: PropTypes.func.isRequired, +}; + +export default Main; diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js index 720a5aaf0..baa7dc278 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -18,9 +18,9 @@ class SignIn extends React.Component { e.preventDefault(); this.props.onForgotPasswordLink(); }; - handleRegisterLink = e => { + handleSignUpLink = e => { e.preventDefault(); - this.props.onRegisterLink(); + this.props.onSignUpLink(); }; handleEmailChange = e => this.props.onEmailChange(e.target.value); handlePasswordChange = e => this.props.onPasswordChange(e.target.value); @@ -113,7 +113,7 @@ class SignIn extends React.Component { {t('sign_in.need_an_account')} - + {t('sign_in.register')} @@ -130,7 +130,7 @@ SignIn.propTypes = { onEmailChange: PropTypes.func.isRequired, onPasswordChange: PropTypes.func.isRequired, onForgotPasswordLink: PropTypes.func.isRequired, - onRegisterLink: PropTypes.func.isRequired, + onSignUpLink: PropTypes.func.isRequired, onRecaptchaVerify: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, errorMessage: PropTypes.string.isRequired, diff --git a/plugins/talk-plugin-auth/client/login/constants.js b/plugins/talk-plugin-auth/client/login/constants.js new file mode 100644 index 000000000..3997c11f4 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/constants.js @@ -0,0 +1,5 @@ +const prefix = 'TALK_AUTH'; + +export const SET_VIEW = `${prefix}_SET_VIEW`; +export const SET_EMAIL = `${prefix}_SET_EMAIL`; +export const SET_PASSWORD = `${prefix}_SET_PASSWORD`; diff --git a/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js new file mode 100644 index 000000000..36ee1384e --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js @@ -0,0 +1,64 @@ +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'; +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import * as views from '../enums/views'; +import { setView, setEmail } from '../actions'; + +class ForgotPasswordContainer extends Component { + handleSubmit = () => { + this.props.forgotPassword(this.props.email); + }; + + handleSignUpLink = () => { + this.props.setView(views.SIGN_UP); + }; + + handleSignInLink = () => { + this.props.setView(views.SIGN_IN); + }; + + render() { + return ( + + ); + } +} + +ForgotPasswordContainer.propTypes = { + success: PropTypes.bool.isRequired, + forgotPassword: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + setView: PropTypes.func.isRequired, + email: PropTypes.string.isRequired, + setEmail: PropTypes.func.isRequired, +}; + +const mapStateToProps = ({ talkPluginAuth: state }) => ({ + email: state.email, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + setView, + setEmail, + }, + dispatch + ); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withForgotPassword +)(ForgotPasswordContainer); diff --git a/plugins/talk-plugin-auth/client/login/containers/Main.js b/plugins/talk-plugin-auth/client/login/containers/Main.js index 5baedcf08..3391fe1f4 100644 --- a/plugins/talk-plugin-auth/client/login/containers/Main.js +++ b/plugins/talk-plugin-auth/client/login/containers/Main.js @@ -1,10 +1,36 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Main from '../components/Main'; +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import { setView } from '../actions'; +import * as views from '../enums/views'; class MainContainer extends React.Component { + resetView = () => { + this.props.setView(views.SIGN_IN); + }; + render() { - return
; + return
; } } -export default MainContainer; +MainContainer.propTypes = { + view: PropTypes.string.isRequired, + setView: PropTypes.func.isRequired, +}; + +const mapStateToProps = ({ talkPluginAuth: state }) => ({ + view: state.view, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + setView, + }, + dispatch + ); + +export default connect(mapStateToProps, mapDispatchToProps)(MainContainer); diff --git a/plugins/talk-plugin-auth/client/login/containers/SignIn.js b/plugins/talk-plugin-auth/client/login/containers/SignIn.js index 14d87bf0f..7247a4ffe 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignIn.js @@ -3,34 +3,36 @@ import PropTypes from 'prop-types'; import { withSignIn } from 'coral-framework/hocs'; import { compose } from 'recompose'; import SignIn from '../components/SignIn'; +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import * as views from '../enums/views'; +import { setView, setEmail, setPassword } from '../actions'; class SignInContainer extends Component { state = { - email: '', - password: '', recaptchaResponse: '', }; handleSubmit = () => { this.props.signIn( - this.state.email, - this.state.password, + this.props.email, + this.props.password, this.state.recaptchaResponse ); }; - handleEmailChange = email => { - this.setState({ email }); - }; - - handlePasswordChange = password => { - this.setState({ password }); - }; - handleRecaptchaVerify = recaptchaResponse => { this.setState({ recaptchaResponse }); }; + handleForgotPasswordLink = () => { + this.props.setView(views.FORGOT_PASSWORD); + }; + + handleSignUpLink = () => { + this.props.setView(views.SIGN_UP); + }; + componentWillReceiveProps(nextProps) { if (nextProps.success) { window.close(); @@ -41,8 +43,10 @@ class SignInContainer extends Component { return ( ({ + email: state.email, + password: state.password, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + setView, + setEmail, + setPassword, + }, + dispatch + ); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withSignIn +)(SignInContainer); diff --git a/plugins/talk-plugin-auth/client/login/enums/views.js b/plugins/talk-plugin-auth/client/login/enums/views.js new file mode 100644 index 000000000..4f92f4439 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/enums/views.js @@ -0,0 +1,4 @@ +export const SIGN_IN = 'SIGN_IN'; +export const FORGOT_PASSWORD = 'FORGOT_PASSWORD'; +export const SIGN_UP = 'SIGN_UP'; +export const VERIFY_EMAIL = 'VERIFY_EMAIL'; diff --git a/plugins/talk-plugin-auth/client/login/reducer.js b/plugins/talk-plugin-auth/client/login/reducer.js new file mode 100644 index 000000000..004aff8f8 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/reducer.js @@ -0,0 +1,30 @@ +import * as actions from './constants'; +import * as views from './enums/views'; + +const initialState = { + view: views.SIGN_IN, + email: '', + password: '', +}; + +export default function reducer(state = initialState, action) { + switch (action.type) { + case actions.SET_VIEW: + return { + ...state, + view: action.view, + }; + case actions.SET_EMAIL: + return { + ...state, + email: action.email, + }; + case actions.SET_PASSWORD: + return { + ...state, + password: action.password, + }; + default: + return state; + } +}