From d703cc21681cf7d0d7e55e4baf44d8520e3a738d Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 19:53:37 -0500 Subject: [PATCH] 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'));