From f8e055690419d30e228083e2e711761c2c514af9 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 6 Feb 2018 20:30:31 +0100 Subject: [PATCH 01/37] Auth Refactor part 1 --- client/coral-admin/src/components/Drawer.js | 10 +- client/coral-admin/src/components/Header.js | 10 +- client/coral-admin/src/components/Layout.js | 12 ++- client/coral-admin/src/components/Login.css | 37 +++++++ client/coral-admin/src/components/Login.js | 89 +++++++++++++++++ .../coral-admin/src/components/NotFound.css | 32 ------ client/coral-admin/src/containers/Layout.js | 99 +++++++------------ client/coral-admin/src/containers/Login.js | 44 +++++++++ .../routes/Configure/containers/Configure.js | 2 +- .../Moderation/containers/Moderation.js | 2 +- client/coral-framework/actions/auth.js | 97 ++++++++++++++++++ client/coral-framework/constants/auth.js | 8 ++ client/coral-framework/hocs/index.js | 1 + client/coral-framework/hocs/withLogin.js | 75 ++++++++++++++ client/coral-framework/reducers/auth.js | 42 ++++++++ client/coral-framework/services/bootstrap.js | 5 + client/coral-framework/utils/index.js | 9 ++ 17 files changed, 463 insertions(+), 111 deletions(-) create mode 100644 client/coral-admin/src/components/Login.css create mode 100644 client/coral-admin/src/components/Login.js create mode 100644 client/coral-admin/src/containers/Login.js create mode 100644 client/coral-framework/actions/auth.js create mode 100644 client/coral-framework/constants/auth.js create mode 100644 client/coral-framework/hocs/withLogin.js create mode 100644 client/coral-framework/reducers/auth.js diff --git a/client/coral-admin/src/components/Drawer.js b/client/coral-admin/src/components/Drawer.js index 60973e8c2..29365bc6d 100644 --- a/client/coral-admin/src/components/Drawer.js +++ b/client/coral-admin/src/components/Drawer.js @@ -7,12 +7,12 @@ import t from 'coral-framework/services/i18n'; import { can } from 'coral-framework/services/perms'; import cn from 'classnames'; -const CoralDrawer = ({ handleLogout, auth = {} }) => ( +const CoralDrawer = ({ handleLogout, currentUser }) => ( - {auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? ( + {currentUser && can(currentUser, 'ACCESS_ADMIN') ? (
- {can(auth.user, 'MODERATE_COMMENTS') && ( + {can(currentUser, 'MODERATE_COMMENTS') && ( ( > {t('configure.community')} - {can(auth.user, 'UPDATE_CONFIG') && ( + {can(currentUser, 'UPDATE_CONFIG') && ( ( CoralDrawer.propTypes = { handleLogout: PropTypes.func.isRequired, restricted: PropTypes.bool, // hide app elements from a logged out user - auth: PropTypes.object, + currentUser: PropTypes.object, }; export default CoralDrawer; diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index 6b2274d27..731c9b228 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -13,7 +13,7 @@ import CommunityIndicator from '../routes/Community/containers/Indicator'; const CoralHeader = ({ handleLogout, showShortcuts = () => {}, - auth, + currentUser, root, data, }) => { @@ -22,9 +22,9 @@ const CoralHeader = ({
- {auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? ( + {currentUser && can(currentUser, 'ACCESS_ADMIN') ? ( - {can(auth.user, 'MODERATE_COMMENTS') && ( + {can(currentUser, 'MODERATE_COMMENTS') && ( - {can(auth.user, 'UPDATE_CONFIG') && ( + {can(currentUser, 'UPDATE_CONFIG') && ( {}, toggleShortcutModal = () => {}, restricted = false, - auth, + currentUser, }) => (
+ -
{children}
); Layout.propTypes = { children: PropTypes.node, - auth: PropTypes.object, + currentUser: PropTypes.object, handleLogout: PropTypes.func, toggleShortcutModal: PropTypes.func, restricted: PropTypes.bool, // hide elements from a user that's logged out diff --git a/client/coral-admin/src/components/Login.css b/client/coral-admin/src/components/Login.css new file mode 100644 index 000000000..9977f858a --- /dev/null +++ b/client/coral-admin/src/components/Login.css @@ -0,0 +1,37 @@ +.layout { + max-width: 800px; + margin: 0 auto; +} + +.loginLayout { + max-width: 400px; + margin: 0 auto; +} + +.loginHeader, .loginCTA, .forgotPasswordCTA, .passwordRequestSuccess { + 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 { + 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 new file mode 100644 index 000000000..66ab81e78 --- /dev/null +++ b/client/coral-admin/src/components/Login.js @@ -0,0 +1,89 @@ +import React from 'react'; +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 cn from 'classnames'; + +class AdminLogin extends React.Component { + constructor(props) { + super(props); + } + + 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. + +

+ +
+ + ); + } +} + +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, +}; + +export default AdminLogin; diff --git a/client/coral-admin/src/components/NotFound.css b/client/coral-admin/src/components/NotFound.css index 9977f858a..6743a144d 100644 --- a/client/coral-admin/src/components/NotFound.css +++ b/client/coral-admin/src/components/NotFound.css @@ -3,35 +3,3 @@ margin: 0 auto; } -.loginLayout { - max-width: 400px; - margin: 0 auto; -} - -.loginHeader, .loginCTA, .forgotPasswordCTA, .passwordRequestSuccess { - 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 { - font-size: 30px; -} - -.passwordRequestSuccess { - cursor: pointer; - padding: 8px 14px; -} diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index c06e0739b..ea98000b8 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -3,85 +3,64 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Layout from '../components/Layout'; import { fetchConfig } from '../actions/config'; -import AdminLogin from '../components/AdminLogin'; +import Login from '../containers/Login'; import { FullLoading } from '../components/FullLoading'; import BanUserDialog from './BanUserDialog'; import SuspendUserDialog from './SuspendUserDialog'; import { toggleModal as toggleShortcutModal } from '../actions/moderation'; -import { - checkLogin, - handleLogin, - requestPasswordReset, - logout, -} from '../actions/auth'; +import { logout } from 'coral-framework/actions/auth'; import { can } from 'coral-framework/services/perms'; import UserDetail from 'coral-admin/src/containers/UserDetail'; import PropTypes from 'prop-types'; class LayoutContainer extends React.Component { componentWillMount() { - const { checkLogin, fetchConfig } = this.props; + const { fetchConfig } = this.props; - checkLogin(); fetchConfig(); } render() { const { - user, - loggedIn, - loadingUser, - loginError, - loginMaxExceeded, - passwordRequestSuccess, - } = this.props.auth; - - const { + currentUser, + checkedInitialLogin, children, logout, toggleShortcutModal, - TALK_RECAPTCHA_PUBLIC, } = this.props; - if (loadingUser) { + if (!checkedInitialLogin) { return ; } - if (!loggedIn) { - return ( - - ); + if (!currentUser) { + return ; } - if (can(user, 'ACCESS_ADMIN') && loggedIn) { - return ( - - - - - {children} - - ); - } else if (loggedIn) { - return ( - -

- This page is for team use only. Please contact an administrator if - you want to join this team. -

-
- ); + if (currentUser) { + if (can(currentUser, 'ACCESS_ADMIN')) { + return ( + + + + + {children} + + ); + } else { + return ( + +

+ This page is for team use only. Please contact an administrator if + you want to join this team. +

+
+ ); + } } return ; } @@ -89,29 +68,23 @@ class LayoutContainer extends React.Component { LayoutContainer.propTypes = { children: PropTypes.node, - requestPasswordReset: PropTypes.func, - handleLogin: PropTypes.func, - auth: PropTypes.object, - handleLogout: PropTypes.func, + currentUser: PropTypes.object, + checkedInitialLogin: PropTypes.bool, logout: PropTypes.func, toggleShortcutModal: PropTypes.func, TALK_RECAPTCHA_PUBLIC: PropTypes.string, - checkLogin: PropTypes.func, fetchConfig: PropTypes.func, }; const mapStateToProps = state => ({ - auth: state.auth, - TALK_RECAPTCHA_PUBLIC: state.config.data.TALK_RECAPTCHA_PUBLIC, + currentUser: state.authCore.user, + checkedInitialLogin: state.authCore.checkedInitialLogin, }); const mapDispatchToProps = dispatch => bindActionCreators( { - checkLogin, fetchConfig, - handleLogin, - requestPasswordReset, toggleShortcutModal, logout, }, diff --git a/client/coral-admin/src/containers/Login.js b/client/coral-admin/src/containers/Login.js new file mode 100644 index 000000000..3d32a2af2 --- /dev/null +++ b/client/coral-admin/src/containers/Login.js @@ -0,0 +1,44 @@ +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: '', + }; + + handleSubmit = () => { + this.props.login(this.state.email, this.state.password); + }; + + handleEmailChange = email => { + this.setState({ email }); + }; + + handlePasswordChange = password => { + this.setState({ password }); + }; + + render() { + return ( + + ); + } +} + +LoginContainer.propTypes = { + login: PropTypes.func, + errorMessage: PropTypes.string, +}; + +export default compose(withLogin)(LoginContainer); diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js index ce33fa1f4..ee39c584a 100644 --- a/client/coral-admin/src/routes/Configure/containers/Configure.js +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -71,7 +71,7 @@ const withConfigureQuery = withQuery( ); const mapStateToProps = state => ({ - auth: state.auth, + auth: state.authCore, pending: state.configure.pending, canSave: state.configure.canSave, activeSection: state.configure.activeSection, diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index c0e2e60a6..01fd82c8a 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -515,7 +515,7 @@ const withModQueueQuery = withQuery( const mapStateToProps = state => ({ moderation: state.moderation, - auth: state.auth, + auth: state.authCore, }); const mapDispatchToProps = dispatch => ({ diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js new file mode 100644 index 000000000..46d8c3835 --- /dev/null +++ b/client/coral-framework/actions/auth.js @@ -0,0 +1,97 @@ +import * as actions from '../constants/auth'; +import jwtDecode from 'jwt-decode'; + +function cleanAuthData(storage) { + storage.removeItem('token'); + storage.removeItem('exp'); +} + +/** + * Check Login + */ +export const checkLogin = () => ( + dispatch, + _, + { rest, client, pym, storage } +) => { + dispatch(checkLoginRequest()); + rest('/auth') + .then(result => { + if (!result.user) { + if (storage) { + cleanAuthData(storage); + } + dispatch(checkLoginSuccess(null)); + return; + } + + // Reset the websocket. + client.resetWebsocket(); + + dispatch(checkLoginSuccess(result.user)); + pym.sendMessage('coral-auth-changed', JSON.stringify(result.user)); + }) + .catch(error => { + if (error.status && error.status === 401 && storage) { + // Unauthorized. + cleanAuthData(storage); + } else { + console.error(error); + } + dispatch(checkLoginFailure(error)); + }); +}; + +const checkLoginRequest = () => ({ type: actions.CHECK_LOGIN_REQUEST }); + +const checkLoginFailure = error => ({ + type: actions.CHECK_LOGIN_FAILURE, + error, +}); + +const checkLoginSuccess = user => ({ + type: actions.CHECK_LOGIN_SUCCESS, + user, +}); + +/** + * Login + */ +export const handleSuccessfulLogin = (user, token) => ( + dispatch, + _, + { client, storage } +) => { + if (storage) { + storage.setItem('exp', jwtDecode(token).exp); + storage.setItem('token', token); + } + + client.resetWebsocket(); + + dispatch({ + type: actions.HANDLE_SUCCESSFUL_LOGIN, + user, + }); +}; + +/** + * Logout + */ +export const logout = () => async ( + dispatch, + _, + { rest, client, pym, storage } +) => { + await rest('/auth', { method: 'DELETE' }); + + if (storage) { + cleanAuthData(storage); + } + + // Reset the websocket. + client.resetWebsocket(); + + dispatch({ type: actions.LOGOUT }); + pym.sendMessage('coral-auth-changed'); +}; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js new file mode 100644 index 000000000..655c29260 --- /dev/null +++ b/client/coral-framework/constants/auth.js @@ -0,0 +1,8 @@ +const prefix = `TALK_FRAMEWORK`; + +export const CHECK_LOGIN_REQUEST = `${prefix}_CHECK_LOGIN_REQUEST`; +export const CHECK_LOGIN_SUCCESS = `${prefix}_CHECK_LOGIN_SUCCESS`; +export const CHECK_LOGIN_FAILURE = `${prefix}_CHECK_LOGIN_FAILURE`; + +export const LOGOUT = `${prefix}_LOGOUT`; +export const HANDLE_SUCCESSFUL_LOGIN = `${prefix}_HANDLE_SUCCESSFUL_LOGIN`; diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index 174f25a63..c871306ae 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -6,3 +6,4 @@ 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'; diff --git a/client/coral-framework/hocs/withLogin.js b/client/coral-framework/hocs/withLogin.js new file mode 100644 index 000000000..56586b7af --- /dev/null +++ b/client/coral-framework/hocs/withLogin.js @@ -0,0 +1,75 @@ +import React from 'react'; +import hoistStatics from 'recompose/hoistStatics'; +import PropTypes from 'prop-types'; +import { handleSuccessfulLogin } from '../actions/auth'; +import { translateError } from '../utils'; +import { t } from '../services/i18n'; + +/** + * WithLogin provides properties `login`, `loading` and `errorMessage`, `requireRecaptcha`. + */ +export default hoistStatics(WrappedComponent => { + class WithLogin extends React.Component { + static contextTypes = { + store: PropTypes.object, + rest: PropTypes.func, + }; + + state = { + error: null, + loading: false, + }; + + login = (email, password, recaptchaResponse) => { + const { store, rest } = this.context; + const params = { + method: 'POST', + body: { + email, + password, + }, + }; + + if (recaptchaResponse) { + params.headers = { + 'X-Recaptcha-Response': recaptchaResponse, + }; + } + + rest('/auth/local', params) + .then(({ user, token }) => { + this.setState({ loading: false, error: null }); + store.dispatch(handleSuccessfulLogin(user, token)); + }) + .catch(error => { + if (!error.status || error.status !== 401) { + console.error(error); + } + this.setState({ loading: false, error }); + }); + }; + + getErrorMessage() { + if (!this.state.error) { + return ''; + } + return this.state.error.translation_key === 'NOT_AUTHORIZED' + ? t('error.email_password') + : translateError(this.state.error); + } + + render() { + return ( + + ); + } + } + + return WithLogin; +}); diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js new file mode 100644 index 000000000..04e75bbb4 --- /dev/null +++ b/client/coral-framework/reducers/auth.js @@ -0,0 +1,42 @@ +import * as actions from '../constants/auth'; + +const initialState = { + checkedInitialLogin: false, + initialLoginError: null, + user: null, +}; + +const purge = user => { + const {settings, ...userData} = user; // eslint-disable-line + return userData; +}; + +export default function auth(state = initialState, action) { + switch (action.type) { + case actions.CHECK_LOGIN_FAILURE: + return { + ...state, + initialLoginError: action.error, + checkedInitialLogin: true, + user: null, + }; + case actions.CHECK_LOGIN_SUCCESS: + return { + ...state, + checkedInitialLogin: true, + user: action.user ? purge(action.user) : null, + }; + case actions.HANDLE_SUCCESSFUL_LOGIN: + return { + ...state, + user: action.user ? purge(action.user) : null, + }; + case actions.LOGOUT: + return { + ...state, + user: null, + }; + default: + return state; + } +} diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 66da3ff29..18bacdd7f 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -22,6 +22,8 @@ import { import { createHistory } from 'coral-framework/services/history'; import { createIntrospection } from 'coral-framework/services/introspection'; import introspectionData from 'coral-framework/graphql/introspection.json'; +import auth from '../reducers/auth'; +import { checkLogin } from '../actions/auth'; /** * getAuthToken returns the active auth token or null @@ -143,6 +145,7 @@ export async function createContext({ // Create our redux store. const finalReducers = { + authCore: auth, ...reducers, ...plugins.getReducers(), }; @@ -162,6 +165,8 @@ export async function createContext({ [client.middleware(), apolloErrorReporter, createReduxEmitter(eventEmitter)] ); + store.dispatch(checkLogin()); + // Run pre initialization. if (preInit) { await preInit(context); diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index d53b9b496..66792b9b3 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -252,3 +252,12 @@ export function mapLeaves(o, mapper) { return mapper(val); }); } + +export function translateError(error) { + if (error.translation_key) { + return t(`error.${error.translation_key}`); + } else if (error.networkError) { + return t('error.network_error'); + } + return error.toString(); +} From ee3d576a49ea0dfa79cab647992c0638761fdd70 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 18:42:03 +0100 Subject: [PATCH 02/37] 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; }); From ffbcd93c3f43061fb9e158c2541e3814b1859b9c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 19:04:35 +0100 Subject: [PATCH 03/37] Adapt to master changes --- .../src/components/ForgotPassword.js | 3 +- client/coral-admin/src/components/Header.js | 14 +++++---- client/coral-framework/actions/auth.js | 30 +++++++++---------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/client/coral-admin/src/components/ForgotPassword.js b/client/coral-admin/src/components/ForgotPassword.js index 1237fd7fb..3bd9804f5 100644 --- a/client/coral-admin/src/components/ForgotPassword.js +++ b/client/coral-admin/src/components/ForgotPassword.js @@ -23,7 +23,8 @@ class ForgotPassword extends React.Component { renderSuccess() { return (
- {this.props.success}{' '} + If you have a registered account, a password reset link was sent to that + email.{' '} - - {t('configure.sign_out')} - + {currentUser && ( + + {t('configure.sign_out')} + + )}
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 46d8c3835..c72bd8e68 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -1,9 +1,9 @@ import * as actions from '../constants/auth'; import jwtDecode from 'jwt-decode'; -function cleanAuthData(storage) { - storage.removeItem('token'); - storage.removeItem('exp'); +function cleanAuthData(localStorage) { + localStorage.removeItem('token'); + localStorage.removeItem('exp'); } /** @@ -12,14 +12,14 @@ function cleanAuthData(storage) { export const checkLogin = () => ( dispatch, _, - { rest, client, pym, storage } + { rest, client, pym, localStorage } ) => { dispatch(checkLoginRequest()); rest('/auth') .then(result => { if (!result.user) { - if (storage) { - cleanAuthData(storage); + if (localStorage) { + cleanAuthData(localStorage); } dispatch(checkLoginSuccess(null)); return; @@ -32,9 +32,9 @@ export const checkLogin = () => ( pym.sendMessage('coral-auth-changed', JSON.stringify(result.user)); }) .catch(error => { - if (error.status && error.status === 401 && storage) { + if (error.status && error.status === 401 && localStorage) { // Unauthorized. - cleanAuthData(storage); + cleanAuthData(localStorage); } else { console.error(error); } @@ -60,11 +60,11 @@ const checkLoginSuccess = user => ({ export const handleSuccessfulLogin = (user, token) => ( dispatch, _, - { client, storage } + { client, localStorage } ) => { - if (storage) { - storage.setItem('exp', jwtDecode(token).exp); - storage.setItem('token', token); + if (localStorage) { + localStorage.setItem('exp', jwtDecode(token).exp); + localStorage.setItem('token', token); } client.resetWebsocket(); @@ -81,12 +81,12 @@ export const handleSuccessfulLogin = (user, token) => ( export const logout = () => async ( dispatch, _, - { rest, client, pym, storage } + { rest, client, pym, localStorage } ) => { await rest('/auth', { method: 'DELETE' }); - if (storage) { - cleanAuthData(storage); + if (localStorage) { + cleanAuthData(localStorage); } // Reset the websocket. From bad036362868adefea89b00275225aa220c58cb2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 21:08:37 +0100 Subject: [PATCH 04/37] Save static config to redux --- client/coral-framework/actions/static.js | 6 ++++++ client/coral-framework/constants/static.js | 3 +++ client/coral-framework/reducers/index.js | 7 +++++++ client/coral-framework/reducers/static.js | 15 +++++++++++++++ client/coral-framework/services/bootstrap.js | 10 +++++++--- 5 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 client/coral-framework/actions/static.js create mode 100644 client/coral-framework/constants/static.js create mode 100644 client/coral-framework/reducers/index.js create mode 100644 client/coral-framework/reducers/static.js diff --git a/client/coral-framework/actions/static.js b/client/coral-framework/actions/static.js new file mode 100644 index 000000000..fd28ad39d --- /dev/null +++ b/client/coral-framework/actions/static.js @@ -0,0 +1,6 @@ +import * as actions from '../constants/static'; + +export const setStaticConfiguration = config => ({ + type: actions.SET_STATIC_CONFIGURATION, + config, +}); diff --git a/client/coral-framework/constants/static.js b/client/coral-framework/constants/static.js new file mode 100644 index 000000000..5f3804662 --- /dev/null +++ b/client/coral-framework/constants/static.js @@ -0,0 +1,3 @@ +const prefix = `TALK_FRAMEWORK`; + +export const SET_STATIC_CONFIGURATION = `${prefix}_SET_STATIC_CONFIGURATION`; diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js new file mode 100644 index 000000000..d54932b9b --- /dev/null +++ b/client/coral-framework/reducers/index.js @@ -0,0 +1,7 @@ +import auth from './auth'; +import staticConfiguration from './static'; + +export default { + auth, + static: staticConfiguration, +}; diff --git a/client/coral-framework/reducers/static.js b/client/coral-framework/reducers/static.js new file mode 100644 index 000000000..c8bc1afc2 --- /dev/null +++ b/client/coral-framework/reducers/static.js @@ -0,0 +1,15 @@ +import * as actions from '../constants/static'; + +const initialState = {}; + +export default function auth(state = initialState, action) { + switch (action.type) { + case actions.SET_STATIC_CONFIGURATION: + return { + ...state, + ...action.config, + }; + default: + return state; + } +} diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index f0ab7c41f..9b5982edc 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -22,8 +22,9 @@ import { import { createHistory } from 'coral-framework/services/history'; import { createIntrospection } from 'coral-framework/services/introspection'; import introspectionData from 'coral-framework/graphql/introspection.json'; -import auth from '../reducers/auth'; +import coreReducers from '../reducers'; import { checkLogin } from '../actions/auth'; +import { setStaticConfiguration } from '../actions/static'; /** * getAuthToken returns the active auth token or null @@ -100,7 +101,8 @@ export async function createContext({ token, }); - let { LIVE_URI: liveUri } = getStaticConfiguration(); + const staticConfig = getStaticConfiguration(); + let { LIVE_URI: liveUri } = staticConfig; if (liveUri == null) { // The protocol must match the origin protocol, secure/insecure. const protocol = location.protocol === 'https:' ? 'wss' : 'ws'; @@ -163,7 +165,8 @@ export async function createContext({ // Create our redux store. const finalReducers = { - authCore: auth, + authCore: coreReducers.auth, + static: coreReducers.static, ...reducers, ...plugins.getReducers(), }; @@ -183,6 +186,7 @@ export async function createContext({ [client.middleware(), apolloErrorReporter, createReduxEmitter(eventEmitter)] ); + store.dispatch(setStaticConfiguration(staticConfig)); store.dispatch(checkLogin()); // Run pre initialization. From aaad7aa86f61a8c86b271ce6ec63f9488c02ab8e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 22:10:05 +0100 Subject: [PATCH 05/37] Reimplement recaptcha --- client/coral-admin/src/actions/config.js | 7 --- client/coral-admin/src/components/SignIn.js | 27 ++++++++-- client/coral-admin/src/containers/Layout.js | 10 ---- client/coral-admin/src/containers/SignIn.js | 12 ++++- client/coral-admin/src/reducers/config.js | 17 ------ client/coral-admin/src/reducers/index.js | 2 - .../coral-framework/components/Recaptcha.js | 52 +++++++++++++++++++ client/coral-framework/components/Slot.js | 3 +- client/coral-framework/hocs/withSignIn.js | 10 +++- client/coral-framework/services/bootstrap.js | 2 +- client/coral-framework/services/plugins.js | 5 +- 11 files changed, 100 insertions(+), 47 deletions(-) delete mode 100644 client/coral-admin/src/actions/config.js delete mode 100644 client/coral-admin/src/reducers/config.js create mode 100644 client/coral-framework/components/Recaptcha.js diff --git a/client/coral-admin/src/actions/config.js b/client/coral-admin/src/actions/config.js deleted file mode 100644 index 5e2995e85..000000000 --- a/client/coral-admin/src/actions/config.js +++ /dev/null @@ -1,7 +0,0 @@ -export const CONFIG_UPDATED = 'CONFIG_UPDATED'; - -export const fetchConfig = () => dispatch => { - let json = document.getElementById('data'); - let data = JSON.parse(json.textContent); - dispatch({ type: CONFIG_UPDATED, data }); -}; diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index 71f14d12a..85bfb9810 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -2,11 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './SignIn.css'; import { Button, TextField, Alert } from 'coral-ui'; +import Recaptcha from 'coral-framework/components/Recaptcha'; class SignIn extends React.Component { - constructor(props) { - super(props); - } + recaptcha = null; handleForgotPasswordLink = e => { e.preventDefault(); @@ -18,10 +17,23 @@ class SignIn extends React.Component { handleSubmit = e => { e.preventDefault(); this.props.onSubmit(); + + // Reset recaptcha because each response can only + // be used once. + if (this.recaptcha) { + this.recaptcha.reset(); + } + }; + + handleRecaptchaRef = ref => { + this.recaptcha = ref; + setTimeout(() => { + console.log(ref); + }, 1000) }; render() { - const { email, password, errorMessage } = this.props; + const { email, password, errorMessage, requireRecaptcha } = this.props; return (
{errorMessage && {errorMessage}} @@ -57,6 +69,12 @@ class SignIn extends React.Component { Request a new one.

+ {requireRecaptcha && ( + + )} ); } @@ -68,6 +86,7 @@ SignIn.propTypes = { onEmailChange: PropTypes.func.isRequired, onPasswordChange: PropTypes.func.isRequired, onForgotPasswordLink: PropTypes.func.isRequired, + onRecaptchaVerify: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, errorMessage: PropTypes.string.isRequired, requireRecaptcha: PropTypes.bool.isRequired, diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index ea98000b8..484467936 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -2,7 +2,6 @@ import React from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Layout from '../components/Layout'; -import { fetchConfig } from '../actions/config'; import Login from '../containers/Login'; import { FullLoading } from '../components/FullLoading'; import BanUserDialog from './BanUserDialog'; @@ -14,12 +13,6 @@ import UserDetail from 'coral-admin/src/containers/UserDetail'; import PropTypes from 'prop-types'; class LayoutContainer extends React.Component { - componentWillMount() { - const { fetchConfig } = this.props; - - fetchConfig(); - } - render() { const { currentUser, @@ -72,8 +65,6 @@ LayoutContainer.propTypes = { checkedInitialLogin: PropTypes.bool, logout: PropTypes.func, toggleShortcutModal: PropTypes.func, - TALK_RECAPTCHA_PUBLIC: PropTypes.string, - fetchConfig: PropTypes.func, }; const mapStateToProps = state => ({ @@ -84,7 +75,6 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => bindActionCreators( { - fetchConfig, toggleShortcutModal, logout, }, diff --git a/client/coral-admin/src/containers/SignIn.js b/client/coral-admin/src/containers/SignIn.js index ab7886c3a..02dc8ba01 100644 --- a/client/coral-admin/src/containers/SignIn.js +++ b/client/coral-admin/src/containers/SignIn.js @@ -8,10 +8,15 @@ class SignInContainer extends Component { state = { email: '', password: '', + recaptchaResponse: '', }; handleSubmit = () => { - this.props.signIn(this.state.email, this.state.password); + this.props.signIn( + this.state.email, + this.state.password, + this.state.recaptchaResponse + ); }; handleEmailChange = email => { @@ -22,6 +27,10 @@ class SignInContainer extends Component { this.setState({ password }); }; + handleRecaptchaVerify = recaptchaResponse => { + this.setState({ recaptchaResponse }); + }; + render() { return ( ); diff --git a/client/coral-admin/src/reducers/config.js b/client/coral-admin/src/reducers/config.js deleted file mode 100644 index 0588f0a13..000000000 --- a/client/coral-admin/src/reducers/config.js +++ /dev/null @@ -1,17 +0,0 @@ -import * as actions from '../actions/config'; - -const initialState = { - data: {}, -}; - -export default function config(state = initialState, action) { - switch (action.type) { - case actions.CONFIG_UPDATED: - return { - ...state, - data: action.data, - }; - default: - return state; - } -} diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 11801a379..9175698cf 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -4,7 +4,6 @@ import configure from './configure'; import community from './community'; import moderation from './moderation'; import install from './install'; -import config from './config'; import banUserDialog from './banUserDialog'; import suspendUserDialog from './suspendUserDialog'; import userDetail from './userDetail'; @@ -20,6 +19,5 @@ export default { community, moderation, install, - config, ui, }; diff --git a/client/coral-framework/components/Recaptcha.js b/client/coral-framework/components/Recaptcha.js new file mode 100644 index 000000000..f0825df30 --- /dev/null +++ b/client/coral-framework/components/Recaptcha.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import ReactRecaptcha from 'react-recaptcha'; + +class Recaptcha extends React.Component { + static contextTypes = { + store: PropTypes.object, + }; + + ref = null; + + handleRef = ref => { + this.ref = ref; + }; + + reset = () => this.ref.reset(); + + getSiteKey() { + // This should be fine because it's static and will never change. + // Prefer this to connect HOC because wie expose the instance method + // `reset` + return this.context.store.getState().static.TALK_RECAPTCHA_PUBLIC; + } + + render() { + return ( + + ); + } +} + +Recaptcha.defaultProps = { + render: 'explicit', + theme: 'dark', +}; + +Recaptcha.propTypes = { + onLoad: PropTypes.func, + onVerify: PropTypes.func.isRequired, + theme: PropTypes.string, + render: PropTypes.string, + sitekey: PropTypes.string.isRequired, +}; + +export default Recaptcha; diff --git a/client/coral-framework/components/Slot.js b/client/coral-framework/components/Slot.js index af524b21d..fe63b4f30 100644 --- a/client/coral-framework/components/Slot.js +++ b/client/coral-framework/components/Slot.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import kebabCase from 'lodash/kebabCase'; import PropTypes from 'prop-types'; import isEqual from 'lodash/isEqual'; +import get from 'lodash/get'; import { getShallowChanges } from 'coral-framework/utils'; const emptyConfig = {}; @@ -68,7 +69,7 @@ class Slot extends React.Component { } = this.props; const { plugins } = this.context; let children = this.getChildren(); - const pluginConfig = reduxState.config.pluginConfig || emptyConfig; + const pluginConfig = get(reduxState, 'config.pluginConfig') || emptyConfig; if (children.length === 0 && DefaultComponent) { const props = plugins.getSlotComponentProps( DefaultComponent, diff --git a/client/coral-framework/hocs/withSignIn.js b/client/coral-framework/hocs/withSignIn.js index a725f01e9..02c631068 100644 --- a/client/coral-framework/hocs/withSignIn.js +++ b/client/coral-framework/hocs/withSignIn.js @@ -18,6 +18,7 @@ export default hoistStatics(WrappedComponent => { state = { error: null, loading: false, + requireRecaptcha: false, }; signIn = (email, password, recaptchaResponse) => { @@ -45,7 +46,12 @@ export default hoistStatics(WrappedComponent => { if (!error.status || error.status !== 401) { console.error(error); } - this.setState({ loading: false, error }); + const changeSet = { loading: false, error }; + if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { + changeSet.requireRecaptcha = !!this.context.store.getState().static + .TALK_RECAPTCHA_PUBLIC; + } + this.setState(changeSet); }); }; @@ -65,7 +71,7 @@ export default hoistStatics(WrappedComponent => { signIn={this.signIn} loading={this.state.loading} errorMessage={this.getErrorMessage()} - requireRecaptcha={false} + requireRecaptcha={this.state.requireRecaptcha} /> ); } diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 9b5982edc..fa1f907af 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -35,7 +35,7 @@ import { setStaticConfiguration } from '../actions/static'; const getAuthToken = (store, storage) => { let state = store.getState(); - if (state.config.auth_token) { + if (state.config && state.config.auth_token) { // if an auth_token exists in config, use it. return state.config.auth_token; } else if (!bowser.safari && !bowser.ios && storage) { diff --git a/client/coral-framework/services/plugins.js b/client/coral-framework/services/plugins.js index 8f9fa8080..6f99a2291 100644 --- a/client/coral-framework/services/plugins.js +++ b/client/coral-framework/services/plugins.js @@ -6,6 +6,7 @@ import flattenDeep from 'lodash/flattenDeep'; import isEmpty from 'lodash/isEmpty'; import flatten from 'lodash/flatten'; import mapValues from 'lodash/mapValues'; +import get from 'lodash/get'; import { getDisplayName } from 'coral-framework/helpers/hoc'; import camelize from '../helpers/camelize'; @@ -83,7 +84,7 @@ class PluginsService { * query datas are only passed to the component if it is defined in `component.fragments`. */ getSlotComponentProps(component, reduxState, props, queryData) { - const pluginConfig = reduxState.config.plugin_config || emptyConfig; + const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; return { ...props, config: pluginConfig, @@ -97,7 +98,7 @@ class PluginsService { * Returns React Elements for given slot. */ getSlotElements(slot, reduxState, props = {}, queryData = {}) { - const pluginConfig = reduxState.config.plugin_config || emptyConfig; + const pluginConfig = get(reduxState, 'config.plugin_config') || emptyConfig; const isDisabled = component => { if ( From d526a404aeea35530578118783e6fc7c9397e8f3 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 7 Feb 2018 22:17:52 +0100 Subject: [PATCH 06/37] Finish admin auth refactor --- client/coral-admin/src/actions/auth.js | 181 ------------------ client/coral-admin/src/containers/Layout.js | 4 +- client/coral-admin/src/reducers/auth.js | 65 ------- client/coral-admin/src/reducers/index.js | 2 - .../routes/Configure/components/Configure.js | 6 +- .../routes/Configure/containers/Configure.js | 6 +- .../Moderation/components/Moderation.js | 4 +- .../Moderation/containers/Moderation.js | 8 +- client/coral-framework/services/bootstrap.js | 2 +- 9 files changed, 15 insertions(+), 263 deletions(-) delete mode 100644 client/coral-admin/src/actions/auth.js delete mode 100644 client/coral-admin/src/reducers/auth.js diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js deleted file mode 100644 index 193c6ac44..000000000 --- a/client/coral-admin/src/actions/auth.js +++ /dev/null @@ -1,181 +0,0 @@ -import bowser from 'bowser'; -import * as actions from '../constants/auth'; -import t from 'coral-framework/services/i18n'; -import jwtDecode from 'jwt-decode'; - -//============================================================================== -// SIGN IN -//============================================================================== - -export const handleLogin = (email, password, recaptchaResponse) => ( - dispatch, - _, - { rest, client, localStorage } -) => { - dispatch({ type: actions.LOGIN_REQUEST }); - - const params = { - method: 'POST', - body: { - email, - password, - }, - }; - - if (recaptchaResponse) { - params.headers = { - 'X-Recaptcha-Response': recaptchaResponse, - }; - } - - return rest('/auth/local', params) - .then(({ user, token }) => { - if (!user) { - if (!bowser.safari && !bowser.ios && localStorage) { - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - return dispatch(checkLoginFailure('not logged in')); - } - - dispatch(handleAuthToken(token)); - client.resetWebsocket(); - dispatch(checkLoginSuccess(user)); - }) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - - if (error.translation_key === 'NOT_AUTHORIZED') { - // invalid credentials - dispatch({ - type: actions.LOGIN_FAILURE, - message: t('error.email_password'), - }); - } else if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { - dispatch({ - type: actions.LOGIN_MAXIMUM_EXCEEDED, - message: t(`error.${error.translation_key}`), - }); - } else { - dispatch({ - type: actions.LOGIN_FAILURE, - message: errorMessage, - }); - } - }); -}; - -//============================================================================== -// FORGOT PASSWORD -//============================================================================== - -const forgotPasswordRequest = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_REQUEST, -}); - -const forgotPasswordSuccess = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_SUCCESS, -}); - -const forgotPasswordFailure = error => ({ - type: actions.FETCH_FORGOT_PASSWORD_FAILURE, - error, -}); - -export const requestPasswordReset = email => (dispatch, _, { rest }) => { - dispatch(forgotPasswordRequest(email)); - const redirectUri = location.href; - - return rest('/account/password/reset', { - method: 'POST', - body: { email, loc: redirectUri }, - }) - .then(() => dispatch(forgotPasswordSuccess())) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(forgotPasswordFailure(errorMessage)); - }); -}; - -//============================================================================== -// CHECK LOGIN -//============================================================================== - -const checkLoginRequest = () => ({ - type: actions.CHECK_LOGIN_REQUEST, -}); - -const checkLoginSuccess = (user, isAdmin) => ({ - type: actions.CHECK_LOGIN_SUCCESS, - user, - isAdmin, -}); - -const checkLoginFailure = error => ({ - type: actions.CHECK_LOGIN_FAILURE, - error, -}); - -export const checkLogin = () => ( - dispatch, - _, - { rest, client, localStorage } -) => { - dispatch(checkLoginRequest()); - return rest('/auth') - .then(({ user }) => { - if (!user) { - if (!bowser.safari && !bowser.ios && localStorage) { - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - return dispatch(checkLoginFailure('not logged in')); - } - - client.resetWebsocket(); - dispatch(checkLoginSuccess(user)); - }) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(checkLoginFailure(errorMessage)); - }); -}; - -//============================================================================== -// LOGOUT -//============================================================================== - -export const logout = () => (dispatch, _, { rest, client, localStorage }) => { - return rest('/auth', { method: 'DELETE' }).then(() => { - if (localStorage) { - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - - // Reset the websocket. - client.resetWebsocket(); - - dispatch({ type: actions.LOGOUT }); - }); -}; - -//============================================================================== -// AUTH TOKEN -//============================================================================== - -export const handleAuthToken = token => (dispatch, _, { localStorage }) => { - if (localStorage) { - localStorage.setItem('exp', jwtDecode(token).exp); - localStorage.setItem('token', token); - } - dispatch({ type: 'HANDLE_AUTH_TOKEN' }); -}; diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index 484467936..e8fc7bc49 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -68,8 +68,8 @@ LayoutContainer.propTypes = { }; const mapStateToProps = state => ({ - currentUser: state.authCore.user, - checkedInitialLogin: state.authCore.checkedInitialLogin, + currentUser: state.auth.user, + checkedInitialLogin: state.auth.checkedInitialLogin, }); const mapDispatchToProps = dispatch => diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js deleted file mode 100644 index 14aa2c21c..000000000 --- a/client/coral-admin/src/reducers/auth.js +++ /dev/null @@ -1,65 +0,0 @@ -import * as actions from '../constants/auth'; - -const initialState = { - loggedIn: false, - user: null, - loginError: null, - loginMaxExceeded: false, - passwordRequestSuccess: null, -}; - -export default function auth(state = initialState, action) { - switch (action.type) { - case actions.CHECK_LOGIN_REQUEST: - return { - ...state, - loadingUser: true, - }; - case actions.CHECK_LOGIN_FAILURE: - return { - ...state, - loggedIn: false, - loadingUser: false, - user: null, - }; - case actions.CHECK_LOGIN_SUCCESS: - return { - ...state, - loggedIn: true, - loadingUser: false, - user: action.user, - }; - case actions.LOGOUT: - return initialState; - case actions.LOGIN_SUCCESS: - return { - ...state, - loginMaxExceeded: false, - loginError: null, - }; - case actions.LOGIN_FAILURE: - return { - ...state, - loginError: action.message, - }; - case actions.FETCH_FORGOT_PASSWORD_REQUEST: - return { - ...state, - passwordRequestSuccess: null, - }; - case actions.FETCH_FORGOT_PASSWORD_SUCCESS: - return { - ...state, - passwordRequestSuccess: - 'If you have a registered account, a password reset link was sent to that email.', - }; - case actions.LOGIN_MAXIMUM_EXCEEDED: - return { - ...state, - loginMaxExceeded: true, - loginError: action.message, - }; - default: - return state; - } -} diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 9175698cf..be0a8a606 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,4 +1,3 @@ -import auth from './auth'; import stories from './stories'; import configure from './configure'; import community from './community'; @@ -10,7 +9,6 @@ import userDetail from './userDetail'; import ui from './ui'; export default { - auth, banUserDialog, configure, suspendUserDialog, diff --git a/client/coral-admin/src/routes/Configure/components/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js index f81bf7735..140222f68 100644 --- a/client/coral-admin/src/routes/Configure/components/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -24,7 +24,7 @@ export default class Configure extends Component { render() { const { - auth: { user }, + currentUser, canSave, savePending, setActiveSection, @@ -32,7 +32,7 @@ export default class Configure extends Component { } = this.props; const SectionComponent = this.getSectionComponent(activeSection); - if (!can(user, 'UPDATE_CONFIG')) { + if (!can(currentUser, 'UPDATE_CONFIG')) { return (

You must be an administrator to access config settings. Please find @@ -87,7 +87,7 @@ export default class Configure extends Component { Configure.propTypes = { savePending: PropTypes.func.isRequired, - auth: PropTypes.object.isRequired, + currentUser: PropTypes.object.isRequired, data: PropTypes.object.isRequired, root: PropTypes.object.isRequired, settings: PropTypes.object.isRequired, diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js index ee39c584a..fe87766b6 100644 --- a/client/coral-admin/src/routes/Configure/containers/Configure.js +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -30,7 +30,7 @@ class ConfigureContainer extends Component { return ( ({ - auth: state.authCore, + currentUser: state.auth.user, pending: state.configure.pending, canSave: state.configure.canSave, activeSection: state.configure.activeSection, @@ -97,7 +97,7 @@ ConfigureContainer.propTypes = { updateSettings: PropTypes.func.isRequired, clearPending: PropTypes.func.isRequired, setActiveSection: PropTypes.func.isRequired, - auth: PropTypes.object.isRequired, + currentUser: PropTypes.object.isRequired, data: PropTypes.object.isRequired, root: PropTypes.object.isRequired, canSave: PropTypes.bool.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/components/Moderation.js b/client/coral-admin/src/routes/Moderation/components/Moderation.js index c1de008fe..2d9b793ff 100644 --- a/client/coral-admin/src/routes/Moderation/components/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/components/Moderation.js @@ -185,7 +185,7 @@ class Moderation extends Component { commentBelongToQueue={this.props.commentBelongToQueue} isLoadingMore={this.state.isLoadingMore} commentCount={activeTabCount} - currentUserId={this.props.auth.user.id} + currentUserId={this.props.currentUser.id} viewUserDetail={viewUserDetail} selectCommentId={props.selectCommentId} cleanUpQueue={props.cleanUpQueue} @@ -225,7 +225,7 @@ Moderation.propTypes = { cleanUpQueue: PropTypes.func.isRequired, storySearchChange: PropTypes.func.isRequired, moderation: PropTypes.object.isRequired, - auth: PropTypes.object.isRequired, + currentUser: PropTypes.object.isRequired, queueConfig: PropTypes.object.isRequired, commentBelongToQueue: PropTypes.func.isRequired, handleCommentChange: PropTypes.func.isRequired, diff --git a/client/coral-admin/src/routes/Moderation/containers/Moderation.js b/client/coral-admin/src/routes/Moderation/containers/Moderation.js index 01fd82c8a..f35255732 100644 --- a/client/coral-admin/src/routes/Moderation/containers/Moderation.js +++ b/client/coral-admin/src/routes/Moderation/containers/Moderation.js @@ -110,7 +110,7 @@ class ModerationContainer extends Component { comment.status_history[comment.status_history.length - 1] .assigned_by; const notifyText = - this.props.auth.user.id === user.id + this.props.currentUser.id === user.id ? '' : t( 'modqueue.notify_accepted', @@ -131,7 +131,7 @@ class ModerationContainer extends Component { comment.status_history[comment.status_history.length - 1] .assigned_by; const notifyText = - this.props.auth.user.id === user.id + this.props.currentUser.id === user.id ? '' : t( 'modqueue.notify_rejected', @@ -152,7 +152,7 @@ class ModerationContainer extends Component { comment.status_history[comment.status_history.length - 1] .assigned_by; const notifyText = - this.props.auth.user.id === user.id + this.props.currentUser.id === user.id ? '' : t( 'modqueue.notify_reset', @@ -515,7 +515,7 @@ const withModQueueQuery = withQuery( const mapStateToProps = state => ({ moderation: state.moderation, - auth: state.authCore, + currentUser: state.auth.user, }); const mapDispatchToProps = dispatch => ({ diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index fa1f907af..04b58adb4 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -165,8 +165,8 @@ export async function createContext({ // Create our redux store. const finalReducers = { + ...coreReducers, authCore: coreReducers.auth, - static: coreReducers.static, ...reducers, ...plugins.getReducers(), }; From 37dd6ae044c0f56de17b5f2e0b9f463f6f58a078 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 8 Feb 2018 23:06:01 +0100 Subject: [PATCH 07/37] Auth refactor part 3 --- .../src/actions/{auth.js => login.js} | 2 +- .../src/components/Embed.js | 16 ++-- .../src/constants/{auth.js => login.js} | 0 .../src/containers/Embed.js | 78 ++++++++++++++----- client/coral-embed-stream/src/index.js | 37 --------- .../coral-embed-stream/src/reducers/index.js | 6 +- .../src/reducers/{auth.js => login.js} | 4 +- .../coral-embed-stream/src/reducers/stream.js | 2 +- .../profile/containers/ProfileContainer.js | 32 ++++---- .../src/tabs/stream/components/Stream.js | 35 +++++---- .../src/tabs/stream/containers/Stream.js | 21 +++-- client/coral-framework/actions/auth.js | 15 ++-- client/coral-framework/actions/config.js | 6 ++ client/coral-framework/actions/static.js | 6 -- .../coral-framework/components/Recaptcha.js | 2 +- client/coral-framework/constants/config.js | 3 + client/coral-framework/constants/static.js | 3 - client/coral-framework/hocs/withSignIn.js | 4 +- .../reducers/{static.js => config.js} | 6 +- client/coral-framework/reducers/index.js | 5 +- client/coral-framework/services/bootstrap.js | 56 +++++++++++-- plugin-api/beta/client/hocs/withReaction.js | 3 +- .../client/components/ChangeUsername.js | 2 +- .../client/components/SignInButton.js | 8 +- .../client/components/SignInContainer.js | 2 +- .../client/components/UserBox.js | 2 +- 26 files changed, 195 insertions(+), 161 deletions(-) rename client/coral-embed-stream/src/actions/{auth.js => login.js} (99%) rename client/coral-embed-stream/src/constants/{auth.js => login.js} (100%) rename client/coral-embed-stream/src/reducers/{auth.js => login.js} (98%) create mode 100644 client/coral-framework/actions/config.js delete mode 100644 client/coral-framework/actions/static.js create mode 100644 client/coral-framework/constants/config.js delete mode 100644 client/coral-framework/constants/static.js rename client/coral-framework/reducers/{static.js => config.js} (51%) diff --git a/client/coral-embed-stream/src/actions/auth.js b/client/coral-embed-stream/src/actions/login.js similarity index 99% rename from client/coral-embed-stream/src/actions/auth.js rename to client/coral-embed-stream/src/actions/login.js index d7e2b7826..3a110c1b9 100644 --- a/client/coral-embed-stream/src/actions/auth.js +++ b/client/coral-embed-stream/src/actions/login.js @@ -1,6 +1,6 @@ import jwtDecode from 'jwt-decode'; import bowser from 'bowser'; -import * as actions from '../constants/auth'; +import * as actions from '../constants/login'; import { notify } from 'coral-framework/actions/notification'; import t from 'coral-framework/services/i18n'; import get from 'lodash/get'; diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 0fd257ef2..059e72ed3 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -16,17 +16,10 @@ import cn from 'classnames'; export default class Embed extends React.Component { changeTab = tab => { - // TODO: move data fetching to appropiate containers. - switch (tab) { - case 'profile': - this.props.data.refetch(); - break; - } this.props.setActiveTab(tab); }; getTabs() { - const { user } = this.props.auth; const tabs = [ , ]; - if (can(user, 'UPDATE_ASSET_CONFIG')) { + if (can(this.props.currentUser, 'UPDATE_ASSET_CONFIG')) { tabs.push( ; } - return ; + return ( + + ); } } @@ -255,21 +267,45 @@ const EMBED_QUERY = gql` `; export const withEmbedQuery = withQuery(EMBED_QUERY, { - options: ({ auth, commentId, assetId, assetUrl, sortBy, sortOrder }) => ({ + options: ({ + currentUser, + commentId, + assetId, + assetUrl, + sortBy, + sortOrder, + }) => ({ variables: { assetId, assetUrl, commentId, hasComment: commentId !== '', - excludeIgnored: Boolean(auth && auth.user && auth.user.id), + excludeIgnored: Boolean(currentUser && currentUser.id), sortBy, sortOrder, }, }), }); +EmbedContainer.propTypes = { + setActiveTab: PropTypes.func, + currentUser: PropTypes.object, + blurSignInDialog: PropTypes.func, + focusSignInDialog: PropTypes.func, + hideSignInDialog: PropTypes.func, + router: PropTypes.object, + commentId: PropTypes.string, + root: PropTypes.object, + activeTab: PropTypes.string, + data: PropTypes.object, + fetchAssetSuccess: PropTypes.func, + showSignInDialog: PropTypes.bool, + signInDialogFocus: PropTypes.bool, +}; + const mapStateToProps = state => ({ - auth: state.auth, + currentUser: state.auth.user, + checkedInitialLogin: state.auth.checkedInitialLogin, commentId: state.stream.commentId, assetId: state.stream.assetId, assetUrl: state.stream.assetUrl, @@ -277,6 +313,8 @@ const mapStateToProps = state => ({ config: state.config, sortOrder: state.stream.sortOrder, sortBy: state.stream.sortBy, + showSignInDialog: state.login.showSignInDialog, + signInDialogFocus: state.login.signInDialogFocus, }); const mapDispatchToProps = dispatch => @@ -297,6 +335,6 @@ const mapDispatchToProps = dispatch => export default compose( connect(mapStateToProps, mapDispatchToProps), - branch(props => !props.auth.checkedInitialLogin, renderComponent(Spinner)), + branch(props => !props.checkedInitialLogin, renderComponent(Spinner)), withEmbedQuery )(EmbedContainer); diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 7100c0d28..2ab279eb8 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -1,55 +1,18 @@ import React from 'react'; import { render } from 'react-dom'; -import { - checkLogin, - handleAuthToken, - logout, -} from 'coral-embed-stream/src/actions/auth'; import graphqlExtension from './graphql'; -import { addExternalConfig } from 'coral-embed-stream/src/actions/config'; import { createContext } from 'coral-framework/services/bootstrap'; import AppRouter from './AppRouter'; import reducers from './reducers'; import TalkProvider from 'coral-framework/components/TalkProvider'; import pluginsConfig from 'pluginsConfig'; -// TODO: move init code into `bootstrap` service after auth has been refactored. -function preInit({ store, pym, inIframe }) { - // TODO: This is popup specific code and needs to be refactored. - if (!inIframe) { - store.dispatch(addExternalConfig({})); - store.dispatch(checkLogin()); - return; - } - - pym.onMessage('login', token => { - if (token) { - store.dispatch(handleAuthToken(token)); - } - store.dispatch(checkLogin()); - }); - - pym.onMessage('logout', () => { - store.dispatch(logout()); - }); - - return new Promise(resolve => { - pym.sendMessage('getConfig'); - pym.onMessage('config', config => { - store.dispatch(addExternalConfig(JSON.parse(config))); - store.dispatch(checkLogin()); - resolve(); - }); - }); -} - async function main() { const context = await createContext({ reducers, graphqlExtension, pluginsConfig, - preInit, }); render( diff --git a/client/coral-embed-stream/src/reducers/index.js b/client/coral-embed-stream/src/reducers/index.js index ac581557b..039db7ec3 100644 --- a/client/coral-embed-stream/src/reducers/index.js +++ b/client/coral-embed-stream/src/reducers/index.js @@ -1,15 +1,13 @@ -import auth from './auth'; +import login from './login'; import asset from './asset'; import embed from './embed'; -import config from './config'; import configure from './configure'; import stream from './stream'; export default { - auth, + login, asset, embed, - config, configure, stream, }; diff --git a/client/coral-embed-stream/src/reducers/auth.js b/client/coral-embed-stream/src/reducers/login.js similarity index 98% rename from client/coral-embed-stream/src/reducers/auth.js rename to client/coral-embed-stream/src/reducers/login.js index faa576fff..db9106691 100644 --- a/client/coral-embed-stream/src/reducers/auth.js +++ b/client/coral-embed-stream/src/reducers/login.js @@ -1,4 +1,4 @@ -import * as actions from '../constants/auth'; +import * as actions from '../constants/login'; import pym from 'coral-framework/services/pym'; import merge from 'lodash/merge'; @@ -28,7 +28,7 @@ const purge = user => { return userData; }; -export default function auth(state = initialState, action) { +export default function login(state = initialState, action) { switch (action.type) { case actions.FOCUS_SIGNIN_DIALOG: return { diff --git a/client/coral-embed-stream/src/reducers/stream.js b/client/coral-embed-stream/src/reducers/stream.js index 42af4453a..30b9fe22a 100644 --- a/client/coral-embed-stream/src/reducers/stream.js +++ b/client/coral-embed-stream/src/reducers/stream.js @@ -1,5 +1,5 @@ import * as actions from '../constants/stream'; -import * as authActions from '../constants/auth'; +import * as authActions from 'coral-framework/constants/auth'; function getQueryVariable(variable) { let query = window.location.search.substring(1); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js b/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js index a818ebec4..57aa2b0fe 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js @@ -10,12 +10,7 @@ import NotLoggedIn from '../components/NotLoggedIn'; import { Spinner } from 'coral-ui'; import CommentHistory from '../components/CommentHistory'; -// TODO: Auth logic needs refactoring. -import { - showSignInDialog, - checkLogin, -} from 'coral-embed-stream/src/actions/auth'; - +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; import { appendNewNodes } from 'plugin-api/beta/client/utils'; import update from 'immutability-helper'; import { getSlotFragmentSpreads } from 'coral-framework/utils'; @@ -24,7 +19,7 @@ import t from 'coral-framework/services/i18n'; class ProfileContainer extends Component { componentWillReceiveProps(nextProps) { - if (!this.props.auth.loggedIn && nextProps.auth.loggedIn) { + if (!this.props.currentUser && nextProps.currentUser) { // Refetch after login. this.props.data.refetch(); } @@ -55,13 +50,7 @@ class ProfileContainer extends Component { }; render() { - const { - auth, - auth: { user: authUser }, - showSignInDialog, - root, - data, - } = this.props; + const { currentUser, showSignInDialog, root, data } = this.props; const { me } = this.props.root; const loading = this.props.data.loading; @@ -69,7 +58,7 @@ class ProfileContainer extends Component { return

{this.props.data.error.message}
; } - if (!auth.loggedIn) { + if (!currentUser) { return ; } @@ -77,7 +66,7 @@ class ProfileContainer extends Component { return ; } - const localProfile = authUser.profiles.find(p => p.provider === 'local'); + const localProfile = currentUser.profiles.find(p => p.provider === 'local'); const emailAddress = localProfile && localProfile.id; return ( @@ -168,15 +157,20 @@ const withProfileQuery = withQuery( ${getSlotFragmentSpreads(slots, 'root')} } ${CommentFragment} -` +`, + { + options: { + fetchPolicy: 'network-only', + }, + } ); const mapStateToProps = state => ({ - auth: state.auth, + currentUser: state.auth.user, }); const mapDispatchToProps = dispatch => - bindActionCreators({ showSignInDialog, checkLogin }, dispatch); + bindActionCreators({ showSignInDialog }, dispatch); export default compose( connect(mapStateToProps, mapDispatchToProps), diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index 23f438045..951733397 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -59,7 +59,7 @@ class Stream extends React.Component { deleteAction, showSignInDialog, loadNewReplies, - auth: { user }, + currentUser, emit, viewAllComments, } = this.props; @@ -111,7 +111,7 @@ class Stream extends React.Component { disableReply={!open} postComment={postComment} asset={asset} - currentUser={user} + currentUser={currentUser} highlighted={comment.id} postFlag={postFlag} postDontAgree={postDontAgree} @@ -150,7 +150,7 @@ class Stream extends React.Component { setActiveStreamTab, loadNewReplies, loadMoreComments, - auth: { user }, + currentUser, emit, sortOrder, sortBy, @@ -200,7 +200,7 @@ class Stream extends React.Component { notify={notify} disableReply={asset.isClosed} postComment={postComment} - currentUser={user} + currentUser={currentUser} postFlag={postFlag} postDontAgree={postDontAgree} loadMore={loadMoreComments} @@ -230,21 +230,23 @@ class Stream extends React.Component { postComment, notify, updateItem, - auth: { loggedIn, user }, + currentUser, } = this.props; const { keepCommentBox } = this.state; const open = !asset.isClosed; - const banned = get(user, 'status.banned.status'); - const suspensionUntil = get(user, 'status.suspension.until'); - const rejectedUsername = get(user, 'status.username.status') === 'REJECTED'; - const changedUsername = get(user, 'status.username.status') === 'CHANGED'; + const banned = get(currentUser, 'status.banned.status'); + const suspensionUntil = get(currentUser, 'status.suspension.until'); + const rejectedUsername = + get(currentUser, 'status.username.status') === 'REJECTED'; + const changedUsername = + get(currentUser, 'status.username.status') === 'CHANGED'; const temporarilySuspended = - user && suspensionUntil && new Date(suspensionUntil) > new Date(); + currentUser && suspensionUntil && new Date(suspensionUntil) > new Date(); const showCommentBox = - loggedIn && + currentUser && ((!banned && !temporarilySuspended && !rejectedUsername && @@ -289,7 +291,8 @@ class Stream extends React.Component { )} {changedUsername && } - {!banned && rejectedUsername && } + {!banned && + rejectedUsername && } {banned && } {showCommentBox && ( @@ -312,10 +315,10 @@ class Stream extends React.Component { - {loggedIn && ( + {currentUser && ( )} @@ -342,7 +345,7 @@ Stream.propTypes = { deleteAction: PropTypes.func, showSignInDialog: PropTypes.func, loadNewReplies: PropTypes.func, - auth: PropTypes.object, + currentUser: PropTypes.object, emit: PropTypes.func, sortOrder: PropTypes.string, sortBy: PropTypes.string, diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 51b4d9d9e..59a449244 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -14,8 +14,8 @@ import { withEditComment, } from 'coral-framework/graphql/mutations'; -import * as authActions from 'coral-embed-stream/src/actions/auth'; -import * as notificationActions from 'coral-framework/actions/notification'; +import { showSignInDialog, editName } from 'coral-embed-stream/src/actions/login'; +import { notify } from 'coral-framework/actions/notification'; import { setActiveReplyBox, setActiveTab, @@ -40,9 +40,6 @@ import { } from '../../../graphql/utils'; import StreamError from '../components/StreamError'; -const { showSignInDialog, editName } = authActions; -const { notify } = notificationActions; - class StreamContainer extends React.Component { commentsAddedSubscription = null; commentsEditedSubscription = null; @@ -60,8 +57,8 @@ class StreamContainer extends React.Component { // Ignore mutations from me. // TODO: need way to detect mutations created by this client, and allow mutations from other clients. if ( - this.props.auth.user && - commentEdited.user.id === this.props.auth.user.id + this.props.currentUser && + commentEdited.user.id === this.props.currentUser.id ) { return prev; } @@ -92,8 +89,8 @@ class StreamContainer extends React.Component { // Ignore mutations from me. // TODO: need way to detect mutations created by this client, and allow mutations from other clients. if ( - this.props.auth.user && - commentAdded.user.id === this.props.auth.user.id + this.props.currentUser && + commentAdded.user.id === this.props.currentUser.id ) { return prev; } @@ -204,8 +201,8 @@ class StreamContainer extends React.Component { } } - userIsDegraged({ auth: { user } } = this.props) { - return !can(user, 'INTERACT_WITH_COMMUNITY'); + userIsDegraged({ currentUser } = this.props) { + return !can(currentUser, 'INTERACT_WITH_COMMUNITY'); } render() { @@ -396,7 +393,7 @@ const fragments = { }; const mapStateToProps = state => ({ - auth: state.auth, + currentUser: state.auth.user, activeReplyBox: state.stream.activeReplyBox, commentId: state.stream.commentId, assetId: state.stream.assetId, diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index c72bd8e68..ed5ae4d97 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -6,9 +6,6 @@ function cleanAuthData(localStorage) { localStorage.removeItem('exp'); } -/** - * Check Login - */ export const checkLogin = () => ( dispatch, _, @@ -54,9 +51,15 @@ const checkLoginSuccess = user => ({ user, }); -/** - * Login - */ +export const setAuthToken = token => (dispatch, _, { localStorage }) => { + if (localStorage) { + localStorage.setItem('exp', jwtDecode(token).exp); + localStorage.setItem('token', token); + } + + dispatch(checkLogin()); +}; + export const handleSuccessfulLogin = (user, token) => ( dispatch, _, diff --git a/client/coral-framework/actions/config.js b/client/coral-framework/actions/config.js new file mode 100644 index 000000000..dd1522333 --- /dev/null +++ b/client/coral-framework/actions/config.js @@ -0,0 +1,6 @@ +import { MERGE_CONFIG } from '../constants/config'; + +export const mergeConfig = config => ({ + type: MERGE_CONFIG, + config, +}); diff --git a/client/coral-framework/actions/static.js b/client/coral-framework/actions/static.js deleted file mode 100644 index fd28ad39d..000000000 --- a/client/coral-framework/actions/static.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as actions from '../constants/static'; - -export const setStaticConfiguration = config => ({ - type: actions.SET_STATIC_CONFIGURATION, - config, -}); diff --git a/client/coral-framework/components/Recaptcha.js b/client/coral-framework/components/Recaptcha.js index f0825df30..e2e461382 100644 --- a/client/coral-framework/components/Recaptcha.js +++ b/client/coral-framework/components/Recaptcha.js @@ -19,7 +19,7 @@ class Recaptcha extends React.Component { // This should be fine because it's static and will never change. // Prefer this to connect HOC because wie expose the instance method // `reset` - return this.context.store.getState().static.TALK_RECAPTCHA_PUBLIC; + return this.context.store.getState().config.static.TALK_RECAPTCHA_PUBLIC; } render() { diff --git a/client/coral-framework/constants/config.js b/client/coral-framework/constants/config.js new file mode 100644 index 000000000..bf846af1d --- /dev/null +++ b/client/coral-framework/constants/config.js @@ -0,0 +1,3 @@ +const prefix = `TALK_FRAMEWORK`; + +export const MERGE_CONFIG = `${prefix}_MERGE_CONFIG`; diff --git a/client/coral-framework/constants/static.js b/client/coral-framework/constants/static.js deleted file mode 100644 index 5f3804662..000000000 --- a/client/coral-framework/constants/static.js +++ /dev/null @@ -1,3 +0,0 @@ -const prefix = `TALK_FRAMEWORK`; - -export const SET_STATIC_CONFIGURATION = `${prefix}_SET_STATIC_CONFIGURATION`; diff --git a/client/coral-framework/hocs/withSignIn.js b/client/coral-framework/hocs/withSignIn.js index 02c631068..e842c4263 100644 --- a/client/coral-framework/hocs/withSignIn.js +++ b/client/coral-framework/hocs/withSignIn.js @@ -48,8 +48,8 @@ export default hoistStatics(WrappedComponent => { } const changeSet = { loading: false, error }; if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { - changeSet.requireRecaptcha = !!this.context.store.getState().static - .TALK_RECAPTCHA_PUBLIC; + changeSet.requireRecaptcha = !!this.context.store.getState().config + .static.TALK_RECAPTCHA_PUBLIC; } this.setState(changeSet); }); diff --git a/client/coral-framework/reducers/static.js b/client/coral-framework/reducers/config.js similarity index 51% rename from client/coral-framework/reducers/static.js rename to client/coral-framework/reducers/config.js index c8bc1afc2..a3a0409eb 100644 --- a/client/coral-framework/reducers/static.js +++ b/client/coral-framework/reducers/config.js @@ -1,10 +1,10 @@ -import * as actions from '../constants/static'; +import { MERGE_CONFIG } from '../constants/config'; const initialState = {}; -export default function auth(state = initialState, action) { +export default function config(state = initialState, action) { switch (action.type) { - case actions.SET_STATIC_CONFIGURATION: + case MERGE_CONFIG: return { ...state, ...action.config, diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index d54932b9b..3661ebaef 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -1,7 +1,8 @@ import auth from './auth'; -import staticConfiguration from './static'; +import config from './config'; export default { auth, - static: staticConfiguration, + login: auth, + config, }; diff --git a/client/coral-framework/services/bootstrap.js b/client/coral-framework/services/bootstrap.js index 04b58adb4..a7dd6df98 100644 --- a/client/coral-framework/services/bootstrap.js +++ b/client/coral-framework/services/bootstrap.js @@ -23,8 +23,9 @@ import { createHistory } from 'coral-framework/services/history'; import { createIntrospection } from 'coral-framework/services/introspection'; import introspectionData from 'coral-framework/graphql/introspection.json'; import coreReducers from '../reducers'; -import { checkLogin } from '../actions/auth'; -import { setStaticConfiguration } from '../actions/static'; +import { checkLogin as checkLoginAction } from '../actions/auth'; +import { mergeConfig } from '../actions/config'; +import { setAuthToken, logout } from '../actions/auth'; /** * getAuthToken returns the active auth token or null @@ -54,6 +55,19 @@ function areWeInIframe() { } } +function initExternalConfig({ store, pym, inIframe }) { + if (!inIframe) { + return; + } + return new Promise(resolve => { + pym.sendMessage('getConfig'); + pym.onMessage('config', config => { + store.dispatch(mergeConfig(JSON.parse(config))); + resolve(); + }); + }); +} + /** * createContext setups and returns Talk dependencies that should be * passed to `TalkProvider`. @@ -73,6 +87,8 @@ export async function createContext({ notification, preInit, init = noop, + checkLogin = true, + addExternalConfig = true, } = {}) { const inIframe = areWeInIframe(); const eventEmitter = new EventEmitter({ wildcard: true }); @@ -166,7 +182,6 @@ export async function createContext({ // Create our redux store. const finalReducers = { ...coreReducers, - authCore: coreReducers.auth, ...reducers, ...plugins.getReducers(), }; @@ -186,12 +201,39 @@ export async function createContext({ [client.middleware(), apolloErrorReporter, createReduxEmitter(eventEmitter)] ); - store.dispatch(setStaticConfiguration(staticConfig)); - store.dispatch(checkLogin()); + if (inIframe) { + pym.onMessage('login', token => { + if (token) { + store.dispatch(setAuthToken(token)); + } + }); + + pym.onMessage('logout', () => { + store.dispatch(logout()); + }); + } + + const preInitList = []; + + store.dispatch( + mergeConfig({ + static: staticConfig, + }) + ); + + if (preInit) { + preInitList.push(preInit(context)); + } + + if (addExternalConfig) { + preInitList.push(initExternalConfig(context)); + } // Run pre initialization. - if (preInit) { - await preInit(context); + await Promise.all(preInitList); + + if (checkLogin) { + store.dispatch(checkLoginAction()); } // Run initialization. diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js index 6020d4002..95d3f5bcf 100644 --- a/plugin-api/beta/client/hocs/withReaction.js +++ b/plugin-api/beta/client/hocs/withReaction.js @@ -15,8 +15,7 @@ import * as PropTypes from 'prop-types'; import { getDefinitionName } from '../utils'; import { t, can } from 'plugin-api/beta/client/services'; -// TODO: Auth logic needs refactoring. -import { showSignInDialog } from 'coral-embed-stream/src/actions/auth'; +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; /* * Disable false-positive warning below, as it doesn't work well with how we currently diff --git a/plugins/talk-plugin-auth/client/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/components/ChangeUsername.js index e814a9d05..2eab273cc 100644 --- a/plugins/talk-plugin-auth/client/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/components/ChangeUsername.js @@ -17,7 +17,7 @@ import { invalidForm, validForm, updateUsername, -} from 'coral-embed-stream/src/actions/auth'; +} from 'coral-embed-stream/src/actions/login'; class ChangeUsernameContainer extends React.Component { constructor(props) { diff --git a/plugins/talk-plugin-auth/client/components/SignInButton.js b/plugins/talk-plugin-auth/client/components/SignInButton.js index 163a5e5f8..9ad19599e 100644 --- a/plugins/talk-plugin-auth/client/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/components/SignInButton.js @@ -2,12 +2,12 @@ import React from 'react'; import { Button } from 'plugin-api/beta/client/components/ui'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { showSignInDialog } from 'coral-embed-stream/src/actions/auth'; +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; import t from 'coral-framework/services/i18n'; -const SignInButton = ({ loggedIn, showSignInDialog }) => ( +const SignInButton = ({ currentUser, showSignInDialog }) => (
- {!loggedIn ? ( + {!currentUser ? ( @@ -16,7 +16,7 @@ const SignInButton = ({ loggedIn, showSignInDialog }) => ( ); const mapStateToProps = ({ auth }) => ({ - loggedIn: auth.loggedIn, + currentUser: auth.user, }); const mapDispatchToProps = dispatch => diff --git a/plugins/talk-plugin-auth/client/components/SignInContainer.js b/plugins/talk-plugin-auth/client/components/SignInContainer.js index 6f99f9ce6..a184f3256 100644 --- a/plugins/talk-plugin-auth/client/components/SignInContainer.js +++ b/plugins/talk-plugin-auth/client/components/SignInContainer.js @@ -19,7 +19,7 @@ import { facebookCallback, invalidForm, validForm, -} from 'coral-embed-stream/src/actions/auth'; +} from 'coral-embed-stream/src/actions/login'; class SignInContainer extends React.Component { constructor(props) { diff --git a/plugins/talk-plugin-auth/client/components/UserBox.js b/plugins/talk-plugin-auth/client/components/UserBox.js index d601ace74..96d66cc7b 100644 --- a/plugins/talk-plugin-auth/client/components/UserBox.js +++ b/plugins/talk-plugin-auth/client/components/UserBox.js @@ -3,7 +3,7 @@ import styles from './styles.css'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import t from 'coral-framework/services/i18n'; -import { logout } from 'coral-embed-stream/src/actions/auth'; +import { logout } from 'coral-embed-stream/src/actions/login'; const UserBox = ({ loggedIn, user, logout, onShowProfile }) => (
From 4883bb17b9d249fb5b7f67798f9ac262c7798c17 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Feb 2018 00:08:31 +0100 Subject: [PATCH 08/37] Finish refactor embed stream auth --- client/coral-embed-stream/src/AppRouter.js | 25 ------ .../coral-embed-stream/src/actions/login.js | 86 +++---------------- .../src/components/Embed.js | 8 +- .../src/containers/Embed.js | 9 +- client/coral-embed-stream/src/index.js | 4 +- .../coral-embed-stream/src/reducers/login.js | 17 +++- .../src/tabs/stream/containers/Stream.js | 5 +- client/coral-framework/actions/auth.js | 10 +++ client/coral-framework/constants/auth.js | 3 + client/coral-framework/reducers/auth.js | 19 ++++ client/coral-login/src/containers/Main.js | 9 ++ client/coral-login/src/index.js | 23 +++++ client/coral-login/src/reducers/index.js | 1 + .../client/components/UserBox.js | 7 +- routes/index.js | 4 + views/login.ejs | 20 +++++ webpack.config.js | 1 + 17 files changed, 132 insertions(+), 119 deletions(-) delete mode 100644 client/coral-embed-stream/src/AppRouter.js create mode 100644 client/coral-login/src/containers/Main.js create mode 100644 client/coral-login/src/index.js create mode 100644 client/coral-login/src/reducers/index.js create mode 100644 views/login.ejs diff --git a/client/coral-embed-stream/src/AppRouter.js b/client/coral-embed-stream/src/AppRouter.js deleted file mode 100644 index ec7b6300a..000000000 --- a/client/coral-embed-stream/src/AppRouter.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { Router, Route } from 'react-router'; -import PropTypes from 'prop-types'; - -import Embed from './containers/Embed'; -import { LoginContainer } from './containers/LoginContainer'; - -const routes = ( -
- - -
-); - -class AppRouter extends React.Component { - static contextTypes = { - history: PropTypes.object, - }; - - render() { - return ; - } -} - -export default AppRouter; diff --git a/client/coral-embed-stream/src/actions/login.js b/client/coral-embed-stream/src/actions/login.js index 3a110c1b9..84c98ea78 100644 --- a/client/coral-embed-stream/src/actions/login.js +++ b/client/coral-embed-stream/src/actions/login.js @@ -3,12 +3,7 @@ import bowser from 'bowser'; import * as actions from '../constants/login'; import { notify } from 'coral-framework/actions/notification'; import t from 'coral-framework/services/i18n'; -import get from 'lodash/get'; - -export const updateStatus = status => ({ - type: actions.UPDATE_STATUS, - status, -}); +import { checkLogin } from 'coral-framework/actions/auth'; export const showSignInDialog = () => ({ type: actions.SHOW_SIGNIN_DIALOG, @@ -27,10 +22,6 @@ export const hideSignInDialog = () => dispatch => { dispatch({ type: actions.HIDE_SIGNIN_DIALOG }); }; -export const resetSignInDialog = () => dispatch => { - dispatch({ type: actions.HIDE_SIGNIN_DIALOG }); -}; - export const focusSignInDialog = () => ({ type: actions.FOCUS_SIGNIN_DIALOG, }); @@ -39,6 +30,17 @@ export const blurSignInDialog = () => ({ type: actions.BLUR_SIGNIN_DIALOG, }); +// TODO: remove the rest. + +export const updateStatus = status => ({ + type: actions.UPDATE_STATUS, + status, +}); + +export const resetSignInDialog = () => dispatch => { + dispatch({ type: actions.HIDE_SIGNIN_DIALOG }); +}; + export const showCreateUsernameDialog = () => ({ type: actions.SHOW_CREATEUSERNAME_DIALOG, }); @@ -276,70 +278,6 @@ export const logout = () => async ( pym.sendMessage('coral-auth-changed'); }; -//============================================================================== -// CHECK LOGIN -//============================================================================== - -const checkLoginRequest = () => ({ type: actions.CHECK_LOGIN_REQUEST }); -const checkLoginFailure = error => ({ - type: actions.CHECK_LOGIN_FAILURE, - error, -}); - -const checkLoginSuccess = (user, isAdmin) => ({ - type: actions.CHECK_LOGIN_SUCCESS, - user, - isAdmin, -}); - -const ErrNotLoggedIn = new Error('Not logged in'); - -export const checkLogin = () => ( - dispatch, - _, - { rest, client, pym, localStorage } -) => { - dispatch(checkLoginRequest()); - rest('/auth') - .then(result => { - if (!result.user) { - if (localStorage) { - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - throw ErrNotLoggedIn; - } - - // Reset the websocket. - client.resetWebsocket(); - - dispatch(checkLoginSuccess(result.user)); - pym.sendMessage('coral-auth-changed', JSON.stringify(result.user)); - - // This is for login via social. Usernames should be set. - if ( - get(result.user, 'status.username.status') === 'UNSET' && - !get(result.user, 'status.banned.status') - ) { - dispatch(showCreateUsernameDialog()); - } - }) - .catch(error => { - if (error !== ErrNotLoggedIn) { - console.error(error); - } - if (error.status && error.status === 401 && localStorage) { - // Unauthorized. - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(checkLoginFailure(errorMessage)); - }); -}; - export const validForm = () => ({ type: actions.VALID_FORM }); export const invalidForm = error => ({ type: actions.INVALID_FORM, error }); diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 059e72ed3..b7eb6d580 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -62,7 +62,7 @@ export default class Embed extends React.Component { blurSignInDialog, focusSignInDialog, hideSignInDialog, - router: { location: { query: { parentUrl } } }, + parentUrl, } = this.props; const hasHighlightedComment = !!commentId; @@ -75,9 +75,7 @@ export default class Embed extends React.Component { ); } @@ -297,6 +296,7 @@ EmbedContainer.propTypes = { commentId: PropTypes.string, root: PropTypes.object, activeTab: PropTypes.string, + parentUrl: PropTypes.string, data: PropTypes.object, fetchAssetSuccess: PropTypes.func, showSignInDialog: PropTypes.bool, @@ -315,13 +315,12 @@ const mapStateToProps = state => ({ sortBy: state.stream.sortBy, showSignInDialog: state.login.showSignInDialog, signInDialogFocus: state.login.signInDialogFocus, + parentUrl: state.login.parentUrl, }); const mapDispatchToProps = dispatch => bindActionCreators( { - logout, - checkLogin, setActiveTab, fetchAssetSuccess, notify, diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 2ab279eb8..a4c6bcd39 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -1,9 +1,9 @@ import React from 'react'; import { render } from 'react-dom'; +import Embed from './containers/Embed'; import graphqlExtension from './graphql'; import { createContext } from 'coral-framework/services/bootstrap'; -import AppRouter from './AppRouter'; import reducers from './reducers'; import TalkProvider from 'coral-framework/components/TalkProvider'; import pluginsConfig from 'pluginsConfig'; @@ -16,7 +16,7 @@ async function main() { }); render( - + , document.querySelector('#talk-embed-stream-container') ); diff --git a/client/coral-embed-stream/src/reducers/login.js b/client/coral-embed-stream/src/reducers/login.js index db9106691..febf4fd51 100644 --- a/client/coral-embed-stream/src/reducers/login.js +++ b/client/coral-embed-stream/src/reducers/login.js @@ -3,11 +3,14 @@ import pym from 'coral-framework/services/pym'; import merge from 'lodash/merge'; const initialState = { + parentUrl: pym.parentUrl || location.href, + showSignInDialog: false, + signInDialogFocus: false, + + // TODO: remove the rest isLoading: false, loggedIn: false, user: null, - showSignInDialog: false, - signInDialogFocus: false, showCreateUsernameDialog: false, checkedInitialLogin: false, view: 'SIGNIN', @@ -46,8 +49,16 @@ export default function login(state = initialState, action) { showSignInDialog: true, signInDialogFocus: true, }; - case actions.RESET_SIGNIN_DIALOG: + case actions.HIDE_SIGNIN_DIALOG: + return { + ...state, + showSignInDialog: false, + signInDialogFocus: false, + }; + + // TODO: remove the rest. + case actions.RESET_SIGNIN_DIALOG: return { ...state, isLoading: false, diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 59a449244..5e0e3131b 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -14,7 +14,10 @@ import { withEditComment, } from 'coral-framework/graphql/mutations'; -import { showSignInDialog, editName } from 'coral-embed-stream/src/actions/login'; +import { + showSignInDialog, + editName, +} from 'coral-embed-stream/src/actions/login'; import { notify } from 'coral-framework/actions/notification'; import { setActiveReplyBox, diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index ed5ae4d97..0c4780548 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -98,3 +98,13 @@ export const logout = () => async ( dispatch({ type: actions.LOGOUT }); pym.sendMessage('coral-auth-changed'); }; + +export const updateStatus = status => ({ + type: actions.UPDATE_STATUS, + status, +}); + +export const updateUsername = username => ({ + type: actions.UPDATE_USERNAME, + username, +}); diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 655c29260..25e2f6a78 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -6,3 +6,6 @@ export const CHECK_LOGIN_FAILURE = `${prefix}_CHECK_LOGIN_FAILURE`; export const LOGOUT = `${prefix}_LOGOUT`; export const HANDLE_SUCCESSFUL_LOGIN = `${prefix}_HANDLE_SUCCESSFUL_LOGIN`; + +export const UPDATE_STATUS = '${prefix}_UPDATE_STATUS'; +export const UPDATE_USERNAME = '${prefix}_UPDATE_USERNAME'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 04e75bbb4..3af606d0b 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -1,4 +1,5 @@ import * as actions from '../constants/auth'; +import merge from 'lodash/merge'; const initialState = { checkedInitialLogin: false, @@ -36,6 +37,24 @@ export default function auth(state = initialState, action) { ...state, user: null, }; + case actions.UPDATE_STATUS: { + return { + ...state, + user: { + ...state.user, + status: merge({}, state.user.status, action.status), + }, + }; + } + case actions.UPDATE_USERNAME: + return { + ...state, + user: { + ...state.user, + username: action.username, + lowercaseUsername: action.username.toLowerCase(), + }, + }; default: return state; } diff --git a/client/coral-login/src/containers/Main.js b/client/coral-login/src/containers/Main.js new file mode 100644 index 000000000..23193bbb6 --- /dev/null +++ b/client/coral-login/src/containers/Main.js @@ -0,0 +1,9 @@ +import React from 'react'; + +class Main extends React.Component { + render() { + return
Hello World
; + } +} + +export default Main; diff --git a/client/coral-login/src/index.js b/client/coral-login/src/index.js new file mode 100644 index 000000000..71eaaabe8 --- /dev/null +++ b/client/coral-login/src/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { render } from 'react-dom'; + +import { createContext } from 'coral-framework/services/bootstrap'; +import Main from './containers/Main'; +import reducers from './reducers'; +import TalkProvider from 'coral-framework/components/TalkProvider'; +import pluginsConfig from 'pluginsConfig'; + +async function main() { + const context = await createContext({ + reducers, + pluginsConfig, + }); + render( + +
+ , + document.querySelector('#talk-login-container') + ); +} + +main(); diff --git a/client/coral-login/src/reducers/index.js b/client/coral-login/src/reducers/index.js new file mode 100644 index 000000000..ff8b4c563 --- /dev/null +++ b/client/coral-login/src/reducers/index.js @@ -0,0 +1 @@ +export default {}; diff --git a/plugins/talk-plugin-auth/client/components/UserBox.js b/plugins/talk-plugin-auth/client/components/UserBox.js index 96d66cc7b..09e4302b6 100644 --- a/plugins/talk-plugin-auth/client/components/UserBox.js +++ b/plugins/talk-plugin-auth/client/components/UserBox.js @@ -3,11 +3,11 @@ import styles from './styles.css'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import t from 'coral-framework/services/i18n'; -import { logout } from 'coral-embed-stream/src/actions/login'; +import { logout } from 'coral-framework/actions/auth'; -const UserBox = ({ loggedIn, user, logout, onShowProfile }) => ( +const UserBox = ({ user, logout, onShowProfile }) => (
- {loggedIn ? ( + {user ? (
{t('sign_in.logged_in_as')} @@ -25,7 +25,6 @@ const UserBox = ({ loggedIn, user, logout, onShowProfile }) => ( ); const mapStateToProps = ({ auth }) => ({ - loggedIn: auth.loggedIn, user: auth.user, }); diff --git a/routes/index.js b/routes/index.js index 48981faff..242b753ce 100644 --- a/routes/index.js +++ b/routes/index.js @@ -160,6 +160,10 @@ if (process.env.NODE_ENV !== 'production') { }); } +router.use('/login', staticTemplate, async (req, res, next) => { + return res.render('login'); +}); + // Inject server route plugins. plugins.get('server', 'router').forEach(plugin => { debug(`added plugin '${plugin.plugin.name}'`); diff --git a/views/login.ejs b/views/login.ejs new file mode 100644 index 000000000..634054074 --- /dev/null +++ b/views/login.ejs @@ -0,0 +1,20 @@ + + + + + + + + <%_ if (locals.customCssUrl) { _%> + + <%_ } _%> + <%_ if (data != null) { _%> + + <%_ } _%> + + + +
+ + + diff --git a/webpack.config.js b/webpack.config.js index 6a018caba..fb6240d07 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,6 +21,7 @@ debug(`Using ${pluginsPath} as the plugin configuration path`); const buildTargets = [ 'coral-admin', + 'coral-login', 'coral-docs', { name: 'coral-auth-callback', disablePolyfill: true }, ]; From 1774f98519725285a0eb9b73bcd538b877e8a942 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Feb 2018 00:19:22 +0100 Subject: [PATCH 09/37] Refactor Stream container passing props --- .../src/tabs/stream/components/Stream.js | 1 - .../src/tabs/stream/containers/Stream.js | 52 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/tabs/stream/components/Stream.js b/client/coral-embed-stream/src/tabs/stream/components/Stream.js index 951733397..a431b1986 100644 --- a/client/coral-embed-stream/src/tabs/stream/components/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/components/Stream.js @@ -350,7 +350,6 @@ Stream.propTypes = { sortOrder: PropTypes.string, sortBy: PropTypes.string, loading: PropTypes.bool, - editName: PropTypes.func, appendItemArray: PropTypes.func, updateItem: PropTypes.func, viewAllComments: PropTypes.func, diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 5e0e3131b..6884bcd8e 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { gql, compose } from 'react-apollo'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; @@ -225,8 +226,28 @@ class StreamContainer extends React.Component { return ( Date: Fri, 9 Feb 2018 20:12:02 +0100 Subject: [PATCH 10/37] Refactor talk-plugin-auth part 1 --- client/coral-admin/src/components/SignIn.css | 9 + client/coral-admin/src/components/SignIn.js | 21 +- .../src/containers/LoginContainer.js | 4 - .../coral-framework/components/Recaptcha.js | 8 +- client/coral-framework/hocs/withSignIn.js | 8 +- client/coral-login/src/containers/Main.js | 3 +- plugins/talk-plugin-auth/client/actions.js | 0 .../client/components/FakeComment.css | 38 ---- .../client/components/FakeComment.js | 37 ---- .../client/components/ForgotContent.js | 81 ------- .../client/components/ResendVerification.css | 15 -- .../client/components/ResendVerification.js | 54 ----- .../client/components/SignDialog.js | 32 --- .../client/components/SignInContainer.js | 202 ------------------ .../client/components/SignInContent.js | 108 ---------- .../client/components/SignUpContent.js | 143 ------------- .../client/components/UserBox.js | 33 --- .../client/components/styles.css | 167 --------------- plugins/talk-plugin-auth/client/index.js | 11 +- .../client/login/components/Main.css | 8 + .../client/login/components/Main.js | 13 ++ .../client/login/components/SignIn.css | 47 ++++ .../client/login/components/SignIn.js | 140 ++++++++++++ .../client/login/containers/Main.js | 10 + .../client/login/containers/SignIn.js | 65 ++++++ .../stream/components/SetUsernameDialog.css | 0 .../components/SetUsernameDialog.js} | 6 +- .../client/stream/components/SignInButton.css | 0 .../{ => stream}/components/SignInButton.js | 16 +- .../client/stream/components/UserBox.css | 21 ++ .../client/stream/components/UserBox.js | 32 +++ .../containers/SetUsernameDialog.js} | 6 +- .../client/stream/containers/SignInButton.js | 13 ++ .../client/stream/containers/UserBox.js | 12 ++ views/login.ejs | 1 + yarn.lock | 4 +- 36 files changed, 413 insertions(+), 955 deletions(-) delete mode 100644 client/coral-embed-stream/src/containers/LoginContainer.js create mode 100644 plugins/talk-plugin-auth/client/actions.js delete mode 100644 plugins/talk-plugin-auth/client/components/FakeComment.css delete mode 100644 plugins/talk-plugin-auth/client/components/FakeComment.js delete mode 100644 plugins/talk-plugin-auth/client/components/ForgotContent.js delete mode 100644 plugins/talk-plugin-auth/client/components/ResendVerification.css delete mode 100644 plugins/talk-plugin-auth/client/components/ResendVerification.js delete mode 100644 plugins/talk-plugin-auth/client/components/SignDialog.js delete mode 100644 plugins/talk-plugin-auth/client/components/SignInContainer.js delete mode 100644 plugins/talk-plugin-auth/client/components/SignInContent.js delete mode 100644 plugins/talk-plugin-auth/client/components/SignUpContent.js delete mode 100644 plugins/talk-plugin-auth/client/components/UserBox.js delete mode 100644 plugins/talk-plugin-auth/client/components/styles.css create mode 100644 plugins/talk-plugin-auth/client/login/components/Main.css create mode 100644 plugins/talk-plugin-auth/client/login/components/Main.js create mode 100644 plugins/talk-plugin-auth/client/login/components/SignIn.css create mode 100644 plugins/talk-plugin-auth/client/login/components/SignIn.js create mode 100644 plugins/talk-plugin-auth/client/login/containers/Main.js create mode 100644 plugins/talk-plugin-auth/client/login/containers/SignIn.js create mode 100644 plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css rename plugins/talk-plugin-auth/client/{components/CreateUsernameDialog.js => stream/components/SetUsernameDialog.js} (95%) create mode 100644 plugins/talk-plugin-auth/client/stream/components/SignInButton.css rename plugins/talk-plugin-auth/client/{ => stream}/components/SignInButton.js (52%) create mode 100644 plugins/talk-plugin-auth/client/stream/components/UserBox.css create mode 100644 plugins/talk-plugin-auth/client/stream/components/UserBox.js rename plugins/talk-plugin-auth/client/{components/ChangeUsername.js => stream/containers/SetUsernameDialog.js} (97%) create mode 100644 plugins/talk-plugin-auth/client/stream/containers/SignInButton.js create mode 100644 plugins/talk-plugin-auth/client/stream/containers/UserBox.js diff --git a/client/coral-admin/src/components/SignIn.css b/client/coral-admin/src/components/SignIn.css index 88d8a452c..10ab54c09 100644 --- a/client/coral-admin/src/components/SignIn.css +++ b/client/coral-admin/src/components/SignIn.css @@ -12,3 +12,12 @@ font-weight: normal; text-decoration: none; } + +.recaptcha { + margin-top: 16px; + margin-bottom: 6px; +} + +.signInButton { + margin-top: 10px; +} diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index 85bfb9810..1525145a7 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './SignIn.css'; import { Button, TextField, Alert } from 'coral-ui'; +import cn from 'classnames'; import Recaptcha from 'coral-framework/components/Recaptcha'; class SignIn extends React.Component { @@ -27,9 +28,6 @@ class SignIn extends React.Component { handleRecaptchaRef = ref => { this.recaptcha = ref; - setTimeout(() => { - console.log(ref); - }, 1000) }; render() { @@ -50,9 +48,16 @@ class SignIn extends React.Component { onChange={this.handlePasswordChange} type="password" /> -
+ {requireRecaptcha && ( +
+ +
+ )} - {}} - parentCommentId={'commentID'} - currentUserId={{}} - /> -
-
- - -
-
-
-); diff --git a/plugins/talk-plugin-auth/client/components/ForgotContent.js b/plugins/talk-plugin-auth/client/components/ForgotContent.js deleted file mode 100644 index bfadbb001..000000000 --- a/plugins/talk-plugin-auth/client/components/ForgotContent.js +++ /dev/null @@ -1,81 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import styles from './styles.css'; -import { Button, TextField } from 'plugin-api/beta/client/components/ui'; -import t from 'coral-framework/services/i18n'; - -class ForgotContent extends React.Component { - state = { value: '' }; - - handleSubmit = e => { - e.preventDefault(); - this.props.fetchForgotPassword(this.state.value); - }; - - handleChangeEmail = e => { - const { value } = e.target; - this.setState({ value }); - }; - - render() { - const { changeView, auth } = this.props; - const { passwordRequestSuccess, passwordRequestFailure } = auth; - - return ( -
-
-

{t('sign_in.recover_password')}

-
-
-
- -
- - {passwordRequestSuccess ? ( -

- {passwordRequestSuccess} -

- ) : null} - {passwordRequestFailure ? ( -

- {passwordRequestFailure} -

- ) : null} -
-
- - {t('sign_in.need_an_account')}{' '} - changeView('SIGNUP')}>{t('sign_in.register')} - - - {t('sign_in.already_have_an_account')}{' '} - changeView('SIGNIN')}>{t('sign_in.sign_in')} - -
-
- ); - } -} - -ForgotContent.propTypes = { - auth: PropTypes.object, - changeView: PropTypes.func, - fetchForgotPassword: PropTypes.func, -}; - -export default ForgotContent; diff --git a/plugins/talk-plugin-auth/client/components/ResendVerification.css b/plugins/talk-plugin-auth/client/components/ResendVerification.css deleted file mode 100644 index dc59a9ff1..000000000 --- a/plugins/talk-plugin-auth/client/components/ResendVerification.css +++ /dev/null @@ -1,15 +0,0 @@ -.header { - margin-bottom: 20px; - text-align: center; - font-size: 1.2em; -} - -.notVerified { - padding: 10px; - margin-bottom: 20px; - border-radius: 2px; - border: solid 1px #dddd00; - background: #FFFF9C; - color: #777700; -} - diff --git a/plugins/talk-plugin-auth/client/components/ResendVerification.js b/plugins/talk-plugin-auth/client/components/ResendVerification.js deleted file mode 100644 index d2d71dd45..000000000 --- a/plugins/talk-plugin-auth/client/components/ResendVerification.js +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { - Button, - Spinner, - Success, - Alert, -} from 'plugin-api/beta/client/components/ui'; -import PropTypes from 'prop-types'; -import styles from './ResendVerification.css'; -import t from 'coral-framework/services/i18n'; - -class ResendVerification extends React.Component { - render() { - const { resendVerification, error, loading, success, email } = this.props; - return ( -
-

{t('sign_in.email_verify_cta')}

- - {error && ( - - {error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString()} - - )} -
- {t('error.email_not_verified', email)} -
-
- - {loading && } - {success && } -
-
- ); - } -} - -ResendVerification.propTypes = { - resendVerification: PropTypes.bool.isRequired, - error: PropTypes.object, - loading: PropTypes.bool, - success: PropTypes.bool, - email: PropTypes.string.isRequired, -}; - -export default ResendVerification; diff --git a/plugins/talk-plugin-auth/client/components/SignDialog.js b/plugins/talk-plugin-auth/client/components/SignDialog.js deleted file mode 100644 index a33baeb99..000000000 --- a/plugins/talk-plugin-auth/client/components/SignDialog.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { Dialog } from 'plugin-api/beta/client/components/ui'; -import styles from './styles.css'; - -import SignInContent from './SignInContent'; -import SignUpContent from './SignUpContent'; -import ForgotContent from './ForgotContent'; -import ResendVerification from './ResendVerification'; - -const SignDialog = ({ open, view, resetSignInDialog, ...props }) => ( - - {view !== 'SIGNIN' && ( - - × - - )} - {view === 'SIGNIN' && } - {view === 'SIGNUP' && } - {view === 'FORGOT' && } - {view === 'RESEND_VERIFICATION' && ( - - )} - -); - -export default SignDialog; diff --git a/plugins/talk-plugin-auth/client/components/SignInContainer.js b/plugins/talk-plugin-auth/client/components/SignInContainer.js deleted file mode 100644 index a184f3256..000000000 --- a/plugins/talk-plugin-auth/client/components/SignInContainer.js +++ /dev/null @@ -1,202 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import SignDialog from './SignDialog'; -import { bindActionCreators } from 'redux'; -import t from 'coral-framework/services/i18n'; -import errorMsj from 'coral-framework/helpers/error'; -import validate from 'coral-framework/helpers/validate'; - -import { - changeView, - fetchSignUp, - fetchSignIn, - hideSignInDialog, - fetchSignInFacebook, - fetchSignUpFacebook, - fetchForgotPassword, - requestConfirmEmail, - resetSignInDialog, - facebookCallback, - invalidForm, - validForm, -} from 'coral-embed-stream/src/actions/login'; - -class SignInContainer extends React.Component { - constructor(props) { - super(props); - - this.state = { - formData: { - email: '', - username: '', - password: '', - confirmPassword: '', - }, - errors: {}, - showErrors: false, - }; - } - - componentDidMount() { - this.listenToStorageChanges(); - const { formData } = this.state; - const errors = Object.keys(formData).reduce((map, prop) => { - map[prop] = t('sign_in.required_field'); - return map; - }, {}); - this.setState({ errors }); - } - - componentWillUnmount() { - this.unlisten(); - } - - listenToStorageChanges() { - window.addEventListener('storage', this.handleAuth); - } - - unlisten() { - window.removeEventListener('storage', this.handleAuth); - } - - handleAuth = e => { - // Listening to FB changes - // FB localStorage key is 'auth' - const authCallback = this.props.facebookCallback; - - if (e.key === 'auth') { - const { err, data } = JSON.parse(e.newValue); - authCallback(err, data); - this.unlisten(); - localStorage.removeItem('auth'); - } - }; - - handleChange = e => { - const { name, value } = e.target; - this.setState( - state => ({ - ...state, - formData: { - ...state.formData, - [name]: value, - }, - }), - () => { - this.validation(name, value); - } - ); - }; - - resendVerification = () => { - this.props.requestConfirmEmail(this.props.auth.email).then(() => { - setTimeout(() => { - // allow success UI to be shown for a second, and then close the modal - this.props.resetSignInDialog(); - }, 2500); - }); - }; - - addError = (name, error) => { - return this.setState(state => ({ - errors: { - ...state.errors, - [name]: error, - }, - })); - }; - - validation = (name, value) => { - const { addError } = this; - const { formData } = this.state; - - if (!value.length) { - addError(name, t('sign_in.required_field')); - } else if ( - name === 'confirmPassword' && - formData.confirmPassword !== formData.password - ) { - addError('confirmPassword', t('sign_in.passwords_dont_match')); - } else if (!validate[name](value)) { - addError(name, errorMsj[name]); - } else { - const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line - // Removes Error - this.setState(state => ({ ...state, errors })); - } - }; - - isCompleted = () => { - const { formData } = this.state; - return !Object.keys(formData).filter(prop => !formData[prop].length).length; - }; - - displayErrors = (show = true) => { - this.setState({ showErrors: show }); - }; - - handleSignUp = e => { - e.preventDefault(); - const { errors } = this.state; - const { fetchSignUp, validForm, invalidForm } = this.props; - this.displayErrors(); - if (this.isCompleted() && !Object.keys(errors).length) { - fetchSignUp(this.state.formData); - validForm(); - } else { - invalidForm(t('sign_in.check_the_form')); - } - }; - - handleSignIn = e => { - e.preventDefault(); - this.props.fetchSignIn(this.state.formData); - }; - - render() { - const { auth } = this.props; - const { - requireEmailConfirmation, - emailVerificationLoading, - emailVerificationSuccess, - } = auth; - - return ( - - ); - } -} - -const mapStateToProps = state => ({ - auth: state.auth, -}); - -const mapDispatchToProps = dispatch => - bindActionCreators( - { - facebookCallback, - fetchSignUp, - fetchSignIn, - fetchSignInFacebook, - fetchSignUpFacebook, - fetchForgotPassword, - requestConfirmEmail, - changeView, - hideSignInDialog, - resetSignInDialog, - invalidForm, - validForm, - }, - dispatch - ); - -export default connect(mapStateToProps, mapDispatchToProps)(SignInContainer); diff --git a/plugins/talk-plugin-auth/client/components/SignInContent.js b/plugins/talk-plugin-auth/client/components/SignInContent.js deleted file mode 100644 index e74de4ff2..000000000 --- a/plugins/talk-plugin-auth/client/components/SignInContent.js +++ /dev/null @@ -1,108 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { - Button, - TextField, - Spinner, - Alert, -} from 'plugin-api/beta/client/components/ui'; -import styles from './styles.css'; -import t from 'coral-framework/services/i18n'; - -const SignInContent = ({ - handleChange, - formData, - changeView, - handleSignIn, - auth, - fetchSignInFacebook, -}) => { - return ( -
-
-

{t('sign_in.sign_in_to_join')}

-
- {auth.error && ( - - {auth.error.translation_key - ? t(`error.${auth.error.translation_key}`) - : auth.error.toString()} - - )} -
-
- -
-
-

{t('sign_in.or')}

-
-
- - -
- {!auth.isLoading ? ( - - ) : ( - - )} -
- -
- -
- ); -}; - -SignInContent.propTypes = { - auth: PropTypes.shape({ - isLoading: PropTypes.bool.isRequired, - error: PropTypes.string, - emailVerificationFailure: PropTypes.bool, - }).isRequired, - fetchSignInFacebook: PropTypes.func.isRequired, - handleSignIn: PropTypes.func.isRequired, - handleChange: PropTypes.func.isRequired, - changeView: PropTypes.func.isRequired, - emailVerificationLoading: PropTypes.bool.isRequired, - emailVerificationSuccess: PropTypes.bool.isRequired, - resendVerification: PropTypes.func.isRequired, - formData: PropTypes.object, -}; - -export default SignInContent; diff --git a/plugins/talk-plugin-auth/client/components/SignUpContent.js b/plugins/talk-plugin-auth/client/components/SignUpContent.js deleted file mode 100644 index 845fe502c..000000000 --- a/plugins/talk-plugin-auth/client/components/SignUpContent.js +++ /dev/null @@ -1,143 +0,0 @@ -import styles from './styles.css'; -import React from 'react'; -import { - Button, - TextField, - Spinner, - Success, - Alert, -} from 'plugin-api/beta/client/components/ui'; -import t from 'coral-framework/services/i18n'; - -class SignUpContent extends React.Component { - componentWillReceiveProps(next) { - if ( - !this.props.emailVerificationEnabled && - !this.props.auth.successSignUp && - next.auth.successSignUp - ) { - setTimeout(() => { - this.props.changeView('SIGNIN'); - }, 2000); - } - } - - render() { - const { - handleChange, - formData, - emailVerificationEnabled, - auth, - errors, - showErrors, - changeView, - handleSignUp, - fetchSignUpFacebook, - } = this.props; - - return ( -
-
-

{t('sign_in.sign_up')}

-
- - {auth.error && {auth.error}} - {!auth.successSignUp && ( -
-
- -
-
-

{t('sign_in.or')}

-
-
- - - - {errors.password && ( - - {' '} - Password must be at least 8 characters.{' '} - - )} - -
- - {auth.isLoading && } -
- -
- )} - {auth.successSignUp && ( -
- - {emailVerificationEnabled && ( -

- {t('sign_in.verify_email')} -
-
- {t('sign_in.verify_email2')} -

- )} -
- )} -
- {t('sign_in.already_have_an_account')}{' '} - changeView('SIGNIN')}> - {t('sign_in.sign_in')} - -
-
- ); - } -} - -export default SignUpContent; diff --git a/plugins/talk-plugin-auth/client/components/UserBox.js b/plugins/talk-plugin-auth/client/components/UserBox.js deleted file mode 100644 index 09e4302b6..000000000 --- a/plugins/talk-plugin-auth/client/components/UserBox.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import t from 'coral-framework/services/i18n'; -import { logout } from 'coral-framework/actions/auth'; - -const UserBox = ({ user, logout, onShowProfile }) => ( -
- {user ? ( -
- - {t('sign_in.logged_in_as')} - - {user.username}. {t('sign_in.not_you')} - logout()} - > - {t('sign_in.logout')} - -
- ) : null} -
-); - -const mapStateToProps = ({ auth }) => ({ - user: auth.user, -}); - -const mapDispatchToProps = dispatch => bindActionCreators({ logout }, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(UserBox); diff --git a/plugins/talk-plugin-auth/client/components/styles.css b/plugins/talk-plugin-auth/client/components/styles.css deleted file mode 100644 index 735f34d31..000000000 --- a/plugins/talk-plugin-auth/client/components/styles.css +++ /dev/null @@ -1,167 +0,0 @@ -.dialog { - border: none; - box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 280px; - top: 10px; -} - -.header { - margin-bottom: 20px; -} - -.header h1, .separator h1{ - text-align: center; - font-size: 1.2em; -} - -.footer { - margin: 20px auto 10px; - text-align: center; -} - -.footer span { - display: block; - margin-bottom: 5px; -} - -.footer a { - color: #2c69b6; - cursor: pointer; - margin: 0 5px; -} - -.socialConnections { - margin-bottom: 20px; -} - -.signInButton { - margin-top: 10px; - background-color: #2a2a2a; -} - -.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; -} - -input.error{ - border: solid 2px #f44336; -} - -.errorMsg, .hint { - color: grey; - font-weight: 600; - padding: 3px 0 16px; -} - -.userBox { - margin: 10px 0 20px; - letter-spacing: 0.1px; -} - -.userBoxLoggedIn { - font-weight: bold; -} - -.userBox a { - color: black; - font-weight: bold; - cursor: pointer; - margin: 0px; - margin-left: 4px; - padding-bottom: 2px; -} - -.userBox .logout { - border-bottom: solid 1px black; -} - -.attention { - display: inline-block; - width: 15px; - height: 15px; - background: #B71C1C; - color: #FFEBEE; - font-weight: bolder; - padding: 4px; - vertical-align: middle; - border-radius: 20px; - box-sizing: border-box; - font-size: 9px; - line-height: 7px; - text-align: center; - margin-right: 5px; -} - -.action { - margin-top: 0px; -} - -.passwordRequestSuccess { - border: 1px solid green; - background-color: lightgreen; - padding: 10px; -} - -.passwordRequestFailure { - border: 1px solid orange; - background-color: 1px solid coral; - padding: 10px; -} - -.emailConfirmDialog { - margin-top: 15px; -} - -.confirmLabel { - display: block; -} - -/* Change username Dialog*/ - -.dialogusername { - border: none; - box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 400px; - top: 10px; -} - -.yourusername { - display: block; -} - -.example { - display: block; -} - -.ifyoudont { - display: block; - margin-top: 15px; -} - -.saveusername { - display: block; - width: 100%; -} - -.savebutton { - display: inline; - background-color: rgb(105,105,105); - color: white; -} - -.fakeComment { - display: block; - margin-bottom: 5px; -} diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 12aa8ccd5..0c70cb112 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -1,13 +1,12 @@ -import UserBox from './components/UserBox'; -import SignInButton from './components/SignInButton'; -import SignInContainer from './components/SignInContainer'; -import ChangeUserNameContainer from './components/ChangeUsername'; +import UserBox from './stream/containers/UserBox'; +import SignInButton from './stream/containers/SignInButton'; import translations from './translations.yml'; +import Login from './login/containers/Main'; export default { translations, slots: { - stream: [UserBox, SignInButton, ChangeUserNameContainer], - login: [SignInContainer], + stream: [UserBox, SignInButton], + login: [Login], }, }; diff --git a/plugins/talk-plugin-auth/client/login/components/Main.css b/plugins/talk-plugin-auth/client/login/components/Main.css new file mode 100644 index 000000000..9d73e7403 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/Main.css @@ -0,0 +1,8 @@ +.dialog { + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 280px; + top: 10px; + font-family: Helvetica, 'Helvetica Neue', Verdana, sans-serif; + font-size: 14px; +} diff --git a/plugins/talk-plugin-auth/client/login/components/Main.js b/plugins/talk-plugin-auth/client/login/components/Main.js new file mode 100644 index 000000000..e74a38b66 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/Main.js @@ -0,0 +1,13 @@ +import React from 'react'; +import { Dialog } from 'plugin-api/beta/client/components/ui'; +import styles from './Main.css'; + +import SignIn from '../containers/SignIn'; + +const SignDialog = () => ( + + + +); + +export default SignDialog; diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.css b/plugins/talk-plugin-auth/client/login/components/SignIn.css new file mode 100644 index 000000000..e6442d6f3 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.css @@ -0,0 +1,47 @@ +.header { + margin-bottom: 20px; +} + +.header h1, .separator h1{ + text-align: center; + font-size: 1.2em; +} + +.socialConnections { + margin-bottom: 20px; +} + +.separator h1{ + text-align: center; + font-size: 1.2em; +} + +.action { + margin-top: 0px; +} + +.signInButton { + 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; +} + +.recaptcha { + margin-top: 16px; + margin-bottom: 16px; +} diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js new file mode 100644 index 000000000..720a5aaf0 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -0,0 +1,140 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + TextField, + Spinner, + Alert, +} from 'plugin-api/beta/client/components/ui'; +import styles from './SignIn.css'; +import t from 'coral-framework/services/i18n'; +import cn from 'classnames'; +import Recaptcha from 'coral-framework/components/Recaptcha'; + +class SignIn extends React.Component { + recaptcha = null; + + handleForgotPasswordLink = e => { + e.preventDefault(); + this.props.onForgotPasswordLink(); + }; + handleRegisterLink = e => { + e.preventDefault(); + this.props.onRegisterLink(); + }; + handleEmailChange = e => this.props.onEmailChange(e.target.value); + handlePasswordChange = e => this.props.onPasswordChange(e.target.value); + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + + // Reset recaptcha because each response can only + // be used once. + if (this.recaptcha) { + this.recaptcha.reset(); + } + }; + + handleRecaptchaRef = ref => { + this.recaptcha = ref; + }; + + render() { + const { + email, + password, + errorMessage, + requireRecaptcha, + loading, + } = this.props; + return ( +
+
+

{t('sign_in.sign_in_to_join')}

+
+ {errorMessage && {errorMessage}} +
+
+ Social +
+
+

{t('sign_in.or')}

+
+
+ + + {requireRecaptcha && ( +
+ +
+ )} +
+ {!loading ? ( + + ) : ( + + )} +
+ +
+
+ + + {t('sign_in.forgot_your_pass')} + + + + {t('sign_in.need_an_account')} + + {t('sign_in.register')} + + +
+
+ ); + } +} + +SignIn.propTypes = { + loading: PropTypes.bool.isRequired, + email: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + onEmailChange: PropTypes.func.isRequired, + onPasswordChange: PropTypes.func.isRequired, + onForgotPasswordLink: PropTypes.func.isRequired, + onRegisterLink: PropTypes.func.isRequired, + onRecaptchaVerify: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + requireRecaptcha: PropTypes.bool.isRequired, +}; + +export default SignIn; diff --git a/plugins/talk-plugin-auth/client/login/containers/Main.js b/plugins/talk-plugin-auth/client/login/containers/Main.js new file mode 100644 index 000000000..5baedcf08 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/containers/Main.js @@ -0,0 +1,10 @@ +import React from 'react'; +import Main from '../components/Main'; + +class MainContainer extends React.Component { + render() { + return
; + } +} + +export default MainContainer; diff --git a/plugins/talk-plugin-auth/client/login/containers/SignIn.js b/plugins/talk-plugin-auth/client/login/containers/SignIn.js new file mode 100644 index 000000000..14d87bf0f --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/containers/SignIn.js @@ -0,0 +1,65 @@ +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: '', + recaptchaResponse: '', + }; + + handleSubmit = () => { + this.props.signIn( + this.state.email, + this.state.password, + this.state.recaptchaResponse + ); + }; + + handleEmailChange = email => { + this.setState({ email }); + }; + + handlePasswordChange = password => { + this.setState({ password }); + }; + + handleRecaptchaVerify = recaptchaResponse => { + this.setState({ recaptchaResponse }); + }; + + componentWillReceiveProps(nextProps) { + if (nextProps.success) { + window.close(); + } + } + + render() { + return ( + + ); + } +} + +SignInContainer.propTypes = { + signIn: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + requireRecaptcha: PropTypes.bool.isRequired, + loading: PropTypes.bool.isRequired, + success: PropTypes.bool.isRequired, +}; + +export default compose(withSignIn)(SignInContainer); diff --git a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js similarity index 95% rename from plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js rename to plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js index 08ddc171e..681f444ee 100644 --- a/plugins/talk-plugin-auth/client/components/CreateUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js @@ -10,7 +10,7 @@ import { import { FakeComment } from './FakeComment'; import t from 'coral-framework/services/i18n'; -const CreateUsernameDialog = ({ +const SetUsernameDialog = ({ open, handleClose, formData, @@ -70,7 +70,7 @@ const CreateUsernameDialog = ({ ); -CreateUsernameDialog.propTypes = { +SetUsernameDialog.propTypes = { open: PropTypes.bool, handleClose: PropTypes.func, formData: PropTypes.object, @@ -80,4 +80,4 @@ CreateUsernameDialog.propTypes = { errors: PropTypes.object, }; -export default CreateUsernameDialog; +export default SetUsernameDialog; diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.css b/plugins/talk-plugin-auth/client/stream/components/SignInButton.css new file mode 100644 index 000000000..e69de29bb diff --git a/plugins/talk-plugin-auth/client/components/SignInButton.js b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js similarity index 52% rename from plugins/talk-plugin-auth/client/components/SignInButton.js rename to plugins/talk-plugin-auth/client/stream/components/SignInButton.js index 9ad19599e..dcc6e5c4a 100644 --- a/plugins/talk-plugin-auth/client/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js @@ -1,8 +1,6 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Button } from 'plugin-api/beta/client/components/ui'; -import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; -import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; import t from 'coral-framework/services/i18n'; const SignInButton = ({ currentUser, showSignInDialog }) => ( @@ -15,11 +13,9 @@ const SignInButton = ({ currentUser, showSignInDialog }) => (
); -const mapStateToProps = ({ auth }) => ({ - currentUser: auth.user, -}); +SignInButton.propTypes = { + currentUser: PropTypes.object, + showSignInDialog: PropTypes.func, +}; -const mapDispatchToProps = dispatch => - bindActionCreators({ showSignInDialog }, dispatch); - -export default connect(mapStateToProps, mapDispatchToProps)(SignInButton); +export default SignInButton; diff --git a/plugins/talk-plugin-auth/client/stream/components/UserBox.css b/plugins/talk-plugin-auth/client/stream/components/UserBox.css new file mode 100644 index 000000000..799468177 --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/components/UserBox.css @@ -0,0 +1,21 @@ +.userBox { + margin: 10px 0 20px; + letter-spacing: 0.1px; +} + +.userBoxLoggedIn { + font-weight: bold; +} + +.userBox a { + color: black; + font-weight: bold; + cursor: pointer; + margin: 0px; + margin-left: 4px; + padding-bottom: 2px; +} + +.userBox .logout { + border-bottom: solid 1px black; +} diff --git a/plugins/talk-plugin-auth/client/stream/components/UserBox.js b/plugins/talk-plugin-auth/client/stream/components/UserBox.js new file mode 100644 index 000000000..7f48513b8 --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/components/UserBox.js @@ -0,0 +1,32 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './UserBox.css'; +import t from 'coral-framework/services/i18n'; +import cn from 'classnames'; + +const UserBox = ({ user, logout, onShowProfile }) => ( +
+ {user ? ( +
+ + {t('sign_in.logged_in_as')} + + {user.username}. {t('sign_in.not_you')} + logout()} + > + {t('sign_in.logout')} + +
+ ) : null} +
+); + +UserBox.propTypes = { + user: PropTypes.object, + logout: PropTypes.func, + onShowProfile: PropTypes.func, +}; + +export default UserBox; diff --git a/plugins/talk-plugin-auth/client/components/ChangeUsername.js b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js similarity index 97% rename from plugins/talk-plugin-auth/client/components/ChangeUsername.js rename to plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js index 2eab273cc..cba032cd6 100644 --- a/plugins/talk-plugin-auth/client/components/ChangeUsername.js +++ b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js @@ -19,7 +19,7 @@ import { updateUsername, } from 'coral-embed-stream/src/actions/login'; -class ChangeUsernameContainer extends React.Component { +class SetUsernameDialog extends React.Component { constructor(props) { super(props); @@ -152,7 +152,7 @@ class ChangeUsernameContainer extends React.Component { } } -ChangeUsernameContainer.propTypes = { +SetUsernameDialog.propTypes = { auth: PropTypes.object, hideCreateUsernameDialog: PropTypes.func, validForm: PropTypes.func, @@ -180,4 +180,4 @@ const mapDispatchToProps = dispatch => export default compose( withSetUsername, connect(mapStateToProps, mapDispatchToProps) -)(ChangeUsernameContainer); +)(SetUsernameDialog); diff --git a/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js b/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js new file mode 100644 index 000000000..4fcd98ae3 --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; +import SignInButton from '../components//SignInButton'; + +const mapStateToProps = ({ auth }) => ({ + currentUser: auth.user, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators({ showSignInDialog }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(SignInButton); diff --git a/plugins/talk-plugin-auth/client/stream/containers/UserBox.js b/plugins/talk-plugin-auth/client/stream/containers/UserBox.js new file mode 100644 index 000000000..ca50c7b6e --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/containers/UserBox.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { logout } from 'coral-framework/actions/auth'; +import UserBox from '../components/UserBox'; + +const mapStateToProps = ({ auth }) => ({ + user: auth.user, +}); + +const mapDispatchToProps = dispatch => bindActionCreators({ logout }, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(UserBox); diff --git a/views/login.ejs b/views/login.ejs index 634054074..04e64d93c 100644 --- a/views/login.ejs +++ b/views/login.ejs @@ -15,6 +15,7 @@
+ diff --git a/yarn.lock b/yarn.lock index d13397fee..62ebbc4f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7697,8 +7697,8 @@ react-portal@^4.1.2: prop-types "^15.5.8" react-recaptcha@^2.2.6: - version "2.3.5" - resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.5.tgz#a5db337125bb00fb13c2fa2e4ebfbe8b0cd06bb7" + version "2.3.6" + resolved "https://registry.yarnpkg.com/react-recaptcha/-/react-recaptcha-2.3.6.tgz#afe07b5552f3ea4d37ecd22d9881c2776719ec2b" react-redux@^4.4.5: version "4.4.8" From 4b6a3b5b9d2228fe6580eb4e2f7e0a3aede5189a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 9 Feb 2018 23:18:21 +0100 Subject: [PATCH 11/37] More refactoring --- .../src/components/ForgotPassword.js | 4 +- .../src/containers/ForgotPassword.js | 1 - locales/en.yml | 1 + plugins/talk-plugin-auth/client/actions.js | 0 plugins/talk-plugin-auth/client/index.js | 2 + .../talk-plugin-auth/client/login/actions.js | 16 ++++ .../login/components/ForgotPassword.css | 42 ++++++++++ .../client/login/components/ForgotPassword.js | 79 +++++++++++++++++++ .../client/login/components/Main.css | 16 ++++ .../client/login/components/Main.js | 21 ++++- .../client/login/components/SignIn.js | 8 +- .../client/login/constants.js | 5 ++ .../client/login/containers/ForgotPassword.js | 64 +++++++++++++++ .../client/login/containers/Main.js | 30 ++++++- .../client/login/containers/SignIn.js | 57 +++++++++---- .../client/login/enums/views.js | 4 + .../talk-plugin-auth/client/login/reducer.js | 30 +++++++ 17 files changed, 352 insertions(+), 28 deletions(-) delete mode 100644 plugins/talk-plugin-auth/client/actions.js create mode 100644 plugins/talk-plugin-auth/client/login/actions.js create mode 100644 plugins/talk-plugin-auth/client/login/components/ForgotPassword.css create mode 100644 plugins/talk-plugin-auth/client/login/components/ForgotPassword.js create mode 100644 plugins/talk-plugin-auth/client/login/constants.js create mode 100644 plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js create mode 100644 plugins/talk-plugin-auth/client/login/enums/views.js create mode 100644 plugins/talk-plugin-auth/client/login/reducer.js 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 ( + + ); + } +} + +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; + } +} From 142c5b32f129212a3fda4d884d1913c1d1fe232b Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 00:54:15 +0100 Subject: [PATCH 12/37] More refactoring --- client/coral-framework/hocs/index.js | 4 + .../hocs/withForgotPassword.js | 7 +- .../hocs/withResendEmailConfirmation.js | 65 +++++++ client/coral-framework/hocs/withSignIn.js | 12 +- client/coral-framework/hocs/withSignUp.js | 118 ++++++++++++ .../client/login/components/ForgotPassword.js | 2 - .../client/login/components/Main.js | 4 + .../components/ResendEmailConfirmation.css | 15 ++ .../components/ResendEmailConfirmation.js | 56 ++++++ .../client/login/components/SignUp.css | 49 +++++ .../client/login/components/SignUp.js | 168 ++++++++++++++++++ .../client/login/containers/Main.js | 16 ++ .../containers/ResendEmailConfirmation.js | 62 +++++++ .../client/login/containers/SignIn.js | 9 +- .../client/login/containers/SignUp.js | 126 +++++++++++++ .../client/login/enums/views.js | 2 +- 16 files changed, 706 insertions(+), 9 deletions(-) create mode 100644 client/coral-framework/hocs/withResendEmailConfirmation.js create mode 100644 client/coral-framework/hocs/withSignUp.js create mode 100644 plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.css create mode 100644 plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js create mode 100644 plugins/talk-plugin-auth/client/login/components/SignUp.css create mode 100644 plugins/talk-plugin-auth/client/login/components/SignUp.js create mode 100644 plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js create mode 100644 plugins/talk-plugin-auth/client/login/containers/SignUp.js diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index cfc41676b..c8b8f6bb6 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -7,4 +7,8 @@ export { default as excludeIf } from './excludeIf'; export { default as connect } from './connect'; export { default as withMergedSettings } from './withMergedSettings'; export { default as withSignIn } from './withSignIn'; +export { default as withSignUp } from './withSignUp'; export { default as withForgotPassword } from './withForgotPassword'; +export { + default as withResendEmailConfirmation, +} from './withResendEmailConfirmation'; diff --git a/client/coral-framework/hocs/withForgotPassword.js b/client/coral-framework/hocs/withForgotPassword.js index c8b42b8c5..fb8eb434c 100644 --- a/client/coral-framework/hocs/withForgotPassword.js +++ b/client/coral-framework/hocs/withForgotPassword.js @@ -11,6 +11,7 @@ export default hoistStatics(WrappedComponent => { static contextTypes = { store: PropTypes.object, rest: PropTypes.func, + pym: PropTypes.object, }; state = { @@ -19,9 +20,11 @@ export default hoistStatics(WrappedComponent => { success: false, }; - forgotPassword = email => { + forgotPassword = (email, redirectUri) => { + if (!redirectUri) { + redirectUri = this.context.pym.parentUrl || location.href; + } const { rest } = this.context; - const redirectUri = location.href; this.setState({ loading: true, error: null, success: false }); rest('/account/password/reset', { diff --git a/client/coral-framework/hocs/withResendEmailConfirmation.js b/client/coral-framework/hocs/withResendEmailConfirmation.js new file mode 100644 index 000000000..6b5f8375d --- /dev/null +++ b/client/coral-framework/hocs/withResendEmailConfirmation.js @@ -0,0 +1,65 @@ +import React from 'react'; +import hoistStatics from 'recompose/hoistStatics'; +import PropTypes from 'prop-types'; +import { translateError } from '../utils'; + +/** + * WithResendEmailConfirmaton provides properties `forgotPasssword`, `loading`, `errorMessage`, `success`. + */ +export default hoistStatics(WrappedComponent => { + class WithResendEmailConfirmaton extends React.Component { + static contextTypes = { + store: PropTypes.object, + rest: PropTypes.func, + pym: PropTypes.object, + }; + + state = { + error: null, + loading: false, + success: false, + }; + + resendEmailConfirmation = (email, redirectUri) => { + if (!redirectUri) { + redirectUri = this.context.pym.parentUrl || location.href; + } + const { rest } = this.context; + this.setState({ loading: true, error: null, success: false }); + + rest('/users/resend-verify', { + method: 'POST', + body: { email }, + headers: { 'X-Pym-Url': 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 WithResendEmailConfirmaton; +}); diff --git a/client/coral-framework/hocs/withSignIn.js b/client/coral-framework/hocs/withSignIn.js index fa70138f9..53c3ab6d1 100644 --- a/client/coral-framework/hocs/withSignIn.js +++ b/client/coral-framework/hocs/withSignIn.js @@ -6,7 +6,13 @@ import { translateError } from '../utils'; import { t } from '../services/i18n'; /** - * WithSignIn provides properties `signIn`, `loading`, `errorMessage`, `requireRecaptcha`, 'success'. + * WithSignIn provides properties + * `signIn` + * `loading` + * `errorMessage` + * `requireRecaptcha` + * `requireEmailConfirmation` + * 'success' */ export default hoistStatics(WrappedComponent => { class WithSignIn extends React.Component { @@ -20,6 +26,7 @@ export default hoistStatics(WrappedComponent => { loading: false, success: false, requireRecaptcha: false, + requireEmailConfirmation: false, }; signIn = (email, password, recaptchaResponse) => { @@ -51,6 +58,8 @@ export default hoistStatics(WrappedComponent => { if (error.translation_key === 'LOGIN_MAXIMUM_EXCEEDED') { changeSet.requireRecaptcha = !!this.context.store.getState().config .static.TALK_RECAPTCHA_PUBLIC; + } else if (error.translation_key === 'EMAIL_NOT_VERIFIED') { + changeSet.requireEmailConfirmation = true; } this.setState(changeSet); }); @@ -73,6 +82,7 @@ export default hoistStatics(WrappedComponent => { loading={this.state.loading} errorMessage={this.getErrorMessage()} requireRecaptcha={this.state.requireRecaptcha} + requireEmailConfirmation={this.state.requireEmailConfirmation} success={this.state.success} /> ); diff --git a/client/coral-framework/hocs/withSignUp.js b/client/coral-framework/hocs/withSignUp.js new file mode 100644 index 000000000..8274b3b8a --- /dev/null +++ b/client/coral-framework/hocs/withSignUp.js @@ -0,0 +1,118 @@ +import React from 'react'; +import hoistStatics from 'recompose/hoistStatics'; +import { compose, gql } from 'react-apollo'; +import PropTypes from 'prop-types'; +import { translateError } from '../utils'; +import validate from '../helpers/validate'; +import errorMsg from 'coral-framework/helpers/error'; +import t from '../services/i18n'; +import withQuery from './withQuery'; +import get from 'lodash/get'; + +const requiredFields = ['username', 'email', 'password']; +const allFields = requiredFields; + +const QUERY = gql` + query TalkFramework_WithSignUpQuery { + settings { + requireEmailConfirmation + } + } +`; + +export const withSettingsQuery = withQuery(QUERY); + +/** + * withSignUp provides properties `signUp`, `loading`, `errorMessage`, `requireEmailVerification`, 'success', 'validate'. + */ +const withSignUp = hoistStatics(WrappedComponent => { + class WithSignUp extends React.Component { + static contextTypes = { + store: PropTypes.object, + rest: PropTypes.func, + pym: PropTypes.object, + }; + + static propTypes = { + root: PropTypes.object.isRequired, + }; + + state = { + error: null, + loading: false, + success: false, + }; + + validate = (field, value) => { + if (!allFields.includes(field)) { + return ''; + } + + if (requiredFields.includes(field) && !value) { + return t('sign_in.required_field'); + } + + if (field in validate) { + return validate[field](value) ? '' : errorMsg[field]; + } + + return ''; + }; + + signUp = ({ username, email, password }, redirectUri) => { + if (!redirectUri) { + redirectUri = this.context.pym.parentUrl || location.href; + } + + const { rest } = this.context; + const params = { + method: 'POST', + body: { + username, + email, + password, + }, + headers: { 'X-Pym-Url': redirectUri }, + }; + + rest('/users', params) + .then(() => { + this.setState({ success: true, loading: false, error: null }); + }) + .catch(error => { + if (!error.status || error.status !== 401) { + console.error(error); + } + const changeSet = { success: false, loading: false, error }; + this.setState(changeSet); + }); + }; + + getErrorMessage() { + if (!this.state.error) { + return ''; + } + return translateError(this.state.error); + } + + render() { + return ( + + ); + } + } + + return WithSignUp; +}); + +export default compose(withSettingsQuery, withSignUp); diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js index 84daa04b7..12a309c28 100644 --- a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js @@ -5,8 +5,6 @@ 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(); diff --git a/plugins/talk-plugin-auth/client/login/components/Main.js b/plugins/talk-plugin-auth/client/login/components/Main.js index 1aed0f229..992d7a2fd 100644 --- a/plugins/talk-plugin-auth/client/login/components/Main.js +++ b/plugins/talk-plugin-auth/client/login/components/Main.js @@ -3,7 +3,9 @@ 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 SignUp from '../containers/SignUp'; import ForgotPassword from '../containers/ForgotPassword'; +import ResendEmailConfirmation from '../containers/ResendEmailConfirmation'; import * as views from '../enums/views'; const Main = ({ view, onResetView }) => ( @@ -14,7 +16,9 @@ const Main = ({ view, onResetView }) => ( )} {view === views.SIGN_IN && } + {view === views.SIGN_UP && } {view === views.FORGOT_PASSWORD && } + {view === views.RESEND_EMAIL_CONFIRMATION && } ); diff --git a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.css b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.css new file mode 100644 index 000000000..dc59a9ff1 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.css @@ -0,0 +1,15 @@ +.header { + margin-bottom: 20px; + text-align: center; + font-size: 1.2em; +} + +.notVerified { + padding: 10px; + margin-bottom: 20px; + border-radius: 2px; + border: solid 1px #dddd00; + background: #FFFF9C; + color: #777700; +} + diff --git a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js new file mode 100644 index 000000000..082492128 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { + Button, + Spinner, + Success, + Alert, +} from 'plugin-api/beta/client/components/ui'; +import PropTypes from 'prop-types'; +import styles from './ResendEmailConfirmation.css'; +import t from 'coral-framework/services/i18n'; + +class ResendVerification extends React.Component { + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + render() { + const { email, errorMessage, loading, success } = this.props; + return ( +
+

{t('sign_in.email_verify_cta')}

+ + {errorMessage && {errorMessage}} +
+ {t('error.email_not_verified', email)} +
+
+ {!loading && + !success && ( + + )} + {loading && } + {success && } +
+
+ ); + } +} + +ResendVerification.propTypes = { + success: PropTypes.bool.isRequired, + loading: PropTypes.bool.isRequired, + email: PropTypes.string.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, +}; + +export default ResendVerification; diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.css b/plugins/talk-plugin-auth/client/login/components/SignUp.css new file mode 100644 index 000000000..057713757 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.css @@ -0,0 +1,49 @@ +.header { + margin-bottom: 20px; +} + +.header h1, .separator h1{ + text-align: center; + font-size: 1.2em; +} + +.socialConnections { + margin-bottom: 20px; +} + +.separator h1{ + text-align: center; + font-size: 1.2em; +} + +.hint { + color: grey; + font-weight: 600; + padding: 3px 0 16px; +} + +.action { + margin-top: 0px; +} + +.signInButton { + 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; +} + diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js new file mode 100644 index 000000000..014d0db89 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -0,0 +1,168 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Button, + TextField, + Spinner, + Success, + Alert, +} from 'plugin-api/beta/client/components/ui'; +import t from 'coral-framework/services/i18n'; +import styles from './SignUp.css'; + +class SignUp extends React.Component { + handleSignInLink = e => { + e.preventDefault(); + this.props.onSignInLink(); + }; + + handleUsernameChange = e => this.props.onUsernameChange(e.target.value); + handleEmailChange = e => this.props.onEmailChange(e.target.value); + handlePasswordChange = e => this.props.onPasswordChange(e.target.value); + handlePasswordRepeatChange = e => + this.props.onPasswordRepeatChange(e.target.value); + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + render() { + const { + username, + email, + password, + passwordRepeat, + usernameError, + emailError, + passwordError, + passwordRepeatError, + loading, + errorMessage, + requireEmailConfirmation, + success, + } = this.props; + + return ( +
+
+

{t('sign_in.sign_up')}

+
+ + {errorMessage && {errorMessage}} + {!success && ( +
+
Social
+
+

{t('sign_in.or')}

+
+
+ + + + {passwordError && ( + + {' '} + Password must be at least 8 characters.{' '} + + )} + +
+ + {loading && } +
+ +
+ )} + {success && ( +
+ + {requireEmailConfirmation && ( +

+ {t('sign_in.verify_email')} +
+
+ {t('sign_in.verify_email2')} +

+ )} +
+ )} +
+ {t('sign_in.already_have_an_account')}{' '} + + {t('sign_in.sign_in')} + +
+
+ ); + } +} + +SignUp.propTypes = { + loading: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired, + usernameError: PropTypes.string.isRequired, + email: PropTypes.string.isRequired, + emailError: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + passwordError: PropTypes.string.isRequired, + passwordRepeat: PropTypes.string.isRequired, + passwordRepeatError: PropTypes.string.isRequired, + onUsernameChange: PropTypes.func.isRequired, + onEmailChange: PropTypes.func.isRequired, + onPasswordChange: PropTypes.func.isRequired, + onPasswordRepeatChange: PropTypes.func.isRequired, + onSignInLink: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + requireEmailConfirmation: PropTypes.bool.isRequired, + success: PropTypes.bool.isRequired, +}; + +export default SignUp; diff --git a/plugins/talk-plugin-auth/client/login/containers/Main.js b/plugins/talk-plugin-auth/client/login/containers/Main.js index 3391fe1f4..b75030e0b 100644 --- a/plugins/talk-plugin-auth/client/login/containers/Main.js +++ b/plugins/talk-plugin-auth/client/login/containers/Main.js @@ -11,6 +11,22 @@ class MainContainer extends React.Component { this.props.setView(views.SIGN_IN); }; + resizeHeight() { + setTimeout(() => { + const height = document.getElementById('signInDialog').offsetHeight + 100; + window.resizeTo(500, height); + }, 20); + } + + componentDidMount() { + this.resizeHeight(); + } + + componentDidUpdate(prevProps) { + if (prevProps.view !== this.props.view) { + this.resizeHeight(); + } + } render() { return
; } diff --git a/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js new file mode 100644 index 000000000..3b4f4b564 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js @@ -0,0 +1,62 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withResendEmailConfirmation } from 'coral-framework/hocs'; +import { compose } from 'recompose'; +import ResendEmailConfirmaton from '../components/ResendEmailConfirmation'; +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import * as views from '../enums/views'; +import { setView } from '../actions'; + +class ResendEmailConfirmatonContainer extends Component { + handleSubmit = () => { + this.props.resendEmailConfirmation(this.props.email); + }; + + componentWillReceiveProps(nextProps) { + if (nextProps.success) { + setTimeout(() => { + // allow success UI to be shown for a second, and then close the modal + this.props.setView(views.SIGN_IN); + }, 2500); + } + } + + render() { + return ( + + ); + } +} + +ResendEmailConfirmatonContainer.propTypes = { + success: PropTypes.bool.isRequired, + loading: PropTypes.bool.isRequired, + resendEmailConfirmation: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, + setView: PropTypes.func.isRequired, + email: PropTypes.string.isRequired, +}; + +const mapStateToProps = ({ talkPluginAuth: state }) => ({ + email: state.email, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + setView, + }, + dispatch + ); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withResendEmailConfirmation +)(ResendEmailConfirmatonContainer); diff --git a/plugins/talk-plugin-auth/client/login/containers/SignIn.js b/plugins/talk-plugin-auth/client/login/containers/SignIn.js index 7247a4ffe..3b84999fe 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignIn.js @@ -34,7 +34,9 @@ class SignInContainer extends Component { }; componentWillReceiveProps(nextProps) { - if (nextProps.success) { + if (nextProps.requireEmailConfirmation) { + this.props.setView(views.RESEND_EMAIL_CONFIRMATION); + } else if (nextProps.success) { window.close(); } } @@ -47,8 +49,8 @@ class SignInContainer extends Component { onPasswordChange={this.props.setPassword} onForgotPasswordLink={this.handleForgotPasswordLink} onSignUpLink={this.handleSignUpLink} - email={this.state.email} - password={this.state.password} + email={this.props.email} + password={this.props.password} errorMessage={this.props.errorMessage} onRecaptchaVerify={this.handleRecaptchaVerify} requireRecaptcha={this.props.requireRecaptcha} @@ -62,6 +64,7 @@ SignInContainer.propTypes = { signIn: PropTypes.func.isRequired, errorMessage: PropTypes.string.isRequired, requireRecaptcha: PropTypes.bool.isRequired, + requireEmailConfirmation: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired, success: PropTypes.bool.isRequired, setView: PropTypes.func.isRequired, diff --git a/plugins/talk-plugin-auth/client/login/containers/SignUp.js b/plugins/talk-plugin-auth/client/login/containers/SignUp.js new file mode 100644 index 000000000..5eb5535d0 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/containers/SignUp.js @@ -0,0 +1,126 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { withSignUp } from 'coral-framework/hocs'; +import { compose } from 'recompose'; +import SignUp from '../components/SignUp'; +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import * as views from '../enums/views'; +import { setView, setEmail, setPassword } from '../actions'; +import t from 'coral-framework/services/i18n'; + +class SignUpContainer extends Component { + state = { + username: '', + passwordRepeat: '', + usernameError: '', + emailError: '', + passwordError: '', + passwordRepeatError: '', + }; + + validate = data => { + let valid = true; + const changes = {}; + Object.keys(data).forEach(name => { + const error = this.props.validate(name, data[name]); + if (error) { + valid = false; + } + changes[`${name}Error`] = error; + }); + + if (data.password !== data.passwordRepeat) { + changes['passwordRepeatError'] = t('sign_in.passwords_dont_match'); + valid = false; + } + + this.setState(changes); + return valid; + }; + + handleSubmit = () => { + const data = { + username: this.state.username, + email: this.props.email, + password: this.props.password, + passwordRepeat: this.state.passwordRepeat, + }; + + if (this.validate(data)) { + this.props.signUp(data); + } + }; + + setUsername = username => this.setState({ username }); + setPasswordRepeat = passwordRepeat => this.setState({ passwordRepeat }); + + handleForgotPasswordLink = () => { + this.props.setView(views.FORGOT_PASSWORD); + }; + + handleSignInLink = () => { + this.props.setView(views.SIGN_IN); + }; + + render() { + return ( + + ); + } +} + +SignUpContainer.propTypes = { + setView: PropTypes.func.isRequired, + email: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + setEmail: PropTypes.func.isRequired, + setPassword: PropTypes.func.isRequired, + signUp: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, + errorMessage: PropTypes.string.isRequired, + requireEmailConfirmation: PropTypes.bool.isRequired, + success: PropTypes.bool.isRequired, + validate: PropTypes.func.isRequired, +}; + +const mapStateToProps = ({ talkPluginAuth: state }) => ({ + email: state.email, + password: state.password, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators( + { + setView, + setEmail, + setPassword, + }, + dispatch + ); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withSignUp +)(SignUpContainer); diff --git a/plugins/talk-plugin-auth/client/login/enums/views.js b/plugins/talk-plugin-auth/client/login/enums/views.js index 4f92f4439..5c6732755 100644 --- a/plugins/talk-plugin-auth/client/login/enums/views.js +++ b/plugins/talk-plugin-auth/client/login/enums/views.js @@ -1,4 +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'; +export const RESEND_EMAIL_CONFIRMATION = 'RESEND_EMAIL_CONFIRMATION'; From f5409b11bfa3521125f5af9fc9c38afb00449a3a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 11:14:12 +0100 Subject: [PATCH 13/37] Use plugin-api --- plugin-api/beta/client/actions/auth.js | 5 +++++ plugin-api/beta/client/actions/stream.js | 1 + plugin-api/beta/client/hocs/index.js | 14 ++++++++++---- .../client/login/containers/ForgotPassword.js | 3 +-- .../login/containers/ResendEmailConfirmation.js | 6 ++++-- .../client/login/containers/SignIn.js | 3 +-- .../client/login/containers/SignUp.js | 5 ++--- .../client/stream/containers/SignInButton.js | 4 ++-- .../client/stream/containers/UserBox.js | 2 +- 9 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 plugin-api/beta/client/actions/auth.js diff --git a/plugin-api/beta/client/actions/auth.js b/plugin-api/beta/client/actions/auth.js new file mode 100644 index 000000000..85e2bd844 --- /dev/null +++ b/plugin-api/beta/client/actions/auth.js @@ -0,0 +1,5 @@ +export { + setAuthToken, + handleSuccessfulLogin, + logout, +} from 'coral-framework/actions/auth'; diff --git a/plugin-api/beta/client/actions/stream.js b/plugin-api/beta/client/actions/stream.js index 2751f4a17..bb3d465d9 100644 --- a/plugin-api/beta/client/actions/stream.js +++ b/plugin-api/beta/client/actions/stream.js @@ -1 +1,2 @@ export { setSort } from 'coral-embed-stream/src/actions/stream'; +export { showSignInDialog } from 'coral-embed-stream/src/actions/login'; diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 0d7cac460..715ff56fa 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -1,10 +1,16 @@ export { default as withReaction } from './withReaction'; export { default as withTags } from './withTags'; export { default as withSortOption } from './withSortOption'; -export { default as withFragments } from 'coral-framework/hocs/withFragments'; -export { default as excludeIf } from 'coral-framework/hocs/excludeIf'; -export { default as connect } from 'coral-framework/hocs/connect'; -export { default as withEmit } from 'coral-framework/hocs/withEmit'; +export { + connect, + withEmit, + excludeIf, + withFragments, + withForgotPassword, + withSignIn, + withSignUp, + withResendEmailConfirmation, +} from 'coral-framework/hocs'; export { withIgnoreUser, withBanUser, diff --git a/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js index 36ee1384e..9b1f6cb48 100644 --- a/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js @@ -1,9 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withForgotPassword } from 'coral-framework/hocs'; +import { connect, withForgotPassword } from 'plugin-api/beta/client/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'; diff --git a/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js index 3b4f4b564..bd7c9ad8c 100644 --- a/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js +++ b/plugins/talk-plugin-auth/client/login/containers/ResendEmailConfirmation.js @@ -1,9 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withResendEmailConfirmation } from 'coral-framework/hocs'; +import { + connect, + withResendEmailConfirmation, +} from 'plugin-api/beta/client/hocs'; import { compose } from 'recompose'; import ResendEmailConfirmaton from '../components/ResendEmailConfirmation'; -import { connect } from 'plugin-api/beta/client/hocs'; import { bindActionCreators } from 'redux'; import * as views from '../enums/views'; import { setView } from '../actions'; diff --git a/plugins/talk-plugin-auth/client/login/containers/SignIn.js b/plugins/talk-plugin-auth/client/login/containers/SignIn.js index 3b84999fe..ac887d7d2 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignIn.js @@ -1,9 +1,8 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withSignIn } from 'coral-framework/hocs'; +import { connect, withSignIn } from 'plugin-api/beta/client/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'; diff --git a/plugins/talk-plugin-auth/client/login/containers/SignUp.js b/plugins/talk-plugin-auth/client/login/containers/SignUp.js index 5eb5535d0..ea19a8940 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignUp.js @@ -1,13 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { withSignUp } from 'coral-framework/hocs'; +import { connect, withSignUp } from 'plugin-api/beta/client/hocs'; import { compose } from 'recompose'; import SignUp from '../components/SignUp'; -import { connect } from 'plugin-api/beta/client/hocs'; import { bindActionCreators } from 'redux'; import * as views from '../enums/views'; import { setView, setEmail, setPassword } from '../actions'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; class SignUpContainer extends Component { state = { diff --git a/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js b/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js index 4fcd98ae3..875027a9b 100644 --- a/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/containers/SignInButton.js @@ -1,7 +1,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; -import SignInButton from '../components//SignInButton'; +import { showSignInDialog } from 'plugin-api/beta/client/actions/stream'; +import SignInButton from '../components/SignInButton'; const mapStateToProps = ({ auth }) => ({ currentUser: auth.user, diff --git a/plugins/talk-plugin-auth/client/stream/containers/UserBox.js b/plugins/talk-plugin-auth/client/stream/containers/UserBox.js index ca50c7b6e..a4c68b3cf 100644 --- a/plugins/talk-plugin-auth/client/stream/containers/UserBox.js +++ b/plugins/talk-plugin-auth/client/stream/containers/UserBox.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import { logout } from 'coral-framework/actions/auth'; +import { logout } from 'plugin-api/beta/client/actions/auth'; import UserBox from '../components/UserBox'; const mapStateToProps = ({ auth }) => ({ From d639596047f58eac2823776e506fb5be99d41b29 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 11:26:37 +0100 Subject: [PATCH 14/37] Add External Slot --- .../client/login/components/External.css | 8 ++++++++ .../client/login/components/External.js | 20 +++++++++++++++++++ .../client/login/components/SignIn.css | 11 +--------- .../client/login/components/SignIn.js | 8 ++------ .../client/login/components/SignUp.css | 11 +--------- .../client/login/components/SignUp.js | 6 ++---- 6 files changed, 34 insertions(+), 30 deletions(-) create mode 100644 plugins/talk-plugin-auth/client/login/components/External.css create mode 100644 plugins/talk-plugin-auth/client/login/components/External.js diff --git a/plugins/talk-plugin-auth/client/login/components/External.css b/plugins/talk-plugin-auth/client/login/components/External.css new file mode 100644 index 000000000..945016967 --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/External.css @@ -0,0 +1,8 @@ +.external { + margin-bottom: 20px; +} + +.separator h1{ + text-align: center; + font-size: 1.2em; +} diff --git a/plugins/talk-plugin-auth/client/login/components/External.js b/plugins/talk-plugin-auth/client/login/components/External.js new file mode 100644 index 000000000..53868464e --- /dev/null +++ b/plugins/talk-plugin-auth/client/login/components/External.js @@ -0,0 +1,20 @@ +import React from 'react'; +import t from 'coral-framework/services/i18n'; +import styles from './External.css'; +import { Slot } from 'plugin-api/beta/client/components'; +import { IfSlotIsNotEmpty } from 'plugin-api/beta/client/components'; + +const External = slot => ( +
+ +
+ +
+
+

{t('sign_in.or')}

+
+
+
+); + +export default External; diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.css b/plugins/talk-plugin-auth/client/login/components/SignIn.css index e6442d6f3..6b9c9de85 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.css +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.css @@ -2,16 +2,7 @@ margin-bottom: 20px; } -.header h1, .separator h1{ - text-align: center; - font-size: 1.2em; -} - -.socialConnections { - margin-bottom: 20px; -} - -.separator h1{ +.header h1 { text-align: center; font-size: 1.2em; } diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js index baa7dc278..6eda7db37 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -10,6 +10,7 @@ import styles from './SignIn.css'; import t from 'coral-framework/services/i18n'; import cn from 'classnames'; import Recaptcha from 'coral-framework/components/Recaptcha'; +import External from './External'; class SignIn extends React.Component { recaptcha = null; @@ -55,12 +56,7 @@ class SignIn extends React.Component {
{errorMessage && {errorMessage}}
-
- Social -
-
-

{t('sign_in.or')}

-
+
{ @@ -52,10 +53,7 @@ class SignUp extends React.Component { {errorMessage && {errorMessage}} {!success && (
-
Social
-
-

{t('sign_in.or')}

-
+ Date: Tue, 13 Feb 2018 11:34:30 +0100 Subject: [PATCH 15/37] Listen for auth changes --- .../client/login/containers/Main.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/plugins/talk-plugin-auth/client/login/containers/Main.js b/plugins/talk-plugin-auth/client/login/containers/Main.js index b75030e0b..d99d40872 100644 --- a/plugins/talk-plugin-auth/client/login/containers/Main.js +++ b/plugins/talk-plugin-auth/client/login/containers/Main.js @@ -4,6 +4,10 @@ import Main from '../components/Main'; import { connect } from 'plugin-api/beta/client/hocs'; import { bindActionCreators } from 'redux'; import { setView } from '../actions'; +import { + setAuthToken, + handleSuccessfulLogin, +} from 'plugin-api/beta/client/actions/auth'; import * as views from '../enums/views'; class MainContainer extends React.Component { @@ -20,6 +24,7 @@ class MainContainer extends React.Component { componentDidMount() { this.resizeHeight(); + this.listenToStorageChanges(); } componentDidUpdate(prevProps) { @@ -27,6 +32,41 @@ class MainContainer extends React.Component { this.resizeHeight(); } } + + componentWillUnmount() { + this.unlisten(); + } + + listenToStorageChanges() { + window.addEventListener('storage', this.handleAuth); + } + + unlisten() { + window.removeEventListener('storage', this.handleAuth); + } + + // External logins store auth data into `auth`, we use it to detect + // a successful sign in. + handleAuth = e => { + if (e.key === 'auth') { + const { err, data } = JSON.parse(e.newValue); + if (err) { + console.error(err); + } else if (data && data.token) { + if (data.user) { + this.props.handleSuccessfulLogin(data.user, data.token); + } else { + this.props.setAuthToken(data.token); + } + this.unlisten(); + window.close(); + } else { + console.error('auth was set, but did not contain a token'); + } + localStorage.removeItem('auth'); + } + }; + render() { return
; } @@ -35,6 +75,8 @@ class MainContainer extends React.Component { MainContainer.propTypes = { view: PropTypes.string.isRequired, setView: PropTypes.func.isRequired, + handleSuccessfulLogin: PropTypes.func.isRequired, + setAuthToken: PropTypes.func.isRequired, }; const mapStateToProps = ({ talkPluginAuth: state }) => ({ @@ -45,6 +87,8 @@ const mapDispatchToProps = dispatch => bindActionCreators( { setView, + handleSuccessfulLogin, + setAuthToken, }, dispatch ); From 4224f18ba7eed0af6d8d886ac46713b6aa99e2b2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 11:42:25 +0100 Subject: [PATCH 16/37] Add BareButton --- client/coral-ui/components/BareButton.css | 3 +++ client/coral-ui/components/BareButton.js | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 client/coral-ui/components/BareButton.css create mode 100644 client/coral-ui/components/BareButton.js diff --git a/client/coral-ui/components/BareButton.css b/client/coral-ui/components/BareButton.css new file mode 100644 index 000000000..0a9c81625 --- /dev/null +++ b/client/coral-ui/components/BareButton.css @@ -0,0 +1,3 @@ +.bare { + composes: buttonReset from "coral-framework/styles/reset.css"; +} diff --git a/client/coral-ui/components/BareButton.js b/client/coral-ui/components/BareButton.js new file mode 100644 index 000000000..6d3f9fa93 --- /dev/null +++ b/client/coral-ui/components/BareButton.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './BareButton.css'; +import cn from 'classnames'; + +/** + * BareButton is a button whose styling is stripped off to a minimum. + * Can pass anchor=true to use `a` instead of `button` + */ +const BareButton = ({ anchor, className, ...props }) => { + let Element = 'button'; + if (anchor) { + Element = 'a'; + } + return ; +}; + +BareButton.propTypes = { + className: PropTypes.string, + anchor: PropTypes.bool, +}; + +export default BareButton; From 6c1ed826d0a3d741d7ab1aaed8c2610d588dc6f5 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 11:48:12 +0100 Subject: [PATCH 17/37] Fix External --- .../talk-plugin-auth/client/login/components/External.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/talk-plugin-auth/client/login/components/External.js b/plugins/talk-plugin-auth/client/login/components/External.js index 53868464e..048a9269a 100644 --- a/plugins/talk-plugin-auth/client/login/components/External.js +++ b/plugins/talk-plugin-auth/client/login/components/External.js @@ -1,10 +1,11 @@ import React from 'react'; +import PropTypes from 'prop-types'; import t from 'coral-framework/services/i18n'; import styles from './External.css'; import { Slot } from 'plugin-api/beta/client/components'; import { IfSlotIsNotEmpty } from 'plugin-api/beta/client/components'; -const External = slot => ( +const External = ({ slot }) => (
@@ -17,4 +18,8 @@ const External = slot => (
); +External.propTypes = { + slot: PropTypes.string.isRequired, +}; + export default External; From 3999b806c78ee7bfacfaed91fdd4380ba80ed825 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 12:24:29 +0100 Subject: [PATCH 18/37] Use plugin-api --- client/coral-ui/index.js | 1 + plugin-api/beta/client/components/index.js | 1 + .../client/login/components/External.js | 10 +++++----- .../client/login/components/ForgotPassword.js | 2 +- .../client/login/components/ResendEmailConfirmation.js | 2 +- .../talk-plugin-auth/client/login/components/SignIn.js | 4 ++-- .../talk-plugin-auth/client/login/components/SignUp.js | 2 +- 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 7ab267b0f..38c6a8650 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -28,3 +28,4 @@ export { default as Label } from './components/Label'; export { default as FlagLabel } from './components/FlagLabel'; export { default as Dropdown } from './components/Dropdown'; export { default as Option } from './components/Option'; +export { default as BareButton } from './components/BareButton'; diff --git a/plugin-api/beta/client/components/index.js b/plugin-api/beta/client/components/index.js index 974c4609f..fc6159544 100644 --- a/plugin-api/beta/client/components/index.js +++ b/plugin-api/beta/client/components/index.js @@ -26,3 +26,4 @@ export { export { default as StreamConfiguration, } from 'coral-framework/components/StreamConfiguration'; +export { default as Recaptcha } from 'coral-framework/components/Recaptcha'; diff --git a/plugins/talk-plugin-auth/client/login/components/External.js b/plugins/talk-plugin-auth/client/login/components/External.js index 048a9269a..9d57d63db 100644 --- a/plugins/talk-plugin-auth/client/login/components/External.js +++ b/plugins/talk-plugin-auth/client/login/components/External.js @@ -1,21 +1,21 @@ import React from 'react'; import PropTypes from 'prop-types'; -import t from 'coral-framework/services/i18n'; import styles from './External.css'; import { Slot } from 'plugin-api/beta/client/components'; import { IfSlotIsNotEmpty } from 'plugin-api/beta/client/components'; +import { t } from 'plugin-api/beta/client/services'; const External = ({ slot }) => ( -
- + +

{t('sign_in.or')}

- -
+
+
); External.propTypes = { diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js index 12a309c28..9a5816b38 100644 --- a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js @@ -2,7 +2,7 @@ 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'; +import { t } from 'plugin-api/beta/client/services'; class ForgotPassword extends React.Component { handleSignUpLink = e => { diff --git a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js index 082492128..7c15e9fc7 100644 --- a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js +++ b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js @@ -5,9 +5,9 @@ import { Success, Alert, } from 'plugin-api/beta/client/components/ui'; +import { t } from 'plugin-api/beta/client/services'; import PropTypes from 'prop-types'; import styles from './ResendEmailConfirmation.css'; -import t from 'coral-framework/services/i18n'; class ResendVerification extends React.Component { handleSubmit = e => { diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js index 6eda7db37..9f7fe42b5 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -7,9 +7,9 @@ import { Alert, } from 'plugin-api/beta/client/components/ui'; import styles from './SignIn.css'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; import cn from 'classnames'; -import Recaptcha from 'coral-framework/components/Recaptcha'; +import { Recaptcha } from 'plugin-api/beta/client/components'; import External from './External'; class SignIn extends React.Component { diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js index 842f41051..5de46f9ad 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -7,7 +7,7 @@ import { Success, Alert, } from 'plugin-api/beta/client/components/ui'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; import styles from './SignUp.css'; import External from './External'; From d857585c7fe0eaa2f2ef112ca4de41ed73e16fa4 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 12:26:55 +0100 Subject: [PATCH 19/37] Implement facebook auth client --- plugins.default.json | 1 + .../client/.eslintrc.json | 3 +++ .../client/actions.js | 7 +++++++ .../client/components/FacebookButton.css | 13 ++++++++++++ .../client/components/FacebookButton.js | 11 ++++++++++ .../client/components/SignIn.js | 9 +++++++++ .../client/components/SignUp.js | 9 +++++++++ .../client/containers/FacebookButton.js | 9 +++++++++ .../talk-plugin-facebook-auth/client/index.js | 11 ++++++++++ .../client/translations.yml | 20 +++++++++++++++++++ 10 files changed, 93 insertions(+) create mode 100644 plugins/talk-plugin-facebook-auth/client/.eslintrc.json create mode 100644 plugins/talk-plugin-facebook-auth/client/actions.js create mode 100644 plugins/talk-plugin-facebook-auth/client/components/FacebookButton.css create mode 100644 plugins/talk-plugin-facebook-auth/client/components/FacebookButton.js create mode 100644 plugins/talk-plugin-facebook-auth/client/components/SignIn.js create mode 100644 plugins/talk-plugin-facebook-auth/client/components/SignUp.js create mode 100644 plugins/talk-plugin-facebook-auth/client/containers/FacebookButton.js create mode 100644 plugins/talk-plugin-facebook-auth/client/index.js create mode 100644 plugins/talk-plugin-facebook-auth/client/translations.yml diff --git a/plugins.default.json b/plugins.default.json index e6d1b23cb..07d4d3d78 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -8,6 +8,7 @@ ], "client": [ "talk-plugin-auth", + "talk-plugin-facebook-auth", "talk-plugin-author-menu", "talk-plugin-comment-content", "talk-plugin-featured-comments", diff --git a/plugins/talk-plugin-facebook-auth/client/.eslintrc.json b/plugins/talk-plugin-facebook-auth/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-facebook-auth/client/actions.js b/plugins/talk-plugin-facebook-auth/client/actions.js new file mode 100644 index 000000000..5a9ebc66f --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/actions.js @@ -0,0 +1,7 @@ +export const loginWithFacebook = () => (dispatch, _, { rest }) => { + window.open( + `${rest.uri}/auth/facebook`, + 'Continue with Facebook', + 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' + ); +}; diff --git a/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.css b/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.css new file mode 100644 index 000000000..694d6a322 --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.css @@ -0,0 +1,13 @@ +.button { + background-color: #4267b2; + border-color: #4267b2; + color: rgb(255, 255, 255); + width: 100%; + box-sizing: border-box; + padding: 10px 20px; +} + +.button:hover { + background-color: #365899; + border-color: #365899; +} diff --git a/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.js b/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.js new file mode 100644 index 000000000..d9d1c0ab1 --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/components/FacebookButton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { BareButton } from 'plugin-api/beta/client/components/ui'; +import styles from './FacebookButton.css'; + +export default ({ onClick, children }) => { + return ( + + {children} + + ); +}; diff --git a/plugins/talk-plugin-facebook-auth/client/components/SignIn.js b/plugins/talk-plugin-facebook-auth/client/components/SignIn.js new file mode 100644 index 000000000..1909c1aba --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/components/SignIn.js @@ -0,0 +1,9 @@ +import React from 'react'; +import FacebookButton from '../containers/FacebookButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-facebook-auth.sign_in')} + ); +}; diff --git a/plugins/talk-plugin-facebook-auth/client/components/SignUp.js b/plugins/talk-plugin-facebook-auth/client/components/SignUp.js new file mode 100644 index 000000000..b0a185e3f --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/components/SignUp.js @@ -0,0 +1,9 @@ +import React from 'react'; +import FacebookButton from '../containers/FacebookButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-facebook-auth.sign_up')} + ); +}; diff --git a/plugins/talk-plugin-facebook-auth/client/containers/FacebookButton.js b/plugins/talk-plugin-facebook-auth/client/containers/FacebookButton.js new file mode 100644 index 000000000..8ae3ff01e --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/containers/FacebookButton.js @@ -0,0 +1,9 @@ +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import { loginWithFacebook } from '../actions'; +import FacebookButton from '../components/FacebookButton'; + +const mapDispatchToProps = dispatch => + bindActionCreators({ onClick: loginWithFacebook }, dispatch); + +export default connect(null, mapDispatchToProps)(FacebookButton); diff --git a/plugins/talk-plugin-facebook-auth/client/index.js b/plugins/talk-plugin-facebook-auth/client/index.js new file mode 100644 index 000000000..cb8a8f059 --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/index.js @@ -0,0 +1,11 @@ +import SignIn from './components/SignIn'; +import SignUp from './components/SignUp'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + authExternalSignIn: [SignIn], + authExternalSignUp: [SignUp], + }, +}; diff --git a/plugins/talk-plugin-facebook-auth/client/translations.yml b/plugins/talk-plugin-facebook-auth/client/translations.yml new file mode 100644 index 000000000..35a5c255c --- /dev/null +++ b/plugins/talk-plugin-facebook-auth/client/translations.yml @@ -0,0 +1,20 @@ +en: + talk-plugin-facebook-auth: + sign_in: "Sign in with Facebook" + sign_up: "Sign up with Facebook" +es: + talk-plugin-facebook-auth: + facebook_sign_in: "Entrar con Facebook" + facebook_sign_up: "Registrarse con Facebook" +fr: + talk-plugin-facebook-auth: + facebook_sign_in: "Connectez-vous avec Facebook" + facebook_sign_up: "Inscrivez-vous avec Facebook" +zh_CN: + talk-plugin-facebook-auth: + facebook_sign_in: "使用 Facebook 帐号" + facebook_sign_up: "使用 Facebook 帐号" +zh_TW: + talk-plugin-facebook-auth: + facebook_sign_in: "使用 Facebook 帳號" + facebook_sign_up: "使用 Facebook 帳號" From c54a8bdc4f2c993173cf5a8bed624148a2403417 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 15:00:53 +0100 Subject: [PATCH 20/37] Remove old code --- .../coral-embed-stream/src/actions/login.js | 336 +----------------- .../coral-embed-stream/src/constants/login.js | 53 --- .../coral-embed-stream/src/reducers/login.js | 222 ------------ .../src/tabs/stream/containers/Stream.js | 6 +- client/coral-framework/hocs/index.js | 1 + .../coral-framework/hocs/withSetUsername.js | 100 ++++++ plugin-api/beta/client/hocs/index.js | 1 + plugin-api/beta/client/selectors/auth.js | 6 + plugins/talk-plugin-auth/client/index.js | 3 +- .../client/stream/components/FakeComment.css | 38 ++ .../client/stream/components/FakeComment.js | 42 +++ .../stream/components/SetUsernameDialog.css | 41 +++ .../stream/components/SetUsernameDialog.js | 131 ++++--- .../client/stream/components/SignInButton.js | 2 +- .../client/stream/components/UserBox.js | 2 +- .../stream/containers/SetUsernameDialog.js | 203 +++-------- .../talk-plugin-auth/client/translations.yml | 10 - 17 files changed, 341 insertions(+), 856 deletions(-) create mode 100644 client/coral-framework/hocs/withSetUsername.js create mode 100644 plugin-api/beta/client/selectors/auth.js create mode 100644 plugins/talk-plugin-auth/client/stream/components/FakeComment.css create mode 100644 plugins/talk-plugin-auth/client/stream/components/FakeComment.js diff --git a/client/coral-embed-stream/src/actions/login.js b/client/coral-embed-stream/src/actions/login.js index 84c98ea78..c602bf7b6 100644 --- a/client/coral-embed-stream/src/actions/login.js +++ b/client/coral-embed-stream/src/actions/login.js @@ -1,8 +1,4 @@ -import jwtDecode from 'jwt-decode'; -import bowser from 'bowser'; import * as actions from '../constants/login'; -import { notify } from 'coral-framework/actions/notification'; -import t from 'coral-framework/services/i18n'; import { checkLogin } from 'coral-framework/actions/auth'; export const showSignInDialog = () => ({ @@ -10,15 +6,7 @@ export const showSignInDialog = () => ({ }); export const hideSignInDialog = () => dispatch => { - if (window.opener && window.opener !== window) { - // TODO: We need to address this when we refactor the - // login popup out of the embed. - - // we are in a popup - window.close(); - } else { - dispatch(checkLogin()); - } + dispatch(checkLogin()); dispatch({ type: actions.HIDE_SIGNIN_DIALOG }); }; @@ -29,325 +17,3 @@ export const focusSignInDialog = () => ({ export const blurSignInDialog = () => ({ type: actions.BLUR_SIGNIN_DIALOG, }); - -// TODO: remove the rest. - -export const updateStatus = status => ({ - type: actions.UPDATE_STATUS, - status, -}); - -export const resetSignInDialog = () => dispatch => { - dispatch({ type: actions.HIDE_SIGNIN_DIALOG }); -}; - -export const showCreateUsernameDialog = () => ({ - type: actions.SHOW_CREATEUSERNAME_DIALOG, -}); - -export const hideCreateUsernameDialog = () => ({ - type: actions.HIDE_CREATEUSERNAME_DIALOG, -}); - -export const updateUsername = username => ({ - type: actions.UPDATE_USERNAME, - username, -}); - -export const changeView = view => dispatch => { - dispatch({ - type: actions.CHANGE_VIEW, - view, - }); - - switch (view) { - case 'SIGNUP': - window.resizeTo(500, 800); - break; - case 'FORGOT': - window.resizeTo(500, 400); - break; - default: - window.resizeTo(500, 550); - } -}; - -export const cleanState = () => ({ - type: actions.CLEAN_STATE, -}); - -// Sign In Actions - -const signInRequest = email => ({ - type: actions.FETCH_SIGNIN_REQUEST, - email, -}); - -const signInFailure = error => ({ - type: actions.FETCH_SIGNIN_FAILURE, - error, -}); - -//============================================================================== -// AUTH TOKEN -//============================================================================== - -export const handleAuthToken = token => (dispatch, _, { localStorage }) => { - if (localStorage) { - localStorage.setItem('exp', jwtDecode(token).exp); - localStorage.setItem('token', token); - } - - dispatch({ type: 'HANDLE_AUTH_TOKEN' }); -}; - -//============================================================================== -// SIGN IN -//============================================================================== - -export const fetchSignIn = formData => { - return (dispatch, _, { rest }) => { - dispatch(signInRequest(formData.email)); - - return rest('/auth/local', { method: 'POST', body: formData }) - .then(({ token }) => { - if (!bowser.safari && !bowser.ios) { - dispatch(handleAuthToken(token)); - } - dispatch(hideSignInDialog()); - }) - .catch(error => { - console.error(error); - if (error.metadata) { - // the user might not have a valid email. prompt the user user re-request the confirmation email - dispatch( - signInFailure(t('error.email_not_verified', error.metadata)) - ); - } else if (error.translation_key === 'NOT_AUTHORIZED') { - // invalid credentials - dispatch(signInFailure(t('error.email_password'), error.metadata)); - } else { - dispatch(signInFailure(error)); - } - }); - }; -}; - -//============================================================================== -// SIGN IN - FACEBOOK -//============================================================================== - -const signInFacebookRequest = () => ({ - type: actions.FETCH_SIGNIN_FACEBOOK_REQUEST, -}); - -const signInFacebookSuccess = user => ({ - type: actions.FETCH_SIGNIN_FACEBOOK_SUCCESS, - user, -}); - -const signInFacebookFailure = error => ({ - type: actions.FETCH_SIGNIN_FACEBOOK_FAILURE, - error, -}); - -export const fetchSignInFacebook = () => (dispatch, _, { rest }) => { - dispatch(signInFacebookRequest()); - window.open( - `${rest.uri}/auth/facebook`, - 'Continue with Facebook', - 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' - ); -}; - -//============================================================================== -// SIGN UP - FACEBOOK -//============================================================================== - -const signUpFacebookRequest = () => ({ - type: actions.FETCH_SIGNUP_FACEBOOK_REQUEST, -}); - -export const fetchSignUpFacebook = () => (dispatch, _, { rest }) => { - dispatch(signUpFacebookRequest()); - window.open( - `${rest.uri}/auth/facebook`, - 'Continue with Facebook', - 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' - ); -}; - -export const facebookCallback = (err, data) => dispatch => { - if (err) { - dispatch(signInFacebookFailure(err)); - return; - } - try { - dispatch(handleAuthToken(data.token)); - dispatch(signInFacebookSuccess(data.user)); - dispatch(hideSignInDialog()); - } catch (err) { - dispatch(signInFacebookFailure(err)); - return; - } -}; - -//============================================================================== -// SIGN UP -//============================================================================== - -const signUpRequest = () => ({ type: actions.FETCH_SIGNUP_REQUEST }); -const signUpSuccess = user => ({ type: actions.FETCH_SIGNUP_SUCCESS, user }); -const signUpFailure = error => ({ type: actions.FETCH_SIGNUP_FAILURE, error }); - -export const fetchSignUp = formData => (dispatch, getState, { rest }) => { - const redirectUri = getState().auth.redirectUri; - dispatch(signUpRequest()); - - rest('/users', { - method: 'POST', - body: formData, - headers: { 'X-Pym-Url': redirectUri }, - }) - .then(({ user }) => { - dispatch(signUpSuccess(user)); - }) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(signUpFailure(errorMessage)); - }); -}; - -//============================================================================== -// FORGOT PASSWORD -//============================================================================== - -const forgotPasswordRequest = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_REQUEST, -}); - -const forgotPasswordSuccess = () => ({ - type: actions.FETCH_FORGOT_PASSWORD_SUCCESS, -}); - -const forgotPasswordFailure = error => ({ - type: actions.FETCH_FORGOT_PASSWORD_FAILURE, - error, -}); - -export const fetchForgotPassword = email => (dispatch, getState, { rest }) => { - dispatch(forgotPasswordRequest(email)); - const redirectUri = getState().auth.redirectUri; - rest('/account/password/reset', { - method: 'POST', - body: { email, loc: redirectUri }, - }) - .then(() => dispatch(forgotPasswordSuccess())) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(forgotPasswordFailure(errorMessage)); - }); -}; - -//============================================================================== -// LOGOUT -//============================================================================== - -export const logout = () => async ( - dispatch, - _, - { rest, client, pym, localStorage } -) => { - await rest('/auth', { method: 'DELETE' }); - - if (localStorage) { - localStorage.removeItem('token'); - localStorage.removeItem('exp'); - } - - // Reset the websocket. - client.resetWebsocket(); - - dispatch({ type: actions.LOGOUT }); - pym.sendMessage('coral-auth-changed'); -}; - -export const validForm = () => ({ type: actions.VALID_FORM }); -export const invalidForm = error => ({ type: actions.INVALID_FORM, error }); - -//============================================================================== -// VERIFY EMAIL -//============================================================================== - -const verifyEmailRequest = () => ({ - type: actions.VERIFY_EMAIL_REQUEST, -}); - -const verifyEmailSuccess = () => ({ - type: actions.VERIFY_EMAIL_SUCCESS, -}); - -const verifyEmailFailure = error => ({ - type: actions.VERIFY_EMAIL_FAILURE, - error, -}); - -export const requestConfirmEmail = email => (dispatch, getState, { rest }) => { - const redirectUri = getState().auth.redirectUri; - dispatch(verifyEmailRequest()); - return rest('/users/resend-verify', { - method: 'POST', - body: { email }, - headers: { 'X-Pym-Url': redirectUri }, - }) - .then(() => { - dispatch(verifyEmailSuccess()); - }) - .catch(error => { - console.error(error); - dispatch(verifyEmailFailure(error)); - throw error; - }); -}; - -// Login Popup actions. -export const setRequireEmailVerification = required => ({ - type: actions.SET_REQUIRE_EMAIL_VERIFICATION, - required, -}); - -export const setRedirectUri = uri => ({ - type: actions.SET_REDIRECT_URI, - uri, -}); - -//============================================================================== -// Edit Username -//============================================================================== - -const editUsernameFailure = error => ({ - type: actions.EDIT_USERNAME_FAILURE, - error, -}); -const editUsernameSuccess = () => ({ type: actions.EDIT_USERNAME_SUCCESS }); - -export const editName = username => (dispatch, _, { rest }) => { - return rest('/account/username', { method: 'PUT', body: { username } }) - .then(() => { - dispatch(editUsernameSuccess()); - dispatch(notify('success', t('framework.success_name_update'))); - }) - .catch(error => { - console.error(error); - const errorMessage = error.translation_key - ? t(`error.${error.translation_key}`) - : error.toString(); - dispatch(editUsernameFailure(errorMessage)); - }); -}; diff --git a/client/coral-embed-stream/src/constants/login.js b/client/coral-embed-stream/src/constants/login.js index 3187873fe..1ad142bc2 100644 --- a/client/coral-embed-stream/src/constants/login.js +++ b/client/coral-embed-stream/src/constants/login.js @@ -1,57 +1,4 @@ -export const CHANGE_VIEW = 'CHANGE_VIEW'; -export const CLEAN_STATE = 'CLEAN_STATE'; - export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; export const FOCUS_SIGNIN_DIALOG = 'FOCUS_SIGNIN_DIALOG'; export const BLUR_SIGNIN_DIALOG = 'BLUR_SIGNIN_DIALOG'; - -export const CREATE_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST'; -export const CREATE_USERNAME_SUCCESS = 'CREATE_USERNAME_SUCCESS'; -export const CREATE_USERNAME_FAILURE = 'CREATE_USERNAME_FAILURE'; -export const CREATE_USERNAME = 'CREATE_USERNAME'; -export const SHOW_CREATEUSERNAME_DIALOG = 'SHOW_CREATEUSERNAME_DIALOG'; -export const HIDE_CREATEUSERNAME_DIALOG = 'HIDE_CREATEUSERNAME_DIALOG'; - -export const EDIT_USERNAME_REQUEST = 'CREATE_USERNAME_REQUEST'; -export const EDIT_USERNAME_SUCCESS = 'CREATE_USERNAME_SUCCESS'; -export const EDIT_USERNAME_FAILURE = 'CREATE_USERNAME_FAILURE'; -export const EDIT_USERNAME = 'CREATE_USERNAME'; - -export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST'; -export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE'; -export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS'; - -export const FETCH_SIGNIN_REQUEST = 'FETCH_SIGNIN_REQUEST'; -export const FETCH_SIGNIN_FAILURE = 'FETCH_SIGNIN_FAILURE'; -export const FETCH_SIGNIN_SUCCESS = 'FETCH_SIGNIN_SUCCESS'; - -export const FETCH_SIGNIN_FACEBOOK_REQUEST = 'FETCH_SIGNIN_FACEBOOK_REQUEST'; -export const FETCH_SIGNIN_FACEBOOK_FAILURE = 'FETCH_SIGNIN_FACEBOOK_FAILURE'; -export const FETCH_SIGNIN_FACEBOOK_SUCCESS = 'FETCH_SIGNIN_FACEBOOK_SUCCESS'; - -export const FETCH_SIGNUP_FACEBOOK_REQUEST = 'FETCH_SIGNUP_FACEBOOK_REQUEST'; -export const FETCH_FORGOT_PASSWORD_REQUEST = 'FETCH_FORGOT_PASSWORD_REQUEST'; -export const FETCH_FORGOT_PASSWORD_SUCCESS = 'FETCH_FORGOT_PASSWORD_SUCCESS'; -export const FETCH_FORGOT_PASSWORD_FAILURE = 'FETCH_FORGOT_PASSWORD_FAILURE'; - -export const LOGOUT = 'LOGOUT'; - -export const INVALID_FORM = 'INVALID_FORM'; -export const VALID_FORM = 'VALID_FORM'; - -export const CHECK_LOGIN_REQUEST = 'CHECK_LOGIN_REQUEST'; -export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS'; -export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE'; - -export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST'; -export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS'; -export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE'; -export const UPDATE_USERNAME = 'UPDATE_USERNAME'; - -// Login Popup actions. -export const SET_REQUIRE_EMAIL_VERIFICATION = 'SET_REQUIRE_EMAIL_VERIFICATION'; -export const SET_REDIRECT_URI = 'SET_REDIRECT_URI'; - -export const RESET_SIGNIN_DIALOG = 'RESET_SIGNIN_DIALOG'; -export const UPDATE_STATUS = 'UPDATE_STATUS'; diff --git a/client/coral-embed-stream/src/reducers/login.js b/client/coral-embed-stream/src/reducers/login.js index febf4fd51..c24d36f96 100644 --- a/client/coral-embed-stream/src/reducers/login.js +++ b/client/coral-embed-stream/src/reducers/login.js @@ -1,34 +1,10 @@ import * as actions from '../constants/login'; import pym from 'coral-framework/services/pym'; -import merge from 'lodash/merge'; const initialState = { parentUrl: pym.parentUrl || location.href, showSignInDialog: false, signInDialogFocus: false, - - // TODO: remove the rest - isLoading: false, - loggedIn: false, - user: null, - showCreateUsernameDialog: false, - checkedInitialLogin: false, - view: 'SIGNIN', - error: null, - passwordRequestSuccess: null, - passwordRequestFailure: null, - emailVerificationFailure: false, - emailVerificationLoading: false, - emailVerificationSuccess: false, - successSignUp: false, - fromSignUp: false, - requireEmailConfirmation: false, - redirectUri: pym.parentUrl || location.href, -}; - -const purge = user => { - const {settings, ...userData} = user; // eslint-disable-line - return userData; }; export default function login(state = initialState, action) { @@ -56,204 +32,6 @@ export default function login(state = initialState, action) { showSignInDialog: false, signInDialogFocus: false, }; - - // TODO: remove the rest. - case actions.RESET_SIGNIN_DIALOG: - return { - ...state, - isLoading: false, - showSignInDialog: false, - signInDialogFocus: false, - view: 'SIGNIN', - error: null, - passwordRequestFailure: null, - passwordRequestSuccess: null, - emailVerificationFailure: false, - emailVerificationSuccess: false, - emailVerificationLoading: false, - successSignUp: false, - }; - case actions.SHOW_CREATEUSERNAME_DIALOG: - return { - ...state, - showCreateUsernameDialog: true, - }; - case actions.HIDE_CREATEUSERNAME_DIALOG: - return { - ...state, - showCreateUsernameDialog: false, - }; - case actions.CREATE_USERNAME_SUCCESS: - return { - ...state, - showCreateUsernameDialog: false, - error: null, - }; - case actions.CREATE_USERNAME_FAILURE: - return { - ...state, - error: action.error, - }; - case actions.CHANGE_VIEW: - return { - ...state, - error: action.error, - view: action.view, - }; - case actions.CLEAN_STATE: - return initialState; - case actions.FETCH_SIGNIN_REQUEST: - return { - ...state, - email: action.email, - isLoading: true, - }; - case actions.CHECK_LOGIN_FAILURE: - return { - ...state, - checkedInitialLogin: true, - loggedIn: false, - user: null, - }; - case actions.CHECK_LOGIN_SUCCESS: - return { - ...state, - checkedInitialLogin: true, - loggedIn: true, - user: purge(action.user), - }; - case actions.FETCH_SIGNIN_SUCCESS: - return { - ...state, - loggedIn: true, - user: purge(action.user), - }; - case actions.FETCH_SIGNIN_FAILURE: - return { - ...state, - isLoading: false, - error: action.error, - user: null, - view: - action.error.translation_key === 'EMAIL_NOT_VERIFIED' - ? 'RESEND_VERIFICATION' - : state.view, - }; - case actions.FETCH_SIGNUP_FACEBOOK_REQUEST: - return { - ...state, - fromSignUp: true, - }; - case actions.FETCH_SIGNIN_FACEBOOK_REQUEST: - return { - ...state, - fromSignUp: false, - }; - case actions.FETCH_SIGNIN_FACEBOOK_SUCCESS: - return { - ...state, - loggedIn: true, - user: purge(action.user), - }; - case actions.FETCH_SIGNIN_FACEBOOK_FAILURE: - return { - ...state, - error: action.error, - user: null, - }; - case actions.FETCH_SIGNUP_REQUEST: - return { - ...state, - isLoading: true, - }; - case actions.FETCH_SIGNUP_FAILURE: - return { - ...state, - error: action.error, - isLoading: false, - }; - case actions.FETCH_SIGNUP_SUCCESS: - return { - ...state, - isLoading: false, - successSignUp: true, - }; - case actions.LOGOUT: - return { - ...state, - user: null, - isLoading: false, - loggedIn: false, - }; - case actions.INVALID_FORM: - return { - ...state, - error: action.error, - }; - case actions.VALID_FORM: - return { - ...state, - error: null, - }; - case actions.FETCH_FORGOT_PASSWORD_SUCCESS: - return { - ...state, - passwordRequestFailure: null, - passwordRequestSuccess: - 'If you have a registered account, a password reset link was sent to that email', - }; - case actions.FETCH_FORGOT_PASSWORD_FAILURE: - return { - ...state, - passwordRequestFailure: - 'There was an error sending your password reset email. Please try again soon!', - passwordRequestSuccess: null, - }; - case actions.UPDATE_USERNAME: - return { - ...state, - user: { - ...state.user, - username: action.username, - lowercaseUsername: action.username.toLowerCase(), - }, - }; - case actions.VERIFY_EMAIL_FAILURE: - return { - ...state, - emailVerificationFailure: action.error, - emailVerificationLoading: false, - }; - case actions.VERIFY_EMAIL_REQUEST: - return { - ...state, - emailVerificationLoading: true, - }; - case actions.VERIFY_EMAIL_SUCCESS: - return { - ...state, - emailVerificationSuccess: true, - emailVerificationLoading: false, - }; - case actions.SET_REQUIRE_EMAIL_VERIFICATION: - return { - ...state, - requireEmailConfirmation: action.required, - }; - case actions.SET_REDIRECT_URI: - return { - ...state, - redirectUri: action.uri, - }; - case actions.UPDATE_STATUS: { - return { - ...state, - user: { - ...state.user, - status: merge({}, state.user.status, action.status), - }, - }; - } default: return state; } diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 6884bcd8e..06632dfcc 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -15,10 +15,7 @@ import { withEditComment, } from 'coral-framework/graphql/mutations'; -import { - showSignInDialog, - editName, -} from 'coral-embed-stream/src/actions/login'; +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; import { notify } from 'coral-framework/actions/notification'; import { setActiveReplyBox, @@ -465,7 +462,6 @@ const mapDispatchToProps = dispatch => showSignInDialog, notify, setActiveReplyBox, - editName, viewAllComments, setActiveStreamTab: setActiveTab, }, diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js index c8b8f6bb6..eb481de99 100644 --- a/client/coral-framework/hocs/index.js +++ b/client/coral-framework/hocs/index.js @@ -9,6 +9,7 @@ export { default as withMergedSettings } from './withMergedSettings'; export { default as withSignIn } from './withSignIn'; export { default as withSignUp } from './withSignUp'; export { default as withForgotPassword } from './withForgotPassword'; +export { default as withSetUsername } from './withSetUsername'; export { default as withResendEmailConfirmation, } from './withResendEmailConfirmation'; diff --git a/client/coral-framework/hocs/withSetUsername.js b/client/coral-framework/hocs/withSetUsername.js new file mode 100644 index 000000000..2cec90c00 --- /dev/null +++ b/client/coral-framework/hocs/withSetUsername.js @@ -0,0 +1,100 @@ +import React from 'react'; +import hoistStatics from 'recompose/hoistStatics'; +import PropTypes from 'prop-types'; +import { getErrorMessages } from '../utils'; +import validate from '../helpers/validate'; +import errorMsg from 'coral-framework/helpers/error'; +import t from '../services/i18n'; +import { withSetUsername as withSetUsernameMutation } from 'coral-framework/graphql/mutations'; +import { updateUsername, updateStatus } from '../actions/auth'; +import { compose } from 'recompose'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import get from 'lodash/get'; + +/** + * withSetUsername provides properties + * `setUsername`, + * `loading`, + * `errorMessage`, + * `requireEmailVerification`, + * `success`, + * `validate`. + */ +const withSetUsername = hoistStatics(WrappedComponent => { + class WithSetUsername extends React.Component { + static propTypes = { + setUsername: PropTypes.func.isRequired, + currentUserId: PropTypes.string, + updateUsername: PropTypes.func.isRequired, + updateStatus: PropTypes.func.isRequired, + }; + + state = { + error: null, + loading: false, + success: false, + }; + + validateUsername = value => { + if (!value) { + return t('sign_in.required_field'); + } + return validate.username(value) ? '' : errorMsg.username; + }; + + setUsername = async username => { + if (!this.props.currentUserId) { + throw new Error('User not logged in'); + } + + try { + await this.props.setUsername(this.props.currentUserId, username); + this.props.updateUsername(username); + this.props.updateStatus({ username: { status: 'SET' } }); + this.setState({ success: true, loading: false, error: null }); + } catch (error) { + if (!error.status || error.status !== 401) { + console.error(error); + } + const changeSet = { success: false, loading: false, error }; + this.setState(changeSet); + } + }; + + getErrorMessage() { + if (!this.state.error) { + return ''; + } + return getErrorMessages(this.state.error).join(', '); + } + + render() { + return ( + + ); + } + } + + return WithSetUsername; +}); + +const mapStateToProps = ({ auth }) => ({ + currentUserId: get(auth, 'user.id'), +}); + +const mapDispatchToProps = dispatch => + bindActionCreators({ updateUsername, updateStatus }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withSetUsernameMutation, + withSetUsername +); diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js index 715ff56fa..397d8b94d 100644 --- a/plugin-api/beta/client/hocs/index.js +++ b/plugin-api/beta/client/hocs/index.js @@ -10,6 +10,7 @@ export { withSignIn, withSignUp, withResendEmailConfirmation, + withSetUsername, } from 'coral-framework/hocs'; export { withIgnoreUser, diff --git a/plugin-api/beta/client/selectors/auth.js b/plugin-api/beta/client/selectors/auth.js new file mode 100644 index 000000000..6c7769bc1 --- /dev/null +++ b/plugin-api/beta/client/selectors/auth.js @@ -0,0 +1,6 @@ +import get from 'lodash/get'; + +export const usernameStatusSelector = state => + get(state, 'auth.user.status.username.status'); + +export const usernameSelector = state => get(state, 'auth.user.username'); diff --git a/plugins/talk-plugin-auth/client/index.js b/plugins/talk-plugin-auth/client/index.js index 5ed18d6d0..13abce1d9 100644 --- a/plugins/talk-plugin-auth/client/index.js +++ b/plugins/talk-plugin-auth/client/index.js @@ -1,5 +1,6 @@ import UserBox from './stream/containers/UserBox'; import SignInButton from './stream/containers/SignInButton'; +import SetUsernameDialog from './stream/containers/SetUsernameDialog'; import translations from './translations.yml'; import Login from './login/containers/Main'; import reducer from './login/reducer'; @@ -8,7 +9,7 @@ export default { reducer, translations, slots: { - stream: [UserBox, SignInButton], + stream: [UserBox, SignInButton, SetUsernameDialog], login: [Login], }, }; diff --git a/plugins/talk-plugin-auth/client/stream/components/FakeComment.css b/plugins/talk-plugin-auth/client/stream/components/FakeComment.css new file mode 100644 index 000000000..bc751b115 --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/components/FakeComment.css @@ -0,0 +1,38 @@ +.root { + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + margin-bottom: 10px; + padding: 8px 0px 10px 0px; + position: relative; +} + +.footer { + display: flex; + justify-content: space-between; +} + +.button { + color: #2a2a2a; + margin: 5px 10px 5px 0px; + background: none; + padding: 0px; + border: none; + font-size: inherit; + vertical-align: middle; + + &:hover { + color: #767676; + cursor: pointer; + } +} + +.icon { + font-size: 12px; + padding: 0 2px 0 5px; + vertical-align: middle; +} + +.authorName { + margin-right: 5px; + font-weight: bold; +} \ No newline at end of file diff --git a/plugins/talk-plugin-auth/client/stream/components/FakeComment.js b/plugins/talk-plugin-auth/client/stream/components/FakeComment.js new file mode 100644 index 000000000..e353a7f58 --- /dev/null +++ b/plugins/talk-plugin-auth/client/stream/components/FakeComment.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styles from './FakeComment.css'; +import { Icon } from 'plugin-api/beta/client/components/ui'; +import { CommentTimestamp } from 'plugin-api/beta/client/components'; +import { t } from 'plugin-api/beta/client/services'; + +export const FakeComment = ({ username, created_at, body }) => ( +
+ {username} + +
{body}
+
+
+ + +
+
+ + +
+
+
+); + +FakeComment.propTypes = { + username: PropTypes.string.isRequired, + created_at: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, +}; diff --git a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css index e69de29bb..d61e69dbc 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css +++ b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.css @@ -0,0 +1,41 @@ +.dialogusername { + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 400px; + top: 10px; +} + +.yourusername { + display: block; +} + +.example { + display: block; +} + +.ifyoudont { + display: block; + margin-top: 15px; +} + +.saveusername { + display: block; + width: 100%; +} + +.savebutton { + display: inline; + background-color: rgb(105,105,105); + color: white; +} + +.fakeComment { + display: block; + margin-bottom: 5px; +} + +.hint { + color: grey; + font-weight: 600; + padding: 3px 0 16px; +} diff --git a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js index 681f444ee..a2e641791 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import styles from './styles.css'; +import styles from './SetUsernameDialog.css'; import { Dialog, Alert, @@ -8,76 +8,73 @@ import { Button, } from 'plugin-api/beta/client/components/ui'; import { FakeComment } from './FakeComment'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; -const SetUsernameDialog = ({ - open, - handleClose, - formData, - handleSubmitUsername, - handleChange, - ...props -}) => ( - - - × - -
-
-

{t('createdisplay.write_your_username')}

-
-
-

- {t('createdisplay.your_username')} -

- -

- {t('createdisplay.if_you_dont_change_your_name')} -

- {props.auth.error && {props.auth.error}} - - {props.errors.username && ( - - {' '} - {t('createdisplay.special_characters')}{' '} - - )} -
- - +class SetUsernameDialog extends React.Component { + handleUsernameChange = e => this.props.onUsernameChange(e.target.value); + + handleSubmit = e => { + e.preventDefault(); + this.props.onSubmit(); + }; + + render() { + const { username, usernameError, errorMessage } = this.props; + + return ( + +
+
+

{t('createdisplay.write_your_username')}

- -
-
-
-); +
+

+ {t('createdisplay.your_username')} +

+ + {errorMessage && {errorMessage}} +
+ {usernameError && ( + + {' '} + {t('createdisplay.special_characters')}{' '} + + )} +
+ + +
+
+
+
+ + ); + } +} SetUsernameDialog.propTypes = { - open: PropTypes.bool, - handleClose: PropTypes.func, - formData: PropTypes.object, - handleSubmitUsername: PropTypes.func, - handleChange: PropTypes.func, - auth: PropTypes.object, - errors: PropTypes.object, + loading: PropTypes.bool.isRequired, + username: PropTypes.string.isRequired, + usernameError: PropTypes.string.isRequired, + onUsernameChange: PropTypes.func.isRequired, + onSubmit: PropTypes.func.isRequired, + errorMessage: PropTypes.string.isRequired, }; export default SetUsernameDialog; diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js index dcc6e5c4a..486858151 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button } from 'plugin-api/beta/client/components/ui'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; const SignInButton = ({ currentUser, showSignInDialog }) => (
diff --git a/plugins/talk-plugin-auth/client/stream/components/UserBox.js b/plugins/talk-plugin-auth/client/stream/components/UserBox.js index 7f48513b8..f03573667 100644 --- a/plugins/talk-plugin-auth/client/stream/components/UserBox.js +++ b/plugins/talk-plugin-auth/client/stream/components/UserBox.js @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styles from './UserBox.css'; -import t from 'coral-framework/services/i18n'; +import { t } from 'plugin-api/beta/client/services'; import cn from 'classnames'; const UserBox = ({ user, logout, onShowProfile }) => ( diff --git a/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js index cba032cd6..16ba5cac0 100644 --- a/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js @@ -1,183 +1,64 @@ -import React from 'react'; +import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { compose } from 'react-apollo'; -import { bindActionCreators } from 'redux'; -import errorMsj from 'coral-framework/helpers/error'; -import validate from 'coral-framework/helpers/validate'; -import CreateUsernameDialog from './CreateUsernameDialog'; -import { withSetUsername } from 'coral-framework/graphql/mutations'; -import { forEachError } from 'plugin-api/beta/client/utils'; - -import t from 'coral-framework/services/i18n'; - +import { connect, withSetUsername } from 'plugin-api/beta/client/hocs'; +import { compose, branch, renderNothing } from 'recompose'; import { - showCreateUsernameDialog, - hideCreateUsernameDialog, - invalidForm, - validForm, - updateUsername, -} from 'coral-embed-stream/src/actions/login'; + usernameStatusSelector, + usernameSelector, +} from 'plugin-api/beta/client/selectors/auth'; +import SetUsernameDialog from '../components/SetUsernameDialog'; -class SetUsernameDialog extends React.Component { - constructor(props) { - super(props); - - this.state = { - formData: { - username: (props.auth.user && props.auth.user.username) || '', - }, - errors: {}, - showErrors: false, - }; - } - - componentWillReceiveProps(next) { - if ( - !this.props.auth.showCreateUsernameDialog && - next.auth.showCreateUsernameDialog - ) { - this.setState({ - formData: { - username: - (this.props.auth.user && this.props.auth.user.username) || '', - }, - }); - } - } - - handleChange = e => { - const { name, value } = e.target; - this.setState( - state => ({ - ...state, - formData: { - ...state.formData, - [name]: value, - }, - }), - () => { - this.validation(name, value); - } - ); +class SetUsernameDialogContainer extends Component { + state = { + username: this.props.username, + usernameError: '', }; - addError = (name, error) => { - return this.setState(state => ({ - errors: { - ...state.errors, - [name]: error, - }, - })); - }; - - validation = (name, value) => { - const { addError } = this; - - if (!value.length) { - addError(name, t('createdisplay.required_field')); - } else if (!validate[name](value)) { - addError(name, errorMsj[name]); + handleSubmit = () => { + const validationError = this.props.validateUsername(this.state.username); + if (validationError) { + this.setState({ usernameError: validationError }); } else { - const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line - // Removes Error - this.setState(state => ({ ...state, errors })); + this.props.setUsername(this.state.username); } }; - isCompleted = () => { - const { formData } = this.state; - return !Object.keys(formData).filter(prop => !formData[prop].length).length; - }; - - displayErrors = (show = true) => { - this.setState({ showErrors: show }); - }; - - async setUsernameAndClose(username, props = this.props) { - const { - validForm, - invalidForm, - setUsername, - hideCreateUsernameDialog, - updateUsername, - } = props; - try { - // Perform mutation - await setUsername(this.props.auth.user.id, username); - - // Also change in redux store... - updateUsername(username); - - hideCreateUsernameDialog(); - validForm(); - } catch (error) { - const msgs = []; - forEachError(error, ({ msg }) => msgs.push(msg)); - invalidForm(t(msgs.join(', '))); - } - } - - handleSubmitUsername = e => { - e.preventDefault(); - const { errors, formData: { username } } = this.state; - const { invalidForm } = this.props; - this.displayErrors(); - if (this.isCompleted() && !Object.keys(errors).length) { - this.setUsernameAndClose(username); - } else { - invalidForm(t('createdisplay.check_the_form')); - } - }; - - handleClose = () => { - this.setUsernameAndClose(this.props.auth.user.username); - }; + setUsername = username => this.setState({ username }); render() { - const { loggedIn, auth } = this.props; + if (!this.props.unset) { + return null; + } return ( -
- -
+ ); } } -SetUsernameDialog.propTypes = { - auth: PropTypes.object, - hideCreateUsernameDialog: PropTypes.func, - validForm: PropTypes.func, - invalidForm: PropTypes.func, - loggedIn: PropTypes.bool, - changeUsername: PropTypes.func, +SetUsernameDialogContainer.propTypes = { + unset: PropTypes.bool.isRequired, + username: PropTypes.string, + setUsername: PropTypes.func.isRequired, + loading: PropTypes.bool.isRequired, + errorMessage: PropTypes.string.isRequired, + success: PropTypes.bool.isRequired, + validateUsername: PropTypes.func.isRequired, }; -const mapStateToProps = ({ auth }) => ({ - auth: auth, +const mapStateToProps = state => ({ + unset: usernameStatusSelector(state) === 'UNSET', + username: usernameSelector(state), }); -const mapDispatchToProps = dispatch => - bindActionCreators( - { - showCreateUsernameDialog, - hideCreateUsernameDialog, - invalidForm, - validForm, - updateUsername, - }, - dispatch - ); - export default compose( + connect(mapStateToProps, null), withSetUsername, - connect(mapStateToProps, mapDispatchToProps) -)(SetUsernameDialog); + branch(props => !props.username, renderNothing) +)(SetUsernameDialogContainer); diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index cdc34688d..36af1624b 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -6,8 +6,6 @@ en: verify_email2: "You must verify your account before engaging with the community." not_you: "Not you?" logged_in_as: "Signed in as" - facebook_sign_in: "Sign in with Facebook" - facebook_sign_up: "Sign up with Facebook" logout: "Sign out" sign_in: "Sign in" sign_in_to_join: "Sign in to join the conversation" @@ -51,8 +49,6 @@ es: verify_email2: "Debe confirmarla antes de poder involucrarse en la comunidad." not_you: "¿No eres tu?" logged_in_as: "Entraste como" - facebook_sign_in: "Entrar con Facebook" - facebook_sign_up: "Registrarse con Facebook" logout: "Salir" sign_in: "Entrar" sign_in_to_join: "Entrar para unirte a la conversación" @@ -97,8 +93,6 @@ fr: verify_email2: "Vous devez vérifier votre adresse e-mail avant de vous engager auprès de la communauté." not_you: "Pas vous ?" logged_in_as: "Connecté en tant que" - facebook_sign_in: "Connectez-vous avec Facebook" - facebook_sign_up: "Inscrivez-vous avec Facebook" logout: "Se déconnecter" sign_in: "Se connecter" sign_in_to_join: "Connectez-vous pour participer à la conversation" @@ -141,8 +135,6 @@ zh_CN: verify_email2: "您参与社群前须验证帐号。" not_you: "不是你?" logged_in_as: "登录身份" - facebook_sign_in: "使用 Facebook 帐号" - facebook_sign_up: "使用 Facebook 帐号" logout: "登出" sign_in: "登入" sign_in_to_join: "登入以加入对话" @@ -185,8 +177,6 @@ zh_TW: verify_email2: "您參與社群前須驗證帳號。" not_you: "不是你?" logged_in_as: "登錄身份" - facebook_sign_in: "使用 Facebook 帳號" - facebook_sign_up: "使用 Facebook 帳號" logout: "登出" sign_in: "登入" sign_in_to_join: "登入以加入對話" From 87af05c0d9c1a3e907bd75aa5554a32508040d3e Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 15:33:50 +0100 Subject: [PATCH 21/37] Fix translations --- .../coral-framework/hocs/withSetUsername.js | 2 +- client/coral-framework/hocs/withSignUp.js | 2 +- locales/en.yml | 1 + locales/es.yml | 1 + locales/fr.yml | 1 + locales/zh_CN.yml | 1 + locales/zh_TW.yml | 1 + .../client/login/components/External.js | 2 +- .../client/login/components/ForgotPassword.js | 18 +- .../components/ResendEmailConfirmation.js | 6 +- .../client/login/components/SignIn.js | 14 +- .../client/login/components/SignUp.js | 20 +- .../stream/components/SetUsernameDialog.js | 16 +- .../client/stream/components/SignInButton.js | 2 +- .../client/stream/components/UserBox.js | 7 +- .../talk-plugin-auth/client/translations.yml | 421 +++++++++--------- 16 files changed, 268 insertions(+), 247 deletions(-) diff --git a/client/coral-framework/hocs/withSetUsername.js b/client/coral-framework/hocs/withSetUsername.js index 2cec90c00..459b8f5b1 100644 --- a/client/coral-framework/hocs/withSetUsername.js +++ b/client/coral-framework/hocs/withSetUsername.js @@ -38,7 +38,7 @@ const withSetUsername = hoistStatics(WrappedComponent => { validateUsername = value => { if (!value) { - return t('sign_in.required_field'); + return t('error.required_field'); } return validate.username(value) ? '' : errorMsg.username; }; diff --git a/client/coral-framework/hocs/withSignUp.js b/client/coral-framework/hocs/withSignUp.js index 8274b3b8a..ea0585dce 100644 --- a/client/coral-framework/hocs/withSignUp.js +++ b/client/coral-framework/hocs/withSignUp.js @@ -49,7 +49,7 @@ const withSignUp = hoistStatics(WrappedComponent => { } if (requiredFields.includes(field) && !value) { - return t('sign_in.required_field'); + return t('error.required_field'); } if (field in validate) { diff --git a/locales/en.yml b/locales/en.yml index b19fb6ded..49577873b 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -237,6 +237,7 @@ en: password: "Password must be at least 8 characters" username: "Usernames can contain letters numbers and _ only" unexpected: "Unexpected error occurred. Sorry!" + required_field: "This field is required" flag_comment: "Report comment" flag_reason: "Reason for reporting (Optional)" flag_username: "Report username" diff --git a/locales/es.yml b/locales/es.yml index 6b695851e..a624a8636 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -209,6 +209,7 @@ es: username: "Los nombres pueden contener letras números y _" USERNAME_IN_USE: "Este nombre ya está siendo usado." USERNAME_REQUIRED: "Debe ingresar un nombre" + required_field: "Este campo es requerido" flag_comment: "Reportar este comentario" flag_reason: "Razón por la que hacer este reporte (Opcional)" flag_username: "Reportar el nombre de usuario" diff --git a/locales/fr.yml b/locales/fr.yml index 890582f7a..009f48359 100644 --- a/locales/fr.yml +++ b/locales/fr.yml @@ -159,6 +159,7 @@ fr: organization_name: "Le nom de l'organisation ne peut contenir que des lettres ou des chiffres." password: "Le mot de passe doit être d'au moins 8 caractères" username: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\"" + required_field: "Ce champ est obligatoire" flag_comment: "Signaler un commentaire" flag_reason: "Motif du signalement (facultatif)" flag_username: "Signaler un nom d'utilisateur" diff --git a/locales/zh_CN.yml b/locales/zh_CN.yml index 1b1709a38..42b4fff8b 100644 --- a/locales/zh_CN.yml +++ b/locales/zh_CN.yml @@ -214,6 +214,7 @@ zh_CN: password: "密码长度须至少为 8 字符" username: "用户名只能包含字母、数字跟下划线" unexpected: "发生了异常错误。对不起!" + required_field: "该字段必填" flag_comment: "举报评论" flag_reason: "举报理由(可选)" flag_username: "举报用户名" diff --git a/locales/zh_TW.yml b/locales/zh_TW.yml index 0edceedb4..34f9f4c6c 100644 --- a/locales/zh_TW.yml +++ b/locales/zh_TW.yml @@ -214,6 +214,7 @@ zh_TW: password: "密碼必須至少8個字符" username: "用戶名只能包含字母、數字和下劃線。" unexpected: "發生了意外錯誤。抱歉!" + required_field: "該字段必填" flag_comment: "舉報評論" flag_reason: "舉報原因(可選)" flag_username: "舉報用戶名" diff --git a/plugins/talk-plugin-auth/client/login/components/External.js b/plugins/talk-plugin-auth/client/login/components/External.js index 9d57d63db..7811188f0 100644 --- a/plugins/talk-plugin-auth/client/login/components/External.js +++ b/plugins/talk-plugin-auth/client/login/components/External.js @@ -12,7 +12,7 @@ const External = ({ slot }) => (
-

{t('sign_in.or')}

+

{t('talk-plugin-auth.login.or')}

diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js index 9a5816b38..8fbd48c64 100644 --- a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js @@ -25,7 +25,7 @@ class ForgotPassword extends React.Component { return (
-

{t('sign_in.recover_password')}

+

{t('talk-plugin-auth.login.recover_password')}

@@ -34,13 +34,13 @@ class ForgotPassword extends React.Component { style={{ fontSize: 16 }} id="email" name="email" - label={t('sign_in.email')} + label={t('talk-plugin-auth.login.email')} onChange={this.handleEmailChange} value={email} />
{success ? (

{t('password_reset.mail_sent')}

@@ -51,12 +51,16 @@ class ForgotPassword extends React.Component {
- {t('sign_in.need_an_account')}{' '} - {t('sign_in.register')} + {t('talk-plugin-auth.login.need_an_account')}{' '} + + {t('talk-plugin-auth.login.register')} + - {t('sign_in.already_have_an_account')}{' '} - {t('sign_in.sign_in')} + {t('talk-plugin-auth.login.already_have_an_account')}{' '} + + {t('talk-plugin-auth.login.sign_in')} +
diff --git a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js index 7c15e9fc7..6b316838d 100644 --- a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js +++ b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js @@ -19,7 +19,9 @@ class ResendVerification extends React.Component { const { email, errorMessage, loading, success } = this.props; return (
-

{t('sign_in.email_verify_cta')}

+

+ {t('talk-plugin-auth.login.email_verify_cta')} +

{errorMessage && {errorMessage}}
@@ -34,7 +36,7 @@ class ResendVerification extends React.Component { onClick={this.handleSubmit} full > - {t('sign_in.request_new_verify_email')} + {t('talk-plugin-auth.login.request_new_verify_email')} )} {loading && } diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js index 9f7fe42b5..333ca0b0b 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -52,7 +52,7 @@ class SignIn extends React.Component { return (
-

{t('sign_in.sign_in_to_join')}

+

{t('talk-plugin-auth.login.sign_in_to_join')}

{errorMessage && {errorMessage}}
@@ -61,7 +61,7 @@ class SignIn extends React.Component { - {t('sign_in.sign_in')} + {t('talk-plugin-auth.login.sign_in')} ) : ( @@ -104,13 +104,13 @@ class SignIn extends React.Component { diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js index 5de46f9ad..87acadb68 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -47,7 +47,7 @@ class SignUp extends React.Component { return (
-

{t('sign_in.sign_up')}

+

{t('talk-plugin-auth.login.sign_up')}

{errorMessage && {errorMessage}} @@ -58,7 +58,7 @@ class SignUp extends React.Component { - {t('sign_in.sign_up')} + {t('talk-plugin-auth.login.sign_up')} {loading && }
@@ -123,18 +123,18 @@ class SignUp extends React.Component { {requireEmailConfirmation && (

- {t('sign_in.verify_email')} + {t('talk-plugin-auth.login.verify_email')}

- {t('sign_in.verify_email2')} + {t('talk-plugin-auth.login.verify_email2')}

)}
)}
- {t('sign_in.already_have_an_account')}{' '} + {t('talk-plugin-auth.login.already_have_an_account')}{' '} - {t('sign_in.sign_in')} + {t('talk-plugin-auth.login.sign_in')}
diff --git a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js index a2e641791..d9517a06f 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js @@ -25,24 +25,28 @@ class SetUsernameDialog extends React.Component {
-

{t('createdisplay.write_your_username')}

+

+ {t('talk-plugin-auth.set_username_dialog.write_your_username')} +

- {t('createdisplay.your_username')} + {t('talk-plugin-auth.set_username_dialog.your_username')}

{errorMessage && {errorMessage}}
{usernameError && ( {' '} - {t('createdisplay.special_characters')}{' '} + {t( + 'talk-plugin-auth.set_username_dialog.special_characters' + )}{' '} )}
@@ -50,14 +54,14 @@ class SetUsernameDialog extends React.Component { id="username" style={{ fontSize: 16 }} type="string" - label={t('createdisplay.username')} + label={t('talk-plugin-auth.set_username_dialog.username')} value={username} showErrors={!!usernameError} errorMsg={usernameError} onChange={this.handleUsernameChange} />
diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js index 486858151..f032be1c9 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js @@ -7,7 +7,7 @@ const SignInButton = ({ currentUser, showSignInDialog }) => (
{!currentUser ? ( ) : null}
diff --git a/plugins/talk-plugin-auth/client/stream/components/UserBox.js b/plugins/talk-plugin-auth/client/stream/components/UserBox.js index f03573667..94f4327df 100644 --- a/plugins/talk-plugin-auth/client/stream/components/UserBox.js +++ b/plugins/talk-plugin-auth/client/stream/components/UserBox.js @@ -9,14 +9,15 @@ const UserBox = ({ user, logout, onShowProfile }) => ( {user ? (
- {t('sign_in.logged_in_as')} + {t('talk-plugin-auth.login.logged_in_as')} - {user.username}. {t('sign_in.not_you')} + {user.username}.{' '} + {t('talk-plugin-auth.login.not_you')} logout()} > - {t('sign_in.logout')} + {t('talk-plugin-auth.login.logout')}
) : null} diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index 36af1624b..edde7ed80 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -1,213 +1,218 @@ en: - sign_in: - email_verify_cta: "Please verify your email address." - request_new_verify_email: "Request another email" - verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account." - verify_email2: "You must verify your account before engaging with the community." - not_you: "Not you?" - logged_in_as: "Signed in as" - logout: "Sign out" - sign_in: "Sign in" - sign_in_to_join: "Sign in to join the conversation" - or: "Or" - email: "E-mail Address" - password: "Password" - forgot_your_pass: "Forgot your password?" - need_an_account: "Need an account?" - register: "Register" - sign_up: "Sign Up" - confirm_password: "Confirm Password" - username: "Username" - already_have_an_account: "Already have an account?" - recover_password: "Recover password" - email_in_use: "Email address already in use" - email_or_username_in_use: "Email address or Username already in use" - required_field: "This field is required" - passwords_dont_match: "Passwords don't match." - special_characters: "Usernames can contain letters, numbers and _ only" - sign_in_to_comment: "Sign in to comment" - check_the_form: "Invalid Form. Please, check the fields" - createdisplay: - check_the_form: "Invalid Form. Please check the fields" - continue: "Continue with the same Facebook username" - error_create: "Error when changing username" - fake_comment_body: "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section." - fake_comment_date: "1 minute ago" - if_you_dont_change_your_name: "If you don't change your username at this step your Facebook display name will appear alongside of all your comments." - required_field: "Required field" - save: Save - special_characters: "Usernames can contain letters numbers and _ only" - username: Username - write_your_username: "Edit your username" - your_username: "Your username appears on every comment you post." + talk-plugin-auth: + login: + email_verify_cta: "Please verify your email address." + request_new_verify_email: "Request another email" + verify_email: "Thank you for creating an account! We sent an email to the address you provided to verify your account." + verify_email2: "You must verify your account before engaging with the community." + not_you: "Not you?" + logged_in_as: "Signed in as" + logout: "Sign out" + sign_in: "Sign in" + sign_in_to_join: "Sign in to join the conversation" + or: "Or" + email: "E-mail Address" + password: "Password" + forgot_your_pass: "Forgot your password?" + need_an_account: "Need an account?" + register: "Register" + sign_up: "Sign Up" + confirm_password: "Confirm Password" + username: "Username" + already_have_an_account: "Already have an account?" + recover_password: "Recover password" + email_in_use: "Email address already in use" + email_or_username_in_use: "Email address or Username already in use" + required_field: "This field is required" + passwords_dont_match: "Passwords don't match." + special_characters: "Usernames can contain letters, numbers and _ only" + sign_in_to_comment: "Sign in to comment" + check_the_form: "Invalid Form. Please, check the fields" + set_username_dialog: + check_the_form: "Invalid Form. Please check the fields" + continue: "Continue with the same Facebook username" + error_create: "Error when changing username" + fake_comment_body: "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section." + fake_comment_date: "1 minute ago" + if_you_dont_change_your_name: "If you don't change your username at this step your Facebook display name will appear alongside of all your comments." + required_field: "Required field" + save: Save + special_characters: "Usernames can contain letters numbers and _ only" + username: Username + write_your_username: "Edit your username" + your_username: "Your username appears on every comment you post." es: - sign_in: - email_verify_cta: "Por favor confirme su correo." - request_new_verify_email: "Enviar otro correo" - verify_email: "¡Gracias por crear una cuenta! Le enviamos un correo a la direcció\ - n que dio para confirmar su cuenta." - verify_email2: "Debe confirmarla antes de poder involucrarse en la comunidad." - not_you: "¿No eres tu?" - logged_in_as: "Entraste como" - logout: "Salir" - sign_in: "Entrar" - sign_in_to_join: "Entrar para unirte a la conversación" - or: "O" - email: "Dirección de Correo" - password: "Contraseña" - forgot_your_pass: "¿Has olvidado tu contraseña?" - need_an_account: "¿Necesitas una cuenta?" - register: "Registrar" - sign_up: "Registro" - confirm_password: "Confirmar Contraseña" - username: "Nombre" - already_have_an_account: "¿Ya tienes una cuenta?" - recover_password: "Recuperar la contraseña" - email_in_use: "Este correo se encuentra en uso" - email_or_username_in_use: "Este correo ó nombre de usuario se encuentran en uso" - required_field: "Este campo es requerido" - passwords_dont_match: "Las contraseñas no coinciden." - special_characters: "Los nombres pueden contener letras, números y _" - sign_in_to_comment: "Entrar para comentar" - check_the_form: "Formulario Inválido. Por favor, completa los campos" - createdisplay: - check_the_form: "Formulario Inválido. Por favor verifica los campos" - continue: "Continuar con el mismo nombre que aparece en Facebook" - error_create: "Hubo un error al cambiar el nombre de usuario" - fake_comment_body: "Este es un comentario de ejemplo. Las lectoras pueden compartir\ - \ sus ideas y opiniones con los periodistas en la sección de comentarios." - fake_comment_date: "hace un minuto" - if_you_dont_change_your_name: "Si no modificas tu nombre de usuario en este paso\ - \ tu nombre de Facebook aparecerá al lado de cada comentario que publiques." - required_field: "Campo requerido" - save: Guardar - special_characters: "Los nombres sólo pueden contener letras números y _" - username: Nombre - write_your_username: "Edita tu nombre" - your_username: "Tu nombre aparece en cada comentario que publiques." + talk-plugin-auth: + login: + email_verify_cta: "Por favor confirme su correo." + request_new_verify_email: "Enviar otro correo" + verify_email: "¡Gracias por crear una cuenta! Le enviamos un correo a la direcció\ + n que dio para confirmar su cuenta." + verify_email2: "Debe confirmarla antes de poder involucrarse en la comunidad." + not_you: "¿No eres tu?" + logged_in_as: "Entraste como" + logout: "Salir" + sign_in: "Entrar" + sign_in_to_join: "Entrar para unirte a la conversación" + or: "O" + email: "Dirección de Correo" + password: "Contraseña" + forgot_your_pass: "¿Has olvidado tu contraseña?" + need_an_account: "¿Necesitas una cuenta?" + register: "Registrar" + sign_up: "Registro" + confirm_password: "Confirmar Contraseña" + username: "Nombre" + already_have_an_account: "¿Ya tienes una cuenta?" + recover_password: "Recuperar la contraseña" + email_in_use: "Este correo se encuentra en uso" + email_or_username_in_use: "Este correo ó nombre de usuario se encuentran en uso" + required_field: "Este campo es requerido" + passwords_dont_match: "Las contraseñas no coinciden." + special_characters: "Los nombres pueden contener letras, números y _" + sign_in_to_comment: "Entrar para comentar" + check_the_form: "Formulario Inválido. Por favor, completa los campos" + set_username_dialog: + check_the_form: "Formulario Inválido. Por favor verifica los campos" + continue: "Continuar con el mismo nombre que aparece en Facebook" + error_create: "Hubo un error al cambiar el nombre de usuario" + fake_comment_body: "Este es un comentario de ejemplo. Las lectoras pueden compartir\ + \ sus ideas y opiniones con los periodistas en la sección de comentarios." + fake_comment_date: "hace un minuto" + if_you_dont_change_your_name: "Si no modificas tu nombre de usuario en este paso\ + \ tu nombre de Facebook aparecerá al lado de cada comentario que publiques." + required_field: "Campo requerido" + save: Guardar + special_characters: "Los nombres sólo pueden contener letras números y _" + username: Nombre + write_your_username: "Edita tu nombre" + your_username: "Tu nombre aparece en cada comentario que publiques." fr: - sign_in: - email_verify_cta: "Merci de vérifier votre adresse e-mail." - request_new_verify_email: "Demander un nouvel envoi d'e-mail" - verify_email: "Merci d'avoir créé un compte ! Nous avons envoyé un courrier électronique à l'adresse que vous avez fournie pour vérifier votre adresse e-mail." - verify_email2: "Vous devez vérifier votre adresse e-mail avant de vous engager auprès de la communauté." - not_you: "Pas vous ?" - logged_in_as: "Connecté en tant que" - logout: "Se déconnecter" - sign_in: "Se connecter" - sign_in_to_join: "Connectez-vous pour participer à la conversation" - or: "Ou" - email: "Adresse e-mail" - password: "Mot de passe" - forgot_your_pass: "Mot de passe oublié ?" - need_an_account: "Besoin d'un compte ?" - register: "Inscription" - sign_up: "S'inscrire" - confirm_password: "Confirmez Le mot de passe" - username: "Nom d'utilisateur" - already_have_an_account: "Vous avez déjà un compte ?" - recover_password: "Récupérer le mot de passe" - email_in_use: "L'adresse e-mail est déjà utilisée" - email_or_username_in_use: "Adresse e-mail ou nom d'utilisateur déjà utilisé" - required_field: "Ce champ est obligatoire" - passwords_dont_match: "Les mots de passe ne correspondent pas." - special_characters: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement" - sign_in_to_comment: "Connectez-vous pour commenter" - check_the_form: "Formulaire non valide. Veuillez vérifier les champs" - createdisplay: - check_the_form: "Formulaire non valide. Veuillez vérifier les champs" - continue: "Continuez avec le même nom d'utilisateur que sur Facebook" - error_create: "Erreur lors de la modification du nom d'utilisateur" - fake_comment_body: "Ceci est un exemple de commentaire. Les lecteurs peuvent partager leurs pensées et opinions avec les rédactions dans la section des commentaires." - fake_comment_date: "Il y a 1 minute" - if_you_dont_change_your_name: "Si vous ne modifiez pas votre nom d'utilisateur à cette étape, votre nom d'affichage Facebook apparaîtra à côté de tous vos commentaires." - required_field: "champs requis" - save: Sauvegarder - special_characters: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\"" - username: "Nom d'utilisateur" - write_your_username: "Modifier votre nom d'utilisateur" - your_username: "Votre nom d'utilisateur apparaît sur chaque commentaire que vous publiez." + talk-plugin-auth: + login: + email_verify_cta: "Merci de vérifier votre adresse e-mail." + request_new_verify_email: "Demander un nouvel envoi d'e-mail" + verify_email: "Merci d'avoir créé un compte ! Nous avons envoyé un courrier électronique à l'adresse que vous avez fournie pour vérifier votre adresse e-mail." + verify_email2: "Vous devez vérifier votre adresse e-mail avant de vous engager auprès de la communauté." + not_you: "Pas vous ?" + logged_in_as: "Connecté en tant que" + logout: "Se déconnecter" + sign_in: "Se connecter" + sign_in_to_join: "Connectez-vous pour participer à la conversation" + or: "Ou" + email: "Adresse e-mail" + password: "Mot de passe" + forgot_your_pass: "Mot de passe oublié ?" + need_an_account: "Besoin d'un compte ?" + register: "Inscription" + sign_up: "S'inscrire" + confirm_password: "Confirmez Le mot de passe" + username: "Nom d'utilisateur" + already_have_an_account: "Vous avez déjà un compte ?" + recover_password: "Récupérer le mot de passe" + email_in_use: "L'adresse e-mail est déjà utilisée" + email_or_username_in_use: "Adresse e-mail ou nom d'utilisateur déjà utilisé" + required_field: "Ce champ est obligatoire" + passwords_dont_match: "Les mots de passe ne correspondent pas." + special_characters: "Les noms d'utilisateur ne peuvent contenir que des lettres, des chiffres et \"_\" seulement" + sign_in_to_comment: "Connectez-vous pour commenter" + check_the_form: "Formulaire non valide. Veuillez vérifier les champs" + set_username_dialog: + check_the_form: "Formulaire non valide. Veuillez vérifier les champs" + continue: "Continuez avec le même nom d'utilisateur que sur Facebook" + error_create: "Erreur lors de la modification du nom d'utilisateur" + fake_comment_body: "Ceci est un exemple de commentaire. Les lecteurs peuvent partager leurs pensées et opinions avec les rédactions dans la section des commentaires." + fake_comment_date: "Il y a 1 minute" + if_you_dont_change_your_name: "Si vous ne modifiez pas votre nom d'utilisateur à cette étape, votre nom d'affichage Facebook apparaîtra à côté de tous vos commentaires." + required_field: "champs requis" + save: Sauvegarder + special_characters: "Les noms d'utilisateur ne peuvent contenir que des chiffres, des lettres et \"_\"" + username: "Nom d'utilisateur" + write_your_username: "Modifier votre nom d'utilisateur" + your_username: "Votre nom d'utilisateur apparaît sur chaque commentaire que vous publiez." zh_CN: - sign_in: - email_verify_cta: "请确认您的邮箱地址。" - request_new_verify_email: "请求再次发送邮件" - verify_email: "感谢您创建帐号。我们已向您提供的地址发送了封邮件,您可以通过邮件验证帐号。" - verify_email2: "您参与社群前须验证帐号。" - not_you: "不是你?" - logged_in_as: "登录身份" - logout: "登出" - sign_in: "登入" - sign_in_to_join: "登入以加入对话" - or: "或" - email: "邮箱地址" - password: "密码" - forgot_your_pass: "忘记密码?" - need_an_account: "需要帐号?" - register: "注册" - sign_up: "注册" - confirm_password: "确认密码" - username: "用户名" - already_have_an_account: "已有帐号?" - recover_password: "恢复密码" - email_in_use: "邮箱地址已被使用" - email_or_username_in_use: "邮箱地址或用户名已被使用" - required_field: "该字段必填" - passwords_dont_match: "密码不匹配。" - special_characters: "用户名只能包含字母、数字跟下划线" - sign_in_to_comment: "登入以评论" - check_the_form: "表格无效。请检查字段。" - createdisplay: - check_the_form: "表格无效。请检查字段。" - continue: "使用 Facebook 用户名" - error_create: "更改用户名时发生了错误" - fake_comment_body: "此为评论样例。读者可以在评论部分中分享他们关于新闻编辑室的想法和意见。" - fake_comment_date: "1 分钟前" - if_you_dont_change_your_name: "若您不在此改变用户名,您的 Facebook 用户名将随您的评论一同出现。" - required_field: "必填字段" - save: "保存" - special_characters: "用户名只能包含字母、数字跟下划线" - username: "用户名" - write_your_username: "编辑您的用户名" - your_username: "您的用户名将随您发表的所有评论一同出现。" + talk-plugin-auth: + login: + email_verify_cta: "请确认您的邮箱地址。" + request_new_verify_email: "请求再次发送邮件" + verify_email: "感谢您创建帐号。我们已向您提供的地址发送了封邮件,您可以通过邮件验证帐号。" + verify_email2: "您参与社群前须验证帐号。" + not_you: "不是你?" + logged_in_as: "登录身份" + logout: "登出" + sign_in: "登入" + sign_in_to_join: "登入以加入对话" + or: "或" + email: "邮箱地址" + password: "密码" + forgot_your_pass: "忘记密码?" + need_an_account: "需要帐号?" + register: "注册" + sign_up: "注册" + confirm_password: "确认密码" + username: "用户名" + already_have_an_account: "已有帐号?" + recover_password: "恢复密码" + email_in_use: "邮箱地址已被使用" + email_or_username_in_use: "邮箱地址或用户名已被使用" + required_field: "该字段必填" + passwords_dont_match: "密码不匹配。" + special_characters: "用户名只能包含字母、数字跟下划线" + sign_in_to_comment: "登入以评论" + check_the_form: "表格无效。请检查字段。" + set_username_dialog: + check_the_form: "表格无效。请检查字段。" + continue: "使用 Facebook 用户名" + error_create: "更改用户名时发生了错误" + fake_comment_body: "此为评论样例。读者可以在评论部分中分享他们关于新闻编辑室的想法和意见。" + fake_comment_date: "1 分钟前" + if_you_dont_change_your_name: "若您不在此改变用户名,您的 Facebook 用户名将随您的评论一同出现。" + required_field: "必填字段" + save: "保存" + special_characters: "用户名只能包含字母、数字跟下划线" + username: "用户名" + write_your_username: "编辑您的用户名" + your_username: "您的用户名将随您发表的所有评论一同出现。" zh_TW: - sign_in: - email_verify_cta: "請確認您的郵箱地址。" - request_new_verify_email: "請求再次發送郵件" - verify_email: "感謝您創建帳號。我們已向您提供的地址發送了封郵件,您可以通過郵件驗證帳號。" - verify_email2: "您參與社群前須驗證帳號。" - not_you: "不是你?" - logged_in_as: "登錄身份" - logout: "登出" - sign_in: "登入" - sign_in_to_join: "登入以加入對話" - or: "或" - email: "郵箱地址" - password: "密碼" - forgot_your_pass: "忘記密碼?" - need_an_account: "需要帳號?" - register: "註冊" - sign_up: "註冊" - confirm_password: "確認密碼" - username: "用戶名" - already_have_an_account: "已有帳號?" - recover_password: "恢覆密碼" - email_in_use: "郵箱地址已被使用" - email_or_username_in_use: "郵箱地址或用戶名已被使用" - required_field: "該字段必填" - passwords_dont_match: "密碼不匹配。" - special_characters: "用戶名只能包含字母、數字跟下劃線" - sign_in_to_comment: "登入以評論" - check_the_form: "表格無效。請檢查字段。" - createdisplay: - check_the_form: "表格無效。請檢查字段。" - continue: "使用 Facebook 用戶名" - error_create: "更改用戶名時發生了錯誤" - fake_comment_body: "此為評論樣例。讀者可以在評論部分中分享他們關於新聞編輯室的想法和意見。" - fake_comment_date: "1 分鐘前" - if_you_dont_change_your_name: "若您不在此改變用戶名,您的 Facebook 用戶名將隨您的評論一同出現。" - required_field: "必填字段" - save: "保存" - special_characters: "用戶名只能包含字母、數字跟下劃線" - username: "用戶名" - write_your_username: "編輯您的用戶名" - your_username: "您的用戶名將隨您發表的所有評論一同出現。" + talk-plugin-auth: + login: + email_verify_cta: "請確認您的郵箱地址。" + request_new_verify_email: "請求再次發送郵件" + verify_email: "感謝您創建帳號。我們已向您提供的地址發送了封郵件,您可以通過郵件驗證帳號。" + verify_email2: "您參與社群前須驗證帳號。" + not_you: "不是你?" + logged_in_as: "登錄身份" + logout: "登出" + sign_in: "登入" + sign_in_to_join: "登入以加入對話" + or: "或" + email: "郵箱地址" + password: "密碼" + forgot_your_pass: "忘記密碼?" + need_an_account: "需要帳號?" + register: "註冊" + sign_up: "註冊" + confirm_password: "確認密碼" + username: "用戶名" + already_have_an_account: "已有帳號?" + recover_password: "恢覆密碼" + email_in_use: "郵箱地址已被使用" + email_or_username_in_use: "郵箱地址或用戶名已被使用" + required_field: "該字段必填" + passwords_dont_match: "密碼不匹配。" + special_characters: "用戶名只能包含字母、數字跟下劃線" + sign_in_to_comment: "登入以評論" + check_the_form: "表格無效。請檢查字段。" + set_username_dialog: + check_the_form: "表格無效。請檢查字段。" + continue: "使用 Facebook 用戶名" + error_create: "更改用戶名時發生了錯誤" + fake_comment_body: "此為評論樣例。讀者可以在評論部分中分享他們關於新聞編輯室的想法和意見。" + fake_comment_date: "1 分鐘前" + if_you_dont_change_your_name: "若您不在此改變用戶名,您的 Facebook 用戶名將隨您的評論一同出現。" + required_field: "必填字段" + save: "保存" + special_characters: "用戶名只能包含字母、數字跟下劃線" + username: "用戶名" + write_your_username: "編輯您的用戶名" + your_username: "您的用戶名將隨您發表的所有評論一同出現。" From cad25eedd17c3f6eab8e2238ed69c00949fd68e6 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 15:46:49 +0100 Subject: [PATCH 22/37] CSS cleanup --- client/coral-embed-stream/style/default.css | 11 ----------- .../client/login/components/SignUp.css | 2 +- .../client/login/components/SignUp.js | 2 +- .../client/stream/components/SignInButton.css | 8 ++++++++ .../client/stream/components/SignInButton.js | 8 +++++++- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index a04ee6f05..04797211e 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -72,17 +72,6 @@ body { font-weight: bold; } -/* Coral sign in button */ - -#coralSignInButton { - background-color: #2a2a2a; - color: #FFF; -} - -#coralSignInButton:hover { - background-color: #767676; -} - /* Info Box Styles */ diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.css b/plugins/talk-plugin-auth/client/login/components/SignUp.css index 5a21041ac..6d5474e57 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.css +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.css @@ -17,7 +17,7 @@ margin-top: 0px; } -.signInButton { +.button { margin-top: 10px; background-color: #2a2a2a; } diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js index 87acadb68..601a7dba0 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -108,7 +108,7 @@ class SignUp extends React.Component { type="submit" cStyle="black" id="coralSignUpButton" - className={styles.signInButton} + className={styles.button} full > {t('talk-plugin-auth.login.sign_up')} diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.css b/plugins/talk-plugin-auth/client/stream/components/SignInButton.css index e69de29bb..f155a197c 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.css +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.css @@ -0,0 +1,8 @@ +.button { + background-color: #2a2a2a; + color: #FFF; +} + +.button:hover { + background-color: #767676; +} diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js index f032be1c9..3c64608c5 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js @@ -2,11 +2,17 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Button } from 'plugin-api/beta/client/components/ui'; import { t } from 'plugin-api/beta/client/services'; +import styles from './SignInButton.css'; const SignInButton = ({ currentUser, showSignInDialog }) => (
{!currentUser ? ( - ) : null} From c140a628228b4bee603730e22bd5fa7304e9f74c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Feb 2018 15:53:12 +0100 Subject: [PATCH 23/37] Fix e2e --- .../talk-plugin-auth/client/login/containers/SignUp.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/talk-plugin-auth/client/login/containers/SignUp.js b/plugins/talk-plugin-auth/client/login/containers/SignUp.js index ea19a8940..d1629db1c 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignUp.js @@ -62,6 +62,15 @@ class SignUpContainer extends Component { this.props.setView(views.SIGN_IN); }; + componentWillReceiveProps(nextProps) { + if (nextProps.success) { + setTimeout(() => { + // allow success UI to be shown for a second, and then close the modal + this.props.setView(views.SIGN_IN); + }, 2000); + } + } + render() { return ( Date: Tue, 13 Feb 2018 16:00:03 +0100 Subject: [PATCH 24/37] Use more selectors --- plugin-api/beta/client/selectors/auth.js | 2 ++ .../client/stream/components/SignInButton.css | 1 + .../client/stream/components/SignInButton.js | 6 +++--- .../talk-plugin-auth/client/stream/components/UserBox.js | 8 ++++---- .../client/stream/containers/SignInButton.js | 5 +++-- .../talk-plugin-auth/client/stream/containers/UserBox.js | 5 +++-- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/plugin-api/beta/client/selectors/auth.js b/plugin-api/beta/client/selectors/auth.js index 6c7769bc1..a82e11aab 100644 --- a/plugin-api/beta/client/selectors/auth.js +++ b/plugin-api/beta/client/selectors/auth.js @@ -4,3 +4,5 @@ export const usernameStatusSelector = state => get(state, 'auth.user.status.username.status'); export const usernameSelector = state => get(state, 'auth.user.username'); + +export const isLoggedInSelector = state => !!get(state, 'auth.user'); diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.css b/plugins/talk-plugin-auth/client/stream/components/SignInButton.css index f155a197c..774c6f280 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.css +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.css @@ -5,4 +5,5 @@ .button:hover { background-color: #767676; + color: #FFF; } diff --git a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js index 3c64608c5..53e0bc23d 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SignInButton.js +++ b/plugins/talk-plugin-auth/client/stream/components/SignInButton.js @@ -4,9 +4,9 @@ import { Button } from 'plugin-api/beta/client/components/ui'; import { t } from 'plugin-api/beta/client/services'; import styles from './SignInButton.css'; -const SignInButton = ({ currentUser, showSignInDialog }) => ( +const SignInButton = ({ isLoggedIn, showSignInDialog }) => (
- {!currentUser ? ( + {!isLoggedIn ? (