From e803593fc7fb204b2a2bcd5a598e38772662f259 Mon Sep 17 00:00:00 2001 From: gaba Date: Mon, 30 Jan 2017 11:34:04 -0800 Subject: [PATCH 01/77] Move stuff around. --- client/coral-embed-stream/src/Embed.js | 11 +- client/coral-framework/actions/auth.js | 14 ++ .../components/CreateDisplayNameDialog.js | 44 ++++++ client/coral-framework/components/styles.css | 140 ++++++++++++++++++ client/coral-framework/constants/auth.js | 3 + .../containers/ChangeDisplayNameContainer.js | 140 ++++++++++++++++++ client/coral-framework/reducers/auth.js | 8 + client/coral-framework/translations.json | 15 ++ services/users.js | 12 ++ 9 files changed, 378 insertions(+), 9 deletions(-) create mode 100644 client/coral-framework/components/CreateDisplayNameDialog.js create mode 100644 client/coral-framework/components/styles.css create mode 100644 client/coral-framework/containers/ChangeDisplayNameContainer.js diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 5922266fa..b6fee1184 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -19,6 +19,7 @@ import Count from 'coral-plugin-comment-count/CommentCount'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import UserBox from '../../coral-sign-in/components/UserBox'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; +import ChangeDisplayNameContainer from '../../coral-framework/containers/ChangeDisplayNameContainer'; import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; import RestrictedContent from '../../coral-framework/components/RestrictedContent'; @@ -51,15 +52,6 @@ class Embed extends Component { } componentDidMount () { - - // stream id, logged in user, settings - - // Set up messaging between embedded Iframe an parent component - - // this.props.getStream(path || window.location); - // this.path = window.location.href.split('#')[0]; - // - pym.sendMessage('childReady'); pym.onMessage('DOMContentLoaded', hash => { @@ -138,6 +130,7 @@ class Embed extends Component { :

{asset.settings.closedMessage}

} {!loggedIn && } + {loggedIn && } ({type: actions.SHOW_SIGNIN_DIALOG, offset}); export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG}); +export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG}); +export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG}); +export const createDisplayName = (formData) => (dispatch) => { + coralApi('/users', {method: 'POST', body: formData}) + .then(() => { + dispatch(createDisplayNameSuccess()); + dispatch(hideCreateDisplayNameDialog()); + }) + .catch(() => dispatch(createDisplayNameFailure(lang.t('createdisplay.errorCreate')))); +}; +const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); +const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); + export const changeView = view => dispatch => dispatch({ type: actions.CHANGE_VIEW, @@ -59,6 +72,7 @@ export const facebookCallback = (err, data) => dispatch => { const user = JSON.parse(data); dispatch(signInFacebookSuccess(user)); dispatch(hideSignInDialog()); + dispatch(showCreateDisplayNameDialog()); dispatch(addItem(user, 'users')); } catch (err) { dispatch(signInFacebookFailure(err)); diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-framework/components/CreateDisplayNameDialog.js new file mode 100644 index 000000000..8b61989aa --- /dev/null +++ b/client/coral-framework/components/CreateDisplayNameDialog.js @@ -0,0 +1,44 @@ +import React from 'react'; +import {Dialog} from 'coral-ui'; +import styles from './styles.css'; +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../translations'; +const lang = new I18n(translations); + +const CreateDisplayNameDialog = ({open, handleClose, offset, loggedIn}) => ( + + × +
+
+

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

+ Logged {loggedIn} +
+
+
+); + +//
+// +//
+// +//
+// + +export default CreateDisplayNameDialog; diff --git a/client/coral-framework/components/styles.css b/client/coral-framework/components/styles.css new file mode 100644 index 000000000..f9d0aec6d --- /dev/null +++ b/client/coral-framework/components/styles.css @@ -0,0 +1,140 @@ +.dialog { + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 280px; + top: 10px; +} + +.header { + margin-bottom: 20px; +} + +.header h1, .separator h1{ + text-align: center; + font-size: 1.2em; +} + +.formField { + margin-top: 15px; +} + +.formField label { + font-size: 1.08em; + font-weight: bold; + margin-bottom: 5px; +} + +.formField input { + width: 100%; + display: block; + border: none; + outline: none; + border: 1px solid rgba(0,0,0,.12); + padding: 10px 6px; + box-sizing: border-box; + border-radius: 2px; + margin: 5px auto; +} + +.footer { + margin: 20px auto 10px; + text-align: center; +} + +.footer span { + display: block; + margin-bottom: 5px; +} + +.footer a { + color: #2c69b6; + cursor: pointer; + margin: 0 5px; +} + +.socialConnections { + margin-bottom: 20px; +} + +.signInButton { + margin-top: 10px; +} + +.close { + font-size: 20px; + line-height: 14px; + top: 10px; + right: 10px; + position: absolute; + display: block; + font-weight: bold; + color: #363636; + cursor: pointer; +} + +.close:hover { + color: #6b6b6b; +} + +input.error{ + border: solid 2px #f44336; +} + +.errorMsg, .hint { + color: grey; + font-weight: 600; + padding: 3px 0 16px; +} + +.alert { + padding: 10px; + margin-bottom: 20px; + border-radius: 2px; +} + +.alert--success { + border: solid 1px #1ec00e; + background: #cbf1b8; + color: #006900; +} + +.alert--error { + background: #FFEBEE; + color: #B71C1C; +} + +.userBox { + padding: 10px 0 20px; + letter-spacing: 0.1px; +} + +.userBox a { + color: black; + font-weight: bold; + cursor: pointer; + margin: 0px; + margin-left: 4px; + padding-bottom: 2px; + border-bottom: solid 1px black; +} + +.attention { + display: inline-block; + width: 15px; + height: 15px; + background: #B71C1C; + color: #FFEBEE; + font-weight: bolder; + padding: 4px; + vertical-align: middle; + border-radius: 20px; + box-sizing: border-box; + font-size: 9px; + line-height: 7px; + text-align: center; + margin-right: 5px; +} + +.action { + margin-top: 15px; +} diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 5742adf75..27fa8890f 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -4,6 +4,9 @@ export const CLEAN_STATE = 'CLEAN_STATE'; export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; +export const SHOW_CREATEDISPLAYNAME_DIALOG = 'SHOW_CREATEDISPLAYNAME_DIALOG'; +export const HIDE_CREATEDISPLAYNAME_DIALOG = 'HIDE_CREATEDISPLAYNAME_DIALOG'; + export const FETCH_SIGNUP_REQUEST = 'FETCH_SIGNUP_REQUEST'; export const FETCH_SIGNUP_FAILURE = 'FETCH_SIGNUP_FAILURE'; export const FETCH_SIGNUP_SUCCESS = 'FETCH_SIGNUP_SUCCESS'; diff --git a/client/coral-framework/containers/ChangeDisplayNameContainer.js b/client/coral-framework/containers/ChangeDisplayNameContainer.js new file mode 100644 index 000000000..17e681388 --- /dev/null +++ b/client/coral-framework/containers/ChangeDisplayNameContainer.js @@ -0,0 +1,140 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; + +import CreateDisplayNameDialog from '../components/CreateDisplayNameDialog'; + +// 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'; +const lang = new I18n(translations); + +import { + showCreateDisplayNameDialog, + hideCreateDisplayNameDialog, + invalidForm, + validForm, + createDisplayName +} from '../../coral-framework/actions/auth'; + +class ChangeDisplayNameContainer extends Component { + initialState = { + formData: { + displayName: '', + }, + errors: {}, + showErrors: false + }; + + constructor(props) { + super(props); + this.state = this.initialState; + this.handleChange = this.handleChange.bind(this); + this.handleSubmitForm = this.handleSubmitForm.bind(this); + this.handleClose = this.handleClose.bind(this); + this.addError = this.addError.bind(this); + } + + componentDidMount() { + const {formData} = this.state; + const errors = Object.keys(formData).reduce((map, prop) => { + map[prop] = lang.t('createdisplay.requiredField'); + return map; + }, {}); + this.setState({errors}); + } + + handleChange(e) { + const {name, value} = e.target; + this.setState(state => ({ + ...state, + formData: { + ...state.formData, + [name]: value + } + }), () => { + this.validation(name, value); + }); + } + + addError(name, error) { + return this.setState(state => ({ + errors: { + ...state.errors, + [name]: error + } + })); + } + + validation(name, value) { + const {addError} = this; + + if (!value.length) { + addError(name, lang.t('displayName.requiredField')); + } else { + const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line + // Removes Error + this.setState(state => ({...state, errors})); + } + } + + isCompleted() { + const {formData} = this.state; + return !Object.keys(formData).filter(prop => !formData[prop].length).length; + } + + displayErrors(show = true) { + this.setState({showErrors: show}); + } + + handleSubmitForm(e) { + e.preventDefault(); + const {errors} = this.state; + const {validForm, invalidForm} = this.props; + this.displayErrors(); + if (this.isCompleted() && !Object.keys(errors).length) { + createDisplayName(this.state.formData); + validForm(); + } else { + invalidForm(lang.t('signIn.checkTheForm')); + } + } + + handleClose() { + this.props.hideCreateDisplayNameDialog(); + } + + render() { + const {loggedIn, auth, offset, user} = this.props; + return ( +
+ +
+ ); + } +} + +const mapStateToProps = state => ({ + auth: state.auth.toJS() +}); + +const mapDispatchToProps = dispatch => ({ + createDisplayName: formData => dispatch(createDisplayName(formData)), + showCreateDisplayNameDialog: () => dispatch(showCreateDisplayNameDialog()), + hideCreateDisplayNameDialog: () => dispatch(hideCreateDisplayNameDialog()), + invalidForm: error => dispatch(invalidForm(error)), + validForm: () => dispatch(validForm()) +}); + +export default connect( + mapStateToProps, + mapDispatchToProps +)(ChangeDisplayNameContainer); diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 36f8e0764..ff0f72b4a 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -7,6 +7,7 @@ const initialState = Map({ isAdmin: false, user: null, showSignInDialog: false, + showCreateDisplayNameDialog: false, view: 'SIGNIN', error: '', passwordRequestSuccess: null, @@ -35,6 +36,13 @@ export default function auth (state = initialState, action) { passwordRequestSuccess: null, successSignUp: false })); + case actions.SHOW_CREATEDISPLAYNAME_DIALOG : + return state + .set('showCreateDisplayNameDialog', true); + case actions.HIDE_CREATEDISPLAYNAME_DIALOG : + return state.merge(Map({ + showCreateDisplayNameDialog: false + })); case actions.CHANGE_VIEW : return state .set('error', '') diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 5bf9b40a9..5900aad3e 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -4,6 +4,14 @@ "successBioUpdate": "Your Bio 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", + "createdisplay": { + "writeyourusername": "Write your username", + "yourusername": "Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.", + "displayName": "Display Name", + "save": "Save", + "requiredField": "Required field", + "errorCreate": "Error when changing display name" + }, "error": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", @@ -26,6 +34,13 @@ "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", + "createdisplay": { + "writeyourusername": "Escribe tu nombre", + "yourusername": "Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.", + "displayName": "Nombre", + "save": "Guardar", + "requiredField": "Campo necesario" + }, "error": { "email": "No es un email válido", "password": "La contraseña debe tener por lo menos 8 caracteres", diff --git a/services/users.js b/services/users.js index 0bf22e9e0..d1e7dce05 100644 --- a/services/users.js +++ b/services/users.js @@ -156,6 +156,18 @@ module.exports = class UsersService { }); } + static changeDisplayName(id, displayName) { + return UsersService.isValidDisplayName(displayName) + .then((displayName) => { // displayName is valid + return UserModel.update({id}, { + $inc: {__v: 1}, + $set: { + displayName: displayName + } + }); + }); + } + /** * Creates local users. * @param {Array} users Users to create From 890da0400f8657cfa55a3681aee00c96ad3cbbc9 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 31 Jan 2017 16:31:29 -0300 Subject: [PATCH 02/77] Steps --- client/coral-admin/src/AppRouter.js | 2 ++ client/coral-admin/src/actions/install.js | 4 +++ client/coral-admin/src/constants/install.js | 2 ++ .../containers/Install/InstallContainer.js | 30 +++++++++++++++++++ .../src/containers/Install/index.js | 0 .../src/containers/Install/style.css | 0 client/coral-admin/src/reducers/index.js | 4 ++- client/coral-admin/src/reducers/install.js | 21 +++++++++++++ 8 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 client/coral-admin/src/actions/install.js create mode 100644 client/coral-admin/src/constants/install.js create mode 100644 client/coral-admin/src/containers/Install/InstallContainer.js create mode 100644 client/coral-admin/src/containers/Install/index.js create mode 100644 client/coral-admin/src/containers/Install/style.css create mode 100644 client/coral-admin/src/reducers/install.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 9c0f271cc..ebb79b4e1 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -7,6 +7,7 @@ import Configure from 'containers/Configure/Configure'; import Streams from 'containers/Streams/Streams'; import CommunityContainer from 'containers/Community/CommunityContainer'; import LayoutContainer from 'containers/LayoutContainer'; +import InstallContainer from 'containers/Install/InstallContainer'; const routes = ( @@ -15,6 +16,7 @@ const routes = ( + ); diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js new file mode 100644 index 000000000..460a36e23 --- /dev/null +++ b/client/coral-admin/src/actions/install.js @@ -0,0 +1,4 @@ +import * as actions from '../constants/install'; + +export const nextStep = () => ({type: actions.NEXT_STEP}); +export const previousStep = () => ({type: actions.PREVIOUS_STEP}); diff --git a/client/coral-admin/src/constants/install.js b/client/coral-admin/src/constants/install.js new file mode 100644 index 000000000..f15a39ed4 --- /dev/null +++ b/client/coral-admin/src/constants/install.js @@ -0,0 +1,2 @@ +export const NEXT_STEP = 'NEXT_STEP'; +export const PREVIOUS_STEP = 'PREVIOUS_STEP'; 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..de296acc0 --- /dev/null +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -0,0 +1,30 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import styles from './style.css'; +import {nextStep, previousStep} from '../../actions/install' + +const InstallContainer = props => { + const {nextStep, previousStep, install} = props; + + return ( +
+ Install: +

+ {install.step} +

+ + +
+ ); +}; + +const mapStateToProps = state => ({ + install: state.install.toJS() +}); + +const mapDispatchToProps = dispatch => ({ + nextStep: () => dispatch(nextStep()), + previousStep: () => dispatch(previousStep()) +}); + +export default connect(mapStateToProps, mapDispatchToProps)(InstallContainer); diff --git a/client/coral-admin/src/containers/Install/index.js b/client/coral-admin/src/containers/Install/index.js new file mode 100644 index 000000000..e69de29bb 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..e69de29bb diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index a19a5a087..1943e5976 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -6,6 +6,7 @@ import users from 'reducers/users'; import auth from 'reducers/auth'; import actions from 'reducers/actions'; import assets from 'reducers/assets'; +import install from 'reducers/install'; // Combine all reducers into a main one export default combineReducers({ @@ -15,5 +16,6 @@ export default combineReducers({ 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..1b9ae5b28 --- /dev/null +++ b/client/coral-admin/src/reducers/install.js @@ -0,0 +1,21 @@ +import {Map} from 'immutable'; + +import * as actions from '../constants/install'; + +const initialState = Map({ + step: 0 +}); + +export default function auth (state = initialState, action) { + switch (action.type) { + case actions.NEXT_STEP: + console.log(state) + return state + .set('step', state.get('step') + 1); + case actions.PREVIOUS_STEP: + return state + .set('step', state.get('step') - 1); + default : + return state; + } +} From 80122dfdceb3cd190f1c3de82391efc8d4a9fc0f Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 31 Jan 2017 13:48:45 -0800 Subject: [PATCH 03/77] Adds formfield to components. --- client/coral-framework/components/FormField.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 client/coral-framework/components/FormField.js diff --git a/client/coral-framework/components/FormField.js b/client/coral-framework/components/FormField.js new file mode 100644 index 000000000..ceb2d939e --- /dev/null +++ b/client/coral-framework/components/FormField.js @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './styles.css'; + +const FormField = ({className, showErrors = false, errorMsg, label, ...props}) => ( +
+ + + {showErrors && errorMsg && !{errorMsg}} +
+); + +export default FormField; From a377f61bddf12e8ac4c905560eadf419951ab190 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 31 Jan 2017 13:51:36 -0800 Subject: [PATCH 04/77] Adds service and endpoint to change displayname. --- routes/api/users/index.js | 9 +++++++++ services/users.js | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 3ea3f3213..50e09aba7 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -57,6 +57,15 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next) .catch(next); }); +router.post('/:user_id/displayname', authorization.needed(), (req, res, next) => { + UsersService + .setDisplayName(req.params.user_id, req.body.displayName) + .then((status) => { + res.status(201).json(status); + }) + .catch(next); +}); + // /** // * SendEmailConfirmation sends a confirmation email to the user. // * @param {Request} req express request object diff --git a/services/users.js b/services/users.js index d1e7dce05..bc46c5e62 100644 --- a/services/users.js +++ b/services/users.js @@ -358,6 +358,24 @@ module.exports = class UsersService { return UserModel.update({id}, {$set: {status}}); } + /** + * Set the display name of a user. + * @param {String} id id of a user + * @param {String} displayName display name to set + * @param {Function} done callback after the operation is complete + */ + static setDisplayName(id, displayName) { + + // Check to see if that the displayName is not empty. + if (displayName.length < 1) { + + // User displayName is required! Error out here. + return Promise.reject(new Error('display name should not be empty')); + } + + return UserModel.update({id}, {$set: {'displayName': displayName}}); + } + /** * Finds a user with the id. * @param {String} id user id (uuid) From 37eae516416edd9cf51554fbaba535baee5cc095 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 31 Jan 2017 13:52:40 -0800 Subject: [PATCH 05/77] Adds component to coral-framework to deal with the change of displayname. --- client/coral-framework/actions/auth.js | 18 +++++++--- client/coral-framework/actions/items.js | 1 - .../components/CreateDisplayNameDialog.js | 33 +++++++++---------- client/coral-framework/constants/auth.js | 6 ++++ client/coral-framework/constants/user.js | 1 + .../containers/ChangeDisplayNameContainer.js | 18 +++++----- client/coral-framework/reducers/auth.js | 11 +++++++ client/coral-framework/translations.json | 8 +++-- 8 files changed, 63 insertions(+), 33 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index ee6f6a817..f99450901 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -3,22 +3,32 @@ import translations from './../translations'; const lang = new I18n(translations); import * as actions from '../constants/auth'; import coralApi, {base} from '../helpers/response'; -import {addItem} from './items'; +import {addItem, updateItem} from './items'; // Dialog Actions export const showSignInDialog = (offset = 0) => ({type: actions.SHOW_SIGNIN_DIALOG, offset}); export const hideSignInDialog = () => ({type: actions.HIDE_SIGNIN_DIALOG}); +export const createDisplayNameRequest = () => ({type: actions.CREATE_DISPLAYNAME_REQUEST}); export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG}); export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG}); -export const createDisplayName = (formData) => (dispatch) => { - coralApi('/users', {method: 'POST', body: formData}) + +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(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); + dispatch(updateItem(userId, 'displayName', formData.displayName, 'users')); + dispatch(updateDisplayName(formData.displayName)); }) - .catch(() => dispatch(createDisplayNameFailure(lang.t('createdisplay.errorCreate')))); + .catch(() => { + dispatch(createDisplayNameFailure(lang.t('createdisplay.errorCreate'))); + }); }; + const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index e289e9f60..692ec5648 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -203,7 +203,6 @@ export function postItem (item, type, id, mutate) { parent_id: null } }).then(({data}) => { - console.log('it workt'); console.log(data); }); diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-framework/components/CreateDisplayNameDialog.js index 8b61989aa..02c6eae4d 100644 --- a/client/coral-framework/components/CreateDisplayNameDialog.js +++ b/client/coral-framework/components/CreateDisplayNameDialog.js @@ -1,11 +1,13 @@ import React from 'react'; +import FormField from './FormField'; +import Button from 'coral-ui/components/Button'; import {Dialog} from 'coral-ui'; import styles from './styles.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const CreateDisplayNameDialog = ({open, handleClose, offset, loggedIn}) => ( +const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange}) => ( (

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

- Logged {loggedIn} + +
+

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

+
+ + +
); -//
-// -//
-// -//
-// - export default CreateDisplayNameDialog; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 27fa8890f..a61975420 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -4,6 +4,10 @@ export const CLEAN_STATE = 'CLEAN_STATE'; export const SHOW_SIGNIN_DIALOG = 'SHOW_SIGNIN_DIALOG'; export const HIDE_SIGNIN_DIALOG = 'HIDE_SIGNIN_DIALOG'; +export const CREATE_DISPLAYNAME_REQUEST = 'CREATE_DISPLAYNAME_REQUEST'; +export const CREATEDISPLAYNAME_SUCCESS = 'CREATEDISPLAYNAME_SUCCESS'; +export const CREATEDISPLAYNAME_FAILURE = 'CREATEDISPLAYNAME_FAILURE'; +export const CREATE_DISPLAYNAME = 'CREATE_DISPLAYNAME'; export const SHOW_CREATEDISPLAYNAME_DIALOG = 'SHOW_CREATEDISPLAYNAME_DIALOG'; export const HIDE_CREATEDISPLAYNAME_DIALOG = 'HIDE_CREATEDISPLAYNAME_DIALOG'; @@ -35,3 +39,5 @@ export const CHECK_LOGIN_SUCCESS = 'CHECK_LOGIN_SUCCESS'; export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE'; export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN'; + +export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME'; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 9c6f508fe..57f4e706b 100644 --- a/client/coral-framework/constants/user.js +++ b/client/coral-framework/constants/user.js @@ -5,3 +5,4 @@ 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'; export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; +export const UPDATE_DISPLAYNAME = 'UPDATE_DISPLAYNAME'; diff --git a/client/coral-framework/containers/ChangeDisplayNameContainer.js b/client/coral-framework/containers/ChangeDisplayNameContainer.js index 17e681388..e252119de 100644 --- a/client/coral-framework/containers/ChangeDisplayNameContainer.js +++ b/client/coral-framework/containers/ChangeDisplayNameContainer.js @@ -30,12 +30,13 @@ class ChangeDisplayNameContainer extends Component { super(props); this.state = this.initialState; this.handleChange = this.handleChange.bind(this); - this.handleSubmitForm = this.handleSubmitForm.bind(this); + this.handleSubmitDisplayName = this.handleSubmitDisplayName.bind(this); this.handleClose = this.handleClose.bind(this); this.addError = this.addError.bind(this); } componentDidMount() { + window.authCallback = this.props.facebookCallback; const {formData} = this.state; const errors = Object.keys(formData).reduce((map, prop) => { map[prop] = lang.t('createdisplay.requiredField'); @@ -70,7 +71,7 @@ class ChangeDisplayNameContainer extends Component { const {addError} = this; if (!value.length) { - addError(name, lang.t('displayName.requiredField')); + addError(name, lang.t('createdisplay.requiredField')); } else { const { [name]: prop, ...errors } = this.state.errors; // eslint-disable-line // Removes Error @@ -87,16 +88,16 @@ class ChangeDisplayNameContainer extends Component { this.setState({showErrors: show}); } - handleSubmitForm(e) { + handleSubmitDisplayName(e) { e.preventDefault(); const {errors} = this.state; const {validForm, invalidForm} = this.props; this.displayErrors(); if (this.isCompleted() && !Object.keys(errors).length) { - createDisplayName(this.state.formData); + this.props.createDisplayName(this.props.user.id, this.state.formData); validForm(); } else { - invalidForm(lang.t('signIn.checkTheForm')); + invalidForm(lang.t('createdisplay.checkTheForm')); } } @@ -105,15 +106,16 @@ class ChangeDisplayNameContainer extends Component { } render() { - const {loggedIn, auth, offset, user} = this.props; + const {loggedIn, auth, offset} = this.props; return (
@@ -127,7 +129,7 @@ const mapStateToProps = state => ({ }); const mapDispatchToProps = dispatch => ({ - createDisplayName: formData => dispatch(createDisplayName(formData)), + createDisplayName: (userid, formData) => dispatch(createDisplayName(userid, formData)), showCreateDisplayNameDialog: () => dispatch(showCreateDisplayNameDialog()), hideCreateDisplayNameDialog: () => dispatch(hideCreateDisplayNameDialog()), invalidForm: error => dispatch(invalidForm(error)), diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index ff0f72b4a..cb7b102df 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -43,6 +43,14 @@ export default function auth (state = initialState, action) { return state.merge(Map({ showCreateDisplayNameDialog: false })); + case actions.CREATEDISPLAYNAME_SUCCESS : + return state.merge(Map({ + showCreateDisplayNameDialog: false, + error: '' + })); + case actions.CREATEDISPLAYNAME_FAILURE : + return state + .set('error', action.error); case actions.CHANGE_VIEW : return state .set('error', '') @@ -109,6 +117,9 @@ export default function auth (state = initialState, action) { return state .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!') .set('passwordRequestSuccess', null); + case actions.UPDATE_DISPLAYNAME: + return state + .merge({'user': {'displayName': action.displayName}}); default : return state; } diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 5900aad3e..0e191d89d 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -10,7 +10,8 @@ "displayName": "Display Name", "save": "Save", "requiredField": "Required field", - "errorCreate": "Error when changing display name" + "errorCreate": "Error when changing display name", + "checkTheForm": "Invalid Form. Please, check the fields" }, "error": { "email": "Not a valid E-Mail", @@ -37,9 +38,10 @@ "createdisplay": { "writeyourusername": "Escribe tu nombre", "yourusername": "Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.", - "displayName": "Nombre", + "displayName": "Nombre a mostrar", "save": "Guardar", - "requiredField": "Campo necesario" + "requiredField": "Campo necesario", + "checkTheForm": "Formulario Invalido. Por favor, verifica los campos" }, "error": { "email": "No es un email válido", From 282c7dc4f5ff861e46fa29173b8d00e1ce2632bd Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 31 Jan 2017 14:04:31 -0800 Subject: [PATCH 06/77] Removes duplicate func and adds tests. --- client/coral-embed-stream/src/Embed.js | 4 ++-- services/users.js | 26 ++++++-------------------- test/services/users.js | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index b6fee1184..1eec07213 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -98,7 +98,7 @@ class Embed extends Component { Settings Configure Stream - {loggedIn && } + {loggedIn && user && } { asset.closedAt === null @@ -130,7 +130,7 @@ class Embed extends Component { :

{asset.settings.closedMessage}

} {!loggedIn && } - {loggedIn && } + {loggedIn && user && } { // displayName is valid - return UserModel.update({id}, { - $inc: {__v: 1}, - $set: { - displayName: displayName - } - }); - }); - } - /** * Creates local users. * @param {Array} users Users to create @@ -366,14 +354,12 @@ module.exports = class UsersService { */ static setDisplayName(id, displayName) { - // Check to see if that the displayName is not empty. - if (displayName.length < 1) { - - // User displayName is required! Error out here. - return Promise.reject(new Error('display name should not be empty')); - } - - return UserModel.update({id}, {$set: {'displayName': displayName}}); + return UsersService.isValidDisplayName(displayName) + .then((displayName) => { // displayName is valid + return UserModel.update( + {id}, + {$set: {'displayName': displayName}}); + }); } /** diff --git a/test/services/users.js b/test/services/users.js index e2e5655e4..ba2bedcc3 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -178,6 +178,25 @@ describe('services.UsersService', () => { }); }); + describe('#setDisplayName', () => { + it('should set the display name to a new unique one', () => { + return UsersService + .setDisplayName(mockUsers[0].id, 'maria') + .then(() => UsersService.findById(mockUsers[0].id)) + .then((user) => { + expect(user).to.have.property('displayName', 'maria'); + }); + }); + + it('should return an error when the displayName is not unique', () => { + return UsersService + .setDisplayName(mockUsers[0].id, 'marvel') + .catch((error) => { + expect(error).to.not.be.null; + }); + }); + }); + describe('#ban', () => { it('should set the status to banned', () => { return UsersService From 6a4315d60826fd0f2bfd4a884656cac0f1dd5fa3 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 1 Feb 2017 10:49:16 -0300 Subject: [PATCH 07/77] Working Install Wizard --- client/coral-admin/src/actions/install.js | 1 + client/coral-admin/src/constants/install.js | 1 + .../containers/Install/InstallContainer.js | 29 +++++++++++++------ .../components/Steps/AddOrganizationName.js | 19 ++++++++++++ .../Install/components/Steps/InitialStep.js | 20 +++++++++++++ client/coral-admin/src/reducers/install.js | 3 ++ client/coral-ui/components/Wizard.js | 29 +++++++++++++++++++ client/coral-ui/index.js | 1 + 8 files changed, 94 insertions(+), 9 deletions(-) create mode 100644 client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js create mode 100644 client/coral-admin/src/containers/Install/components/Steps/InitialStep.js create mode 100644 client/coral-ui/components/Wizard.js diff --git a/client/coral-admin/src/actions/install.js b/client/coral-admin/src/actions/install.js index 460a36e23..abaaeceb7 100644 --- a/client/coral-admin/src/actions/install.js +++ b/client/coral-admin/src/actions/install.js @@ -2,3 +2,4 @@ import * as actions from '../constants/install'; export const nextStep = () => ({type: actions.NEXT_STEP}); export const previousStep = () => ({type: actions.PREVIOUS_STEP}); +export const goToStep = step => ({type: actions.GO_TO_STEP, step}); diff --git a/client/coral-admin/src/constants/install.js b/client/coral-admin/src/constants/install.js index f15a39ed4..d33ec4657 100644 --- a/client/coral-admin/src/constants/install.js +++ b/client/coral-admin/src/constants/install.js @@ -1,2 +1,3 @@ export const NEXT_STEP = 'NEXT_STEP'; export const PREVIOUS_STEP = 'PREVIOUS_STEP'; +export const GO_TO_STEP = 'GO_TO_STEP'; diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js index de296acc0..bda32bde2 100644 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -1,19 +1,29 @@ import React from 'react'; import {connect} from 'react-redux'; import styles from './style.css'; -import {nextStep, previousStep} from '../../actions/install' +import {nextStep, previousStep, goToStep} from '../../actions/install'; +import {Button, Wizard} from 'coral-ui'; + +import InitialStep from './components/Steps/InitialStep' +import AddOrganizationName from './components/Steps/AddOrganizationName' const InstallContainer = props => { - const {nextStep, previousStep, install} = props; + const {nextStep, previousStep, goToStep, install} = props; return (
- Install: -

- {install.step} -

- - +

{install.step}

+ + + + +
); }; @@ -24,7 +34,8 @@ const mapStateToProps = state => ({ const mapDispatchToProps = dispatch => ({ nextStep: () => dispatch(nextStep()), - previousStep: () => dispatch(previousStep()) + previousStep: () => dispatch(previousStep()), + goToStep: step => dispatch(goToStep(step)) }); 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..1af4b9839 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js @@ -0,0 +1,19 @@ +import React from 'react'; +// import styles from './style.css'; +import {Button} from 'coral-ui'; + +const InitialStep = props => { + const {nextStep, previousStep} = props; + return ( +
+

Welcome to the Coral Project

+

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

+ +
+ ); +}; + +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..9efdf4ad0 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js @@ -0,0 +1,20 @@ +import React from 'react'; +// import styles from './style.css'; +import {Button} from 'coral-ui'; + +const InitialStep = props => { + const {nextStep} = props; + return ( +
+

Welcome to the Coral Project

+

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

+ +
+ ); +}; + +export default InitialStep; diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js index 1b9ae5b28..95febb887 100644 --- a/client/coral-admin/src/reducers/install.js +++ b/client/coral-admin/src/reducers/install.js @@ -15,6 +15,9 @@ export default function auth (state = initialState, action) { case actions.PREVIOUS_STEP: return state .set('step', state.get('step') - 1); + case actions.GO_TO_STEP: + return state + .set('step', action.step); default : return state; } diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js new file mode 100644 index 000000000..1ba6e56a6 --- /dev/null +++ b/client/coral-ui/components/Wizard.js @@ -0,0 +1,29 @@ +import React, {PropTypes} from 'react'; + +const Wizard = (props) => { + const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props; + return ( +
+ {React.Children.toArray(children) + .filter((child, i) => i === currentStep) + .map((child, i) => + React.cloneElement(child, { + i, + currentStep, + nextStep, + previousStep, + goToStep + }) + )} +
+ ); +}; + +Wizard.propTypes = { + currentStep: PropTypes.number.isRequired, + nextStep: PropTypes.func.isRequired, + previousStep: PropTypes.func.isRequired, + goToStep: PropTypes.func.isRequired +} + +export default Wizard; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 23d5d876a..b58c2a74a 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -14,3 +14,4 @@ export {default as List} from './components/List'; export {default as Item} from './components/Item'; export {default as Card} from './components/Card'; export {default as Pager} from './components/Pager'; +export {default as Wizard} from './components/Wizard'; From 12070309d6bd5db9fdf5e26e702c4fb90327b559 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 1 Feb 2017 12:11:15 -0300 Subject: [PATCH 08/77] Wizard Nav --- .../containers/Install/InstallContainer.js | 29 +++-- .../components/Steps/AddOrganizationName.js | 13 ++- .../Install/components/Steps/InitialStep.js | 13 +-- .../Install/components/Steps/style.css | 30 +++++ .../src/containers/Install/style.css | 12 ++ client/coral-admin/src/reducers/install.js | 1 - client/coral-ui/components/Wizard.js | 4 +- client/coral-ui/components/WizardNav.css | 109 ++++++++++++++++++ client/coral-ui/components/WizardNav.js | 29 +++++ client/coral-ui/index.js | 1 + 10 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 client/coral-admin/src/containers/Install/components/Steps/style.css create mode 100644 client/coral-ui/components/WizardNav.css create mode 100644 client/coral-ui/components/WizardNav.js diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js index bda32bde2..8ed0bbf69 100644 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -2,24 +2,18 @@ import React from 'react'; import {connect} from 'react-redux'; import styles from './style.css'; import {nextStep, previousStep, goToStep} from '../../actions/install'; -import {Button, Wizard} from 'coral-ui'; +import {Wizard, WizardNav} from 'coral-ui'; -import InitialStep from './components/Steps/InitialStep' -import AddOrganizationName from './components/Steps/AddOrganizationName' +import InitialStep from './components/Steps/InitialStep'; +import AddOrganizationName from './components/Steps/AddOrganizationName'; const InstallContainer = props => { const {nextStep, previousStep, goToStep, install} = props; return (
-

{install.step}

- +

Welcome to the Coral Project

+ { install.step !== 0 ? : null } @@ -28,6 +22,19 @@ const InstallContainer = props => { ); }; +const wizardNavitems = [{ + text: '1. Add Organization Name', + step: 1 +}, +{ + text: '2. Create your account', + step: 2 +}, +{ + text: '3. Invite team members', + step: 3 +}]; + const mapStateToProps = state => ({ install: state.install.toJS() }); diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js index 1af4b9839..74f2c3016 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js +++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js @@ -1,17 +1,20 @@ import React from 'react'; -// import styles from './style.css'; +import styles from './style.css'; import {Button} from 'coral-ui'; const InitialStep = props => { - const {nextStep, previousStep} = props; + const {nextStep} = props; return ( -
-

Welcome to the Coral Project

+

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

- +
+ + + +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js index 9efdf4ad0..95043b515 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js +++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js @@ -1,18 +1,17 @@ import React from 'react'; -// import styles from './style.css'; +import styles from './style.css'; import {Button} from 'coral-ui'; const InitialStep = props => { const {nextStep} = props; return ( -
-

Welcome to the Coral Project

+

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

- +
); }; 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..5284b3470 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/style.css @@ -0,0 +1,30 @@ +.step { + + p { + max-width: 450px; + margin: 10px auto 30px; + } + + > button { + min-width: 140px; + } + + form { + max-width: 300px; + margin: 30px auto; + + label { + text-align: left; + display: block; + } + + input { + border: solid 1px rgba(0, 0, 0, 0.23); + padding: 10px 2px; + border-radius: 3px; + width: 100%; + box-sizing: border-box; + margin: 10px 0; + } + } +} diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css index e69de29bb..90c62c20e 100644 --- a/client/coral-admin/src/containers/Install/style.css +++ b/client/coral-admin/src/containers/Install/style.css @@ -0,0 +1,12 @@ +.Install { + max-width: 900px; + margin: 0 auto; + text-align: center; + padding: 40px 0; + + h2 { + font-size: 2em; + font-weight: 500; + margin: 0; + } +} diff --git a/client/coral-admin/src/reducers/install.js b/client/coral-admin/src/reducers/install.js index 95febb887..d97c7f236 100644 --- a/client/coral-admin/src/reducers/install.js +++ b/client/coral-admin/src/reducers/install.js @@ -9,7 +9,6 @@ const initialState = Map({ export default function auth (state = initialState, action) { switch (action.type) { case actions.NEXT_STEP: - console.log(state) return state .set('step', state.get('step') + 1); case actions.PREVIOUS_STEP: diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js index 1ba6e56a6..e598b3c30 100644 --- a/client/coral-ui/components/Wizard.js +++ b/client/coral-ui/components/Wizard.js @@ -3,7 +3,7 @@ import React, {PropTypes} from 'react'; const Wizard = (props) => { const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props; return ( -
+
{React.Children.toArray(children) .filter((child, i) => i === currentStep) .map((child, i) => @@ -15,7 +15,7 @@ const Wizard = (props) => { goToStep }) )} -
+ ); }; diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css new file mode 100644 index 000000000..12b9b3841 --- /dev/null +++ b/client/coral-ui/components/WizardNav.css @@ -0,0 +1,109 @@ +.WizardNav { + ul { + list-style: none; + } + + li { + border: solid 1px #DFDFDF; + background-color: #F0F0F0; + display: inline-block; + padding: 10px; + margin-right: 10px; + color: #A7A7A7; + position: relative; + padding-left: 40px; + + &:hover { + cursor: pointer; + } + + &.active { + background-color: #FFFFFF; + color: #636363; + font-weight: 500; + + span { + &::after { + border-color: transparent transparent transparent #FFFFFF; + } + } + } + + span { + padding: 10px 20px; + margin-right: 10px; + position: absolute; + top: -1px; + right: -51px; + z-index: 10; + + &::before { + content: ""; + display: inline-block; + position: absolute; + border: 23px solid #DFDFDF; + border-color: transparent transparent transparent #DFDFDF; + top: 0; + left: 0; + } + + &::after { + content: ""; + display: inline-block; + position: absolute; + border: 23px solid white; + border-color: transparent transparent transparent #f0f0f0; + top: 0; + left: -1px; + } + } + + &::before { + content: ""; + display: inline-block; + position: absolute; + border: 23px solid #DFDFDF; + border-color: transparent transparent transparent #DFDFDF; + top: 0; + left: 0; + } + + &::after { + content: ""; + display: inline-block; + position: absolute; + border: 23px solid white; + border-color: transparent transparent transparent #fafafa; + top: 0; + left: -1px; + } +/* + &::before { + position: absolute; + top: 0; + left: -1px; + display: block; + border-left: 23px solid white; + border-top: 23px solid transparent; + border-bottom: 23px solid transparent; + width: 0; + height: 0; + content: " "; + } + + &::after{ + z-index: 1; + position: absolute; + top: 0; + right: -23px; + display: block; + border-left: 23px solid #f0f0f0; + border-top: 23px solid transparent; + border-bottom: 23px solid transparent; + width: 0; + height: 0; + content: " "; + }*/ + + } +} diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js new file mode 100644 index 000000000..200adb216 --- /dev/null +++ b/client/coral-ui/components/WizardNav.js @@ -0,0 +1,29 @@ +import React, {PropTypes} from 'react'; +import styles from './WizardNav.css'; + +const WizardNav = props => { + const {goToStep, currentStep, items} = props; + return ( + + ); +}; + +WizardNav.propTypes = { + goToStep: PropTypes.func.isRequired, + currentStep: PropTypes.number.isRequired +}; + +export default WizardNav; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index b58c2a74a..69f63e397 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -15,3 +15,4 @@ export {default as Item} from './components/Item'; export {default as Card} from './components/Card'; export {default as Pager} from './components/Pager'; export {default as Wizard} from './components/Wizard'; +export {default as WizardNav} from './components/WizardNav'; From caa04de09df4c41b47c8e58301ce37889c5ab351 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 1 Feb 2017 12:12:28 -0300 Subject: [PATCH 09/77] linting and proptypes --- client/coral-ui/components/Wizard.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-ui/components/Wizard.js b/client/coral-ui/components/Wizard.js index e598b3c30..429bce72c 100644 --- a/client/coral-ui/components/Wizard.js +++ b/client/coral-ui/components/Wizard.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; const Wizard = (props) => { - const {children, currentStep, nextStep, previousStep, goToStep, className = ''} = props; + const {children, currentStep, nextStep, previousStep, goToStep} = props; return (
{React.Children.toArray(children) @@ -24,6 +24,6 @@ Wizard.propTypes = { nextStep: PropTypes.func.isRequired, previousStep: PropTypes.func.isRequired, goToStep: PropTypes.func.isRequired -} +}; export default Wizard; From 6c01a37f6ab2c5ac20ef5c1caad66e934180dbc9 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 11:48:16 -0500 Subject: [PATCH 10/77] Removing user bio. --- client/coral-embed-stream/src/Comment.js | 9 +-- client/coral-plugin-author-name/AuthorName.js | 27 +++++---- .../coral-settings/components/NotLoggedIn.js | 1 - .../containers/SettingsContainer.js | 55 ++++++++++--------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 529420941..a2a4c4c6e 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -92,14 +92,7 @@ class Comment extends React.Component { style={{marginLeft: depth * 30}}>
+ author={comment.user}/>
diff --git a/client/coral-plugin-author-name/AuthorName.js b/client/coral-plugin-author-name/AuthorName.js index fcf464403..aa48f35d8 100644 --- a/client/coral-plugin-author-name/AuthorName.js +++ b/client/coral-plugin-author-name/AuthorName.js @@ -1,6 +1,7 @@ import React, {Component} from 'react'; -import {Tooltip} from 'coral-ui'; -import FlagBio from '../coral-plugin-flags/FlagBio'; + +// import {Tooltip} from 'coral-ui'; +// import FlagBio from '../coral-plugin-flags/FlagBio'; const packagename = 'coral-plugin-author-name'; export default class AuthorName extends Component { @@ -29,7 +30,8 @@ export default class AuthorName extends Component { render () { const {author} = this.props; - const {showTooltip} = this.state; + + // const {showTooltip} = this.state; return (
{author && author.name} - { showTooltip && author.settings.bio && -
- {author.settings.bio} -
-
- -
-
+ { + + // Hiding bio until moderation is addressed + // showTooltip && author.settings.bio && + //
+ // {author.settings.bio} + //
+ //
+ // + //
+ //
}
); 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..4398ed5c6 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 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( From 37975574ea0f214b1097134dc2962055b8f00671 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 12:26:01 -0500 Subject: [PATCH 11/77] Switching rejected usernames from suspended to banned. --- .../src/containers/ModerationQueue/ModerationContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index eef855df6..aa754b4ac 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -118,7 +118,7 @@ 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(sendNotificationEmail(userId, subject, text))) .then(() => dispatch(fetchModerationQueueComments())) , From 7866d1f8cdd79f6591edbad6cb25d96d4a90bd29 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Wed, 1 Feb 2017 15:17:15 -0300 Subject: [PATCH 12/77] Done Wizard mEnu --- client/coral-admin/src/AppRouter.js | 18 ++++---- .../coral-admin/src/components/ui/Header.js | 35 ++++++++++----- .../containers/Install/InstallContainer.js | 25 +++++++---- .../components/Steps/CreateYourAccount.js | 32 +++++++++++++ .../Install/components/Steps/FinalStep.js | 19 ++++++++ .../components/Steps/InviteTeamMembers.js | 35 +++++++++++++++ .../Install/components/Steps/style.css | 5 +++ client/coral-ui/components/WizardNav.css | 45 +++++++------------ client/coral-ui/components/WizardNav.js | 2 +- 9 files changed, 158 insertions(+), 58 deletions(-) create mode 100644 client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js create mode 100644 client/coral-admin/src/containers/Install/components/Steps/FinalStep.js create mode 100644 client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index ebb79b4e1..bebe6a19b 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -10,14 +10,16 @@ import LayoutContainer from 'containers/LayoutContainer'; import InstallContainer from 'containers/Install/InstallContainer'; const routes = ( - - - - - - - - +
+ + + + + + + + +
); const AppRouter = () => ; diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 4e2b391c8..abfb0ff13 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -6,19 +6,32 @@ 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')} - {lang.t('configure.streams')} - + { + !restricted ? + + + {lang.t('configure.moderate')} + + + {lang.t('configure.community')} + + + {lang.t('configure.configure')} + + + {lang.t('configure.streams')} + + + : + null + }
  • diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js index 8ed0bbf69..aac4654d3 100644 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -3,22 +3,31 @@ import {connect} from 'react-redux'; import styles from './style.css'; import {nextStep, previousStep, goToStep} from '../../actions/install'; import {Wizard, WizardNav} from 'coral-ui'; +import {Layout} from '../../components/ui/Layout'; import InitialStep from './components/Steps/InitialStep'; import AddOrganizationName from './components/Steps/AddOrganizationName'; +import CreateYourAccount from './components/Steps/CreateYourAccount'; +import InviteTeamMembers from './components/Steps/InviteTeamMembers'; +import FinalStep from './components/Steps/FinalStep'; const InstallContainer = props => { const {nextStep, previousStep, goToStep, install} = props; return ( -
    -

    Welcome to the Coral Project

    - { install.step !== 0 ? : null } - - - - -
    + +
    +

    Welcome to the Coral Project

    + { install.step !== 0 ? : null } + + + + + + + +
    +
    ); }; 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..cb29c3429 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js @@ -0,0 +1,32 @@ +import React from 'react'; +import styles from './style.css'; +import {Button} from 'coral-ui'; + +const InitialStep = props => { + const {nextStep} = props; + return ( +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    +
    + ); +}; + +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..0b58508a0 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/FinalStep.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 ( +
    +

    + Thanks for installing Talk! We sent an email to your team member. + While they finish setting up their account, start engaging with your readers now. +

    + + +
    + ); +}; + +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..99ef84d19 --- /dev/null +++ b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js @@ -0,0 +1,35 @@ +import React from 'react'; +import styles from './style.css'; +import {Button} 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 index 5284b3470..c7d41aef0 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/style.css +++ b/client/coral-admin/src/containers/Install/components/Steps/style.css @@ -25,6 +25,11 @@ width: 100%; box-sizing: border-box; margin: 10px 0; + transition: border-color 0.2 ease; + + &:focus { + border-color: black; + } } } } diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css index 12b9b3841..90d07af09 100644 --- a/client/coral-ui/components/WizardNav.css +++ b/client/coral-ui/components/WizardNav.css @@ -12,11 +12,23 @@ color: #A7A7A7; position: relative; padding-left: 40px; - + border-radius: 1px; + &:hover { cursor: pointer; } + &.done { + border-color: #00796B; + background-color: #00796B; + color: #ffffff; + span { + &::after { + border-color: transparent transparent transparent #00796B; + } + } + } + &.active { background-color: #FFFFFF; color: #636363; @@ -64,7 +76,7 @@ position: absolute; border: 23px solid #DFDFDF; border-color: transparent transparent transparent #DFDFDF; - top: 0; + top: -1px; left: 0; } @@ -74,36 +86,9 @@ position: absolute; border: 23px solid white; border-color: transparent transparent transparent #fafafa; - top: 0; + top: -1px; left: -1px; } -/* - &::before { - position: absolute; - top: 0; - left: -1px; - display: block; - border-left: 23px solid white; - border-top: 23px solid transparent; - border-bottom: 23px solid transparent; - width: 0; - height: 0; - content: " "; - } - - &::after{ - z-index: 1; - position: absolute; - top: 0; - right: -23px; - display: block; - border-left: 23px solid #f0f0f0; - border-top: 23px solid transparent; - border-bottom: 23px solid transparent; - width: 0; - height: 0; - content: " "; - }*/ } } diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js index 200adb216..b5a8db549 100644 --- a/client/coral-ui/components/WizardNav.js +++ b/client/coral-ui/components/WizardNav.js @@ -10,7 +10,7 @@ const WizardNav = props => { items.map((item, i) => (
  • goToStep(item.step)}> {item.text}
  • From c9b12fc5d649750b745690fcc53b079a45b1214b Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 1 Feb 2017 10:46:32 -0800 Subject: [PATCH 13/77] Thanks git-godesses for the log on finding a bad remove when merging. --- client/coral-framework/actions/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index ab8a7caea..b0574e04c 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -91,6 +91,7 @@ export const facebookCallback = (err, data) => dispatch => { const user = JSON.parse(data); dispatch(signInFacebookSuccess(user)); dispatch(hideSignInDialog()); + dispatch(showCreateDisplayNameDialog()); } catch (err) { dispatch(signInFacebookFailure(err)); return; From a8e4ce466d26b8eef1735b4fae3336fca58e1019 Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 1 Feb 2017 12:09:27 -0800 Subject: [PATCH 14/77] Adds validation to the dialog. --- client/coral-framework/components/Alert.js | 13 +++++++++++++ .../components/CreateDisplayNameDialog.js | 4 +++- .../containers/ChangeDisplayNameContainer.js | 7 +++++-- 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 client/coral-framework/components/Alert.js diff --git a/client/coral-framework/components/Alert.js b/client/coral-framework/components/Alert.js new file mode 100644 index 000000000..019c3456a --- /dev/null +++ b/client/coral-framework/components/Alert.js @@ -0,0 +1,13 @@ +import React from 'react'; +import styles from './styles.css'; + +const Alert = ({cStyle = 'error', children, className, ...props}) => ( +
    + {children} +
    +); + +export default Alert; diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-framework/components/CreateDisplayNameDialog.js index 02c6eae4d..8b4a99f9a 100644 --- a/client/coral-framework/components/CreateDisplayNameDialog.js +++ b/client/coral-framework/components/CreateDisplayNameDialog.js @@ -1,5 +1,6 @@ import React from 'react'; import FormField from './FormField'; +import Alert from './Alert'; import Button from 'coral-ui/components/Button'; import {Dialog} from 'coral-ui'; import styles from './styles.css'; @@ -7,7 +8,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange}) => ( +const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSubmitDisplayName, handleChange, ...props}) => (

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

    + { props.auth.error && {props.auth.error} }
    Date: Wed, 1 Feb 2017 12:34:02 -0800 Subject: [PATCH 15/77] Adds errors on field when not validated. --- .../coral-framework/components/CreateDisplayNameDialog.js | 1 + .../containers/ChangeDisplayNameContainer.js | 6 ------ client/coral-framework/translations.json | 7 +++++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-framework/components/CreateDisplayNameDialog.js index 8b4a99f9a..6798bad5c 100644 --- a/client/coral-framework/components/CreateDisplayNameDialog.js +++ b/client/coral-framework/components/CreateDisplayNameDialog.js @@ -28,6 +28,7 @@ const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSub

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

    { props.auth.error && {props.auth.error} } + { props.errors.displayName && {lang.t('createdisplay.specialCharacters')} } { - map[prop] = lang.t('createdisplay.requiredField'); - return map; - }, {}); - this.setState({errors}); } handleChange(e) { diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index c08c7e717..88886f8d1 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -11,7 +11,8 @@ "save": "Save", "requiredField": "Required field", "errorCreate": "Error when changing display name", - "checkTheForm": "Invalid Form. Please, check the fields" + "checkTheForm": "Invalid Form. Please, check the fields", + "specialCharacters": "Display names can contain letters, numbers and _ only" }, "error": { "emailNotVerified": "Email address {0} not verified.", @@ -42,7 +43,9 @@ "displayName": "Nombre a mostrar", "save": "Guardar", "requiredField": "Campo necesario", - "checkTheForm": "Formulario Invalido. Por favor, verifica los campos" + "errorCreate": "Hubo un error al cambiar el nombre de usuario", + "checkTheForm": "Formulario Invalido. Por favor, verifica los campos", + "specialCharacters": "Sólo pueden contener letras, números y _" }, "error": { "emailNotVerified": "Dirección de correo electrónico {0} no verificada.", From b2430eaf60437ee498a153c934af0cf3928989d4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 15:55:33 -0500 Subject: [PATCH 16/77] Adding displayname editing and appropriate toggle. --- .../src/components/SuspendUserModal.js | 2 +- graph/resolvers/root_mutation.js | 3 - graph/typeDefs.graphql | 18 +---- models/user.js | 6 ++ routes/api/account/index.js | 13 +-- routes/api/users/index.js | 11 ++- services/users.js | 31 ++++++++ test/routes/api/user/index.js | 79 +++++++++++++++++++ test/services/users.js | 52 ++++++++++++ 9 files changed, 185 insertions(+), 30 deletions(-) diff --git a/client/coral-admin/src/components/SuspendUserModal.js b/client/coral-admin/src/components/SuspendUserModal.js index 5ad8c1616..5afb8ffee 100644 --- a/client/coral-admin/src/components/SuspendUserModal.js +++ b/client/coral-admin/src/components/SuspendUserModal.js @@ -36,7 +36,7 @@ class SuspendUserModal extends Component { } componentDidMount() { - const about = this.props.actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username'); + const about = lang.t('suspenduser.username'); this.setState({email: lang.t('suspenduser.email', about)}); } diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ddc8e4223..ef46b839a 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -8,9 +8,6 @@ const RootMutation = { deleteAction(_, {id}, {mutators: {Action}}) { return Action.delete({id}); }, - updateUserSettings(_, {settings}, {mutators: {User}}) { - return User.updateSettings(settings); - } }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 744a53252..1c1470f58 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -10,11 +10,6 @@ enum SORT_ORDER { # Date represented as an ISO8601 string. scalar Date -type UserSettings { - # bio of the user. - bio: String -} - input CommentsQuery { # current status of a comment. statuses: [COMMENT_STATUS] @@ -22,7 +17,7 @@ input CommentsQuery { # asset that a comment is on. asset_id: ID - # the parent of the comment that we want to retrive. + # the parent of the comment that we want to retrieve. parent_id: ID # comments returned will only be ones which have at least one action of this @@ -61,8 +56,8 @@ type User { # the current roles of the user. roles: [USER_ROLES] - # settings for a user. - settings: UserSettings + # determines whether the user can edit their username + canEditName: Boolean # returns all comments based on a query. comments(query: CommentsQuery): [Comment] @@ -205,11 +200,6 @@ input CreateActionInput { item_id: ID! } -input UpdateUserSettingsInput { - # user bio - bio: String! -} - type RootMutation { # creates a comment on the asset. createComment(asset_id: ID!, parent_id: ID, body: String!): Comment @@ -220,8 +210,6 @@ type RootMutation { # delete an action based on the action id. deleteAction(id: ID!): Boolean - # updates a user's settings, it will return if the query was successful. - updateUserSettings(settings: UpdateUserSettingsInput!): Boolean } schema { diff --git a/models/user.js b/models/user.js index 8b18ee983..7de99d211 100644 --- a/models/user.js +++ b/models/user.js @@ -96,6 +96,12 @@ const UserSchema = new mongoose.Schema({ default: 'ACTIVE' }, + // Determines whether the user can edit their username. + canEditName: { + type: Boolean, + default: false + }, + // User's settings settings: { bio: { diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 57647a89d..4dd6add7c 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -115,20 +115,13 @@ router.put('/password/reset', (req, res, next) => { }); }); -router.put('/settings', authorization.needed(), (req, res, next) => { - - const { - bio - } = req.body; - +router.put('/displayname', authorization.needed(), (req, res, next) => { UsersService - .updateSettings(req.user.id, {bio}) + .editUsername(req.user.id, req.body.displayName) .then(() => { res.status(204).end(); }) - .catch((err) => { - next(err); - }); + .catch(next); }); module.exports = router; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index da47007ee..8b22adb36 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -57,7 +57,16 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next) .catch(next); }); -router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => { +router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => { + UsersService + .toggleUsernameEdit(req.params.user_id, true) + .then(() => { + res.status(204).end(); + }) + .catch(next); +}); + +router.post('/:user_id/email', authorization.needed('ADMIN'), (req, res, next) => { UsersService.findById(req.params.user_id) .then(user => { let localProfile = user.profiles.find((profile) => profile.provider === 'local'); diff --git a/services/users.js b/services/users.js index 635e39e47..42278f1c4 100644 --- a/services/users.js +++ b/services/users.js @@ -613,4 +613,35 @@ module.exports = class UsersService { return UserModel.find({status: 'PENDING'}); } + /** + * Gives the user the ability to edit their username. + * @param {String} id the id of the user to be toggled. + * @param {Boolean} canEditName sets whether the user can edit their name. + * @return {Promise} + */ + static toggleUsernameEdit(id, canEditName) { + return UserModel.update({id}, { + $set: {canEditName} + }); + } + + /** + * Gives the user the ability to edit their username. + * @param {String} id the id of the user to be enabled. + * @param {String} displayName The new displayname for the user. + * @return {Promise} + */ + static editUsername(id, displayName) { + return UserModel.findOne({id}) + .then((user) => { + return user.canEditName ? + UserModel.update({id}, { + $set: { + displayName: displayName.toLowerCase(), + canEditName: false + } + }) + : Promise.reject(new Error('Display name editing disabled for this account.')); + }); + } }; diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index d1771726d..2fdf937b5 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -83,3 +83,82 @@ describe('/api/v1/users/:user_id/actions', () => { }); }); }); + +describe('/api/v1/users/:user_id/username-enable', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#post', () => { + it('it should enable a user to edit their username', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + }); +}); + +describe('/api/v1/account/displayname', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#post', () => { + it('it should enable a user to edit their username if canEditName is enabled', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({displayName: 'MojoJojo'})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + + it('it should return an error if the wrong user tries to edit a username', (done) => { + chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: 'wrongid', roles: []})) + .send({displayName: 'MojoJojo'})) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { + chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({username: 'MojoJojo'}) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); +}); diff --git a/test/services/users.js b/test/services/users.js index e2e5655e4..ef027a317 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -208,4 +208,56 @@ describe('services.UsersService', () => { }); }); }); + + describe('#toggleUsernameEdit', () => { + it('should toggle the canEditName field', () => { + return UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.findById(mockUsers[0].id)) + .then((user) => { + expect(user).to.have.property('canEditName', true); + }); + }); + }); + + describe('#editUsername', () => { + it('should let the user edit their username if the proper toggle is set', () => { + return UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo')) + .then(() => UsersService.findById(mockUsers[0].id)) + .then((user) => { + expect(user).to.have.property('displayName', 'jojo'); + expect(user).to.have.property('canEditName', false); + }); + }); + + it('should return an error if canEditName is false', (done) => { + UsersService + .editUsername(mockUsers[0].id, 'Jojo') + .then(() => UsersService.findById(mockUsers[0].id)) + .then(() => { + done(new Error('Error expected')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('should return an error if the username is already taken', (done) => { + UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel')) + .then(() => UsersService.findById(mockUsers[0].id)) + .then(() => { + done(new Error('Error expected')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); + }); From 5dca1611bcbe182b09064fda3e9eb2992c61c631 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 15:58:07 -0500 Subject: [PATCH 17/77] Moving account endpoint tests to account. --- test/routes/api/account/index.js | 71 ++++++++++++++++++++++++++++++++ test/routes/api/user/index.js | 57 ------------------------- 2 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 test/routes/api/account/index.js diff --git a/test/routes/api/account/index.js b/test/routes/api/account/index.js new file mode 100644 index 000000000..5b9acfa9d --- /dev/null +++ b/test/routes/api/account/index.js @@ -0,0 +1,71 @@ +const passport = require('../../../passport'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +const SettingsService = require('../../../../services/settings'); +const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}}; + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +const UsersService = require('../../../../services/users'); + +describe('/api/v1/account/displayname', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#put', () => { + it('it should enable a user to edit their username if canEditName is enabled', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({displayName: 'MojoJojo'})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + + it('it should return an error if the wrong user tries to edit a username', (done) => { + chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: 'wrongid', roles: []})) + .send({displayName: 'MojoJojo'})) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { + chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({username: 'MojoJojo'}) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); +}); diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index 2fdf937b5..4de799df6 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -105,60 +105,3 @@ describe('/api/v1/users/:user_id/username-enable', () => { }); }); }); - -describe('/api/v1/account/displayname', () => { - let mockUser; - - beforeEach(() => SettingsService.init(settings).then(() => { - return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); - }) - .then((user) => { - mockUser = user; - })); - - describe('#post', () => { - it('it should enable a user to edit their username if canEditName is enabled', () => { - return chai.request(app) - .post(`/api/v1/users/${mockUser.id}/username-enable`) - .set(passport.inject({id: '456', roles: ['ADMIN']})) - .then(() => chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: mockUser.id, roles: []})) - .send({displayName: 'MojoJojo'})) - .then((res) => { - expect(res).to.have.status(204); - }); - }); - - it('it should return an error if the wrong user tries to edit a username', (done) => { - chai.request(app) - .post(`/api/v1/users/${mockUser.id}/username-enable`) - .set(passport.inject({id: '456', roles: ['ADMIN']})) - .then(() => chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: 'wrongid', roles: []})) - .send({displayName: 'MojoJojo'})) - .then(() => { - done(new Error('Exected Error')); - }) - .catch((err) => { - expect(err).to.be.truthy; - done(); - }); - }); - - it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { - chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: mockUser.id, roles: []})) - .send({username: 'MojoJojo'}) - .then(() => { - done(new Error('Exected Error')); - }) - .catch((err) => { - expect(err).to.be.truthy; - done(); - }); - }); - }); -}); From 7e907e18f268bb910396001e8bf68321803781d6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 16:19:42 -0500 Subject: [PATCH 18/77] Enabling canEditName when suspending user. --- client/coral-admin/src/actions/users.js | 8 ++++++ .../src/components/ModerationList.js | 4 +-- .../src/components/{UserAction.js => User.js} | 25 ++----------------- client/coral-admin/src/constants/users.js | 1 + .../ModerationQueue/ModerationContainer.js | 3 ++- .../graphql/fragments/commentView.graphql | 3 --- 6 files changed, 15 insertions(+), 29 deletions(-) rename client/coral-admin/src/components/{UserAction.js => User.js} (75%) 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..c2ecab9a2 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 @@ -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) : []; @@ -48,32 +47,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/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/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index aa754b4ac..4f56a6c69 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'; @@ -119,6 +119,7 @@ const mapDispatchToProps = dispatch => { dispatch(fetchModerationQueueComments()); }), 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-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 From 0c360e62a64d8b5a98c80ae033ffd6c47ae25a22 Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 1 Feb 2017 13:20:17 -0800 Subject: [PATCH 19/77] Display errors that come from the backend. --- client/coral-framework/actions/auth.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index b0574e04c..bd3b81fdf 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -22,8 +22,8 @@ export const createDisplayName = (userId, formData) => dispatch => { dispatch(hideCreateDisplayNameDialog()); dispatch(updateDisplayName(formData.displayName)); }) - .catch(() => { - dispatch(createDisplayNameFailure(lang.t('createdisplay.errorCreate'))); + .catch(error => { + dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`))); }); }; From 4df09ffbd2728b978cfdb875db833a7cf7d184fb Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 1 Feb 2017 13:29:47 -0800 Subject: [PATCH 20/77] Validation for the field underneath. --- .../coral-framework/components/CreateDisplayNameDialog.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-framework/components/CreateDisplayNameDialog.js index 6798bad5c..ee3e133e6 100644 --- a/client/coral-framework/components/CreateDisplayNameDialog.js +++ b/client/coral-framework/components/CreateDisplayNameDialog.js @@ -28,7 +28,6 @@ const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSub

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

{ props.auth.error && {props.auth.error} } - { props.errors.displayName && {lang.t('createdisplay.specialCharacters')} } - - + { props.errors.displayName && {lang.t('createdisplay.specialCharacters')} } +
+ +
+
From e3da45daeb59523db9f3a17c4270f2696e0007b8 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 16:38:01 -0500 Subject: [PATCH 21/77] Displaying appropriate suspension message. --- client/coral-embed-stream/src/Embed.js | 8 +++++++- client/coral-framework/components/SuspendedAccount.js | 8 ++++++-- client/coral-framework/translations.json | 6 ++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 2a7057cca..b57bc799f 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -96,6 +96,8 @@ class Embed extends Component { const openStream = closedAt === null; + const banned = user && user.status === 'BANNED'; + const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 200 } : {}; @@ -118,7 +120,11 @@ class Embed extends Component { content={asset.settings.infoBoxContent} enable={asset.settings.infoBoxEnable} /> - }> + + }> { user ? ( +export default ({canEditName}) => (
- {lang.t('suspendedAccountMsg')} + { + canEditName ? + lang.t('editNameMsg') + : lang.t('bannedAccountMsg') + }
); diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 5bf9b40a9..a61213aab 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -3,7 +3,8 @@ "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", "successBioUpdate": "Your Bio 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", + "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", "error": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", @@ -25,7 +26,8 @@ "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": { "email": "No es un email válido", "password": "La contraseña debe tener por lo menos 8 caracteres", From 445eb97d7647dd25fa35e01980d1234e2c8a4679 Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 1 Feb 2017 14:00:09 -0800 Subject: [PATCH 22/77] Moves create displayname dialog to sign-in plugin. --- client/coral-embed-stream/src/Embed.js | 2 +- client/coral-framework/actions/auth.js | 6 +- client/coral-framework/components/Alert.js | 13 -- .../coral-framework/components/FormField.js | 18 --- client/coral-framework/components/styles.css | 140 ------------------ client/coral-framework/translations.json | 20 --- .../components/CreateDisplayNameDialog.js | 6 +- .../containers/ChangeDisplayNameContainer.js | 0 client/coral-sign-in/translations.js | 24 ++- 9 files changed, 29 insertions(+), 200 deletions(-) delete mode 100644 client/coral-framework/components/Alert.js delete mode 100644 client/coral-framework/components/FormField.js delete mode 100644 client/coral-framework/components/styles.css rename client/{coral-framework => coral-sign-in}/components/CreateDisplayNameDialog.js (84%) rename client/{coral-framework => coral-sign-in}/containers/ChangeDisplayNameContainer.js (100%) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index a5011f853..43b4eecae 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -19,7 +19,7 @@ import Count from 'coral-plugin-comment-count/CommentCount'; import CommentBox from 'coral-plugin-commentbox/CommentBox'; import UserBox from '../../coral-sign-in/components/UserBox'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; -import ChangeDisplayNameContainer from '../../coral-framework/containers/ChangeDisplayNameContainer'; +import ChangeDisplayNameContainer from '../../coral-sign-in/containers/ChangeDisplayNameContainer'; import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; import RestrictedContent from '../../coral-framework/components/RestrictedContent'; diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index bd3b81fdf..914b88095 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -12,6 +12,9 @@ export const createDisplayNameRequest = () => ({type: actions.CREATE_DISPLAYNAME export const showCreateDisplayNameDialog = () => ({type: actions.SHOW_CREATEDISPLAYNAME_DIALOG}); export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISPLAYNAME_DIALOG}); +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 createDisplayName = (userId, formData) => dispatch => { @@ -27,9 +30,6 @@ export const createDisplayName = (userId, formData) => dispatch => { }); }; -const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); -const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); - export const changeView = view => dispatch => dispatch({ type: actions.CHANGE_VIEW, diff --git a/client/coral-framework/components/Alert.js b/client/coral-framework/components/Alert.js deleted file mode 100644 index 019c3456a..000000000 --- a/client/coral-framework/components/Alert.js +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; - -const Alert = ({cStyle = 'error', children, className, ...props}) => ( -
- {children} -
-); - -export default Alert; diff --git a/client/coral-framework/components/FormField.js b/client/coral-framework/components/FormField.js deleted file mode 100644 index ceb2d939e..000000000 --- a/client/coral-framework/components/FormField.js +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react'; -import styles from './styles.css'; - -const FormField = ({className, showErrors = false, errorMsg, label, ...props}) => ( -
- - - {showErrors && errorMsg && !{errorMsg}} -
-); - -export default FormField; diff --git a/client/coral-framework/components/styles.css b/client/coral-framework/components/styles.css deleted file mode 100644 index f9d0aec6d..000000000 --- a/client/coral-framework/components/styles.css +++ /dev/null @@ -1,140 +0,0 @@ -.dialog { - border: none; - box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); - width: 280px; - top: 10px; -} - -.header { - margin-bottom: 20px; -} - -.header h1, .separator h1{ - text-align: center; - font-size: 1.2em; -} - -.formField { - margin-top: 15px; -} - -.formField label { - font-size: 1.08em; - font-weight: bold; - margin-bottom: 5px; -} - -.formField input { - width: 100%; - display: block; - border: none; - outline: none; - border: 1px solid rgba(0,0,0,.12); - padding: 10px 6px; - box-sizing: border-box; - border-radius: 2px; - margin: 5px auto; -} - -.footer { - margin: 20px auto 10px; - text-align: center; -} - -.footer span { - display: block; - margin-bottom: 5px; -} - -.footer a { - color: #2c69b6; - cursor: pointer; - margin: 0 5px; -} - -.socialConnections { - margin-bottom: 20px; -} - -.signInButton { - margin-top: 10px; -} - -.close { - font-size: 20px; - line-height: 14px; - top: 10px; - right: 10px; - position: absolute; - display: block; - font-weight: bold; - color: #363636; - cursor: pointer; -} - -.close:hover { - color: #6b6b6b; -} - -input.error{ - border: solid 2px #f44336; -} - -.errorMsg, .hint { - color: grey; - font-weight: 600; - padding: 3px 0 16px; -} - -.alert { - padding: 10px; - margin-bottom: 20px; - border-radius: 2px; -} - -.alert--success { - border: solid 1px #1ec00e; - background: #cbf1b8; - color: #006900; -} - -.alert--error { - background: #FFEBEE; - color: #B71C1C; -} - -.userBox { - padding: 10px 0 20px; - letter-spacing: 0.1px; -} - -.userBox a { - color: black; - font-weight: bold; - cursor: pointer; - margin: 0px; - margin-left: 4px; - padding-bottom: 2px; - border-bottom: solid 1px black; -} - -.attention { - display: inline-block; - width: 15px; - height: 15px; - background: #B71C1C; - color: #FFEBEE; - font-weight: bolder; - padding: 4px; - vertical-align: middle; - border-radius: 20px; - box-sizing: border-box; - font-size: 9px; - line-height: 7px; - text-align: center; - margin-right: 5px; -} - -.action { - margin-top: 15px; -} diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 88886f8d1..07b719f6b 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -4,16 +4,6 @@ "successBioUpdate": "Your Bio 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", - "createdisplay": { - "writeyourusername": "Write your username", - "yourusername": "Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.", - "displayName": "Display Name", - "save": "Save", - "requiredField": "Required field", - "errorCreate": "Error when changing display name", - "checkTheForm": "Invalid Form. Please, check the fields", - "specialCharacters": "Display names can contain letters, numbers and _ only" - }, "error": { "emailNotVerified": "Email address {0} not verified.", "email": "Not a valid E-Mail", @@ -37,16 +27,6 @@ "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", - "createdisplay": { - "writeyourusername": "Escribe tu nombre", - "yourusername": "Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.", - "displayName": "Nombre a mostrar", - "save": "Guardar", - "requiredField": "Campo necesario", - "errorCreate": "Hubo un error al cambiar el nombre de usuario", - "checkTheForm": "Formulario Invalido. Por favor, verifica los campos", - "specialCharacters": "Sólo pueden contener letras, números y _" - }, "error": { "emailNotVerified": "Dirección de correo electrónico {0} no verificada.", "email": "No es un email válido", diff --git a/client/coral-framework/components/CreateDisplayNameDialog.js b/client/coral-sign-in/components/CreateDisplayNameDialog.js similarity index 84% rename from client/coral-framework/components/CreateDisplayNameDialog.js rename to client/coral-sign-in/components/CreateDisplayNameDialog.js index ee3e133e6..4d8ef9fb3 100644 --- a/client/coral-framework/components/CreateDisplayNameDialog.js +++ b/client/coral-sign-in/components/CreateDisplayNameDialog.js @@ -1,5 +1,5 @@ import React from 'react'; -import FormField from './FormField'; +import FormField from 'coral-ui/components/FormField'; import Alert from './Alert'; import Button from 'coral-ui/components/Button'; import {Dialog} from 'coral-ui'; @@ -25,7 +25,7 @@ const CreateDisplayNameDialog = ({open, handleClose, offset, formData, handleSub
-

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

+ { props.auth.error && {props.auth.error} }
- { props.errors.displayName && {lang.t('createdisplay.specialCharacters')} } + { props.errors.displayName && {lang.t('createdisplay.specialCharacters')} }
diff --git a/client/coral-framework/containers/ChangeDisplayNameContainer.js b/client/coral-sign-in/containers/ChangeDisplayNameContainer.js similarity index 100% rename from client/coral-framework/containers/ChangeDisplayNameContainer.js rename to client/coral-sign-in/containers/ChangeDisplayNameContainer.js diff --git a/client/coral-sign-in/translations.js b/client/coral-sign-in/translations.js index 137141d34..5751370a6 100644 --- a/client/coral-sign-in/translations.js +++ b/client/coral-sign-in/translations.js @@ -26,7 +26,17 @@ export default { passwordsDontMatch: 'Passwords don\'t match.', specialCharacters: 'Display names can contain letters, numbers and _ only', checkTheForm: 'Invalid Form. Please, check the fields' - } + }, + 'createdisplay': { + writeyourusername: 'Write your username', + yourusername: 'Your username is publicly visible on all comments you post. A username is needed before you can post your first comment.', + displayName: 'Display Name', + save: 'Save', + requiredField: 'Required field', + errorCreate: 'Error when changing display name', + checkTheForm: 'Invalid Form. Please, check the fields', + specialCharacters: 'Display names can contain letters, numbers and _ only' + }, }, es: { 'signIn': { @@ -55,6 +65,16 @@ export default { passwordsDontMatch: 'Las contraseñas no coinciden', specialCharacters: 'Los nombres pueden contener letras, números y _', checkTheForm: 'Formulario Inválido. Por favor, completa los campos' - } + }, + 'createdisplay': { + writeyourusername: 'Escribe tu nombre', + yourusername: 'Tu nombre es visible publicamente en todos los comentarios que publiques. Es necesario tener un nombre de usuario antes de poder publicar tu primer comentario.', + displayName: 'Nombre a mostrar', + save: 'Guardar', + requiredField: 'Campo necesario', + errorCreate: 'Hubo un error al cambiar el nombre de usuario', + checkTheForm: 'Formulario Invalido. Por favor, verifica los campos', + specialCharacters: 'Sólo pueden contener letras, números y _' + }, } }; From d703cc21681cf7d0d7e55e4baf44d8520e3a738d Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 19:53:37 -0500 Subject: [PATCH 23/77] Prompting user to update name when banned. --- client/coral-embed-stream/src/Embed.js | 3 + client/coral-framework/actions/user.js | 16 +--- .../components/RestrictedContent.css | 10 +++ .../components/SuspendedAccount.js | 82 +++++++++++++++++-- client/coral-framework/constants/user.js | 6 +- client/coral-framework/translations.json | 9 +- graph/mutators/index.js | 2 - graph/mutators/user.js | 31 ------- graph/typeDefs.graphql | 2 +- models/user.js | 2 +- routes/api/account/index.js | 10 ++- routes/api/users/index.js | 2 +- services/users.js | 7 +- test/services/users.js | 16 ++-- 14 files changed, 123 insertions(+), 75 deletions(-) delete mode 100644 graph/mutators/user.js diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index b57bc799f..ceec3f1a9 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'; @@ -123,6 +124,7 @@ class Embed extends Component { }> { @@ -200,6 +202,7 @@ const mapDispatchToProps = dispatch => ({ loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)), addNotification: (type, text) => dispatch(addNotification(type, text)), 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-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 e6c53d038..0c6347fca 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -1,15 +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 ({canEditName}) => ( -
- { +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(() => window.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 ? - lang.t('editNameMsg') - : lang.t('bannedAccountMsg') - } -
-); +
+
+ {alert} +
+ + this.setState({displayName: e.target.value})} + rows={3}/>
+ +
: null + } +
; + } +} + +export default SuspendedAccount; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 9c6f508fe..a8e4585db 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/translations.json b/client/coral-framework/translations.json index a61213aab..80e865f8e 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -1,10 +1,15 @@ { "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", "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", - "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may 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": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", diff --git a/graph/mutators/index.js b/graph/mutators/index.js index b799cf83d..58d0ed62c 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const Comment = require('./comment'); const Action = require('./action'); -const User = require('./user'); module.exports = (context) => { @@ -10,7 +9,6 @@ module.exports = (context) => { return _.merge(...[ Comment, Action, - User, ].map((mutators) => { // Each set of mutators is a function which takes the context. diff --git a/graph/mutators/user.js b/graph/mutators/user.js deleted file mode 100644 index 96049bbab..000000000 --- a/graph/mutators/user.js +++ /dev/null @@ -1,31 +0,0 @@ -const UsersService = require('../../services/users'); - -/** - * Updates a users settings. - * @param {Object} user the user performing the request - * @param {String} bio the new user bio - * @return {Promise} - */ -const updateUserSettings = ({user}, {bio}) => { - return UsersService.updateSettings(user.id, {bio}); -}; - -module.exports = (context) => { - - // TODO: refactor to something that'll return an error in the event an attempt - // is made to mutate state while not logged in. There's got to be a better way - // to do this. - if (context.user && context.user.can('mutation:updateUserSettings')) { - return { - User: { - updateSettings: (settings) => updateUserSettings(context, settings) - } - }; - } - - return { - User: { - updateSettings: () => {} - } - }; -}; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 1c1470f58..b1f9b82f7 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -145,7 +145,7 @@ type Asset { # The scraped title of the asset. title: String - # The URL that the asset is locaed on. + # The URL that the asset is located on. url: String # The top level comments that are attached to the asset. diff --git a/models/user.js b/models/user.js index 7de99d211..e5db70ea6 100644 --- a/models/user.js +++ b/models/user.js @@ -146,7 +146,7 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createComment', 'mutation:createAction', 'mutation:deleteAction', - 'mutation:updateUserSettings' + 'mutation:editName' ]; /** diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 4dd6add7c..b08bdfc2e 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -117,11 +117,17 @@ router.put('/password/reset', (req, res, next) => { router.put('/displayname', authorization.needed(), (req, res, next) => { UsersService - .editUsername(req.user.id, req.body.displayName) + .editName(req.user.id, req.body.displayName) .then(() => { res.status(204).end(); }) - .catch(next); + .catch(error => { + if (error.code === 11000) { + next(errors.ErrDisplayTaken); + } else { + next(error); + } + }); }); module.exports = router; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 8b22adb36..5d1783cfe 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -59,7 +59,7 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next) router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => { UsersService - .toggleUsernameEdit(req.params.user_id, true) + .toggleNameEdit(req.params.user_id, true) .then(() => { res.status(204).end(); }) diff --git a/services/users.js b/services/users.js index 42278f1c4..ec0967c3b 100644 --- a/services/users.js +++ b/services/users.js @@ -619,7 +619,7 @@ module.exports = class UsersService { * @param {Boolean} canEditName sets whether the user can edit their name. * @return {Promise} */ - static toggleUsernameEdit(id, canEditName) { + static toggleNameEdit(id, canEditName) { return UserModel.update({id}, { $set: {canEditName} }); @@ -631,14 +631,15 @@ module.exports = class UsersService { * @param {String} displayName The new displayname for the user. * @return {Promise} */ - static editUsername(id, displayName) { + static editName(id, displayName) { return UserModel.findOne({id}) .then((user) => { return user.canEditName ? UserModel.update({id}, { $set: { displayName: displayName.toLowerCase(), - canEditName: false + canEditName: false, + status: 'PENDING' } }) : Promise.reject(new Error('Display name editing disabled for this account.')); diff --git a/test/services/users.js b/test/services/users.js index ef027a317..5774d45ae 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -209,10 +209,10 @@ describe('services.UsersService', () => { }); }); - describe('#toggleUsernameEdit', () => { + describe('#toggleNameEdit', () => { it('should toggle the canEditName field', () => { return UsersService - .toggleUsernameEdit(mockUsers[0].id, true) + .toggleNameEdit(mockUsers[0].id, true) .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('canEditName', true); @@ -220,11 +220,11 @@ describe('services.UsersService', () => { }); }); - describe('#editUsername', () => { + describe('#editName', () => { it('should let the user edit their username if the proper toggle is set', () => { return UsersService - .toggleUsernameEdit(mockUsers[0].id, true) - .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo')) + .toggleNameEdit(mockUsers[0].id, true) + .then(() => UsersService.editName(mockUsers[0].id, 'Jojo')) .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('displayName', 'jojo'); @@ -234,7 +234,7 @@ describe('services.UsersService', () => { it('should return an error if canEditName is false', (done) => { UsersService - .editUsername(mockUsers[0].id, 'Jojo') + .editName(mockUsers[0].id, 'Jojo') .then(() => UsersService.findById(mockUsers[0].id)) .then(() => { done(new Error('Error expected')); @@ -247,8 +247,8 @@ describe('services.UsersService', () => { it('should return an error if the username is already taken', (done) => { UsersService - .toggleUsernameEdit(mockUsers[0].id, true) - .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel')) + .toggleNameEdit(mockUsers[0].id, true) + .then(() => UsersService.editName(mockUsers[0].id, 'Marvel')) .then(() => UsersService.findById(mockUsers[0].id)) .then(() => { done(new Error('Error expected')); From 97c3ebc3e039116933359562a86f83422430b0f2 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 20:55:45 -0500 Subject: [PATCH 24/77] Whitelisting usernames once they have been approved. --- .../src/components/ModerationList.js | 2 +- models/user.js | 2 +- routes/api/users/index.js | 2 +- services/users.js | 11 ++++++-- test/routes/api/user/index.js | 25 +++++++------------ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js index c2ecab9a2..47e91e551 100644 --- a/client/coral-admin/src/components/ModerationList.js +++ b/client/coral-admin/src/components/ModerationList.js @@ -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); } } } diff --git a/models/user.js b/models/user.js index d483b1893..06dc3c7d1 100644 --- a/models/user.js +++ b/models/user.js @@ -12,7 +12,7 @@ const USER_STATUS = [ 'ACTIVE', 'BANNED', 'PENDING', - 'SUSPENDED', + 'APPROVED' // Indicates that the users' displayname has been approved ]; // ProfileSchema is the mongoose schema defined as the representation of a diff --git a/routes/api/users/index.js b/routes/api/users/index.js index f136fd776..0b7cd28aa 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -171,7 +171,7 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => { // Set the user status to "pending" for review by moderators if (action_type.slice(0, 4) === 'FLAG') { - return UsersService.setStatus(req.user.id, 'PENDING') + return UsersService.setStatus(req.params.user_id, 'PENDING') .then(() => action); } else { return action; diff --git a/services/users.js b/services/users.js index b9a64b288..157286ccf 100644 --- a/services/users.js +++ b/services/users.js @@ -349,8 +349,15 @@ module.exports = class UsersService { // User status is not supported! Error out here. return Promise.reject(new Error(`status ${status} is not supported`)); } - - return UserModel.update({id}, {$set: {status}}); + + return UserModel.findOne({id}) + .then((user) => { + if (user.status === 'APPROVED' && status === 'PENDING') { + return Promise.resolve(); + } else { + return UserModel.update({id}, {$set: {status}}); + } + }); } /** diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index 4de799df6..f6e202243 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -52,33 +52,26 @@ describe('/api/v1/users/:user_id/email/confirm', () => { describe('/api/v1/users/:user_id/actions', () => { - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; + let mockUser; - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return UsersService.createLocalUsers(users); - }); - }); + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); describe('#post', () => { it('it should update actions', () => { return chai.request(app) - .post('/api/v1/users/abc/actions') + .post(`/api/v1/users/${mockUser.id}/actions`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .send({'action_type': 'FLAG', metadata: {reason: 'Bio is too awesome.'}}) .then((res) => { expect(res).to.have.status(201); expect(res).to.have.body; expect(res.body).to.have.property('action_type', 'FLAG'); - expect(res.body).to.have.property('item_id', 'abc'); + expect(res.body).to.have.property('item_id', mockUser.id); }); }); }); From 65aa5e7e92be6ff9d3f396e39796b05abf071306 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 2 Feb 2017 11:31:43 -0300 Subject: [PATCH 25/77] =?UTF-8?q?=C3=81dding=20Select,=20Options=20to=20Co?= =?UTF-8?q?ral=20Ui=20and=20steps=20with=20formField?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coral-admin/src/components/ui/Header.js | 74 ++++++++++--------- .../containers/Install/InstallContainer.js | 2 +- .../components/Steps/AddOrganizationName.js | 17 +++-- .../components/Steps/CreateYourAccount.js | 50 ++++++++----- .../Install/components/Steps/InitialStep.js | 2 +- .../components/Steps/InviteTeamMembers.js | 45 ++++++----- .../Install/components/Steps/style.css | 52 +++++++++---- .../src/containers/Install/style.css | 2 +- client/coral-ui/components/Option.css | 3 + client/coral-ui/components/Option.js | 14 ++++ client/coral-ui/components/Select.css | 33 +++++++++ client/coral-ui/components/Select.js | 14 ++++ client/coral-ui/components/WizardNav.css | 15 +++- client/coral-ui/components/WizardNav.js | 7 +- client/coral-ui/index.js | 2 + 15 files changed, 230 insertions(+), 102 deletions(-) create mode 100644 client/coral-ui/components/Option.css create mode 100644 client/coral-ui/components/Option.js create mode 100644 client/coral-ui/components/Select.css create mode 100644 client/coral-ui/components/Select.js diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index abfb0ff13..3e6598d6e 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -11,42 +11,44 @@ export default ({handleLogout, restricted = false}) => ( { !restricted ? - - - {lang.t('configure.moderate')} - - - {lang.t('configure.community')} - - - {lang.t('configure.configure')} - - - {lang.t('configure.streams')} - - - : - null - } -
-
    -
  • -
    - - - Sign Out - -
    -
  • -
  • - {`v${process.env.VERSION}`} -
  • -
-
+
+ + + {lang.t('configure.moderate')} + + + {lang.t('configure.community')} + + + {lang.t('configure.configure')} + + + {lang.t('configure.streams')} + + +
+
    +
  • +
    + + + Sign Out + +
    +
  • +
  • + {`v${process.env.VERSION}`} +
  • +
+
+
+ : + null + } ); diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js index aac4654d3..350e60709 100644 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ b/client/coral-admin/src/containers/Install/InstallContainer.js @@ -18,7 +18,7 @@ const InstallContainer = props => {

Welcome to the Coral Project

- { install.step !== 0 ? : null } + { install.step !== 0 ? : null } diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js index 74f2c3016..ff7125ab5 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js +++ b/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js @@ -1,6 +1,6 @@ import React from 'react'; import styles from './style.css'; -import {Button} from 'coral-ui'; +import {FormField, Button} from 'coral-ui'; const InitialStep = props => { const {nextStep} = props; @@ -10,11 +10,16 @@ const InitialStep = props => { Please tell us the name of your organization. This will appear in emails when inviting new team members

- - - - - +
+
{}}> + + + +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js index cb29c3429..f245218d8 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js +++ b/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js @@ -1,30 +1,40 @@ import React from 'react'; import styles from './style.css'; -import {Button} from 'coral-ui'; +import {FormField, Button} from 'coral-ui'; const InitialStep = props => { const {nextStep} = props; return (
-
-
- - -
-
- - -
-
- - -
-
- - -
- -
+
+
+ + + + + + + + + + +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js index 95043b515..51c40d44d 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js +++ b/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js @@ -11,7 +11,7 @@ const InitialStep = props => { Once you complete the following three steps, you will have a free installation and provision of Mongo and Redis.

- +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js index 99ef84d19..156916ab0 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js +++ b/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js @@ -1,6 +1,6 @@ import React from 'react'; import styles from './style.css'; -import {Button} from 'coral-ui'; +import {Button, Select, Option, FormField} from 'coral-ui'; const InviteTeamMembers = props => { const {nextStep} = props; @@ -11,23 +11,32 @@ const InviteTeamMembers = props => { Once registered, new team members will receive an email to Create their password.

-
-
- - -
-
- - -
-
- -
- -
+
+
+ + + + + +
+ + +
+ + + +
); }; diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/containers/Install/components/Steps/style.css index c7d41aef0..9dd95f90b 100644 --- a/client/coral-admin/src/containers/Install/components/Steps/style.css +++ b/client/coral-admin/src/containers/Install/components/Steps/style.css @@ -1,34 +1,54 @@ .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: 10px auto 30px; + margin: 0 auto 30px; + font-size: 1.1em; + text-align: left; } > button { - min-width: 140px; + min-width: 145px; } - form { + .form { max-width: 300px; margin: 30px auto; - label { - text-align: left; - display: block; + form > button { + margin: 30px 0; } - input { - border: solid 1px rgba(0, 0, 0, 0.23); - padding: 10px 2px; - border-radius: 3px; - width: 100%; - box-sizing: border-box; - margin: 10px 0; - transition: border-color 0.2 ease; + .formField { + label { + text-align: left; + display: block; + margin: 10px 0; + font-weight: 400; + font-size: 1.08em; + } - &:focus { - border-color: black; + > 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; + } } } } diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/containers/Install/style.css index 90c62c20e..62f7ffe04 100644 --- a/client/coral-admin/src/containers/Install/style.css +++ b/client/coral-admin/src/containers/Install/style.css @@ -2,7 +2,7 @@ max-width: 900px; margin: 0 auto; text-align: center; - padding: 40px 0; + padding: 50px 0; h2 { font-size: 2em; diff --git a/client/coral-ui/components/Option.css b/client/coral-ui/components/Option.css new file mode 100644 index 000000000..54784d8fc --- /dev/null +++ b/client/coral-ui/components/Option.css @@ -0,0 +1,3 @@ +.Option { + +} diff --git a/client/coral-ui/components/Option.js b/client/coral-ui/components/Option.js new file mode 100644 index 000000000..d322ef0ef --- /dev/null +++ b/client/coral-ui/components/Option.js @@ -0,0 +1,14 @@ +import React from 'react'; +import {Option as OptionMDL} from 'react-mdl-selectfield'; +import styles from './Option.css'; + +const Option = props => { + const {children, ...attrs} = props; + return ( + + {children} + + ); +}; + +export default Option; diff --git a/client/coral-ui/components/Select.css b/client/coral-ui/components/Select.css new file mode 100644 index 000000000..ac93a843c --- /dev/null +++ b/client/coral-ui/components/Select.css @@ -0,0 +1,33 @@ +.Select { + position: relative; + width: 100%; + height: 40px; + background: #2c2c2c; + padding: 10px 15px; + box-sizing: border-box; + color: white; + border-radius: 2px; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + + > div { + padding: 0; + } + + i { + position: absolute; + top: 7px; + right: 7px; + } + + input { + padding: 0; + font-size: 13px; + letter-spacing: 0.7px; + font-weight: 400; + } + + &:hover { + cursor: pointer; + } + +} diff --git a/client/coral-ui/components/Select.js b/client/coral-ui/components/Select.js new file mode 100644 index 000000000..731c50ddc --- /dev/null +++ b/client/coral-ui/components/Select.js @@ -0,0 +1,14 @@ +import React from 'react'; +import {SelectField} from 'react-mdl-selectfield'; +import styles from './Select.css'; + +const Select = props => { + const {children, ...attrs} = props; + return ( + + {children} + + ); +}; + +export default Select; diff --git a/client/coral-ui/components/WizardNav.css b/client/coral-ui/components/WizardNav.css index 90d07af09..001ee8603 100644 --- a/client/coral-ui/components/WizardNav.css +++ b/client/coral-ui/components/WizardNav.css @@ -13,7 +13,7 @@ position: relative; padding-left: 40px; border-radius: 1px; - + &:hover { cursor: pointer; } @@ -27,6 +27,10 @@ border-color: transparent transparent transparent #00796B; } } + + i { + opacity: 1; + } } &.active { @@ -41,6 +45,15 @@ } } + i { + transition: opacity 0.2s ease; + opacity: 0; + vertical-align: middle; + font-size: 16px; + margin-top: -5px; + margin-left: 8px; + } + span { padding: 10px 20px; margin-right: 10px; diff --git a/client/coral-ui/components/WizardNav.js b/client/coral-ui/components/WizardNav.js index b5a8db549..9fd9a1f23 100644 --- a/client/coral-ui/components/WizardNav.js +++ b/client/coral-ui/components/WizardNav.js @@ -1,8 +1,9 @@ import React, {PropTypes} from 'react'; import styles from './WizardNav.css'; +import Icon from './Icon'; const WizardNav = props => { - const {goToStep, currentStep, items} = props; + const {goToStep, currentStep, items, icon} = props; return (