diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66ae8ade8..72c75ce27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,9 +1,10 @@ # Contributor's Guide -Welcome! We are very excited that you are interested in contributing to Talk. +Welcome! We are very excited that you are interested in contributing to Talk. This document is a companion to help you approach contributing. If it does not do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)! +By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html). ## Product Roadmap @@ -13,7 +14,7 @@ You can view product ideas and our longer term roadmap here https://trello.com/b ## Contribute to the documentation -Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally. +Clear docs are a prerequisite for a successful open source project. We value non-code and code contributions equally. We are looking for _documentarians_ to: @@ -40,7 +41,7 @@ Talk is designed to integrate into existing environments in a variety of ways: If you're considering deploying Talk, [please let us know](https://github.com/coralproject/talk/wiki/Contact-Us)! We are quite literally doing this for you and want to help you succeed any way we can. -If you are writing custom integration code in your fork of Talk, please consider keeping it generic and filing a Pull Request to contribute it back to the project! See our [forking and merging guidelines](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging) for more info. +If you are writing custom integration code in your fork of Talk, please consider keeping it generic and filing a Pull Request to contribute it back to the project! See our [forking and merging guidelines](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging) for more info. ## Write some code @@ -48,9 +49,9 @@ First, [set up a dev environment](https://github.com/coralproject/talk/blob/mast ### Build a New Feature / Plugin -Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement. +Talk is beginning life as a Commenting Platform, but is architected to support many varieties of community engagement. -Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early and often if you'd like to help. We would love to hear your ideas for features and plugins and help you find a way to productively engage the project. +Please [contact us](https://github.com/coralproject/talk/wiki/Contact-Us) early and often if you'd like to help. We would love to hear your ideas for features and plugins and help you find a way to productively engage the project. To get an idea of where the Coral Team is going, see: @@ -66,13 +67,10 @@ Examples: ### Work on the Core -There is always more work to be done to make an application more stable, scaleable and secure. +There is always more work to be done to make an application more stable, scaleable and secure. If you see issues in the code or have ideas on how we may improve Talk, please consider: * [contributing a fix](https://github.com/coralproject/talk/wiki/Forking,-Branching-and-Merging), * [filing an issue](https://github.com/coralproject/talk/issues), or * or otherwise [letting us know](https://github.com/coralproject/talk/wiki/Contact-Us). - - - diff --git a/INSTALL.md b/INSTALL.md index f5f82f294..008c4cba3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,95 +1,94 @@ -# Installing a dev environment - -By contributing to this project you agree to the [Code of Conduct](https://coralproject.net/code-of-conduct.html). +# Installation ## Requirements ### System -- Any flavor of Linux, OSX or Windows +- Any flavour of Linux, OSX or Windows - 1GB memory (minimum) - 5GB storage (minimum) -### Software +## Installation From Source -* [Node](https://nodejs.org/es/download/package-manager) v7 or later -* Mongo v3.2 or later -* Redis v3.2 or later +### Requirements + +There are some runtime requirements for running Talk from source: + +- [Node](https://nodejs.org/) v7 or later +- [MongoDB](https://www.mongodb.com/) v3.2 or later +- [Redis](https://redis.io/) v3.2 or later +- [Yarn](https://yarnpkg.com/) v0.19.1 or later _Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_ -## First time setup +### Installing -### Installation +```bash +# Download the tarball containing the repository +curl -L https://github.com/coralproject/talk/tarball/master -o coralproject-talk.tar.gz -Navigate to a directory. +# Untar that file and change to that directory +tar xpf coralproject-talk.tar.gz +mv coralproject-talk-* coralproject-talk +cd coralproject-talk -``` -git clone https://github.com/coralproject/talk -cd talk -yarn install -``` +# Install package dependancies +yarn -### Environmental Variables - -Talk uses environmental variables for configuration. You can learn about them in the [README file](README.md). - - -## Workflows - -### The server - -Starting the server: - -``` -yarn start -``` - -Browse to `http://localhost:3000` (or your custom port.) - -### Building the front end - -Our build process will build all front end components registered [here](https://github.com/coralproject/talk/blob/6052cac1d3494f8060325a88bb2ce03c88c2f94c/webpack.config.dev.js#L9-L15). - -One time build: - -``` +# Build static files yarn build ``` -Build, then rebuild when a file is updated (development build): +### Running -``` -yarn build-watch +Refer to the `README.md` file for required configuration variables to add to the +environment. + +You can start the server after configuring the server using the command: + +```bash +yarn start ``` +You can see other scripts we've made available by consulting the `package.json` +file under the `scripts` key including: -### Testing +- `yarn test` run unit tests +- `yarn e2e` run end to end tests +- `yarn build-watch` watch for changes to client files and build static assets +- `yarn dev-start` watch for changes to server files and reload the server -Run all tests once: +## Installation From Docker Hub -` -yarn test -` +### Requirements -Run our end to end tests (will install Selenium and nightwatch): +There are some runtime requirements for running Talk for Docker: -` -yarn e2e -` +- [MongoDB](https://www.mongodb.com/) v3.2 or later +- [Redis](https://redis.io/) v3.2 or later +- [Docker](https://www.docker.com/) v1.13.0 or later +- [Docker Compose](https://docs.docker.com/compose/) v1.10.0 or later -_Please ensure all tests are passing before submitting a PR!_ +_Please be sure to check the versions of these requirements. Insufficient versions of these may lead to unexpected errors!_ -## Troubleshooting +### Installing +```bash +# Create a directory for talk +mkdir coralproject-talk +cd coralproject-talk -##### Can't ping the redis server! +# Download the docker-compose.yml file from the repository +curl -LO https://raw.githubusercontent.com/coralproject/talk/master/docker-compose.yml +``` -- Check that Redis Server is running. -- Check that TALK_REDIS_URL is set. - -##### Authenticaiton doesn't work! - -- Make sure Redis is the correct version. +At this stage, you should refer to the `README.md` file for required +configuration variables to add to the environment key for the `talk` service +listed in the `docker-compose.yml` file. +### Running +```bash +# Start the services using compose +docker-compose up -d +``` diff --git a/README.md b/README.md index 274fd7f3d..f47fbd2f6 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ See our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CO To set up a development environment or build from source, see [INSTALL.md](https://github.com/coralproject/talk/blob/master/INSTALL.md). -To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly!! +To launch a Talk server of your own from your browser without any need to muck about in a terminal or think about engineering concepts, stay tuned. We will launch [our installer](https://github.com/coralproject/talk-install) shortly! ### Configuration @@ -37,6 +37,7 @@ Facebook Login enabled app. - `TALK_SMTP_PASSWORD` (*required for email*) - password for the SMTP provider you are using. - `TALK_SMTP_HOST` (*required for email*) - SMTP host url with format `smtp.domain.com`. - `TALK_SMTP_PORT` (*required for email*) - SMTP port. +- `TALK_INSTALL_LOCK` (_optional for dynamic setup_) - Defaults to `FALSE`. When `TRUE`, disables the dynamic setup endpoint. Refer to the wiki page on [Configuration Loading](https://github.com/coralproject/talk/wiki/Configuration-Loading) for alternative methods of loading configuration during development. diff --git a/bin/cli b/bin/cli index 5b52d96b9..c0690e455 100755 --- a/bin/cli +++ b/bin/cli @@ -4,7 +4,7 @@ * Module dependencies. */ -const util = require('../util'); +// const util = require('./util'); const program = require('./commander'); program @@ -15,18 +15,14 @@ program .command('users', 'work with the application auth') .parse(process.argv); -// If there is no command listed, output help. -if (!process.argv.slice(2).length) { - program.outputHelp(); - return; -} - -// The ensures that the child process that is created here is always cleaned up -// properly when the parent process dies. -util.onshutdown([ - (signal) => { - if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) { - program.runningCommand.kill(signal); - } +/** + * When this provess exists, check to see if we have a running command, if we do + * check to see if it is still running. If it is, then kill it with a SIGINT + * signal. This is for the use case where we want to kill the process that is + * labled with the PID written out by the parent process. + */ +process.once('exit', () => { + if ((program.runningCommand.killed === false) && (program.runningCommand.exitCode === null)) { + program.runningCommand.kill('SIGINT'); } -]); +}); diff --git a/bin/cli-assets b/bin/cli-assets index 76ca5735b..166152baf 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -10,7 +10,7 @@ const Table = require('cli-table'); const AssetModel = require('../models/asset'); const mongoose = require('../services/mongoose'); const scraper = require('../services/scraper'); -const util = require('../util'); +const util = require('./util'); // Register the shutdown criteria. util.onshutdown([ diff --git a/bin/cli-jobs b/bin/cli-jobs index 4d348fabb..f22e1068b 100755 --- a/bin/cli-jobs +++ b/bin/cli-jobs @@ -7,7 +7,7 @@ const program = require('./commander'); const scraper = require('../services/scraper'); const mailer = require('../services/mailer'); -const util = require('../util'); +const util = require('./util'); const mongoose = require('../services/mongoose'); const kue = require('../services/kue'); diff --git a/bin/cli-serve b/bin/cli-serve index 0427dcf34..08b451904 100755 --- a/bin/cli-serve +++ b/bin/cli-serve @@ -2,13 +2,12 @@ const app = require('../app'); const program = require('./commander'); -const debug = require('debug')('talk:server'); const http = require('http'); const scraper = require('../services/scraper'); const mailer = require('../services/mailer'); const kue = require('../services/kue'); const mongoose = require('../services/mongoose'); -const util = require('../util'); +const util = require('./util'); /** * Get port from environment and store in Express. @@ -79,7 +78,7 @@ function onListening() { let bind = typeof addr === 'string' ? `pipe ${ addr}` : `port ${ addr.port}`; - debug(`Listening on ${ bind}`); + console.log(`Listening on ${ bind}`); } /** diff --git a/bin/cli-setup b/bin/cli-setup index eaa2b90b1..185b04f01 100755 --- a/bin/cli-setup +++ b/bin/cli-setup @@ -9,7 +9,10 @@ const inquirer = require('inquirer'); const mongoose = require('../services/mongoose'); const SettingModel = require('../models/setting'); const SettingsService = require('../services/settings'); -const util = require('../util'); +const SetupService = require('../services/setup'); +const UsersService = require('../services/users'); +const util = require('./util'); +const errors = require('../errors'); // Register the shutdown criteria. util.onshutdown([ @@ -29,60 +32,165 @@ program // Setup the application //============================================================================== -SettingsService - .init() - .then((settings) => { - if (program.defaults) { - return settings.save(); - } +const performSetup = () => { - console.log('We\'ll ask you some questions in order to setup your installation of Talk.\n'); - - return inquirer.prompt([ - { - type: 'input', - name: 'organizationName', - message: 'Organization Name', - default: settings.organizationName, - validate: (input) => { - if (input && input.length > 0) { - return true; - } - - return 'Organization Name is required.'; - } - }, - { - type: 'list', - choices: SettingModel.MODERATION_OPTIONS, - name: 'moderation', - default: settings.moderation, - message: 'Select a moderation mode' - }, - { - type: 'confirm', - name: 'requireEmailConfirmation', - default: settings.requireEmailConfirmation, - message: 'Should emails always be confirmed' - } - ]) - .then((answers) => { - - // Update the settings that were changed. - Object.keys(answers).forEach((key) => { - if (answers[key] !== undefined) { - settings[key] = answers[key]; - } + if (program.defaults) { + return SettingsService + .init() + .then(() => { + console.log('Settings created.'); + console.log('\nTalk is now installed!'); + util.shutdown(); + }) + .catch((err) => { + console.error(err); + util.shutdown(1); }); + } - return settings.save(); + // Get the current settings, we are expecing an error here. + return SettingsService + .retrieve() + .then(() => { + + // We should NOT have gotten a settings object, this means that the + // application is already setup. Error out here. + throw errors.ErrSettingsInit; + + }) + .catch((err) => { + + // If the error is `not init`, then we're good, otherwise, it's something + // else. + if (err !== errors.ErrSettingsNotInit) { + throw err; + } + + }) + .then(() => { + + // Create the base settings model. + let settings = new SettingModel(); + + console.log('We\'ll ask you some questions in order to setup your installation of Talk.\n'); + + return inquirer.prompt([ + { + type: 'input', + name: 'organizationName', + message: 'Organization Name', + default: settings.organizationName, + validate: (input) => { + if (input && input.length > 0) { + return true; + } + + return 'Organization Name is required.'; + } + }, + { + type: 'list', + choices: SettingModel.MODERATION_OPTIONS, + name: 'moderation', + default: settings.moderation, + message: 'Select a moderation mode' + }, + { + type: 'confirm', + name: 'requireEmailConfirmation', + default: settings.requireEmailConfirmation, + message: 'Should emails always be confirmed' + } + ]) + .then((answers) => { + + // Update the settings that were changed. + Object.keys(answers).forEach((key) => { + if (answers[key] !== undefined) { + settings[key] = answers[key]; + } + }); + + console.log('\nWe\'ll ask you some questions about your first admin user.\n'); + + return inquirer.prompt([ + { + type: 'input', + name: 'displayName', + message: 'Display Name', + filter: (displayName) => { + return UsersService + .isValidDisplayName(displayName, false) + .catch((err) => { + throw err.message; + }); + } + }, + { + name: 'email', + message: 'Email', + format: 'email', + validate: (value) => { + if (value && value.length >= 3) { + return true; + } + + return 'Email is required'; + } + }, + { + name: 'password', + message: 'Password', + type: 'password', + filter: (password) => { + return UsersService + .isValidPassword(password) + .catch((err) => { + throw err.message; + }); + } + }, + { + name: 'confirmPassword', + message: 'Confirm Password', + type: 'password', + filter: (confirmPassword) => { + return UsersService + .isValidPassword(confirmPassword) + .catch((err) => { + throw err.message; + }); + } + }, + ]); + }) + .then((user) => { + + if (user.password !== user.confirmPassword) { + return Promise.reject(new Error('Passwords do not match')); + } + + return SetupService.setup({ + settings: settings.toObject(), + user: { + email: user.email, + displayName: user.displayName, + password: user.password + } + }); + }); + }) + .then(({user}) => { + console.log('Settings created.'); + console.log(`User ${user.id} created.`); + console.log('\nTalk is now installed!'); + util.shutdown(); + }) + .catch((err) => { + console.error(err); + util.shutdown(1); }); - }) - .then(() => { - console.log('Talk is now installed!'); - util.shutdown(); - }) - .catch((err) => { - console.error(err); - util.shutdown(1); - }); +}; + +// Start tthe setup process. +performSetup(); diff --git a/bin/cli-users b/bin/cli-users index 072a143ee..01f46ad2e 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -9,7 +9,7 @@ const inquirer = require('inquirer'); const UsersService = require('../services/users'); const UserModel = require('../models/user'); const mongoose = require('../services/mongoose'); -const util = require('../util'); +const util = require('./util'); const Table = require('cli-table'); const validateRequired = (msg = 'Field is required', len = 1) => (input) => { diff --git a/bin/commander.js b/bin/commander.js index 2a3d1e363..ad346a911 100644 --- a/bin/commander.js +++ b/bin/commander.js @@ -2,7 +2,6 @@ const pkg = require('../package.json'); const dotenv = require('dotenv'); const fs = require('fs'); const program = require('commander'); -const util = require('../util'); // Perform rewrites to the runtime environment variables based on the contents // of the process.env.REWRITE_ENV if it exists. This is done here as it is the @@ -27,6 +26,10 @@ const parseArgs = require('minimist')(process.argv.slice(2), { } }); +/** + * If the config flag is present, then we have to load the configuration from + * the file specified. We will then load those values into the environment. + */ if (parseArgs.config) { let envConfig = dotenv.parse(fs.readFileSync(parseArgs.config, {encoding: 'utf8'})); @@ -35,8 +38,15 @@ if (parseArgs.config) { }); } -// If the `--pid` flag is used, put the current pid there. +/** + * If the pid flag is present, then we have to create a pid file at the location + * specified. + */ if (parseArgs.pid) { + const util = require('./util'); + + console.log('Wrote PID'); + util.pid(parseArgs.pid); } diff --git a/util.js b/bin/util.js similarity index 100% rename from util.js rename to bin/util.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 9c0f271cc..9721123fa 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,21 +1,25 @@ import React from 'react'; import {Router, Route, IndexRoute, browserHistory} from 'react-router'; -import ModerationContainer from 'containers/ModerationQueue/ModerationContainer'; -import CommentStream from 'containers/CommentStream/CommentStream'; -import Configure from 'containers/Configure/Configure'; import Streams from 'containers/Streams/Streams'; -import CommunityContainer from 'containers/Community/CommunityContainer'; +import Configure from 'containers/Configure/Configure'; import LayoutContainer from 'containers/LayoutContainer'; +import CommentStream from 'containers/CommentStream/CommentStream'; +import InstallContainer from 'containers/Install/InstallContainer'; +import CommunityContainer from 'containers/Community/CommunityContainer'; +import ModerationContainer from 'containers/ModerationQueue/ModerationContainer'; const routes = ( - - - - - - - +
+ + + + + + + + +
); const AppRouter = () => ; diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index 3c1437de7..ac7af12fd 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -15,7 +15,7 @@ export const fetchModerationQueueComments = () => { return Promise.all([ coralApi('/queue/comments/premod'), - coralApi('/queue/users/pending'), + coralApi('/queue/users/flagged'), coralApi('/queue/comments/rejected'), coralApi('/queue/comments/flagged') ]) @@ -46,7 +46,7 @@ export const fetchPendingUsersQueue = () => { return dispatch => { dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST}); - return coralApi('/queue/users/pending') + return coralApi('/queue/users/flagged') .then(addUsersCommentsActions.bind(this, dispatch)); }; }; diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js new file mode 100644 index 000000000..a74a1929f --- /dev/null +++ b/client/coral-admin/src/actions/install.js @@ -0,0 +1,109 @@ +import coralApi from 'coral-framework/helpers/response'; +import * as actions from '../constants/install'; +import validate from 'coral-framework/helpers/validate'; +import errorMsj from 'coral-framework/helpers/error'; + +export const nextStep = () => ({type: actions.NEXT_STEP}); +export const previousStep = () => ({type: actions.PREVIOUS_STEP}); +export const goToStep = step => ({type: actions.GO_TO_STEP, step}); + +const installRequest = () => ({type: actions.INSTALL_REQUEST}); +const installSuccess = () => ({type: actions.INSTALL_SUCCESS}); +const installFailure = error => ({type: actions.INSTALL_FAILURE, error}); + +const addError = (name, error) => ({type: actions.ADD_ERROR, name, error}); +const hasError = error => ({type: actions.HAS_ERROR, error}); +const clearErrors = () => ({type: actions.CLEAR_ERRORS}); + +const validation = (formData, dispatch, next) => { + if (!(formData != null)) { + return dispatch(hasError()); + } + + // Required Validation + const empty = Object.keys(formData).filter(name => { + const cond = !formData[name].length; + + if (cond) { + + // Adding Error + dispatch(addError(name, 'This field is required.')); + } else { + dispatch(addError(name, '')); + } + + return cond; + }); + + if (empty.length) { + return dispatch(hasError()); + } + + // RegExp Validation + const validation = Object.keys(formData).filter(name => { + const cond = !validate[name](formData[name]); + if (cond) { + + // Adding Error + dispatch(addError(name, errorMsj[name])); + } else { + dispatch(addError(name, '')); + } + + return cond; + }); + + if (validation.length) { + return dispatch(hasError()); + } + + dispatch(clearErrors()); + next(); +}; + +export const submitSettings = () => (dispatch, getState) => { + const settingsFormData = getState().install.toJS().data.settings; + validation(settingsFormData, dispatch, function() { + dispatch(nextStep()); + }); +}; + +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.message}`)); + }); + }); +}; + +export const updateSettingsFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_SETTINGS, name, value}); +export const updateUserFormData = (name, value) => ({type: actions.UPDATE_FORMDATA_USER, name, value}); + +const checkInstallRequest = () => ({type: actions.CHECK_INSTALL_REQUEST}); +const checkInstallSuccess = installed => ({type: actions.CHECK_INSTALL_SUCCESS, installed}); +const checkInstallFailure = error => ({type: actions.CHECK_INSTALL_FAILURE, error}); + +export const checkInstall = next => dispatch => { + dispatch(checkInstallRequest()); + coralApi('/setup') + .then(({installed}) => { + dispatch(checkInstallSuccess(installed)); + if (installed) { + next(); + } + }) + .catch(error => { + console.error(error); + dispatch(checkInstallFailure(`${error.message}`)); + }); +}; diff --git a/client/coral-admin/src/actions/settings.js b/client/coral-admin/src/actions/settings.js index 85ad5149e..32f56e111 100644 --- a/client/coral-admin/src/actions/settings.js +++ b/client/coral-admin/src/actions/settings.js @@ -11,6 +11,7 @@ export const SAVE_SETTINGS_SUCCESS = 'SAVE_SETTINGS_SUCCESS'; export const SAVE_SETTINGS_FAILED = 'SAVE_SETTINGS_FAILED'; export const WORDLIST_UPDATED = 'WORDLIST_UPDATED'; +export const DOMAINLIST_UPDATED = 'DOMAINLIST_UPDATED'; export const fetchSettings = () => dispatch => { dispatch({type: SETTINGS_LOADING}); @@ -33,6 +34,10 @@ export const updateWordlist = (listName, list) => { return {type: WORDLIST_UPDATED, listName, list}; }; +export const updateDomainlist = (listName, list) => { + return {type: DOMAINLIST_UPDATED, listName, list}; +}; + export const saveSettingsToServer = () => (dispatch, getState) => { let settings = getState().settings.toJS().settings; if (settings.charCount) { diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 44f0325d9..954d89820 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -21,3 +21,11 @@ export const sendNotificationEmail = (userId, subject, body) => { .catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error})); }; }; + +// let a user edit their username +export const enableUsernameEdit = (userId) => { + return (dispatch) => { + return coralApi(`/users/${userId}/username-enable`, {method: 'POST'}) + .catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error})); + }; +}; diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js index c1df40d31..47e91e551 100644 --- a/client/coral-admin/src/components/ModerationList.js +++ b/client/coral-admin/src/components/ModerationList.js @@ -3,7 +3,7 @@ import styles from './ModerationList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from './Comment'; -import UserAction from './UserAction'; +import User from './User'; import SuspendUserModal from './SuspendUserModal'; // Each action has different meaning and configuration @@ -138,7 +138,7 @@ export default class ModerationList extends React.Component { if (menuOption === 'REJECTED') { this.setState({suspendUserModal: action}); } else if (menuOption === 'ACCEPTED') { - this.props.userStatusUpdate('ACTIVE', action.item_id); + this.props.userStatusUpdate('APPROVED', action.item_id); } } } @@ -177,7 +177,7 @@ export default class ModerationList extends React.Component { // If the item is an action... const user = users[item.item_id]; - modItem = user && { +const User = props => { const {action, user} = props; let userStatus = user.status; - const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : []; // Do not display unless the user status is 'pending' or 'banned'. // This means that they have already been reviewed and approved. @@ -27,8 +21,6 @@ const UserAction = props => { {user.displayName}
- {links ? - Contains Link : null}
{props.modActions.map( (action, i) => @@ -48,32 +40,12 @@ const UserAction = props => { {lang.t('comment.banned_user')} : null}
- { - user.settings.bio && -
-
-
{lang.t('user.user_bio')}:
- - - - - -
-
- }
{`${action.count} ${action.action_type === 'flag_bio' ? lang.t('user.bio_flags') : lang.t('user.username_flags')}`}
; }; -export default UserAction; - -const linkStyles = { - backgroundColor: 'rgb(255, 219, 135)', - padding: '1px 2px' -}; +export default User; const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 4e2b391c8..3e6598d6e 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -6,34 +6,49 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; import {Logo} from './Logo'; -export default ({handleLogout}) => ( +export default ({handleLogout, restricted = false}) => (
- - {lang.t('configure.moderate')} - {lang.t('configure.community')} - {lang.t('configure.configure')} + { + !restricted ? +
+ + + {lang.t('configure.moderate')} + + + {lang.t('configure.community')} + + + {lang.t('configure.configure')} + {lang.t('configure.streams')} - -
-
    -
  • -
    - - - Sign Out - -
    -
  • -
  • - {`v${process.env.VERSION}`} -
  • -
-
+ activeClassName={styles.active}> + {lang.t('configure.streams')} + + +
+
    +
  • +
    + + + Sign Out + +
    +
  • +
  • + {`v${process.env.VERSION}`} +
  • +
+
+
+ : + null + }
); diff --git a/client/coral-admin/src/constants/install.js b/client/coral-admin/src/constants/install.js new file mode 100644 index 000000000..b3fd7e0d0 --- /dev/null +++ b/client/coral-admin/src/constants/install.js @@ -0,0 +1,16 @@ +export const NEXT_STEP = 'NEXT_STEP'; +export const GO_TO_STEP = 'GO_TO_STEP'; +export const PREVIOUS_STEP = 'PREVIOUS_STEP'; +export const ADD_ERROR = 'ADD_ERROR'; +export const HAS_ERROR = 'HAS_ERROR'; +export const SHOW_ERRORS = 'SHOW_ERRORS'; +export const CLEAR_ERRORS = 'CLEAR_ERRORS'; +export const INSTALL_REQUEST = 'INSTALL_REQUEST'; +export const INSTALL_SUCCESS = 'INSTALL_SUCCESS'; +export const INSTALL_FAILURE = 'INSTALL_FAILURE'; +export const UPDATE_FORMDATA_USER = 'UPDATE_FORMDATA_USER'; +export const UPDATE_FORMDATA_SETTINGS = 'UPDATE_FORMDATA_SETTINGS'; + +export const CHECK_INSTALL_REQUEST = 'CHECK_INSTALL_REQUEST'; +export const CHECK_INSTALL_SUCCESS = 'CHECK_INSTALL_SUCCESS'; +export const CHECK_INSTALL_FAILURE = 'CHECK_INSTALL_FAILURE'; diff --git a/client/coral-admin/src/constants/users.js b/client/coral-admin/src/constants/users.js index 3cd5e5b4b..9cd7e2093 100644 --- a/client/coral-admin/src/constants/users.js +++ b/client/coral-admin/src/constants/users.js @@ -2,4 +2,5 @@ export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST'; export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS'; export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE'; export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE'; +export const USERNAME_ENABLE_FAILURE = 'USERNAME_ENABLE_FAILURE'; export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS'; diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/containers/Configure/Configure.js index 8ad6cacfd..c9c88eeee 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/containers/Configure/Configure.js @@ -5,6 +5,7 @@ import { updateSettings, saveSettingsToServer, updateWordlist, + updateDomainlist } from '../../actions/settings'; import {Button, List, Item} from 'coral-ui'; @@ -14,6 +15,7 @@ import translations from '../../translations.json'; import EmbedLink from './EmbedLink'; import CommentSettings from './CommentSettings'; import Wordlist from './Wordlist'; +import Domainlist from './Domainlist'; import has from 'lodash/has'; class Configure extends Component { @@ -47,6 +49,11 @@ class Configure extends Component { this.props.dispatch(updateWordlist(listName, list)); } + onChangeDomainlist = (listName, list) => { + this.setState({changed: true}); + this.props.dispatch(updateDomainlist(listName, list)); + } + onSettingUpdate = (setting) => { this.setState({changed: true}); this.props.dispatch(updateSettings(setting)); @@ -73,7 +80,14 @@ class Configure extends Component { errors={this.state.errors} settingsError={this.onSettingError}/>; case 'embed': - return ; + return has(this, 'props.settings.domains.whitelist') + ?
+ + +
+ : ; case 'wordlist': return has(this, 'props.settings.wordlist') ? ( +
+

{lang.t('configure.domain-list-title')}

+ +

{lang.t('configure.domain-list-text')}

+ data.split(',').map(d => d.trim())} + onChange={tags => onChangeDomainlist('whitelist', tags)} + /> +
+
+); + +export default Domainlist; + +const lang = new I18n(translations); diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js new file mode 100644 index 000000000..7a8634180 --- /dev/null +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -0,0 +1,93 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import styles from './style.css'; +import {Wizard, WizardNav} from 'coral-ui'; +import {Layout} from '../../components/ui/Layout'; + +import { + nextStep, + previousStep, + goToStep, + updateUserFormData, + updateSettingsFormData, + submitSettings, + submitUser, + checkInstall +} from '../../actions/install'; + +import InitialStep from './components/Steps/InitialStep'; +import AddOrganizationName from './components/Steps/AddOrganizationName'; +import CreateYourAccount from './components/Steps/CreateYourAccount'; +import FinalStep from './components/Steps/FinalStep'; + +class InstallContainer extends Component { + componentDidMount() { + const {checkInstall} = this.props; + checkInstall(() => { + this.context.router.push('/admin'); + }); + } + + render() { + const {install} = this.props; + + return ( + +
+ { + !install.alreadyInstalled ? ( +
+

Welcome to the Coral Project

+ { install.step !== 0 ? : null } + + + + + + +
+ ) : ( +
Talk is already installed
+ ) + } +
+
+ ); + } +} + +InstallContainer.contextTypes = { + router: React.PropTypes.object +}; + +const mapStateToProps = state => ({ + install: state.install.toJS() +}); + +const mapDispatchToProps = dispatch => ({ + checkInstall: next => dispatch(checkInstall(next)), + nextStep: () => dispatch(nextStep()), + previousStep: () => dispatch(previousStep()), + goToStep: step => dispatch(goToStep(step)), + handleSettingsChange: e => { + const {name, value} = e.currentTarget; + dispatch(updateSettingsFormData(name, value)); + }, + handleUserChange: e => { + const {name, value} = e.currentTarget; + dispatch(updateUserFormData(name, value)); + }, + handleSettingsSubmit: e => { + e.preventDefault(); + dispatch(submitSettings()); + }, + handleUserSubmit: e => { + e.preventDefault(); + dispatch(submitUser()); + } +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(InstallContainer); diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js new file mode 100644 index 000000000..0f85bbad4 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js @@ -0,0 +1,30 @@ +import React from 'react'; +import styles from './style.css'; +import {FormField, Button} from 'coral-ui'; + +const AddOrganizationName = props => { + const {handleSettingsChange, handleSettingsSubmit, install} = props; + return ( +
+

+ Please tell us the name of your organization. This will appear in emails when + inviting new team members +

+
+
+ + + +
+
+ ); +}; + +export default AddOrganizationName; diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js new file mode 100644 index 000000000..91c664c37 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js @@ -0,0 +1,65 @@ +import React from 'react'; +import styles from './style.css'; +import {FormField, Button, Spinner} from 'coral-ui'; + +const InitialStep = props => { + const {handleUserChange, handleUserSubmit, install} = props; + return ( +
+
+
+ + + + + + + + + { + !props.install.isLoading ? + + : + + } + {props.install.installRequest === 'FAILURE' &&
Error: {props.install.installRequestError}
} + +
+
+ ); +}; + +export default InitialStep; diff --git a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js new file mode 100644 index 000000000..e549ebe63 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js @@ -0,0 +1,20 @@ +import React from 'react'; +import styles from './style.css'; +import {Button} from 'coral-ui'; +import {Link} from 'react-router'; + +const InitialStep = () => { + return ( +
+

+ Thanks for installing Talk! We sent an email to verify your email + address. While you finish setting the account, you can start engaging + with your readers now. +

+ + +
+ ); +}; + +export default InitialStep; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js new file mode 100644 index 000000000..9f9770fe9 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js @@ -0,0 +1,19 @@ +import React from 'react'; +import styles from './style.css'; +import {Button} from 'coral-ui'; + +const InitialStep = props => { + const {nextStep} = props; + return ( +
+

+ The remainder of the Talk installation will take about ten minutes. + Once you complete the following two steps, you will have a free + installation and provision of Mongo and Redis. +

+ +
+ ); +}; + +export default InitialStep; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js new file mode 100644 index 000000000..156916ab0 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js @@ -0,0 +1,44 @@ +import React from 'react'; +import styles from './style.css'; +import {Button, Select, Option, FormField} from 'coral-ui'; + +const InviteTeamMembers = props => { + const {nextStep} = props; + return ( +
+

Invite Team Members

+

+ Once registered, new team members will receive an email to Create + their password. +

+
+
+ + + + + +
+ + +
+ + + +
+
+ ); +}; + +export default InviteTeamMembers; diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/containers/Install/components/Steps/style.css new file mode 100644 index 000000000..161c72685 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/style.css @@ -0,0 +1,77 @@ +.step { + padding: 20px 0; + + h3 { + max-width: 450px; + margin: 0 auto; + text-align: left; + font-size: 1.4em; + font-weight: bold; + } + + p { + max-width: 450px; + margin: 0 auto 30px; + font-size: 1.1em; + text-align: left; + } + + > button { + min-width: 145px; + a { + text-decoration: none; + color: inherit; + } + } + + .form { + max-width: 300px; + margin: 30px auto; + text-align: left; + + form > button { + margin: 30px 0; + } + + .formField { + text-align: left; + + label { + text-align: left; + display: block; + margin: 10px 0; + font-weight: 400; + font-size: 1.08em; + } + + > input { + border: solid 1px rgba(0, 0, 0, 0.23); + padding: 10px; + border-radius: 3px; + width: 100%; + box-sizing: border-box; + transition: border-color 0.2s ease; + font-size: 1em; + + &:focus { + border-color: black; + } + } + } + } +} + +.finalStep { + button { + width: 225px; + margin-right: 10px; + } +} + +.error { + background: #FFEBEE; + color: #B71C1C; + padding: 10px; + margin-bottom: 20px; + border-radius: 2px; +} diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css new file mode 100644 index 000000000..62f7ffe04 --- /dev/null +++ b/client/coral-admin/src/containers/Install/style.css @@ -0,0 +1,12 @@ +.Install { + max-width: 900px; + margin: 0 auto; + text-align: center; + padding: 50px 0; + + h2 { + font-size: 2em; + font-weight: 500; + margin: 0; + } +} diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index d1d1afd53..327913927 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -11,7 +11,7 @@ import { fetchFlaggedQueue, fetchModerationQueueComments, } from 'actions/comments'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {userStatusUpdate, sendNotificationEmail, enableUsernameEdit} from 'actions/users'; import {fetchSettings} from 'actions/settings'; import ModerationQueue from './ModerationQueue'; @@ -122,7 +122,8 @@ const mapDispatchToProps = dispatch => { userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { dispatch(fetchModerationQueueComments()); }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) + suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId)) + .then(() => dispatch(enableUsernameEdit(userId))) .then(() => dispatch(sendNotificationEmail(userId, subject, text))) .then(() => dispatch(fetchModerationQueueComments())) , diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 84d1b84ad..306461dd0 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -1,6 +1,15 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import {render} from 'react-dom'; +import {ApolloProvider} from 'react-apollo'; + +import {client} from './services/client'; +import store from './services/store'; + import App from './components/App'; -// Render the application into the DOM -ReactDOM.render(, document.querySelector('#root')); +render( + + + + , document.querySelector('#root') +); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index a19a5a087..f12de96ba 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,19 @@ -import {combineReducers} from 'redux'; +import auth from 'reducers/auth'; +import users from 'reducers/users'; +import assets from 'reducers/assets'; +import actions from 'reducers/actions'; +import install from 'reducers/install'; import comments from 'reducers/comments'; import settings from 'reducers/settings'; import community from 'reducers/community'; -import users from 'reducers/users'; -import auth from 'reducers/auth'; -import actions from 'reducers/actions'; -import assets from 'reducers/assets'; -// Combine all reducers into a main one -export default combineReducers({ +export default { settings, comments, community, auth, actions, assets, - users -}); + users, + install +}; diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js new file mode 100644 index 000000000..a49f76b5e --- /dev/null +++ b/client/coral-admin/src/reducers/install.js @@ -0,0 +1,91 @@ +import {Map} from 'immutable'; + +import * as actions from '../constants/install'; + +const initialState = Map({ + isLoading: false, + data: Map({ + settings: Map({ + organizationName: '' + }), + user: Map({ + displayName: '', + email: '', + password: '', + confirmPassword: '' + }) + }), + errors: Map({ + organizationName: '', + displayName: '', + email: '', + password: '', + confirmPassword: '' + }), + showErrors: false, + hasError: false, + error: null, + step: 0, + navItems: [{ + text: '1. Add Organization Name', + step: 1 + }, + { + text: '2. Create your account', + step: 2 + }], + installRequest: null, + installRequestError: null, + alreadyInstalled: false +}); + +export default function install (state = initialState, action) { + switch (action.type) { + case actions.NEXT_STEP: + return state + .set('step', state.get('step') + 1); + case actions.PREVIOUS_STEP: + return state + .set('step', state.get('step') - 1); + case actions.GO_TO_STEP: + return state + .set('step', action.step); + case actions.UPDATE_FORMDATA_SETTINGS: + return state + .setIn(['data', 'settings', action.name], action.value); + case actions.UPDATE_FORMDATA_USER: + return state + .setIn(['data', 'user', action.name], action.value); + case actions.HAS_ERROR: + return state + .merge({ + hasError: true, + showErrors: true + }); + case actions.ADD_ERROR: + return state + .setIn(['errors', action.name], action.error); + case actions.CLEAR_ERRORS: + return state + .set('errors', Map()); + case actions.INSTALL_REQUEST: + return state + .set('isLoading', true); + case actions.INSTALL_SUCCESS: + return state + .set('isLoading', false) + .set('installRequest', 'SUCCESS'); + case actions.INSTALL_FAILURE: + return state + .merge({ + isLoading: false, + installRequest: 'FAILURE', + installRequestError: action.error + }); + case actions.CHECK_INSTALL_SUCCESS: + return state + .set('alreadyInstalled', action.installed); + default : + return state; + } +} diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js index 12b16d9ad..4f743bc0a 100644 --- a/client/coral-admin/src/reducers/settings.js +++ b/client/coral-admin/src/reducers/settings.js @@ -6,6 +6,9 @@ const initialState = Map({ wordlist: Map({ banned: List(), suspect: List() + }), + domains: Map({ + whitelist: List() }) }), saveSettingsError: null, @@ -24,6 +27,7 @@ export default (state = initialState, action) => { case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action); case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action); case types.WORDLIST_UPDATED: return updateWordlist(state, action); + case types.DOMAINLIST_UPDATED: return updateDomainlist(state, action); default: return state; } }; @@ -40,6 +44,10 @@ const updateWordlist = (state, action) => { return state.setIn(['settings', 'wordlist', action.listName], action.list); }; +const updateDomainlist = (state, action) => { + return state.setIn(['settings', 'domains', action.listName], action.list); +}; + const saveComplete = (state, action) => { const s = state.set('fetchingSettings', false).set('saveSettingsError', null); const settings = s.get('settings').merge(action.settings); diff --git a/client/coral-framework/client.js b/client/coral-admin/src/services/client.js similarity index 100% rename from client/coral-framework/client.js rename to client/coral-admin/src/services/client.js diff --git a/client/coral-admin/src/services/store.js b/client/coral-admin/src/services/store.js index 0700dc579..bf2735e45 100644 --- a/client/coral-admin/src/services/store.js +++ b/client/coral-admin/src/services/store.js @@ -1,17 +1,24 @@ - -import {createStore, applyMiddleware} from 'redux'; +import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from 'reducers'; +import mainReducer from '../reducers'; +import {client} from './client'; -/** - * Create the store by merging the app reducers with - * the talk adapter. The talk adapter is the wire between - * this client and the coral backend. The idea is we can - * write different adapters for other platforms if we want - */ +const middlewares = [ + applyMiddleware(client.middleware()), + applyMiddleware(thunk) +]; + +if (window.devToolsExtension) { + + // we can't have the last argument of compose() be undefined + middlewares.push(window.devToolsExtension()); +} export default createStore( - mainReducer, - window.devToolsExtension && window.devToolsExtension(), - applyMiddleware(thunk) + combineReducers({ + ...mainReducer, + apollo: client.reducer() + }), + {}, + compose(...middlewares) ); diff --git a/client/coral-framework/subscriptions.js b/client/coral-admin/src/services/subscriptions.js similarity index 100% rename from client/coral-framework/subscriptions.js rename to client/coral-admin/src/services/subscriptions.js diff --git a/client/coral-framework/transport.js b/client/coral-admin/src/services/transport.js similarity index 100% rename from client/coral-framework/transport.js rename to client/coral-admin/src/services/transport.js diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index a4256848e..0eb1ab98d 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -50,7 +50,7 @@ "configure": { "enable-pre-moderation": "Enable pre-moderation", "enable-pre-moderation-text": "Moderators must approve any comment before it is published.", - "require-email-verification": "Require Email Confirmation", + "require-email-verification": "Require Email Verification", "require-email-verification-text": "New Users must verify their email before commenting", "include-comment-stream": "Include Comment Stream Description for Readers.", "include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.", @@ -79,7 +79,9 @@ "comment-count-header": "Limit Comment Length", "comment-count-text-pre": "Comments will be limited to ", "comment-count-text-post": " characters.", - "comment-count-error": "Please enter a valid number." + "comment-count-error": "Please enter a valid number.", + "domain-list-title": "Domain Whitelist", + "domain-list-text": "Some instructions on how to type the urls." }, "bandialog": { "ban_user": "Ban User?", @@ -187,7 +189,12 @@ "comment-count-header": "Limitar el largo del comentario", "comment-count-text-pre": "El largo de comentarios será ", "comment-count-text-post": " caracteres", - "comment-count-error": "Por favor escribe un número válido." + "comment-count-error": "Por favor escribe un número válido.", + "domain-list-title": "Lista de Dominios Permitidos", + "domain-list-text": "Instrucciones de como ingresar las URLs." + }, + "embedlink": { + "copy": "Copiar" }, "bandialog": { "ban_user": "Quieres suspender el Usuario?", diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 778614880..c0c0d57d1 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -94,14 +94,7 @@ class Comment extends React.Component { style={{marginLeft: depth * 30}}>
+ author={comment.user}/>
diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index d31b716e9..a853efead 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -11,6 +11,7 @@ const {fetchAssetSuccess} = assetActions; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postAction, deleteAction} from 'coral-framework/graphql/mutations'; +import {editName} from 'coral-framework/actions/user'; import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; @@ -84,6 +85,8 @@ class Embed extends Component { const openStream = closedAt === null; + const banned = user && user.status === 'BANNED'; + const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 200 } : {}; @@ -100,7 +103,7 @@ class Embed extends Component { Settings Configure Stream - {loggedIn && } + {loggedIn && } { openStream @@ -109,7 +112,12 @@ class Embed extends Component { content={asset.settings.infoBoxContent} enable={asset.settings.infoBoxEnable} /> - }> + + }> { user ? : null @@ -131,7 +138,7 @@ class Embed extends Component {
:

{asset.settings.closedMessage}

} - {!loggedIn && } + {!loggedIn && } {loggedIn && user && } - - + + - - + + - + - + ); } @@ -193,6 +200,7 @@ const mapDispatchToProps = dispatch => ({ }); }, clearNotification: () => dispatch(clearNotification()), + editName: (displayName) => dispatch(editName(displayName)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), logout: () => dispatch(logout()), dispatch: d => dispatch(d) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index f75146f0a..3dc69400c 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -2,8 +2,8 @@ import React from 'react'; import {render} from 'react-dom'; import {ApolloProvider} from 'react-apollo'; -import {client} from 'coral-framework/client'; -import store from 'coral-framework/store'; +import {client} from 'coral-framework/services/client'; +import store from 'coral-framework/services/store'; import Embed from './Embed'; diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index edd04de12..bda46ae42 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -15,15 +15,15 @@ export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISP const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); -export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNAME, displayName}); +export const updateDisplayName = ({displayName}) => ({type: actions.UPDATE_DISPLAYNAME, displayName}); export const createDisplayName = (userId, formData) => dispatch => { dispatch(createDisplayNameRequest()); - coralApi(`/users/${userId}/displayname`, {method: 'POST', body: formData}) - .then((user) => { + coralApi('/account/displayname', {method: 'PUT', body: formData}) + .then(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); - dispatch(updateDisplayName(user)); + dispatch(updateDisplayName(formData)); }) .catch(error => { dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`))); @@ -43,7 +43,6 @@ export const cleanState = () => ({type: actions.CLEAN_STATE}); const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); -const emailConfirmError = () => ({type: actions.EMAIL_CONFIRM_ERROR}); export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); @@ -58,7 +57,6 @@ export const fetchSignIn = (formData) => (dispatch) => { // 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))); - dispatch(emailConfirmError()); } else { // invalid credentials @@ -117,15 +115,12 @@ 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) => { +export const fetchSignUp = (formData, redirectUri) => (dispatch) => { dispatch(signUpRequest()); - coralApi('/users', {method: 'POST', body: formData}) + coralApi('/users', {method: 'POST', body: formData, headers: {'X-Pym-Url': redirectUri}}) .then(({user}) => { dispatch(signUpSuccess(user)); - setTimeout(() =>{ - dispatch(changeView('SIGNIN')); - }, 3000); }) .catch(error => { dispatch(signUpFailure(lang.t(`error.${error.message}`))); @@ -186,20 +181,20 @@ export const checkLogin = () => dispatch => { }); }; -const confirmEmailRequest = () => ({type: actions.CONFIRM_EMAIL_REQUEST}); -const confirmEmailSuccess = () => ({type: actions.CONFIRM_EMAIL_SUCCESS}); -const confirmEmailFailure = () => ({type: actions.CONFIRM_EMAIL_FAILURE}); +const verifyEmailRequest = () => ({type: actions.VERIFY_EMAIL_REQUEST}); +const verifyEmailSuccess = () => ({type: actions.VERIFY_EMAIL_SUCCESS}); +const verifyEmailFailure = () => ({type: actions.VERIFY_EMAIL_FAILURE}); -export const requestConfirmEmail = email => dispatch => { - dispatch(confirmEmailRequest()); - return coralApi('/users/resend-confirm', {method: 'POST', body: {email}}) +export const requestConfirmEmail = (email, redirectUri) => dispatch => { + dispatch(verifyEmailRequest()); + return coralApi('/users/resend-verify', {method: 'POST', body: {email}, headers: {'X-Pym-Url': redirectUri}}) .then(() => { - dispatch(confirmEmailSuccess()); + dispatch(verifyEmailSuccess()); }) .catch(err => { - console.log('failed to send email confirmation', err); + console.log('failed to send email verification', err); - // email might have already been confirmed - dispatch(confirmEmailFailure()); + // email might have already been verifyed + dispatch(verifyEmailFailure()); }); }; diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 4620262d3..42d2e0398 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,4 +1,3 @@ -import * as actions from '../constants/user'; import {addNotification} from '../actions/notification'; import coralApi from '../helpers/response'; @@ -6,16 +5,9 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST}); -const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings}); -const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error}); - -export const saveBio = (user_id, formData) => dispatch => { - dispatch(saveBioRequest()); - coralApi('/account/settings', {method: 'PUT', body: formData}) +export const editName = (displayName) => (dispatch) => { + return coralApi('/account/displayname', {method: 'PUT', body: {displayName}}) .then(() => { - dispatch(addNotification('success', lang.t('successBioUpdate'))); - dispatch(saveBioSuccess(formData)); - }) - .catch(error => dispatch(saveBioFailure(error))); + dispatch(addNotification('success', lang.t('successNameUpdate'))); + }); }; diff --git a/client/coral-framework/components/RestrictedContent.css b/client/coral-framework/components/RestrictedContent.css index 47ced7b0b..ca5a6517c 100644 --- a/client/coral-framework/components/RestrictedContent.css +++ b/client/coral-framework/components/RestrictedContent.css @@ -2,3 +2,13 @@ background: #D8D8D8; padding: 25px; } + +.editNameInput { + margin-top: 10px; + margin-bottom: 10px; +} + +.alert { + margin-top: 10px; + color: #B71C1C; +} diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index 46d1f1d58..4c221aa81 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -1,11 +1,79 @@ -import React from 'react'; +import React, {Component, PropTypes} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations.json'; const lang = new I18n(translations); import styles from './RestrictedContent.css'; +import {Button} from 'coral-ui'; +import validate from '../helpers/validate'; -export default () => ( -
- {lang.t('suspendedAccountMsg')} -
-); +class SuspendedAccount extends Component { + + static propTypes = { + canEditName: PropTypes.bool, + editName: PropTypes.func.isRequired + } + + state = { + displayName: '', + alert: '' + } + + onSubmitClick = (e) => { + const {editName} = this.props; + const {displayName} = this.state; + e.preventDefault(); + if (validate.displayName(displayName)) { + editName(displayName) + .then(() => location.reload()) + .catch((error) => { + this.setState({alert: lang.t(`error.${error.message}`)}); + }); + } else { + this.setState({alert: lang.t('editName.error')}); + } + + } + + render () { + const {canEditName} = this.props; + const {displayName, alert} = this.state; + + return
+ { + canEditName ? + lang.t('editName.msg') + : lang.t('bannedAccountMsg') + } + { + canEditName ? +
+
+ {alert} +
+ + this.setState({displayName: e.target.value})} + rows={3}/>
+ +
: null + } +
; + } +} + +export default SuspendedAccount; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 2b5f762c1..b6ad3d3a1 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -41,8 +41,7 @@ export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE'; export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN'; +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_DISPLAYNAME = 'UPDATE_DISPLAYNAME'; -export const EMAIL_CONFIRM_ERROR = 'EMAIL_CONFIRM_ERROR'; -export const CONFIRM_EMAIL_REQUEST = 'CONFIRM_EMAIL_REQUEST'; -export const CONFIRM_EMAIL_SUCCESS = 'CONFIRM_EMAIL_SUCCESS'; -export const CONFIRM_EMAIL_FAILURE = 'CONFIRM_EMAIL_FAILURE'; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 57f4e706b..5e0a23d7a 100644 --- a/client/coral-framework/constants/user.js +++ b/client/coral-framework/constants/user.js @@ -1,6 +1,6 @@ -export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST'; -export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS'; -export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE'; +export const EDIT_NAME_REQUEST = 'EDIT_NAME_REQUEST'; +export const EDIT_NAME_SUCCESS = 'EDIT_NAME_SUCCESS'; +export const EDIT_NAME_FAILURE = 'EDIT_NAME_FAILURE'; export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST'; export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS'; export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE'; diff --git a/client/coral-framework/graphql/fragments/commentView.graphql b/client/coral-framework/graphql/fragments/commentView.graphql index fb8051155..29f9b3bfe 100644 --- a/client/coral-framework/graphql/fragments/commentView.graphql +++ b/client/coral-framework/graphql/fragments/commentView.graphql @@ -6,9 +6,6 @@ fragment commentView on Comment { user { id name: displayName - settings { - bio - } } actions { type: action_type diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index 1a9e8d36e..7ce065baa 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -11,6 +11,9 @@ function getQueryVariable(variable) { return decodeURIComponent(pair[1]); } } + + // If no query is included, return a default string for development + return 'http://dev.default.stream'; } export const queryStream = graphql(STREAM_QUERY, { diff --git a/client/coral-framework/helpers/error.js b/client/coral-framework/helpers/error.js index 34ba253ba..882486f6a 100644 --- a/client/coral-framework/helpers/error.js +++ b/client/coral-framework/helpers/error.js @@ -6,5 +6,6 @@ export default { email: lang.t('error.email'), password: lang.t('error.password'), displayName: lang.t('error.displayName'), - confirmPassword: lang.t('error.confirmPassword') + confirmPassword: lang.t('error.confirmPassword'), + organizationName: lang.t('error.organizationName'), }; diff --git a/client/coral-framework/helpers/response.js b/client/coral-framework/helpers/response.js index d95773c04..64803faef 100644 --- a/client/coral-framework/helpers/response.js +++ b/client/coral-framework/helpers/response.js @@ -14,7 +14,8 @@ const buildOptions = (inputOptions = {}) => { _csrf: csurfDOM ? csurfDOM.content : false }; - const options = Object.assign({}, defaultOptions, inputOptions); + let options = Object.assign({}, defaultOptions, inputOptions); + options.headers = Object.assign({}, defaultOptions.headers, inputOptions.headers); if (options._csrf) { switch (options.method.toLowerCase()) { diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js index b267fb7c0..69d81966f 100644 --- a/client/coral-framework/helpers/validate.js +++ b/client/coral-framework/helpers/validate.js @@ -2,5 +2,6 @@ export default { email: email => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)), password: pass => (/^(?=.{8,}).*$/.test(pass)), confirmPassword: () => true, - displayName: displayName => (/^[a-zA-Z0-9_]+$/.test(displayName)) + displayName: displayName => (/^[a-zA-Z0-9_]+$/.test(displayName)), + organizationName: org => (/^[a-zA-Z0-9_ ]+$/).test(org) }; diff --git a/client/coral-framework/index.js b/client/coral-framework/index.js index d2cd4197b..56a2e522c 100644 --- a/client/coral-framework/index.js +++ b/client/coral-framework/index.js @@ -1,5 +1,5 @@ -import store from './store'; -import pym from './PymConnection'; +import store from './services/store'; +import pym from './services/PymConnection'; import I18n from './modules/i18n/i18n'; import * as authActions from './actions/auth'; import * as assetActions from './actions/asset'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 2da589507..c894ecde4 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -1,4 +1,4 @@ -import {Map} from 'immutable'; +import {Map, fromJS} from 'immutable'; import * as actions from '../constants/auth'; const initialState = Map({ @@ -12,16 +12,16 @@ const initialState = Map({ error: '', passwordRequestSuccess: null, passwordRequestFailure: null, - emailConfirmationFailure: false, - emailConfirmationLoading: false, - emailConfirmationSuccess: false, + emailVerificationFailure: false, + emailVerificationLoading: false, + emailVerificationSuccess: false, successSignUp: false, fromSignUp: false }); const purge = user => { const {settings, profiles, ...userData} = user; // eslint-disable-line - return userData; + return fromJS(userData); }; export default function auth (state = initialState, action) { @@ -38,9 +38,9 @@ export default function auth (state = initialState, action) { error: '', passwordRequestFailure: null, passwordRequestSuccess: null, - emailConfirmationFailure: false, - emailConfirmationSuccess: false, - emailConfirmationLoading: false, + emailVerificationFailure: false, + emailVerificationSuccess: false, + emailVerificationLoading: false, successSignUp: false })); case actions.SHOW_CREATEDISPLAYNAME_DIALOG : @@ -131,18 +131,19 @@ export default function auth (state = initialState, action) { .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!') .set('passwordRequestSuccess', null); case actions.UPDATE_DISPLAYNAME: + console.log('Action', action); return state - .set('user', purge(action.displayName)); - case actions.EMAIL_CONFIRM_ERROR: + .setIn(['user', 'displayName'], action.displayName); + case actions.VERIFY_EMAIL_FAILURE: return state - .set('emailConfirmationFailure', true) - .set('emailConfirmationLoading', false); - case actions.CONFIRM_EMAIL_REQUEST: - return state.set('emailConfirmationLoading', true); - case actions.CONFIRM_EMAIL_SUCCESS: + .set('emailVerificationFailure', true) + .set('emailVerificationLoading', false); + case actions.VERIFY_EMAIL_REQUEST: + return state.set('emailVerificationLoading', true); + case actions.VERIFY_EMAIL_SUCCESS: return state - .set('emailConfirmationSuccess', true) - .set('emailConfirmationLoading', false); + .set('emailVerificationSuccess', true) + .set('emailVerificationLoading', false); default : return state; } diff --git a/client/coral-framework/PymConnection.js b/client/coral-framework/services/PymConnection.js similarity index 77% rename from client/coral-framework/PymConnection.js rename to client/coral-framework/services/PymConnection.js index ca592b824..6c492f2b8 100644 --- a/client/coral-framework/PymConnection.js +++ b/client/coral-framework/services/PymConnection.js @@ -1,4 +1,4 @@ -import Pym from '../../node_modules/pym.js'; +import Pym from '../../../node_modules/pym.js'; const pym = new Pym.Child({polling: 100}); export default pym; diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js new file mode 100644 index 000000000..b4a7a38df --- /dev/null +++ b/client/coral-framework/services/client.js @@ -0,0 +1,14 @@ +import ApolloClient, {addTypename} from 'apollo-client'; +import getNetworkInterface from './transport'; + +export const client = new ApolloClient({ + connectToDevTools: true, + queryTransformer: addTypename, + dataIdFromObject: (result) => { + if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle + return result.__typename + result.id; // eslint-disable-line no-underscore-dangle + } + return null; + }, + networkInterface: getNetworkInterface() +}); diff --git a/client/coral-framework/store.js b/client/coral-framework/services/store.js similarity index 93% rename from client/coral-framework/store.js rename to client/coral-framework/services/store.js index 439569ee1..bf2735e45 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/services/store.js @@ -1,6 +1,6 @@ import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from './reducers'; +import mainReducer from '../reducers'; import {client} from './client'; const middlewares = [ diff --git a/client/coral-framework/services/subscriptions.js b/client/coral-framework/services/subscriptions.js new file mode 100644 index 000000000..818a6fb33 --- /dev/null +++ b/client/coral-framework/services/subscriptions.js @@ -0,0 +1,16 @@ +import {print} from 'graphql-tag/printer'; + +// quick way to add the subscribe and unsubscribe functions to the network interface +const addGraphQLSubscriptions = (networkInterface, wsClient) => { + return Object.assign(networkInterface, { + subscribe: (request, handler) => wsClient.subscribe({ + query: print(request.query), + variables: request.variables, + }, handler), + unsubscribe: (id) => { + wsClient.unsubscribe(id); + }, + }); +}; + +export default addGraphQLSubscriptions; diff --git a/client/coral-framework/services/transport.js b/client/coral-framework/services/transport.js new file mode 100644 index 000000000..2bd6ac636 --- /dev/null +++ b/client/coral-framework/services/transport.js @@ -0,0 +1,11 @@ +import {createNetworkInterface} from 'apollo-client'; + +export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) { + return new createNetworkInterface({ + uri: apiUrl, + opts: { + credentials: 'same-origin', + headers, + }, + }); +} diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 07b719f6b..9b56ba92c 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -1,15 +1,22 @@ { "en": { "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", - "successBioUpdate": "Your Bio has been updated", + "successNameUpdate": "Your display name has been updated", "contentNotAvailable": "This content is not available", - "suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", + "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", + "editName": { + "msg": "Your account is currently suspended because your display name has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", + "label": "New Display Name", + "button": "Submit", + "error": "Display names can contain letters, numbers and _ only" + }, "error": { "emailNotVerified": "Email address {0} not verified.", "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", "displayName": "Display names can contain letters, numbers and _ only", "confirmPassword": "Passwords don't match. Please, check again", + "organizationName": "Organization name must only contain letters or numbers.", "emailPasswordError": "Email and/or password combination incorrect.", "EMAIL_REQUIRED": "An email address is required", "PASSWORD_REQUIRED": "Must input a password", @@ -26,12 +33,14 @@ "successUpdateSettings": "La configuración de este articulo fue actualizada", "successBioUpdate": "Tu bio fue actualizada", "contentNotAvailable": "El contenido no se encuentra disponible", - "suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", + "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", + "editNameMsg": "", "error": { "emailNotVerified": "Dirección de correo electrónico {0} no verificada.", "email": "No es un email válido", "password": "La contraseña debe tener por lo menos 8 caracteres", "displayName": "Los nombres pueden contener letras, números y _", + "organizationName": "El nombre de la organización debe contener letras y/o números.", "confirmPassword": "Las contraseñas no coinciden", "emailPasswordError": "Email y/o contraseña incorrecta.", "EMAIL_REQUIRED": "Se requiere una dirección de correo electrónico", diff --git a/client/coral-plugin-author-name/AuthorName.js b/client/coral-plugin-author-name/AuthorName.js index 624750c60..bca1d716b 100644 --- a/client/coral-plugin-author-name/AuthorName.js +++ b/client/coral-plugin-author-name/AuthorName.js @@ -1,6 +1,4 @@ import React, {Component} from 'react'; -import {Tooltip} from 'coral-ui'; -import FlagBio from 'coral-plugin-flags/FlagBio'; const packagename = 'coral-plugin-author-name'; import styles from './styles.css'; @@ -24,24 +22,11 @@ export default class AuthorName extends Component { render () { const {author} = this.props; - const {showTooltip} = this.state; return ( -
- - {author && author.name} - {author.settings.bio ? : null} - - {showTooltip && author.settings.bio - && ( - -
- {author.settings.bio} -
-
- -
-
- )} +
+ {author && author.name}
); } diff --git a/client/coral-settings/components/NotLoggedIn.js b/client/coral-settings/components/NotLoggedIn.js index 8266a745e..095d43a8f 100644 --- a/client/coral-settings/components/NotLoggedIn.js +++ b/client/coral-settings/components/NotLoggedIn.js @@ -15,7 +15,6 @@ export default ({showSignInDialog}) => ( From the Settings Page you can
  • See your comment history
  • -
  • Write a bio about yourself to display to the community
diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index f02badd80..9e72cdadc 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -4,12 +4,10 @@ import React, {Component} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import {myCommentHistory} from 'coral-framework/graphql/queries'; -import {saveBio} from 'coral-framework/actions/user'; -import BioContainer from './BioContainer'; -import {link} from 'coral-framework/PymConnection'; +import {link} from 'coral-framework/services/PymConnection'; import NotLoggedIn from '../components/NotLoggedIn'; -import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; +import {Spinner} from 'coral-ui'; import SettingsHeader from '../components/SettingsHeader'; import CommentHistory from 'coral-plugin-history/CommentHistory'; @@ -33,8 +31,7 @@ class SettingsContainer extends Component { } render() { - const {loggedIn, userData, asset, showSignInDialog, data, user} = this.props; - const {activeTab} = this.state; + const {loggedIn, asset, showSignInDialog, data} = this.props; const {me} = this.props.data; if (!loggedIn || !me) { @@ -48,25 +45,30 @@ class SettingsContainer extends Component { return (
- - {lang.t('allComments')} ({user.myComments.length}) - {lang.t('profileSettings')} - - - { - me.comments.length ? - - : -

{lang.t('userNoComment')}

- } -
- - - + { + + // Hiding bio until moderation can get figured out + /* + {lang.t('allComments')} ({user.myComments.length}) + {lang.t('profileSettings')} + + */ + me.comments.length ? + + : +

{lang.t('userNoComment')}

+ + // Hiding user bio pending effective moderation system. + /*
+ + + */ + } +
); } @@ -78,8 +80,9 @@ const mapStateToProps = state => ({ auth: state.auth.toJS() }); -const mapDispatchToProps = dispatch => ({ - saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)) +const mapDispatchToProps = () => ({ + + // saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)) }); export default compose( diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js index 0645f110f..6243472b4 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/client/coral-sign-in/components/SignDialog.js @@ -17,12 +17,7 @@ const SignDialog = ({open, view, handleClose, offset, ...props}) => ( }}> × {view === 'SIGNIN' && } - { - view === 'SIGNUP' && - } + {view === 'SIGNUP' && } {view === 'FORGOT' && } ); diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index e27185b52..eebaf960b 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -10,35 +10,28 @@ const SignInContent = ({ handleChange, handleChangeEmail, emailToBeResent, - handleResendConfirmation, - emailConfirmationLoading, - emailConfirmationSuccess, + handleResendVerification, + emailVerificationLoading, + emailVerificationSuccess, formData, - ...props + changeView, + handleSignIn, + auth, + fetchSignInFacebook }) => { return (

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

-
- -
-
-

- {lang.t('signIn.or')} -

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

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

+ auth.emailVerificationFailure + ? +

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

- {emailConfirmationLoading && } - {emailConfirmationSuccess && } + {emailVerificationLoading && } + {emailVerificationSuccess && } - :
- - -
- { - !props.auth.isLoading ? - - : - - } + :
+
+
- +
+

+ {lang.t('signIn.or')} +

+
+
+ + +
+ { + !auth.isLoading ? + + : + + } +
+ +
}
- props.changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} + changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} {lang.t('signIn.needAnAccount')} - props.changeView('SIGNUP')} id='coralRegister'> + changeView('SIGNUP')} id='coralRegister'> {lang.t('signIn.register')} @@ -90,9 +95,17 @@ const SignInContent = ({ }; SignInContent.propTypes = { - emailConfirmationLoading: PropTypes.bool.isRequired, - emailConfirmationSuccess: PropTypes.bool.isRequired, - handleResendConfirmation: PropTypes.func.isRequired, + auth: PropTypes.shape({ + isLoading: PropTypes.bool.isRequired, + error: PropTypes.string, + emailVerificationFailure: PropTypes.bool + }).isRequired, + fetchSignInFacebook: PropTypes.func.isRequired, + handleSignIn: PropTypes.func.isRequired, + changeView: PropTypes.func.isRequired, + emailVerificationLoading: PropTypes.bool.isRequired, + emailVerificationSuccess: PropTypes.bool.isRequired, + handleResendVerification: PropTypes.func.isRequired, handleChangeEmail: PropTypes.func.isRequired, emailToBeResent: PropTypes.string.isRequired }; diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 80a6fcde4..03c083070 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import Alert from './Alert'; import {Button, FormField, Spinner, Success} from 'coral-ui'; import styles from './styles.css'; @@ -6,83 +6,148 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignUpContent = ({handleChange, formData, ...props}) => ( -
-
-

- {lang.t('signIn.signUp')} -

-
-
- -
-
-

- {lang.t('signIn.or')} -

-
- { props.auth.error && {props.auth.error} } -
- - - - { props.errors.password && Password must be at least 8 characters. } - -
- { !props.auth.isLoading && !props.auth.successSignUp && ( - - )} - { props.auth.isLoading && } - { !props.auth.isLoading && props.auth.successSignUp && } + +
+ + { auth.error && {auth.error} } + { beforeSignup && +
+
+ +
+
+

+ {lang.t('signIn.or')} +

+
+ + + + + { errors.password && Password must be at least 8 characters. } + +
+ + { auth.isLoading && } +
+ +
+ } + { + successfulSignup && +
+ + { + emailVerificationEnabled && +

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

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

+ } +
+ } +
+ + {lang.t('signIn.alreadyHaveAnAccount')} + changeView('SIGNIN')}> + {lang.t('signIn.signIn')} + + +
- -
- - {lang.t('signIn.alreadyHaveAnAccount')} - props.changeView('SIGNIN')}> - {lang.t('signIn.signIn')} - - -
-
-); + ); + } +} export default SignUpContent; diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 937c830c9..c92fe024d 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, {Component, PropTypes} from 'react'; import {connect} from 'react-redux'; import SignDialog from '../components/SignDialog'; import Button from 'coral-ui/components/Button'; @@ -6,6 +6,7 @@ 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'; const lang = new I18n(translations); import { @@ -42,12 +43,16 @@ class SignInContainer extends Component { this.state = this.initialState; this.handleChange = this.handleChange.bind(this); this.handleChangeEmail = this.handleChangeEmail.bind(this); - this.handleResendConfirmation = this.handleResendConfirmation.bind(this); + this.handleResendVerification = this.handleResendVerification.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.handleSignIn = this.handleSignIn.bind(this); this.addError = this.addError.bind(this); } + static propTypes = { + requireEmailConfirmation: PropTypes.bool.isRequired + } + componentWillMount () { this.props.checkLogin(); } @@ -80,9 +85,9 @@ class SignInContainer extends Component { this.setState({emailToBeResent: value}); } - handleResendConfirmation(e) { + handleResendVerification(e) { e.preventDefault(); - this.props.requestConfirmEmail(this.state.emailToBeResent) + this.props.requestConfirmEmail(this.state.emailToBeResent, pym.parentUrl || location.href) .then(() => { setTimeout(() => { @@ -133,7 +138,7 @@ class SignInContainer extends Component { const {fetchSignUp, validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - fetchSignUp(this.state.formData); + fetchSignUp(this.state.formData, pym.parentUrl || location.href); validForm(); } else { invalidForm(lang.t('signIn.checkTheForm')); @@ -146,8 +151,9 @@ class SignInContainer extends Component { } render() { - const {auth, showSignInDialog, noButton, offset} = this.props; - const {emailConfirmationLoading, emailConfirmationSuccess} = auth; + const {auth, showSignInDialog, noButton, offset, requireEmailConfirmation} = this.props; + const {emailVerificationLoading, emailVerificationSuccess} = auth; + return (
{!noButton &&