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: "登入以加入對話"