mirror of
https://github.com/wassname/talk.git
synced 2026-07-19 11:28:50 +08:00
Activating SuspendUserModal and updating language for clarity.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}`));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user