diff --git a/.eslintignore b/.eslintignore index fc0d50a25..96e49da53 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,5 +3,10 @@ client/lib **/*.html plugins/* !plugins/coral-plugin-facebook-auth +!plugins/coral-plugin-auth !plugins/coral-plugin-respect +!plugins/coral-plugin-offtopic +!plugins/coral-plugin-like +!plugins/coral-plugin-mod +!plugins/coral-plugin-love node_modules diff --git a/.gitignore b/.gitignore index f0c3b2a4b..11e738d57 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ coverage/ plugins.json plugins/* !plugins/coral-plugin-facebook-auth +!plugins/coral-plugin-auth !plugins/coral-plugin-respect !plugins/coral-plugin-offtopic !plugins/coral-plugin-like diff --git a/README.md b/README.md index 718847d9e..ffb54a4fd 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ alternative methods of loading configuration during development. - iPad - iPad Pro +- iPhone 7 Plus +- iPhone 7 - iPhone 6 Plus - iPhone 6 - iPhone 5 diff --git a/app.js b/app.js index 16840f965..d0f0f316b 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,8 @@ const errors = require('./errors'); const {createGraphOptions} = require('./graph'); const apollo = require('graphql-server-express'); const accepts = require('accepts'); +const compression = require('compression'); +const cookieParser = require('cookie-parser'); const app = express(); @@ -31,6 +33,8 @@ app.set('trust proxy', 1); app.use(helmet({ frameguard: false })); +app.use(compression()); +app.use(cookieParser()); app.use(bodyParser.json()); //============================================================================== diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f5c2f848a..8a18a8e3f 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -8,7 +8,7 @@ import { UPDATE_ASSETS } from '../constants/assets'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; /** * Action disptacher related to assets diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index 29971e294..ca48764f1 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -1,6 +1,7 @@ +import bowser from 'bowser'; import * as actions from '../constants/auth'; +import coralApi from 'coral-framework/helpers/request'; import * as Storage from 'coral-framework/helpers/storage'; -import coralApi from 'coral-framework/helpers/response'; import {handleAuthToken} from 'coral-framework/actions/auth'; //============================================================================== @@ -9,16 +10,31 @@ import {handleAuthToken} from 'coral-framework/actions/auth'; export const handleLogin = (email, password, recaptchaResponse) => (dispatch) => { dispatch({type: actions.LOGIN_REQUEST}); - const params = {method: 'POST', body: {email, password}}; + + const params = { + method: 'POST', + body: { + email, + password + } + }; + if (recaptchaResponse) { - params.headers = {'X-Recaptcha-Response': recaptchaResponse}; + params.headers = { + 'X-Recaptcha-Response': recaptchaResponse + }; } + return coralApi('/auth/local', params) .then(({user, token}) => { + if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } + dispatch(handleAuthToken(token)); dispatch(checkLoginSuccess(user)); }) @@ -52,7 +68,9 @@ const forgotPassowordFailure = () => ({ export const requestPasswordReset = (email) => (dispatch) => { dispatch(forgotPassowordRequest(email)); - return coralApi('/account/password/reset', {method: 'POST', body: {email}}) + const redirectUri = location.href; + + return coralApi('/account/password/reset', {method: 'POST', body: {email, loc: redirectUri}}) .then(() => dispatch(forgotPassowordSuccess())) .catch((error) => dispatch(forgotPassowordFailure(error))); }; @@ -81,7 +99,9 @@ export const checkLogin = () => (dispatch) => { return coralApi('/auth') .then(({user}) => { if (!user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } return dispatch(checkLoginFailure('not logged in')); } diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index 9a8743762..36b9ffb0a 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -14,7 +14,7 @@ import { HIDE_SUSPENDUSER_DIALOG } from '../constants/community'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const fetchAccounts = (query = {}) => (dispatch) => { diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 21ff58de1..15413e220 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -1,4 +1,4 @@ -import coralApi from 'coral-framework/helpers/response'; +import coralApi from 'coral-framework/helpers/request'; import * as actions from '../constants/install'; import validate from 'coral-framework/helpers/validate'; import errorMsj from 'coral-framework/helpers/error'; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index b0530e77e..65c200420 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; export const SETTINGS_LOADING = 'SETTINGS_LOADING'; export const SETTINGS_RECEIVED = 'SETTINGS_RECEIVED'; diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 2642db90d..e888d5b74 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -1,4 +1,4 @@ -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from '../../../coral-framework/helpers/request'; import * as userTypes from '../constants/users'; /** diff --git a/client/coral-embed-stream/src/AppRouter.js b/client/coral-embed-stream/src/AppRouter.js index e2553889c..6ee63caab 100644 --- a/client/coral-embed-stream/src/AppRouter.js +++ b/client/coral-embed-stream/src/AppRouter.js @@ -2,12 +2,12 @@ import React from 'react'; import {Router, Route, browserHistory} from 'react-router'; import Embed from './containers/Embed'; -import SignInContainer from 'coral-sign-in/containers/SignInContainer'; +import {LoginContainer} from 'coral-sign-in/containers/LoginContainer'; const routes = (
- - + +
); diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 5f27293d7..7d135d7f0 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -1,74 +1,68 @@ import React from 'react'; +const lang = new I18n(translations); +import Stream from '../containers/Stream'; +import Slot from 'coral-framework/components/Slot'; +import {can} from 'coral-framework/services/perms'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations'; -import {can} from 'coral-framework/services/perms'; -const lang = new I18n(translations); - import {TabBar, Tab, TabContent, Button} from 'coral-ui'; - -import Stream from '../containers/Stream'; import Count from 'coral-plugin-comment-count/CommentCount'; -import UserBox from 'coral-sign-in/components/UserBox'; import ProfileContainer from 'coral-settings/containers/ProfileContainer'; -import ConfigureStreamContainer from 'coral-configure/containers/ConfigureStreamContainer'; +import ConfigureStreamContainer + from 'coral-configure/containers/ConfigureStreamContainer'; export default class Embed extends React.Component { changeTab = (tab) => { - switch(tab) { + switch (tab) { case 0: this.props.setActiveTab('stream'); break; case 1: this.props.setActiveTab('profile'); - // TODO: move data fetching to profile container. + // TODO: move data fetching to profile container. this.props.data.refetch(); break; case 2: this.props.setActiveTab('config'); - // TODO: move data fetching to config container. + // TODO: move data fetching to config container. this.props.data.refetch(); break; } - } + }; handleShowProfile = () => this.props.setActiveTab('profile'); - render () { - const {activeTab, logout, viewAllComments, commentId} = this.props; + render() { + const {activeTab, viewAllComments, commentId} = this.props; const {asset: {totalCommentCount}} = this.props.root; - const {loggedIn, user} = this.props.auth; - - const userBox = ; + const {user} = this.props.auth; return (
- + {lang.t('myProfile')} Configure Stream - { - commentId && - - } + {commentId && + } + - { loggedIn ? userBox : null } - { loggedIn ? userBox : null }
@@ -81,5 +75,5 @@ Embed.propTypes = { data: React.PropTypes.shape({ loading: React.PropTypes.bool, error: React.PropTypes.object - }).isRequired, + }).isRequired }; diff --git a/client/coral-embed-stream/src/components/Stream.js b/client/coral-embed-stream/src/components/Stream.js index be47b5c90..53f716954 100644 --- a/client/coral-embed-stream/src/components/Stream.js +++ b/client/coral-embed-stream/src/components/Stream.js @@ -1,22 +1,18 @@ import React, {PropTypes} from 'react'; - -import {Button} from 'coral-ui'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; + import Comment from '../containers/Comment'; +import SuspendedAccount from './SuspendedAccount'; +import Slot from 'coral-framework/components/Slot'; import InfoBox from 'coral-plugin-infobox/InfoBox'; +import {can} from 'coral-framework/services/perms'; +import I18n from 'coral-framework/modules/i18n/i18n'; import {ModerationLink} from 'coral-plugin-moderation'; +import translations from 'coral-framework/translations'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import QuestionBox from 'coral-plugin-questionbox/QuestionBox'; import IgnoredCommentTombstone from './IgnoredCommentTombstone'; -import SuspendedAccount from './SuspendedAccount'; -import RestrictedMessageBox - from 'coral-framework/components/RestrictedMessageBox'; -import {can} from 'coral-framework/services/perms'; -import ChangeUsernameContainer - from 'coral-sign-in/containers/ChangeUsernameContainer'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from 'coral-framework/translations'; const lang = new I18n(translations); @@ -55,7 +51,10 @@ class Stream extends React.Component { : comment; const banned = user && user.status === 'BANNED'; - const temporarilySuspended = user && user.suspension.until && new Date(user.suspension.until) > new Date(); + const temporarilySuspended = + user && + user.suspension.until && + new Date(user.suspension.until) > new Date(); const hasOlderComments = !!(asset && asset.lastComment && @@ -66,10 +65,15 @@ class Stream extends React.Component { ? asset.comments[0].created_at : new Date(Date.now() - 1000 * 60 * 60 * 24 * 7).toISOString(); const commentIsIgnored = (comment) => { - return me && me.ignoredUsers && me.ignoredUsers.find((u) => u.id === comment.user.id); + return ( + me && + me.ignoredUsers && + me.ignoredUsers.find((u) => u.id === comment.user.id) + ); }; return (
+ {open ?
- {!banned && temporarilySuspended && + {!banned && + temporarilySuspended && - { - lang.t('temporarilySuspended', - this.props.root.settings.organizationName, - lang.timeago(user.suspension.until), - ) - } - - } + {lang.t( + 'temporarilySuspended', + this.props.root.settings.organizationName, + lang.timeago(user.suspension.until) + )} + } {banned && - } - {loggedIn && !banned && !temporarilySuspended && + />} + {loggedIn && + !banned && + !temporarilySuspended && - } + addNotification={this.props.addNotification} + postComment={this.props.postComment} + appendItemArray={this.props.appendItemArray} + updateItem={this.props.updateItem} + setCommentCountCache={this.props.setCommentCountCache} + commentCountCache={commentCountCache} + assetId={asset.id} + premod={asset.settings.moderation} + isReply={false} + authorId={user.id} + charCountEnable={asset.settings.charCountEnable} + maxCharCount={asset.settings.charCount} + />}
:

{asset.settings.closedMessage}

} - {!loggedIn && - } {loggedIn && - user && - } - {loggedIn && } + } {/* the highlightedComment is isolated after the user followed a permalink */} {highlightedComment @@ -163,41 +159,38 @@ class Stream extends React.Component { setCommentCountCache={this.props.setCommentCountCache} />
- {comments.map( - (comment) => { - return (commentIsIgnored(comment) - ? - : - ); - } - )} + {comments.map((comment) => { + return commentIsIgnored(comment) + ? + : ; + })}
(dispatch) => { +export const showSignInDialog = () => (dispatch, getState) => { const signInPopUp = window.open( '/embed/stream/login', 'Login', @@ -21,6 +21,10 @@ export const showSignInDialog = () => (dispatch) => { let loaded = false; signInPopUp.onload = () => { loaded = true; + + // Fire some actions inside the popups reducer, to initialize required state. + const required = getState().asset.toJS().settings.requireEmailConfirmation; + signInPopUp.coralStore.dispatch(setRequireEmailVerification(required)); }; // Use `onunload` instead of `onbeforeunload` which is not supported in IOS Safari. @@ -96,6 +100,11 @@ export const cleanState = () => ({ type: actions.CLEAN_STATE }); +export const setRequireEmailVerification = (required) => ({ + type: actions.SET_REQUIRE_EMAIL_VERIFICATION, + required, +}); + // Sign In Actions const signInRequest = () => ({ @@ -121,26 +130,31 @@ export const handleAuthToken = (token) => (dispatch) => { // SIGN IN //============================================================================== -export const fetchSignIn = (formData) => (dispatch) => { - dispatch(signInRequest()); - return coralApi('/auth/local', {method: 'POST', body: formData}) - .then(({token}) => { - dispatch(handleAuthToken(token)); - dispatch(hideSignInDialog()); - }) - .catch((error) => { - if (error.metadata) { +export const fetchSignIn = (formData) => { + return (dispatch) => { + dispatch(signInRequest()); - // the user might not have a valid email. prompt the user user re-request the confirmation email - dispatch( - signInFailure(lang.t('error.emailNotVerified', error.metadata)) - ); - } else { + return coralApi('/auth/local', {method: 'POST', body: formData}) + .then(({token}) => { + if (!bowser.safari && !bowser.ios) { + dispatch(handleAuthToken(token)); + } + dispatch(hideSignInDialog()); + }) + .catch((error) => { + if (error.metadata) { - // invalid credentials - dispatch(signInFailure(lang.t('error.emailPasswordError'))); - } - }); + // the user might not have a valid email. prompt the user user re-request the confirmation email + dispatch( + signInFailure(lang.t('error.emailNotVerified', error.metadata)) + ); + } else { + + // invalid credentials + dispatch(signInFailure(lang.t('error.emailPasswordError'))); + } + }); + }; }; //============================================================================== @@ -187,7 +201,7 @@ export const fetchSignUpFacebook = () => (dispatch) => { ); }; -export const facebookCallback = (err, data) => (dispatch, getState) => { +export const facebookCallback = (err, data) => (dispatch) => { if (err) { dispatch(signInFacebookFailure(err)); return; @@ -196,10 +210,6 @@ export const facebookCallback = (err, data) => (dispatch, getState) => { dispatch(handleAuthToken(data.token)); dispatch(signInFacebookSuccess(data.user)); dispatch(hideSignInDialog()); - const {user: {canEditName, status}} = getState().auth.toJS(); - if (canEditName && status !== 'BANNED') { - dispatch(showCreateUsernameDialog()); - } } catch (err) { dispatch(signInFacebookFailure(err)); return; @@ -269,7 +279,9 @@ export const fetchForgotPassword = (email) => (dispatch) => { export const logout = () => (dispatch) => { return coralApi('/auth', {method: 'DELETE'}).then(() => { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } dispatch({type: actions.LOGOUT}); }); }; @@ -292,11 +304,18 @@ export const checkLogin = () => (dispatch) => { coralApi('/auth') .then((result) => { if (!result.user) { - Storage.removeItem('token'); + if (!bowser.safari && !bowser.ios) { + Storage.removeItem('token'); + } throw new Error('Not logged in'); } dispatch(checkLoginSuccess(result.user)); + + // Display create username dialog if necessary. + if (result.user.canEditName && result.user.status !== 'BANNED') { + dispatch(showCreateUsernameDialog()); + } }) .catch((error) => { console.error(error); diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 910fe0865..95d4116a6 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,5 +1,5 @@ import {addNotification} from '../actions/notification'; -import coralApi from '../helpers/response'; +import coralApi from '../helpers/request'; import * as actions from '../constants/auth'; import I18n from 'coral-framework/modules/i18n/i18n'; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 24599ac50..a9ac52739 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -46,3 +46,6 @@ 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'; + +export const SET_REQUIRE_EMAIL_VERIFICATION = 'SET_REQUIRE_EMAIL_VERIFICATION'; + diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/request.js similarity index 75% rename from client/coral-framework/helpers/response.js rename to client/coral-framework/helpers/request.js index 794a73e30..0e15003e1 100644 --- a/client/coral-framework/helpers/response.js +++ b/client/coral-framework/helpers/request.js @@ -1,22 +1,26 @@ +import bowser from 'bowser'; import * as Storage from './storage'; +import merge from 'lodash/merge'; const buildOptions = (inputOptions = {}) => { const defaultOptions = { method: 'GET', headers: { Accept: 'application/json', - Authorization: `Bearer ${Storage.getItem('token')}`, - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, credentials: 'same-origin' }; - let options = Object.assign({}, defaultOptions, inputOptions); - options.headers = Object.assign( - {}, - defaultOptions.headers, - inputOptions.headers - ); + let options = merge({}, defaultOptions, inputOptions); + + if (!bowser.safari && !bowser.ios) { + let authorization = Storage.getItem('token'); + + if (authorization) { + options.headers.Authorization = `Bearer ${authorization}`; + } + } if (options.method.toLowerCase() !== 'get') { options.body = JSON.stringify(options.body); diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 8fa4ed75b..7a1744d59 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -16,7 +16,8 @@ const initialState = Map({ emailVerificationLoading: false, emailVerificationSuccess: false, successSignUp: false, - fromSignUp: false + fromSignUp: false, + requireEmailConfirmation: false, }); const purge = (user) => { @@ -142,6 +143,9 @@ export default function auth (state = initialState, action) { return state .set('emailVerificationSuccess', true) .set('emailVerificationLoading', false); + case actions.SET_REQUIRE_EMAIL_VERIFICATION: + return state + .set('requireEmailConfirmation', action.required); default : return state; } diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js index e421ca3da..0c430a021 100644 --- a/client/coral-framework/services/transport.js +++ b/client/coral-framework/services/transport.js @@ -1,5 +1,6 @@ import {createNetworkInterface} from 'apollo-client'; import * as Storage from '../helpers/storage'; +import bowser from 'bowser'; //============================================================================== // NETWORK INTERFACE @@ -21,7 +22,11 @@ networkInterface.use([{ if (!req.options.headers) { req.options.headers = {}; // Create the header object if needed. } - req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + + if (!bowser.safari && !bowser.ios) { + req.options.headers['authorization'] = `Bearer ${Storage.getItem('token')}`; + } + next(); } }]); diff --git a/client/coral-sign-in/components/FakeComment.js b/client/coral-sign-in/components/FakeComment.js deleted file mode 100644 index 37630383d..000000000 --- a/client/coral-sign-in/components/FakeComment.js +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import styles from 'coral-embed-stream/src/components/Comment.css'; - -import AuthorName from 'coral-plugin-author-name/AuthorName'; -import Content from 'coral-plugin-commentcontent/CommentContent'; -import PubDate from 'coral-plugin-pubdate/PubDate'; -import {ReplyButton} from 'coral-plugin-replies'; - -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; - -const lang = new I18n(translations); - -class FakeComment extends React.Component { - constructor (props) { - super(props); - } - - render () { - const {username, created_at, body} = this.props; - - return ( -
-
- - - -
-
- -
- {}} - parentCommentId={'commentID'} - currentUserId={{}} - banned={false} - /> -
-
-
- -
-
- -
-
-
- ); - } -} - -export default FakeComment; diff --git a/client/coral-sign-in/components/UserBox.js b/client/coral-sign-in/components/UserBox.js deleted file mode 100644 index a44c3d4fb..000000000 --- a/client/coral-sign-in/components/UserBox.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; -const lang = new I18n(translations); - -const UserBox = ({className, user, onLogout, onShowProfile}) => ( -
- {lang.t('signIn.loggedInAs')} - {user.username}. {lang.t('signIn.notYou')} - {lang.t('signIn.logout')} -
-); - -export default UserBox; diff --git a/client/coral-sign-in/containers/LoginContainer.js b/client/coral-sign-in/containers/LoginContainer.js new file mode 100644 index 000000000..8d19e5198 --- /dev/null +++ b/client/coral-sign-in/containers/LoginContainer.js @@ -0,0 +1,6 @@ +import React from 'react'; +import Slot from 'coral-framework/components/Slot'; + +export const LoginContainer = () => ( + +); diff --git a/package.json b/package.json index 84d0fdf52..152871fae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "1.6.0", + "version": "1.7.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "scripts": { @@ -54,10 +54,13 @@ "app-module-path": "^2.2.0", "bcrypt": "^1.0.2", "body-parser": "^1.17.1", + "bowser": "^1.7.0", "cli-table": "^0.3.1", "colors": "^1.1.2", "commander": "^2.9.0", + "compression": "^1.6.2", "connect-redis": "^3.1.0", + "cookie-parser": "^1.4.3", "cross-spawn": "^5.1.0", "csurf": "^1.9.0", "dataloader": "^1.3.0", diff --git a/plugins.default.json b/plugins.default.json index b3443af48..3f64a383b 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -1,9 +1,13 @@ { "server": [ "coral-plugin-respect", - "coral-plugin-facebook-auth" + "coral-plugin-like", + "coral-plugin-facebook-auth", + "coral-plugin-auth" ], "client": [ - "coral-plugin-respect" + "coral-plugin-respect", + "coral-plugin-like", + "coral-plugin-auth" ] } diff --git a/plugins/coral-plugin-auth/client/.babelrc b/plugins/coral-plugin-auth/client/.babelrc new file mode 100644 index 000000000..60be246eb --- /dev/null +++ b/plugins/coral-plugin-auth/client/.babelrc @@ -0,0 +1,14 @@ +{ + "presets": [ + "es2015" + ], + "plugins": [ + "add-module-exports", + "transform-class-properties", + "transform-decorators-legacy", + "transform-object-assign", + "transform-object-rest-spread", + "transform-async-to-generator", + "transform-react-jsx" + ] +} \ No newline at end of file diff --git a/plugins/coral-plugin-auth/client/.eslintrc.json b/plugins/coral-plugin-auth/client/.eslintrc.json new file mode 100644 index 000000000..9fe56bd14 --- /dev/null +++ b/plugins/coral-plugin-auth/client/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "env": { + "browser": true, + "es6": true, + "mocha": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + } + }, + "parser": "babel-eslint", + "plugins": [ + "react" + ], + "rules": { + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] + } +} diff --git a/client/coral-sign-in/containers/ChangeUsernameContainer.js b/plugins/coral-plugin-auth/client/components/ChangeUsername.js similarity index 52% rename from client/coral-sign-in/containers/ChangeUsernameContainer.js rename to plugins/coral-plugin-auth/client/components/ChangeUsername.js index cebd9605b..ee02b2e17 100644 --- a/client/coral-sign-in/containers/ChangeUsernameContainer.js +++ b/plugins/coral-plugin-auth/client/components/ChangeUsername.js @@ -1,13 +1,12 @@ -import React, {Component} from 'react'; +import React from 'react'; import {connect} from 'react-redux'; - -import validate from 'coral-framework/helpers/validate'; -import errorMsj from 'coral-framework/helpers/error'; - -import CreateUsernameDialog from '../components/CreateUsernameDialog'; - -import I18n from 'coral-framework/modules/i18n/i18n'; +import {bindActionCreators} from 'redux'; import translations from '../translations'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import errorMsj from 'coral-framework/helpers/error'; +import validate from 'coral-framework/helpers/validate'; +import CreateUsernameDialog from './CreateUsernameDialog'; + const lang = new I18n(translations); import { @@ -16,50 +15,57 @@ import { invalidForm, validForm, createUsername -} from '../../coral-framework/actions/auth'; - -class ChangeUsernameContainer extends Component { - initialState = { - formData: { - username: '', - }, - errors: {}, - showErrors: false - }; +} from 'coral-framework/actions/auth'; +class ChangeUsernameContainer extends React.Component { constructor(props) { super(props); - this.initialState.formData.username = props.user.username; - this.state = this.initialState; - this.handleChange = this.handleChange.bind(this); - this.handleSubmitUsername = this.handleSubmitUsername.bind(this); - this.handleClose = this.handleClose.bind(this); - this.addError = this.addError.bind(this); - } - handleChange(e) { - const {name, value} = e.target; - this.setState((state) => ({ - ...state, + this.state = { formData: { - ...state.formData, - [name]: value - } - }), () => { - this.validation(name, value); - }); + username: (props.auth.user && props.auth.user.username) || '' + }, + errors: {}, + showErrors: false + }; } - addError(name, error) { + 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); + } + ); + }; + + addError = (name, error) => { return this.setState((state) => ({ errors: { ...state.errors, [name]: error } })); - } + }; - validation(name, value) { + validation = (name, value) => { const {addError} = this; if (!value.length) { @@ -67,37 +73,37 @@ class ChangeUsernameContainer extends Component { } else if (!validate[name](value)) { addError(name, errorMsj[name]); } else { - const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line + const {[name]: prop, ...errors} = this.state.errors; // eslint-disable-line // Removes Error this.setState((state) => ({...state, errors})); } - } + }; - isCompleted() { + isCompleted = () => { const {formData} = this.state; return !Object.keys(formData).filter((prop) => !formData[prop].length).length; - } + }; - displayErrors(show = true) { + displayErrors = (show = true) => { this.setState({showErrors: show}); - } + }; - handleSubmitUsername(e) { + handleSubmitUsername = (e) => { e.preventDefault(); const {errors} = this.state; const {validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - this.props.createUsername(this.props.user.id, this.state.formData); + this.props.createUsername(this.props.auth.user.id, this.state.formData); validForm(); } else { invalidForm(lang.t('createdisplay.checkTheForm')); } - } + }; - handleClose() { + handleClose = () => { this.props.hideCreateUsernameDialog(); - } + }; render() { const {loggedIn, auth} = this.props; @@ -117,19 +123,22 @@ class ChangeUsernameContainer extends Component { } } -const mapStateToProps = (state) => ({ - auth: state.auth.toJS() +const mapStateToProps = ({auth}) => ({ + auth: auth.toJS() }); -const mapDispatchToProps = (dispatch) => ({ - createUsername: (userid, formData) => dispatch(createUsername(userid, formData)), - showCreateUsernameDialog: () => dispatch(showCreateUsernameDialog()), - hideCreateUsernameDialog: () => dispatch(hideCreateUsernameDialog()), - invalidForm: (error) => dispatch(invalidForm(error)), - validForm: () => dispatch(validForm()) -}); +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + createUsername, + showCreateUsernameDialog, + hideCreateUsernameDialog, + invalidForm, + validForm + }, + dispatch + ); -export default connect( - mapStateToProps, - mapDispatchToProps -)(ChangeUsernameContainer); +export default connect(mapStateToProps, mapDispatchToProps)( + ChangeUsernameContainer +); diff --git a/client/coral-sign-in/components/CreateUsernameDialog.js b/plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js similarity index 61% rename from client/coral-sign-in/components/CreateUsernameDialog.js rename to plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js index b94b47c42..c352552ae 100644 --- a/client/coral-sign-in/components/CreateUsernameDialog.js +++ b/plugins/coral-plugin-auth/client/components/CreateUsernameDialog.js @@ -1,21 +1,26 @@ import React from 'react'; -import TextField from 'coral-ui/components/TextField'; -import Button from 'coral-ui/components/Button'; -import {Dialog, Alert} from 'coral-ui'; -import FakeComment from './FakeComment'; - import styles from './styles.css'; - -import I18n from 'coral-framework/modules/i18n/i18n'; +import {Dialog, Alert, TextField} from 'coral-ui'; +import {FakeComment} from './FakeComment'; import translations from '../translations'; +import Button from 'coral-ui/components/Button'; +import I18n from 'coral-framework/modules/i18n/i18n'; + const lang = new I18n(translations); -const CreateUsernameDialog = ({open, handleClose, formData, handleSubmitUsername, handleChange, ...props}) => { - return ( +const CreateUsernameDialog = ({ + open, + handleClose, + formData, + handleSubmitUsername, + handleChange, + ...props +}) => ( + open={open} + > ×
@@ -24,17 +29,24 @@ const CreateUsernameDialog = ({open, handleClose, formData, handleSubmitUsername
-

{lang.t('createdisplay.yourusername')}

+

+ {lang.t('createdisplay.yourusername')} +

-

{lang.t('createdisplay.ifyoudontchangeyourname')}

- { props.auth.error && {props.auth.error} } +

+ {lang.t('createdisplay.ifyoudontchangeyourname')} +

+ {props.auth.error && {props.auth.error}}
- { props.errors.username && {lang.t('createdisplay.specialCharacters')} } + {props.errors.username && + + {' '}{lang.t('createdisplay.specialCharacters')}{' '} + }
- +
-
+
- ); -}; +); export default CreateUsernameDialog; diff --git a/plugins/coral-plugin-auth/client/components/FakeComment.js b/plugins/coral-plugin-auth/client/components/FakeComment.js new file mode 100644 index 000000000..349e7d0a4 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/FakeComment.js @@ -0,0 +1,73 @@ +import React from 'react'; +import translations from '../translations'; +import {ReplyButton} from 'coral-plugin-replies'; +import PubDate from 'coral-plugin-pubdate/PubDate'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import AuthorName from 'coral-plugin-author-name/AuthorName'; +import Content from 'coral-plugin-commentcontent/CommentContent'; +import styles from 'coral-embed-stream/src/components/Comment.css'; + +const lang = new I18n(translations); + +export const FakeComment = ({username, created_at, body}) => ( +
+
+ + + +
+
+ +
+ {}} + parentCommentId={'commentID'} + currentUserId={{}} + banned={false} + /> +
+
+
+ +
+
+ +
+
+
+); + diff --git a/client/coral-sign-in/components/ForgotContent.js b/plugins/coral-plugin-auth/client/components/ForgotContent.js similarity index 50% rename from client/coral-sign-in/components/ForgotContent.js rename to plugins/coral-plugin-auth/client/components/ForgotContent.js index 76246fa00..8bd6b27a6 100644 --- a/client/coral-sign-in/components/ForgotContent.js +++ b/plugins/coral-plugin-auth/client/components/ForgotContent.js @@ -1,22 +1,18 @@ import React from 'react'; import styles from './styles.css'; +import translations from '../translations'; import Button from 'coral-ui/components/Button'; import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; + const lang = new I18n(translations); class ForgotContent extends React.Component { - constructor (props) { - super(props); - this.handleSubmit = this.handleSubmit.bind(this); - } - - handleSubmit (e) { + handleSubmit = (e) => { e.preventDefault(); this.props.fetchForgotPassword(this.emailInput.value); - } + }; - render () { + render() { const {changeView, auth} = this.props; const {passwordRequestSuccess, passwordRequestFailure} = auth; @@ -29,29 +25,47 @@ class ForgotContent extends React.Component {
this.emailInput = input} + ref={(input) => (this.emailInput = input)} type="text" style={{fontSize: 16}} id="email" - name="email" /> + name="email" + />
- - { - passwordRequestSuccess - ?

{passwordRequestSuccess}

- : null - } - { - passwordRequestFailure - ?

{passwordRequestFailure}

- : null - } + {passwordRequestSuccess + ?

+ {passwordRequestSuccess} +

+ : null} + {passwordRequestFailure + ?

+ {passwordRequestFailure} +

+ : null}
- {lang.t('signIn.needAnAccount')} changeView('SIGNUP')}>{lang.t('signIn.register')} - {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')} + + {lang.t('signIn.needAnAccount')} + {' '} + changeView('SIGNUP')}> + {lang.t('signIn.register')} + + + + {lang.t('signIn.alreadyHaveAnAccount')} + {' '} + changeView('SIGNIN')}> + {lang.t('signIn.signIn')} + +
); diff --git a/client/coral-sign-in/components/SignDialog.js b/plugins/coral-plugin-auth/client/components/SignDialog.js similarity index 68% rename from client/coral-sign-in/components/SignDialog.js rename to plugins/coral-plugin-auth/client/components/SignDialog.js index ff2464f7c..7f05889a0 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/plugins/coral-plugin-auth/client/components/SignDialog.js @@ -6,12 +6,9 @@ import SignInContent from './SignInContent'; import SignUpContent from './SignUpContent'; import ForgotContent from './ForgotContent'; -const SignDialog = ({open, view, handleClose, ...props}) => ( - - × +const SignDialog = ({open, view, hideSignInDialog, ...props}) => ( + + × {view === 'SIGNIN' && } {view === 'SIGNUP' && } {view === 'FORGOT' && } diff --git a/plugins/coral-plugin-auth/client/components/SignInButton.js b/plugins/coral-plugin-auth/client/components/SignInButton.js new file mode 100644 index 000000000..881233c83 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/SignInButton.js @@ -0,0 +1,24 @@ +import React from 'react'; +import {Button} from 'coral-ui'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {showSignInDialog} from 'coral-framework/actions/auth'; + +const SignInButton = ({loggedIn, showSignInDialog}) => ( +
+ {!loggedIn + ? + : null} +
+); + +const mapStateToProps = ({auth}) => ({ + loggedIn: auth.toJS().loggedIn +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({showSignInDialog}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(SignInButton); diff --git a/client/coral-sign-in/containers/SignInContainer.js b/plugins/coral-plugin-auth/client/components/SignInContainer.js similarity index 58% rename from client/coral-sign-in/containers/SignInContainer.js rename to plugins/coral-plugin-auth/client/components/SignInContainer.js index de1d2086e..9ef868f1d 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/plugins/coral-plugin-auth/client/components/SignInContainer.js @@ -1,11 +1,13 @@ -import React, {Component, PropTypes} from 'react'; +import React from 'react'; import {connect} from 'react-redux'; -import SignDialog from '../components/SignDialog'; -import validate from 'coral-framework/helpers/validate'; -import errorMsj from 'coral-framework/helpers/error'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations'; import {pym} from 'coral-framework'; +import SignDialog from './SignDialog'; +import {bindActionCreators} from 'redux'; +import translations from '../translations'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import errorMsj from 'coral-framework/helpers/error'; +import validate from 'coral-framework/helpers/validate'; + const lang = new I18n(translations); import { @@ -23,34 +25,22 @@ import { checkLogin } from 'coral-framework/actions/auth'; -class SignInContainer extends Component { - initialState = { - formData: { - email: '', - username: '', - password: '', - confirmPassword: '' - }, - emailToBeResent: '', - errors: {}, - showErrors: false - }; - +class SignInContainer extends React.Component { constructor(props) { super(props); - this.state = this.initialState; - this.addError = this.addError.bind(this); - this.handleAuth = this.handleAuth.bind(this); - this.handleSignUp = this.handleSignUp.bind(this); - this.handleSignIn = this.handleSignIn.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleChangeEmail = this.handleChangeEmail.bind(this); - this.handleResendVerification = this.handleResendVerification.bind(this); - } - static propTypes = { - requireEmailConfirmation: PropTypes.bool.isRequired - }; + this.state = { + formData: { + email: '', + username: '', + password: '', + confirmPassword: '' + }, + emailToBeResent: '', + errors: {}, + showErrors: false + }; + } componentWillMount() { this.props.checkLogin(); @@ -71,7 +61,7 @@ class SignInContainer extends Component { window.removeEventListener('storage', this.handleAuth); } - handleAuth(e) { + handleAuth = (e) => { // Listening to FB changes // FB localStorage key is 'auth' @@ -81,9 +71,9 @@ class SignInContainer extends Component { const {err, data} = JSON.parse(e.newValue); authCallback(err, data); } - } + }; - handleChange(e) { + handleChange = (e) => { const {name, value} = e.target; this.setState( (state) => ({ @@ -97,14 +87,14 @@ class SignInContainer extends Component { this.validation(name, value); } ); - } + }; - handleChangeEmail(e) { + handleChangeEmail = (e) => { const {value} = e.target; this.setState({emailToBeResent: value}); - } + }; - handleResendVerification(e) { + handleResendVerification = (e) => { e.preventDefault(); this.props .requestConfirmEmail( @@ -115,21 +105,21 @@ class SignInContainer extends Component { setTimeout(() => { // allow success UI to be shown for a second, and then close the modal - this.props.handleClose(); + this.props.hideSignInDialog(); }, 2500); }); - } + }; - addError(name, error) { + addError = (name, error) => { return this.setState((state) => ({ errors: { ...state.errors, [name]: error } })); - } + }; - validation(name, value) { + validation = (name, value) => { const {addError} = this; const {formData} = this.state; @@ -147,18 +137,18 @@ class SignInContainer extends Component { // Removes Error this.setState((state) => ({...state, errors})); } - } + }; - isCompleted() { + isCompleted = () => { const {formData} = this.state; return !Object.keys(formData).filter((prop) => !formData[prop].length).length; - } + }; - displayErrors(show = true) { + displayErrors = (show = true) => { this.setState({showErrors: show}); - } + }; - handleSignUp(e) { + handleSignUp = (e) => { e.preventDefault(); const {errors} = this.state; const {fetchSignUp, validForm, invalidForm} = this.props; @@ -169,30 +159,28 @@ class SignInContainer extends Component { } else { invalidForm(lang.t('signIn.checkTheForm')); } - } + }; - handleSignIn(e) { + handleSignIn = (e) => { e.preventDefault(); this.props.fetchSignIn(this.state.formData); - } + }; render() { - const {auth, requireEmailConfirmation} = this.props; - const {emailVerificationLoading, emailVerificationSuccess} = auth; + const {auth} = this.props; + const {requireEmailConfirmation, emailVerificationLoading, emailVerificationSuccess} = auth; return ( -
- -
+ ); } } @@ -201,20 +189,23 @@ const mapStateToProps = (state) => ({ auth: state.auth.toJS() }); -const mapDispatchToProps = (dispatch) => ({ - checkLogin: () => dispatch(checkLogin()), - facebookCallback: (err, data) => dispatch(facebookCallback(err, data)), - fetchSignUp: (formData, url) => dispatch(fetchSignUp(formData, url)), - fetchSignIn: (formData) => dispatch(fetchSignIn(formData)), - fetchSignInFacebook: () => dispatch(fetchSignInFacebook()), - fetchSignUpFacebook: () => dispatch(fetchSignUpFacebook()), - fetchForgotPassword: (formData) => dispatch(fetchForgotPassword(formData)), - requestConfirmEmail: (email, url) => - dispatch(requestConfirmEmail(email, url)), - changeView: (view) => dispatch(changeView(view)), - handleClose: () => dispatch(hideSignInDialog()), - invalidForm: (error) => dispatch(invalidForm(error)), - validForm: () => dispatch(validForm()) -}); +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + checkLogin, + facebookCallback, + fetchSignUp, + fetchSignIn, + fetchSignInFacebook, + fetchSignUpFacebook, + fetchForgotPassword, + requestConfirmEmail, + changeView, + hideSignInDialog, + invalidForm, + validForm + }, + dispatch + ); export default connect(mapStateToProps, mapDispatchToProps)(SignInContainer); diff --git a/client/coral-sign-in/components/SignInContent.js b/plugins/coral-plugin-auth/client/components/SignInContent.js similarity index 74% rename from client/coral-sign-in/components/SignInContent.js rename to plugins/coral-plugin-auth/client/components/SignInContent.js index 7460efb97..6f0027084 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/plugins/coral-plugin-auth/client/components/SignInContent.js @@ -18,17 +18,17 @@ const SignInContent = ({ auth, fetchSignInFacebook }) => { - return (

- {auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')} + {auth.emailVerificationFailure + ? lang.t('signIn.emailVerifyCTA') + : lang.t('signIn.signIn')}

- { auth.error && {auth.error} } - { - auth.emailVerificationFailure + {auth.error && {auth.error}} + {auth.emailVerificationFailure ?

{lang.t('signIn.requestNewVerifyEmail')}

- + onChange={handleChangeEmail} + /> + {emailVerificationLoading && } {emailVerificationSuccess && } @@ -70,23 +73,29 @@ const SignInContent = ({ onChange={handleChange} />
- { - !auth.isLoading ? - - : - - } + {!auth.isLoading + ? + : }
-
- } +
}
- changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} + + changeView('FORGOT')}> + {lang.t('signIn.forgotYourPass')} + + {lang.t('signIn.needAnAccount')} - changeView('SIGNUP')} id='coralRegister'> + changeView('SIGNUP')} id="coralRegister"> {lang.t('signIn.register')} diff --git a/client/coral-sign-in/components/SignUpContent.js b/plugins/coral-plugin-auth/client/components/SignUpContent.js similarity index 60% rename from client/coral-sign-in/components/SignUpContent.js rename to plugins/coral-plugin-auth/client/components/SignUpContent.js index 921f045be..2c0cf1f31 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/plugins/coral-plugin-auth/client/components/SignUpContent.js @@ -1,39 +1,26 @@ -import React, {PropTypes} from 'react'; -import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; import styles from './styles.css'; -import I18n from 'coral-framework/modules/i18n/i18n'; +import React from 'react'; import translations from '../translations'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import {Button, TextField, Spinner, Success, Alert} from 'coral-ui'; + const lang = new I18n(translations); class SignUpContent extends React.Component { - constructor (props) { - super(props); - this.successfulSignup = false; + componentWillReceiveProps(next) { + if ( + !this.props.emailVerificationEnabled && + !this.props.auth.successSignUp && + next.auth.successSignUp + ) { + setTimeout(() => { + this.props.changeView('SIGNIN'); + }, 2000); + } } - static propTypes = { - emailVerificationEnabled: PropTypes.bool.isRequired, - fetchSignUpFacebook: PropTypes.func.isRequired, - changeView: PropTypes.func.isRequired, - handleSignUp: PropTypes.func.isRequired, - showErrors: PropTypes.bool, - errors: PropTypes.shape({ - email: PropTypes.string, - username: PropTypes.string, - password: PropTypes.string, - confirmPassword: PropTypes.string, - }), - formData: PropTypes.shape({ - email: PropTypes.string, - username: PropTypes.string, - password: PropTypes.string, - confirmPassword: PropTypes.string - }) - } - - render () { - + render() { const { handleChange, formData, @@ -43,18 +30,8 @@ class SignUpContent extends React.Component { showErrors, changeView, handleSignUp, - fetchSignUpFacebook} = this.props; - - const beforeSignup = !auth.isLoading && !auth.successSignUp; - const successfulSignup = !auth.isLoading && auth.successSignUp; - - // the first time we render a successfulSignup, trigger a timer - if ((this.successfulSignup ^ successfulSignup) && !emailVerificationEnabled) { - setTimeout(() => { - changeView('SIGNIN'); - }, 1000); - this.successfulSignup = true; - } + fetchSignUpFacebook + } = this.props; return (
@@ -64,8 +41,8 @@ class SignUpContent extends React.Component {
- { auth.error && {auth.error} } - { beforeSignup && + {auth.error && {auth.error}} + {!auth.successSignUp &&
- { auth.isLoading && } + {auth.isLoading && }
-
- } - { - successfulSignup && +
} + {auth.successSignUp &&
- { - emailVerificationEnabled && -

{lang.t('signIn.verifyEmail')}

{lang.t('signIn.verifyEmail2')}

- } -
- } + {emailVerificationEnabled && +

+ {lang.t('signIn.verifyEmail')} +
+
+ {lang.t('signIn.verifyEmail2')} +

} + }
- {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}> + {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')} + > {lang.t('signIn.signIn')}
diff --git a/plugins/coral-plugin-auth/client/components/UserBox.js b/plugins/coral-plugin-auth/client/components/UserBox.js new file mode 100644 index 000000000..cfd0fc9d2 --- /dev/null +++ b/plugins/coral-plugin-auth/client/components/UserBox.js @@ -0,0 +1,34 @@ +import React from 'react'; +import styles from './styles.css'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import translations from '../translations'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import {logout} from 'coral-framework/actions/auth'; +const lang = new I18n(translations); + +const UserBox = ({loggedIn, user, logout, onShowProfile}) => ( +
+ { + loggedIn ? ( +
+ {lang.t('signIn.loggedInAs')} + {user.username}. {lang.t('signIn.notYou')} + logout()}> + {lang.t('signIn.logout')} + +
+ ) : null + } +
+); + +const mapStateToProps = ({auth}) => ({ + loggedIn: auth.toJS().loggedIn, + user: auth.toJS().user +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({logout}, dispatch); + +export default connect(mapStateToProps, mapDispatchToProps)(UserBox); diff --git a/client/coral-sign-in/components/styles.css b/plugins/coral-plugin-auth/client/components/styles.css similarity index 100% rename from client/coral-sign-in/components/styles.css rename to plugins/coral-plugin-auth/client/components/styles.css diff --git a/plugins/coral-plugin-auth/client/index.js b/plugins/coral-plugin-auth/client/index.js new file mode 100644 index 000000000..c41c097bd --- /dev/null +++ b/plugins/coral-plugin-auth/client/index.js @@ -0,0 +1,12 @@ +import UserBox from './components/UserBox'; +import SignInButton from './components/SignInButton'; +import SignInContainer from './components/SignInContainer'; +import ChangeUserNameContainer from './components/ChangeUsername'; + +export default { + slots: { + stream: [UserBox, SignInButton, ChangeUserNameContainer], + login: [SignInContainer] + } +}; + diff --git a/plugins/coral-plugin-auth/client/translations.json b/plugins/coral-plugin-auth/client/translations.json new file mode 100644 index 000000000..7ae84b2ff --- /dev/null +++ b/plugins/coral-plugin-auth/client/translations.json @@ -0,0 +1,102 @@ +{ + "en": { + "signIn": { + "emailVerifyCTA": "Please verify your email address.", + "requestNewVerifyEmail": "Request another email:", + "verifyEmail": "Thank you for creating an account! We sent an email to the address you provided to verify your account.", + "verifyEmail2": "You must verify your account before engaging with the community.", + "notYou": "Not you?", + "loggedInAs": "Logged in as", + "facebookSignIn": "Sign in with Facebook", + "facebookSignUp": "Sign up with Facebook", + "logout": "Logout", + "signIn": "Sign in to join the conversation", + "or": "Or", + "email": "E-mail Address", + "password": "Password", + "forgotYourPass": "Forgot your password?", + "needAnAccount": "Need an account?", + "register": "Register", + "signUp": "Sign Up", + "confirmPassword": "Confirm Password", + "username": "Username", + "alreadyHaveAnAccount": "Already have an account?", + "recoverPassword": "Recover password", + "emailInUse": "Email address already in use", + "emailORusernameInUse": "Email address or Username already in use", + "requiredField": "This field is required", + "passwordsDontMatch": "Passwords don't match.", + "specialCharacters": "Usernames can contain letters, numbers and _ only", + "checkTheForm": "Invalid Form. Please, check the fields" + }, + "createdisplay": { + "writeyourusername": "Edit your username", + "yourusername": "Your username appears on every comment you post.", + "ifyoudontchangeyourname": "If you don't change your username at this step, your Facebook display name will appear alongside of all your comments.", + "username": "Username", + "continue": "Continue with the same Facebook username", + "save": "Save", + "fakecommentdate": "1 minute ago", + "fakecommentbody": "This is an example comment. Readers can share their thoughts and opinions with newsrooms in the comments section.", + "requiredField": "Required field", + "errorCreate": "Error when changing username", + "checkTheForm": "Invalid Form. Please, check the fields", + "specialCharacters": "Usernames can contain letters, numbers and _ only" + }, + "permalink": { + "permalink": "Link" + }, + "report": "Report", + "like": "Like" + }, + "es": { + "signIn": { + "emailVerifyCTA": "Por favor verifique su e-mail.", + "requestNewVerifyEmail": "Enviar otro correo:", + "verifyEmail": "¡Gracias por crear una cuenta! Le enviamos un correo a la dirección que dio para verificar su cuenta.", + "verifyEmail2": "Debe verificarla antes de poder involucrarse en la comunidad.", + "notYou": "¿No eres tu?", + "loggedInAs": "Entraste como", + "facebookSignIn": "Entrar con Facebook", + "facebookSignUp": "Regístrate con Facebook", + "logout": "Salir", + "signIn": "Entrar para Unirte a la Conversación", + "or": "o", + "email": "E-mail", + "password": "Contraseña", + "forgotYourPass": "¿Has olvidado tu contraseña?", + "needAnAccount": "¿Necesitas una cuenta?", + "register": "Regístrate", + "signUp": "Registro", + "confirmPassword": "Confirmar Contraseña", + "username": "Nombre", + "alreadyHaveAnAccount": "¿Ya tienes una cuenta?", + "recoverPassword": "Recuperar contraseña", + "emailInUse": "Este e-mail se encuentra en uso", + "emailORusernameInUse": "Este e-mail ó nombre de usuario se encuentran en uso", + "requiredField": "Este campo es requerido", + "passwordsDontMatch": "Las contraseñas no coinciden", + "specialCharacters": "Los nombres pueden contener letras, números y _", + "checkTheForm": "Formulario Inválido. Por favor, completa los campos" + }, + "createdisplay": { + "writeyourusername": "Edita tu nombre", + "yourusername": "Tu nombre aparece en cada comentario que publiques.", + "ifyoudontchangeyourname": "Si no modificas tu nombre de usuario en este paso, tu nombre de Facebook aparecera al lado de cada comentario que publiques.", + "username": "Nombre", + "continue": "Continuar con nombre de Facebook", + "save": "Guardar", + "fakecommentdate": "hace un minuto", + "fakecommentbody": "Este es un comentario de ejemplo. Las lectoras pueden compartir sus ideas y opiniones con los periodistas en la sección de comentarios.", + "requiredField": "Campo necesario", + "errorCreate": "Hubo un error al cambiar el nombre de usuario", + "checkTheForm": "Formulario Inválido. Por favor, verifica los campos", + "specialCharacters": "Sólo pueden contener letras, números y _" + }, + "permalink": { + "permalink": "Enlace" + }, + "report": "Marcar", + "like": "Me gusta" + } +} diff --git a/plugins/coral-plugin-auth/index.js b/plugins/coral-plugin-auth/index.js new file mode 100644 index 000000000..85dfb349b --- /dev/null +++ b/plugins/coral-plugin-auth/index.js @@ -0,0 +1,2 @@ +module.exports = {}; + diff --git a/plugins/coral-plugin-like/client/components/LikeButton.js b/plugins/coral-plugin-like/client/components/LikeButton.js index b91ffd42a..0a88d35a4 100644 --- a/plugins/coral-plugin-like/client/components/LikeButton.js +++ b/plugins/coral-plugin-like/client/components/LikeButton.js @@ -1,19 +1,18 @@ -import React, { Component } from 'react'; +import React, {Component} from 'react'; import styles from './style.css'; -import Icon from './Icon'; -import { I18n } from 'coral-framework'; +import {I18n} from 'coral-framework'; import cn from 'classnames'; import translations from '../translations.json'; -import { getMyActionSummary, getTotalActionCount } from 'coral-framework/utils'; +import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils'; const lang = new I18n(translations); const name = 'coral-plugin-like'; class LikeButton extends Component { handleClick = () => { - const { postLike, showSignInDialog, deleteAction } = this.props; - const { root: { me }, comment } = this.props; + const {postLike, showSignInDialog, deleteAction} = this.props; + const {root: {me}, comment} = this.props; const myLikeActionSummary = getMyActionSummary( 'LikeActionSummary', @@ -42,7 +41,7 @@ class LikeButton extends Component { }; render() { - const { comment } = this.props; + const {comment} = this.props; if (!comment) { return null; @@ -56,7 +55,7 @@ class LikeButton extends Component {