Complete suspend user implementation

This commit is contained in:
Chi Vinh Le
2017-05-19 01:52:03 +07:00
parent 492b973206
commit f641a3ec40
15 changed files with 126 additions and 54 deletions
@@ -2,7 +2,7 @@ import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {toast} from 'react-toastify';
import * as notification from 'coral-admin/src/services/notification';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
@@ -97,6 +97,31 @@ class ModerationContainer extends Component {
this.props.modQueueResort(sort);
}
suspendUser = (args) => {
this.props.suspendUser(args)
.then((result) => {
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
lang.t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
lang.timeago(args.until)),
);
const {commentStatus, commentId} = this.props.moderation.suspendUserDialog;
if (commentStatus !== 'REJECTED') {
return this.props.rejectComment({commentId})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
})
.catch(notification.handleMutationErrors);
this.props.hideSuspendUserDialog();
};
componentWillUnmount() {
key.unbind('s');
key.unbind('shift+/');
@@ -197,6 +222,7 @@ class ModerationContainer extends Component {
assetId={providedAssetId}
sort={this.state.sort}
commentCount={activeTabCount}
currentUserId={this.props.auth.user.id}
/>
<BanUserDialog
open={moderation.banDialog}
@@ -212,25 +238,9 @@ class ModerationContainer extends Component {
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
userId={moderation.suspendUserDialog.userId}
organizationName={data.settings.organizationName}
onCancel={props.hideSuspendUserDialog}
onPerform={(args) => {
props.suspendUser(args)
.then(() => {
toast(
lang.t('suspenduser.notify_suspend_until',
moderation.suspendUserDialog.username,
lang.timeago(args.until)),
{type: 'success'}
);
})
.catch((err) => {
toast(
err,
{type: 'error'}
);
});
props.hideSuspendUserDialog();
}}
onPerform={this.suspendUser}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
@@ -245,6 +255,7 @@ class ModerationContainer extends Component {
const mapStateToProps = (state) => ({
moderation: state.moderation.toJS(),
settings: state.settings.toJS(),
auth: state.auth.toJS(),
assets: state.assets.get('assets')
});
@@ -56,6 +56,7 @@ class ModerationQueue extends React.Component {
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
currentUserId={this.props.currentUserId}
/>;
})
: <EmptyCard>{lang.t('modqueue.emptyqueue')}</EmptyCard>
@@ -66,17 +66,19 @@ const Comment = ({
lang.getLocale().replace('-', '_')
)}
</span>
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
{props.currentUserId !== comment.user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
<CommentType type={commentType} />
</div>
{comment.user.status === 'banned'
@@ -163,6 +165,7 @@ Comment.propTypes = {
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
currentUserId: PropTypes.string.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
@@ -10,7 +10,13 @@ import {dateAdd} from 'coral-framework/utils';
import translations from '../../../translations';
const lang = new I18n(translations);
const initialState = {step: 0, duration: '3', message: lang.t('suspenduser.email_message_suspend')};
const initialState = {step: 0, duration: '3'};
function durationsToDate(hours) {
// Add 1 minute more to help `timeago.js` to display the correct duration.
return dateAdd(new Date(), 'minute', hours * 60 + 1);
}
class SuspendUserDialog extends React.Component {
@@ -30,12 +36,16 @@ class SuspendUserDialog extends React.Component {
this.setState({message: event.target.value});
}
increaseStep = () => {
this.setState({step: this.state.step + 1});
}
resetStep = () => {
this.setState({step: 0});
goToStep1 = () => {
this.setState({
step: 1,
message: lang.t(
'suspenduser.email_message_suspend',
this.props.username,
this.props.organizationName,
lang.timeago(durationsToDate(this.state.duration)),
),
});
}
handlePerform = () => {
@@ -45,7 +55,7 @@ class SuspendUserDialog extends React.Component {
message: this.state.message,
// Add 1 minute more to help `timeago.js` to display the correct duration.
until: dateAdd(new Date(), 'minute', this.state.duration * 60 + 1),
until: durationsToDate(this.state.duration),
});
};
@@ -69,6 +79,7 @@ class SuspendUserDialog extends React.Component {
onChange={this.handleDurationChange}
className={styles.radioGroup}
>
<Radio value='1'>{lang.t('suspenduser.one_hour')}</Radio>
<Radio value='3'>{lang.t('suspenduser.hours', 3)}</Radio>
<Radio value='24'>{lang.t('suspenduser.hours', 24)}</Radio>
<Radio value='168'>{lang.t('suspenduser.days', 7)}</Radio>
@@ -78,7 +89,7 @@ class SuspendUserDialog extends React.Component {
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
</Button>
<Button cStyle="black" className={styles.perform} onClick={this.increaseStep} raised>
<Button cStyle="black" className={styles.perform} onClick={this.goToStep1} raised>
{lang.t('suspenduser.suspend_user')}
</Button>
</div>
@@ -146,7 +157,9 @@ SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string.isRequired,
username: PropTypes.string,
userId: PropTypes.string,
organizationName: PropTypes.string,
};
export default SuspendUserDialog;
@@ -62,4 +62,7 @@ query ModQueue ($asset_id: ID, $sort: SORT_ORDER) {
asset_id: $asset_id,
statuses: [NONE, PREMOD]
})
settings {
organizationName
}
}
@@ -0,0 +1,28 @@
import translations from 'coral-admin/src/translations';
import I18n from 'coral-framework/modules/i18n/i18n';
import {toast} from 'react-toastify';
const lang = new I18n(translations);
export function success(msg) {
return toast(msg, {type: 'success'});
}
export function error(msg) {
return toast(msg, {type: 'error'});
}
export function info(msg) {
return toast(msg, {type: 'info'});
}
export function handleMutationErrors(err) {
const errors = Array.isArray(err) ? err : [err];
errors.forEach((err) => {
console.error(err);
toast(
err.translation_key ? lang.t(`errors.${err.translation_key}`) : err,
{type: 'error'}
);
});
}
+4 -2
View File
@@ -1,7 +1,7 @@
{
"en": {
"errors": {
"NOT_AUTHORIZED": "Your username or password is not recognized by our system.",
"NOT_AUTHORIZED": "You are not authorized to perform this action.",
"LOGIN_MAXIMUM_EXCEEDED": "You have made too many unsuccessful password attempts. Please wait."
},
"community": {
@@ -155,8 +155,9 @@
"username": "username",
"email_subject": "Your account has been suspended",
"email_message_reject": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
"email_message_suspend": "Your user account has been suspended. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
"email_message_suspend": "Dear {0},\n\nIn accordance with {1}s community guidelines, your account has been temporarily suspended. During the suspension, you will be unable to comment, flag or engage with fellow commenters. Please rejoin the conversation {2}.",
"write_message": "Write a message",
"one_hour": "1 hour",
"hours": "{0} hours",
"days": "{0} days",
"suspend_user": "Suspend User",
@@ -240,6 +241,7 @@
"email_message_reject": "Otra persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo a moderator@newsorg.com si tiene alguna pregunta o preocupación",
"write_message": "Escribir un mensaje",
"loading": "Cargando resultados",
"hour": "una hora",
"hours": "{0} horas",
"days": "{0} días",
"suspend_user": "Suspender",
@@ -10,9 +10,14 @@ import CommentBox from 'coral-plugin-commentbox/CommentBox';
import QuestionBox from 'coral-plugin-questionbox/QuestionBox';
import IgnoredCommentTombstone from './IgnoredCommentTombstone';
import SuspendedAccount from './SuspendedAccount';
import RestrictedMessageBox from 'coral-framework/components/RestrictedMessageBox';
import RestrictedMessageBox
from 'coral-framework/components/RestrictedMessageBox';
import ChangeUsernameContainer
from 'coral-sign-in/containers/ChangeUsernameContainer';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-framework/translations';
const lang = new I18n(translations);
class Stream extends React.Component {
setActiveReplyBox = (reactKey) => {
@@ -75,9 +80,12 @@ class Stream extends React.Component {
/>
{!banned && temporarilySuspended &&
<RestrictedMessageBox>
In accordance with &lt;organization name&gt;'s Community Guidlines,
you have been temporarily suspended. You can rejoin the conversation
with our community in x hours.
{
lang.t('temporarilySuspended',
this.props.root.settings.organizationName,
lang.timeago(user.suspension.until),
)
}
</RestrictedMessageBox>
}
{banned &&
@@ -86,7 +94,7 @@ class Stream extends React.Component {
editName={editName}
/>
}
{loggedIn && !banned &&
{loggedIn && !banned && !temporarilySuspended &&
<CommentBox
addNotification={this.props.addNotification}
postComment={this.props.postComment}
@@ -206,6 +206,9 @@ const fragments = {
me {
status
}
settings {
organizationName
}
...${getDefinitionName(Comment.fragments.root)}
}
${Comment.fragments.root}
@@ -1,5 +1,6 @@
.message {
background: #D8D8D8;
padding: 25px;
margin-bottom: 8px;
}
+2
View File
@@ -7,6 +7,8 @@
"successNameUpdate": "Your username has been updated",
"contentNotAvailable": "This content is not available",
"bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Report, or write comments. Please contact us if you have any questions.",
"temporarilySuspended": "In accordance with {0}'s community guidlines, your account has been temporarily suspended. Please rejoin the conversation {1}.",
"editName": {
"msg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. Please contact us if you have any questions.",
"label": "New Username",
+1
View File
@@ -404,6 +404,7 @@ type Settings {
charCountEnable: Boolean
charCount: Int
organizationName: String
}
################################################################################
-1
View File
@@ -1 +0,0 @@
<%= body %>
+1 -1
View File
@@ -1 +1 @@
<%= body %>
<%= body.replace(/\n/g, '<br />') %>
+2 -5
View File
@@ -456,7 +456,6 @@ module.exports = class UsersService {
* @param {Date} until date until the suspension is valid.
*/
static suspendUser(id, message, until) {
console.log('SUSPEND');
return UserModel.findOneAndUpdate(
{id}, {
$set: {
@@ -466,7 +465,6 @@ module.exports = class UsersService {
}
})
.then((user) => {
console.log(user);
if (message) {
let localProfile = user.profiles.find((profile) => profile.provider === 'local');
@@ -477,14 +475,13 @@ module.exports = class UsersService {
locals: { // specifies the template locals.
body: message
},
subject: 'Email Suspension',
subject: 'Your account has been suspended',
to: localProfile.id // This only works if the user has registered via e-mail.
// We may want a standard way to access a user's e-mail address in the future
};
console.log(options);
return MailerService.sendSimple(options);
} else {
return Promise.reject(errors.ErrMissingEmail);
}
}
});