Activating SuspendUserModal and updating language for clarity.

This commit is contained in:
David Jay
2017-01-05 14:50:21 -05:00
parent 9ba0176b3c
commit 890ab810f3
9 changed files with 137 additions and 49 deletions
+4 -1
View File
@@ -18,7 +18,10 @@ export const checkLogin = () => dispatch => {
const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
.catch(error => {
console.error(error);
dispatch(checkLoginFailure(`${error.message}`));
});
};
// Set CSRF Token
+9 -8
View File
@@ -54,16 +54,16 @@ const Comment = props => {
};
// Get the button of the action performed over a comment if any
const getActionButton = (action, i, props) => {
const {comment, author} = props;
const getActionButton = (option, i, props) => {
const {comment, author, menuOptionsMap, action} = props;
const status = comment.status;
const flagged = comment.flagged;
const banned = (author.status === 'banned');
if (action === 'flag' && (status || flagged === true)) {
if (option === 'flag' && (status || flagged === true)) {
return null;
}
if (action === 'ban') {
if (option === 'ban') {
return (
<Button
className='ban'
@@ -76,13 +76,14 @@ const getActionButton = (action, i, props) => {
</Button>
);
}
const menuOption = menuOptionsMap[option];
return (
<FabButton
className={`${action} ${styles.actionButton}`}
cStyle={action}
icon={props.actionsMap[action].icon}
className={`${option} ${styles.actionButton}`}
cStyle={option}
icon={menuOption.icon}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, comment)}
onClick={() => props.onClickAction(menuOption.status, comment, action)}
/>
);
};
@@ -4,9 +4,10 @@ import key from 'keymaster';
import Hammer from 'hammerjs';
import Comment from './Comment';
import UserAction from './UserAction';
import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const actionsMap = {
const menuOptionsMap = {
'reject': {status: 'rejected', icon: 'close', key: 'r'},
'approve': {status: 'accepted', icon: 'done', key: 't'},
'flag': {status: 'flagged', icon: 'flag', filter: 'Untouched'},
@@ -23,7 +24,7 @@ export default class ModerationList extends React.Component {
comments: PropTypes.object,
users: PropTypes.object.isRequired,
actions: PropTypes.object,
onClickAction: PropTypes.func.isRequired,
updateCommentStatus: PropTypes.func.isRequired,
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -32,13 +33,7 @@ export default class ModerationList extends React.Component {
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
}
constructor (props) {
super(props);
this.state = {active: null};
this.onClickAction = this.onClickAction.bind(this);
this.onClickShowBanDialog = this.onClickShowBanDialog.bind(this);
}
state = {active: null, suspendUserModal: null};
// remove key handlers before leaving
componentWillUnmount () {
@@ -76,8 +71,8 @@ export default class ModerationList extends React.Component {
// Add key handlers. Each action has one and added j/k for moving around
bindKeyHandlers () {
const {modActions, isActive} = this.props;
modActions.filter(action => actionsMap[action].key).forEach(action => {
key(actionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(actionsMap[action].status));
modActions.filter(action => menuOptionsMap[action].key).forEach(action => {
key(menuOptionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(menuOptionsMap[action].status));
});
key('j', 'moderationList', () => isActive && this.moveKeyHandler('down'));
key('k', 'moderationList', () => isActive && this.moveKeyHandler('up'));
@@ -121,21 +116,52 @@ export default class ModerationList extends React.Component {
// If we are performing an action over a comment (aka removing from the list) we need to select a new active.
// TODO: In the future this can be improved and look at the actual state to
// resolve since the content of the list could change externally. For now it works as expected
onClickAction (action, id, author_id) {
onClickAction = (menuOption, id, action) => {
// activate the next comment
if (id === this.state.active) {
const {commentIds} = this.props;
if (commentIds[commentIds.length - 1] === this.state.active) {
this.setState({active: commentIds[commentIds.length - 2]});
const moderationIds = this.getModerationIds();
if (moderationIds[moderationIds.length - 1] === this.state.active) {
this.setState({active: moderationIds[moderationIds.length - 2]});
} else {
this.setState({active: commentIds[Math.min(commentIds.indexOf(this.state.active) + 1, commentIds.length - 1)]});
this.setState({active: moderationIds[Math.min(moderationIds.indexOf(this.state.active) + 1, moderationIds.length - 1)]});
}
}
// Update the status right away if this is a comment
if (action.item_type === 'comment') {
this.props.updateCommentStatus(menuOption, id);
} else if (action.item_type === 'user') {
// If a user bio or name is rejected, bring up a dialog before suspending them.
if (menuOption === 'rejected') {
this.setState({suspendUserModal: {stage: 0, actionType: action.action_type}});
} else {
this.props.updateUserStatus(menuOption, action.item_id);
}
}
this.props.onClickAction(action, id, author_id);
}
onClickShowBanDialog(userId, userName, commentId) {
/*
* When an admin clicks to suspend a user a dialog is shown, this function
* handles the possible actions for that dialog.
*/
onClickSuspendModalButton = (stage, menuOption) => () => {
const {updateUserStatus, action} = this.props;
const cancel = () => this.setState({suspendUserModal: null});
const next = () => this.setState((prev) => {
prev.suspendUserModal.stage++;
return prev;
});
const suspend = () => updateUserStatus('suspend', action.item_id);
const suspendModalActions = [
[ cancel, next ],
[ cancel, suspend ]
];
return suspendModalActions[stage][menuOption]();
}
onClickShowBanDialog = (userId, userName, commentId) => {
this.props.onClickShowBanDialog(userId, userName, commentId);
}
@@ -162,7 +188,7 @@ export default class ModerationList extends React.Component {
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
actionsMap={actionsMap}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
} else {
@@ -178,29 +204,37 @@ export default class ModerationList extends React.Component {
onClickAction={this.onClickAction}
onClickShowBanDialog={this.onClickShowBanDialog}
modActions={modActions}
actionsMap={actionsMap}
menuOptionsMap={menuOptionsMap}
isActive={itemId === active}
hideActive={hideActive} />;
}
return modItem;
}
render () {
const {singleView, commentIds = [], actionIds = [], comments, actions, key} = this.props;
// Combine moderations and actions into a single stream and sort by most recently updated.
const moderationIds = [ ...commentIds, ...actionIds ].sort((a, b) => {
getModerationIds = () => {
const {commentIds = [], actionIds = [], comments, actions} = this.props;
return [ ...commentIds, ...actionIds ].sort((a, b) => {
const itemA = comments[a] || actions[a];
const itemB = comments[b] || actions[b];
return itemB.updated_at - itemA.updated_at;
});
}
render () {
const {singleView, key} = this.props;
// Combine moderations and actions into a single stream and sort by most recently updated.
const moderationIds = this.getModerationIds();
return (
<ul
className={`${styles.list} ${singleView ? styles.singleView : ''}`} {...key}
id='moderationList'
>
id='moderationList'>
{moderationIds.map(this.mapModItems)}
<SuspendUserModal
{...this.state.suspendUserModal}
onClose={() => this.setState({suspendUserModal:null})}
onButtonClick={this.onClickSuspendModalButton} />
</ul>
);
}
@@ -0,0 +1,43 @@
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations.json';
import React from 'react';
import Modal from 'components/Modal';
import styles from './SuspendUserModal.css';
import {Button} from 'coral-ui';
const stages = [
{
title: 'suspenduser.title_0',
description: 'suspenduser.description_0',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
}
},
{
title: 'suspenduser.title_1',
description: 'suspenduser.description_1',
options: {
'j': 'suspenduser.cancel',
'k': 'suspenduser.send'
}
}
];
export default ({stage, actionType, onClose, onButtonClick}) =>
actionType ? <Modal open={actionType} onClose={onClose}>
<h3>{lang.t(stages[stage].title)}</h3>
<div className={styles.container}>
<div className={styles.description}>
{lang.t(stages[stage].description)}
</div>
{Object.keys(stages[stage].options).map((key, i) => (
<Button
label={stages[stage].options[key]}
onClick={onButtonClick(stage, i)}/>
))}
</div>
</Modal>
: null;
const lang = new I18n(translations);
@@ -62,34 +62,35 @@ const UserAction = props => {
export default UserAction;
// Get the button of the action performed over a comment if any
const getActionButton = (action, i, props) => {
const {user} = props;
const getActionButton = (option, i, props) => {
const {user, onClickShowBanDialog, onClickAction, menuOptionsMap, action} = props;
const status = user.status;
const banned = (user.status === 'banned');
if (action === 'flag' && status) {
if (option === 'flag' && status) {
return null;
}
if (action === 'ban') {
if (option === 'ban') {
return (
<Button
className='ban'
cStyle='black'
disabled={banned ? 'disabled' : ''}
onClick={() => props.onClickShowBanDialog(user.id, user.displayName)}
onClick={() => onClickShowBanDialog(user.id, user.displayName)}
key={i}
>
{lang.t('comment.ban_user')}
</Button>
);
}
const menuOption = menuOptionsMap[option];
return (
<FabButton
className={`${action} ${styles.actionButton}`}
cStyle={action}
icon={props.actionsMap[action].icon}
className={`${option} ${styles.actionButton}`}
cStyle={option}
icon={menuOption.icon}
key={i}
onClick={() => props.onClickAction(props.actionsMap[action].status, user.id)}
onClick={() => onClickAction(menuOption.status, user.id, action)}
/>
);
};
@@ -31,7 +31,7 @@ export default (props) => (
users={props.users.byId}
actionIds={props.userActionIds}
actions={props.actions.byId}
onClickAction={props.updateStatus}
updateCommentStatus={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
modActions={['reject', 'approve', 'ban']}
loading={props.comments.loading}/>
@@ -50,7 +50,7 @@ export default (props) => (
commentIds={props.rejectedIds}
comments={props.comments.byId}
users={props.users.byId}
onClickAction={props.updateStatus}
updateCommentStatus={props.updateStatus}
modActions={['approve']}
loading={props.comments.loading}
/>
@@ -63,7 +63,7 @@ export default (props) => (
commentIds={props.flaggedIds}
comments={props.comments.byId}
users={props.users.byId}
onClickAction={props.updateStatus}
updateCommentStatus={props.updateStatus}
modActions={['reject', 'approve']}
loading={props.comments.loading}/>
</div>
+4 -1
View File
@@ -141,5 +141,8 @@ export const checkLogin = () => dispatch => {
const isAdmin = !!result.user.roles.filter(i => i === 'admin').length;
dispatch(checkLoginSuccess(result.user, isAdmin));
})
.catch(error => dispatch(checkLoginFailure(error)));
.catch(error => {
console.error(error);
dispatch(checkLoginFailure(`${error.message}`));
});
};
+3
View File
@@ -134,6 +134,9 @@ ActionSchema.statics.getActionSummaries = function(item_ids, current_user_id = '
item_type: {
$last: '$item_type'
},
metadata: {
$push: '$metadata'
},
created_at: {
$min: '$created_at'
},