Skip to content

Commit

Permalink
Flag users as "Always premoderate" (#2145)
Browse files Browse the repository at this point in the history
* Flag users as "Always premoderate"

Status can be set using the action dropdown in the People tab and in User Details.
Users flagged as "Always premoderate" will have their comments sent to the premod queue.
Users can be filtered with the Always Premoderate status.
Include Always Premoderate status changes in User History.
Add spanish translations.

* Reorder CSS as per cvle's suggestion

* Address second comment
  • Loading branch information
gerardogalvez authored and cvle committed Feb 13, 2019
1 parent 19b425d commit 8163b52
Show file tree
Hide file tree
Showing 38 changed files with 714 additions and 7 deletions.
3 changes: 2 additions & 1 deletion bin/cli-users
Expand Up @@ -105,7 +105,8 @@ function printUserAsTable(user) {
Suspension: user.suspended
? `Until ${user.status.suspension.until}`
: false,
}
},
{ 'Always premod comments': user.alwaysPremod }
);

console.log(table.toString());
Expand Down
14 changes: 14 additions & 0 deletions client/coral-admin/src/actions/alwaysPremodUserDialog.js
@@ -0,0 +1,14 @@
import {
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
} from '../constants/alwaysPremodUserDialog.js';

export const showAlwaysPremodUserDialog = ({ userId, username }) => ({
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
userId,
username,
});

export const hideAlwaysPremodUserDialog = () => ({
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
});
11 changes: 11 additions & 0 deletions client/coral-admin/src/actions/community.js
Expand Up @@ -9,6 +9,8 @@ import {
SET_SEARCH_VALUE,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_ALWAYS_PREMOD_USER_DIALOG,
HIDE_ALWAYS_PREMOD_USER_DIALOG,
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG,
SET_INDICATOR_TRACK,
Expand Down Expand Up @@ -61,6 +63,15 @@ export const setSearchValue = value => ({
export const showBanUserDialog = user => ({ type: SHOW_BANUSER_DIALOG, user });
export const hideBanUserDialog = () => ({ type: HIDE_BANUSER_DIALOG });

// Always premod User Dialog
export const showAlwaysPremodUserDialog = user => ({
type: SHOW_ALWAYS_PREMOD_USER_DIALOG,
user,
});
export const hideAlwaysPremodUserDialog = () => ({
type: HIDE_ALWAYS_PREMOD_USER_DIALOG,
});

// Reject Username Dialog
export const showRejectUsernameDialog = user => ({
type: SHOW_REJECT_USERNAME_DIALOG,
Expand Down
29 changes: 29 additions & 0 deletions client/coral-admin/src/components/AlwaysPremodUserDialog.css
@@ -0,0 +1,29 @@
.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: 400px;
top: 50%;
transform: translateY(-50%);
padding: 20px;
border-radius: 4px;
}

.header {
color: black;
font-size: 1.5em;
font-weight: 500;
margin: 0 0 8px 0;
}

.subheader {
color: black;
font-size: 1.3em;
font-weight: 500;
margin: 0 0 8px 0;
}

.buttons {
margin-top: 8px;
margin-bottom: 6px;
text-align: right;
}
68 changes: 68 additions & 0 deletions client/coral-admin/src/components/AlwaysPremodUserDialog.js
@@ -0,0 +1,68 @@
import React from 'react';
import cn from 'classnames';
import PropTypes from 'prop-types';
import { Dialog } from 'coral-ui';
import styles from './AlwaysPremodUserDialog.css';

import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';

class AlwaysPremodUserDialog extends React.Component {
handlePerform = () => {
this.props.onPerform();
};

render() {
const { open, onCancel, username, info } = this.props;
return (
<Dialog
className={cn(styles.dialog, 'talk-always-premod-user-dialog')}
id="alwaysPremodUserDialog"
open={open}
onCancel={onCancel}
title={t('alwayspremoddialog.always_premod_user')}
>
<span className={styles.close} onClick={onCancel}>
×
</span>
<section>
<h2 className={styles.header}>
{t('alwayspremoddialog.always_premod_user')}
</h2>
<h3 className={styles.subheader}>
{t('alwayspremoddialog.are_you_sure', username)}
</h3>
<p className={styles.description}>{info}</p>
<div className={styles.buttons}>
<Button
className={cn('talk-always-premod-user-dialog-button-cancel')}
cStyle="white"
onClick={onCancel}
raised
>
{t('alwayspremoddialog.cancel')}
</Button>
<Button
className={cn('talk-always-premod-user-dialog-button-confirm')}
cStyle="black"
onClick={this.handlePerform}
raised
>
{t('alwayspremoddialog.yes_always_premod_user')}
</Button>
</div>
</section>
</Dialog>
);
}
}

AlwaysPremodUserDialog.propTypes = {
open: PropTypes.bool,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
info: PropTypes.string,
};

export default AlwaysPremodUserDialog;
6 changes: 6 additions & 0 deletions client/coral-admin/src/components/UserDetail.css
Expand Up @@ -137,6 +137,12 @@
display: inline-block;
}

.actionsMenuAlwaysPremod {
background-color: #F0B50B;
border-color: #F0B50B;
color: white;
}

.actionsMenuSuspended {
background-color: #F29336;
border-color: #F29336;
Expand Down
30 changes: 30 additions & 0 deletions client/coral-admin/src/components/UserDetail.js
Expand Up @@ -12,6 +12,7 @@ import {
isUsernameRejected,
isUsernameChanged,
isBanned,
isAlwaysPremod,
getKarma,
} from 'coral-framework/utils/user';

Expand Down Expand Up @@ -45,6 +46,7 @@ function getUserStatusArray(user) {
const statusMap = {
suspended: isSuspended,
banned: isBanned,
alwaysPremod: isAlwaysPremod,
usernameRejected: isUsernameRejected,
usernameChanged: isUsernameChanged,
};
Expand All @@ -68,6 +70,12 @@ class UserDetail extends React.Component {
username: this.props.root.user.username,
});

showAlwaysPremodUserDialog = () =>
this.props.showAlwaysPremodUserDialog({
userId: this.props.root.user.id,
username: this.props.root.user.username,
});

showRejectUsernameDialog = () =>
this.props.showRejectUsernameDialog({
userId: this.props.root.user.id,
Expand Down Expand Up @@ -107,6 +115,8 @@ class UserDetail extends React.Component {
return t('user_detail.suspended');
case 'banned':
return t('user_detail.banned');
case 'alwaysPremod':
return t('user_detail.always_premoded');
case 'usernameRejected':
return (
<span>
Expand Down Expand Up @@ -153,6 +163,7 @@ class UserDetail extends React.Component {
toggleSelectAll,
unbanUser,
unsuspendUser,
removeAlwaysPremodUser,
modal,
acceptComment,
rejectComment,
Expand All @@ -169,6 +180,7 @@ class UserDetail extends React.Component {

const banned = isBanned(user);
const suspended = isSuspended(user);
const alwaysPremod = isAlwaysPremod(user);
const usernameRejected = isUsernameRejected(user);
const usernameChanged = isUsernameChanged(user);

Expand Down Expand Up @@ -200,6 +212,7 @@ class UserDetail extends React.Component {
{
[styles.actionsMenuSuspended]: suspended,
[styles.actionsMenuBanned]: banned,
[styles.actionsMenuAlwaysPremod]: alwaysPremod,
},
'talk-admin-user-detail-actions-button'
)}
Expand Down Expand Up @@ -231,6 +244,21 @@ class UserDetail extends React.Component {
</ActionsMenuItem>
)}

{alwaysPremod ? (
<ActionsMenuItem
onClick={() => removeAlwaysPremodUser({ id: user.id })}
>
{t('user_detail.remove_always_premod')}
</ActionsMenuItem>
) : (
<ActionsMenuItem
disabled={me.id === user.id}
onClick={this.showAlwaysPremodUserDialog}
>
{t('user_detail.always_premod')}
</ActionsMenuItem>
)}

{usernameChanged && (
<ActionsMenuItem onClick={this.goToReportedUsernames}>
{t('user_detail.username_needs_approval')}
Expand Down Expand Up @@ -476,8 +504,10 @@ UserDetail.propTypes = {
showRejectUsernameDialog: PropTypes.func,
showSuspendUserDialog: PropTypes.func,
showBanUserDialog: PropTypes.func,
showAlwaysPremodUserDialog: PropTypes.func,
unbanUser: PropTypes.func.isRequired,
unsuspendUser: PropTypes.func.isRequired,
removeAlwaysPremodUser: PropTypes.func.isRequired,
modal: PropTypes.bool,
rejectUsername: PropTypes.func.isRequired,
};
Expand Down
4 changes: 4 additions & 0 deletions client/coral-admin/src/components/UserHistory.js
Expand Up @@ -48,6 +48,10 @@ const buildActionResponse = (typename, created_at, until, status) => {
return status
? t('user_history.user_banned')
: t('user_history.ban_removed');
case 'AlwaysPremodStatusHistory':
return status
? t('user_history.user_always_premoded')
: t('user_history.always_premod_removed');
case 'SuspensionStatusHistory':
return until
? t('user_history.suspended', readableDuration(created_at, until))
Expand Down
2 changes: 2 additions & 0 deletions client/coral-admin/src/constants/alwaysPremodUserDialog.js
@@ -0,0 +1,2 @@
export const SHOW_ALWAYS_PREMOD_USER_DIALOG = 'SHOW_ALWAYS_PREMOD_USER_DIALOG';
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = 'HIDE_ALWAYS_PREMOD_USER_DIALOG';
3 changes: 3 additions & 0 deletions client/coral-admin/src/constants/community.js
Expand Up @@ -14,6 +14,9 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = `${prefix}_FETCH_FLAGGED_COMMENT
export const SHOW_BANUSER_DIALOG = `${prefix}_SHOW_BANUSER_DIALOG`;
export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`;

export const SHOW_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_SHOW_ALWAYS_PREMOD_USER_DIALOG`;
export const HIDE_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_HIDE_ALWAYS_PREMOD_USER_DIALOG`;

export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`;
export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`;

Expand Down
66 changes: 66 additions & 0 deletions client/coral-admin/src/containers/AlwaysPremodUserDialog.js
@@ -0,0 +1,66 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import AlwaysPremodUserDialog from '../components/AlwaysPremodUserDialog';
import { hideAlwaysPremodUserDialog } from '../actions/alwaysPremodUserDialog';
import { withAlwaysPremodUser } from 'coral-framework/graphql/mutations';
import { compose } from 'react-apollo';
import t from 'coral-framework/services/i18n';

class AlwaysPremodUserDialogContainer extends Component {
alwaysPremodUser = async () => {
const { userId, alwaysPremodUser, hideAlwaysPremodUserDialog } = this.props;
await alwaysPremodUser({ id: userId });
hideAlwaysPremodUserDialog();
};

getInfo() {
let note = t('alwayspremoddialog.note_always_premod_user');
return t('alwayspremoddialog.note', note);
}

render() {
return (
<AlwaysPremodUserDialog
open={this.props.open}
onPerform={this.alwaysPremodUser}
onCancel={this.props.hideAlwaysPremodUserDialog}
username={this.props.username}
info={this.getInfo()}
/>
);
}
}

AlwaysPremodUserDialogContainer.propTypes = {
alwaysPremodUser: PropTypes.func.isRequired,
hideAlwaysPremodUserDialog: PropTypes.func,
open: PropTypes.bool,
username: PropTypes.string,
};

const mapStateToProps = ({
alwaysPremodUserDialog: { open, userId, username },
}) => ({
open,
userId,
username,
});

const mapDispatchToProps = dispatch => ({
...bindActionCreators(
{
hideAlwaysPremodUserDialog,
},
dispatch
),
});

export default compose(
connect(
mapStateToProps,
mapDispatchToProps
),
withAlwaysPremodUser
)(AlwaysPremodUserDialogContainer);
2 changes: 2 additions & 0 deletions client/coral-admin/src/containers/Layout.js
Expand Up @@ -5,6 +5,7 @@ import Layout from '../components/Layout';
import Login from '../containers/Login';
import { FullLoading } from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import AlwaysPremodUserDialog from './AlwaysPremodUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import RejectUsernameDialog from './RejectUsernameDialog';
import { toggleModal as toggleShortcutModal } from '../actions/moderation';
Expand Down Expand Up @@ -40,6 +41,7 @@ class LayoutContainer extends React.Component {
toggleShortcutModal={toggleShortcutModal}
currentUser={this.props.currentUser}
>
<AlwaysPremodUserDialog />
<BanUserDialog />
<SuspendUserDialog />
<RejectUsernameDialog />
Expand Down

0 comments on commit 8163b52

Please sign in to comment.