diff --git a/.babelrc b/.babelrc index 41d27bf34..6186cefaf 100644 --- a/.babelrc +++ b/.babelrc @@ -5,7 +5,6 @@ ], "plugins": [ "add-module-exports", - "transform-async-to-generator", "transform-class-properties", "transform-decorators-legacy", "transform-object-assign", diff --git a/.eslintrc.json b/.eslintrc.json index 34b46b83b..293657679 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,9 @@ "node": true }, "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 2017 + }, "rules": { "indent": ["error", 2 diff --git a/Dockerfile b/Dockerfile index 14d606c0a..fc0b3aca2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,4 @@ -FROM node:7 - -# Install yarn -RUN npm install -g yarn +FROM node:7.6 # Create app directory RUN mkdir -p /usr/src/app diff --git a/README.md b/README.md index f47fbd2f6..64fe2739c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # Talk [![CircleCI](https://circleci.com/gh/coralproject/talk.svg?style=svg)](https://circleci.com/gh/coralproject/talk) -A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html) +A commenting platform from [The Coral Project](https://coralproject.net). Talk enters a closed beta in March 2017, but you can download the code for our alpha here. [Read more about Talk here.](https://coralproject.net/products/talk.html) + +Third party licenses are available via the `/client/3rdpartylicenses.txt` +endpoint when the server is running with built assets. ## Contributing to Talk diff --git a/app.js b/app.js index c2601ad5a..c3f97f831 100644 --- a/app.js +++ b/app.js @@ -45,16 +45,15 @@ const session_opts = { secret: process.env.TALK_SESSION_SECRET, httpOnly: true, rolling: true, - saveUninitialized: false, - resave: false, + saveUninitialized: true, + resave: true, unset: 'destroy', name: 'talk.sid', cookie: { secure: false, - maxAge: 36000000, // 1 hour for expiry. + maxAge: 8.64e+7, // 24 hours for session token expiry }, store: new RedisStore({ - ttl: 1800, client: redis.createClient(), }) }; diff --git a/circle.yml b/circle.yml index b29dfcc4c..2357ecf4c 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: node: - version: 7 + version: 7.6 services: - docker - redis diff --git a/client/.babelrc b/client/.babelrc new file mode 100644 index 000000000..27289288c --- /dev/null +++ b/client/.babelrc @@ -0,0 +1,6 @@ +{ + "extends": "../.babelrc", + "plugins": [ + "transform-async-to-generator", + ] +} diff --git a/client/.eslintrc.json b/client/.eslintrc.json index a935eaec6..1144f985d 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -18,6 +18,7 @@ ], "rules": { "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error" + "react/jsx-uses-vars": "error", + "no-console": ["warn", { "allow": ["warn", "error"] }] } } diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 194c2351f..af748ab7b 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -18,8 +18,8 @@ const routes = (
- - + + diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index c2f3a8056..389e8b4d2 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -1,5 +1,29 @@ import * as actions from '../constants/auth'; -import coralApi from '../../../coral-framework/helpers/response'; +import coralApi from 'coral-framework/helpers/response'; + +// Log In. +export const handleLogin = (email, password) => dispatch => { + dispatch({type: actions.LOGIN_REQUEST}); + return coralApi('/auth/local', {method: 'POST', body: {email, password}}) + .then(result => { + const isAdmin = !!result.user.roles.filter(i => i === 'ADMIN').length; + dispatch(checkLoginSuccess(result.user, isAdmin)); + }) + .catch(error => { + dispatch({type: actions.LOGIN_FAILURE, message: error.translation_key}); + }); +}; + +const forgotPassowordRequest = () => ({type: actions.FETCH_FORGOT_PASSWORD_REQUEST}); +const forgotPassowordSuccess = () => ({type: actions.FETCH_FORGOT_PASSWORD_SUCCESS}); +const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILURE}); + +export const requestPasswordReset = email => dispatch => { + dispatch(forgotPassowordRequest(email)); + return coralApi('/account/password/reset', {method: 'POST', body: {email}}) + .then(() => dispatch(forgotPassowordSuccess())) + .catch(error => dispatch(forgotPassowordFailure(error))); +}; // Check Login @@ -28,7 +52,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE}); export const logout = () => dispatch => { dispatch(logOutRequest()); - coralApi('/auth', {method: 'DELETE'}) + return coralApi('/auth', {method: 'DELETE'}) .then(() => dispatch(logOutSuccess())) .catch(error => dispatch(logOutFailure(error))); }; diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index c1d05d253..ceecf16c9 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -20,13 +20,17 @@ const validation = (formData, dispatch, next) => { return dispatch(hasError()); } + const validKeys = Object.keys(formData) + .filter(name => name !== 'domains'); + // Required Validation - const empty = Object.keys(formData).filter(name => { + const empty = validKeys + .filter(name => { const cond = !formData[name].length; if (cond) { - // Adding Error + // Adding Error dispatch(addError(name, 'This field is required.')); } else { dispatch(addError(name, '')); @@ -40,18 +44,19 @@ const validation = (formData, dispatch, next) => { } // RegExp Validation - const validation = Object.keys(formData).filter(name => { - const cond = !validate[name](formData[name]); - if (cond) { + const validation = validKeys + .filter(name => { + const cond = !validate[name](formData[name]); + if (cond) { // Adding Error - dispatch(addError(name, errorMsj[name])); - } else { - dispatch(addError(name, '')); - } + dispatch(addError(name, errorMsj[name])); + } else { + dispatch(addError(name, '')); + } - return cond; - }); + return cond; + }); if (validation.length) { return dispatch(hasError()); @@ -71,23 +76,27 @@ export const submitSettings = () => (dispatch, getState) => { export const submitUser = () => (dispatch, getState) => { const userFormData = getState().install.toJS().data.user; validation(userFormData, dispatch, function() { - const data = getState().install.toJS().data; - dispatch(installRequest()); - coralApi('/setup', {method: 'POST', body: data}) - .then(result => { - console.log(result); - dispatch(installSuccess()); - dispatch(nextStep()); - }) - .catch(error => { - console.error(error); - dispatch(installFailure(`${error.translation_key}`)); - }); + dispatch(nextStep()); }); }; +export const finishInstall = () => (dispatch, getState) => { + const data = getState().install.toJS().data; + dispatch(installRequest()); + return coralApi('/setup', {method: 'POST', body: data}) + .then(() => { + dispatch(installSuccess()); + dispatch(nextStep()); + }) + .catch(error => { + console.error(error); + dispatch(installFailure(`${error.translation_key}`)); + }); +}; + export const updateSettingsFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_SETTINGS, name, value}); export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_USER, name, value}); +export const updatePermittedDomains = (value) => ({type: actions.UPDATE_PERMITTED_DOMAINS_SETTINGS, value}); const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST}); const checkInstallSuccess = installed => ({type: actions.CHECK_INSTALL_SUCCESS, installed}); diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 1d1d4d9d0..3bd96abad 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,21 +1,16 @@ import React from 'react'; import styles from './ModerationList.css'; -import BanUserButton from './BanUserButton'; -import {FabButton} from 'coral-ui'; +import {Button} from 'coral-ui'; import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap'; -const ActionButton = ({type = '', user, ...props}) => { - if (type === 'BAN') { - return props.showBanUserDialog(props.user, props.id)} />; - } - +const ActionButton = ({type = '', ...props}) => { return ( - + >{menuActionsMap[type].text} ); }; diff --git a/client/coral-admin/src/components/AdminLogin.js b/client/coral-admin/src/components/AdminLogin.js new file mode 100644 index 000000000..7c7d4b00f --- /dev/null +++ b/client/coral-admin/src/components/AdminLogin.js @@ -0,0 +1,92 @@ +import React, {PropTypes} from 'react'; +import Layout from 'coral-admin/src/components/ui/Layout'; +import styles from './NotFound.css'; +import {Button, TextField, Alert, Success} from 'coral-ui'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +class AdminLogin extends React.Component { + + constructor (props) { + super(props); + this.state = {email: '', password: '', requestPassword: false}; + } + + handleSignIn = e => { + e.preventDefault(); + this.props.handleLogin(this.state.email, this.state.password); + } + + handleRequestPassword = e => { + e.preventDefault(); + this.props.requestPasswordReset(this.state.email); + } + + render () { + const {errorMessage} = this.props; + const signInForm = ( +
+ {errorMessage && {lang.t(`errors.${errorMessage}`)}} + this.setState({email: e.target.value})} /> + this.setState({password: e.target.value})} + type='password' /> +
+ +

+ Forgot your password? { + e.preventDefault(); + this.setState({requestPassword: true}); + }}>Request a new one. +

+ + ); + const requestPasswordForm = ( + this.props.passwordRequestSuccess + ?

{ + location.href = location.href; + }}> + {this.props.passwordRequestSuccess} Sign in + +

+ :
+ this.setState({email: e.target.value})} /> + + + ); + return ( + +
+

Team sign in

+

Sign in to interact with your community.

+ { this.state.requestPassword ? requestPasswordForm : signInForm } +
+
+ ); + } +} + +AdminLogin.propTypes = { + handleLogin: PropTypes.func.isRequired, + passwordRequestSuccess: PropTypes.string, + loginError: PropTypes.string +}; + +export default AdminLogin; diff --git a/client/coral-admin/src/components/BanUserButton.css b/client/coral-admin/src/components/BanUserButton.css index 79b805c30..6e8e50f71 100644 --- a/client/coral-admin/src/components/BanUserButton.css +++ b/client/coral-admin/src/components/BanUserButton.css @@ -1,10 +1,11 @@ .banButton { - width: 114px; - letter-spacing: 1px; + -webkit-transform: scale(.8); + transform: scale(.8); + margin: 0; i { vertical-align: middle; - margin-right: 10px; + margin-right: 5px; font-size: 14px; } } diff --git a/client/coral-admin/src/components/BanUserButton.js b/client/coral-admin/src/components/BanUserButton.js index 191164ca5..a54cdd322 100644 --- a/client/coral-admin/src/components/BanUserButton.js +++ b/client/coral-admin/src/components/BanUserButton.js @@ -8,7 +8,7 @@ const lang = new I18n(translations); const BanUserButton = ({user, ...props}) => (
-