Adding actions to suspend users and send e-mail.

This commit is contained in:
David Jay
2017-01-06 15:16:23 -05:00
parent 565e34e09d
commit 9e651a9292
7 changed files with 61 additions and 34 deletions
+9
View File
@@ -14,3 +14,12 @@ export const userStatusUpdate = (status, userId, commentId) => {
.catch(error => dispatch({type: userTypes.UPDATE_STATUS_FAILURE, error}));
};
};
// change status of a user
export const sendNotificationEmail = (userId, subject, text) => {
return (dispatch, getState) => {
const _csrf = getState().auth.get('_csrf');
return coralApi(`/users/${userId}/email`, {method: 'POST', body: {subject, text}, _csrf})
.catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error}));
};
};
@@ -24,7 +24,8 @@ export default class ModerationList extends React.Component {
comments: PropTypes.object,
users: PropTypes.object.isRequired,
actions: PropTypes.object,
updateCommentStatus: PropTypes.func.isRequired,
userStatusUpdate: PropTypes.func.isRequired,
suspendUser: PropTypes.func.isRequired,
// list of actions (flags, etc) associated with the comments
modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
@@ -135,32 +136,13 @@ export default class ModerationList extends React.Component {
// 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}});
this.setState({suspendUserModal: action});
} else {
this.props.updateUserStatus(menuOption, action.item_id);
this.props.userStatusUpdate(menuOption, action.item_id);
}
}
}
/*
* 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);
}
@@ -221,7 +203,7 @@ export default class ModerationList extends React.Component {
}
render () {
const {singleView, key} = this.props;
const {singleView, key, suspendUser} = this.props;
// Combine moderations and actions into a single stream and sort by most recently updated.
const moderationIds = this.getModerationIds();
@@ -232,9 +214,9 @@ export default class ModerationList extends React.Component {
id='moderationList'>
{moderationIds.map(this.mapModItems)}
<SuspendUserModal
{...this.state.suspendUserModal}
action = {this.state.suspendUserModal}
onClose={() => this.setState({suspendUserModal:null})}
onButtonClick={this.onClickSuspendModalButton} />
suspendUser={suspendUser} />
</ul>
);
}
@@ -26,13 +26,13 @@ const stages = [
class SuspendUserModal extends Component {
state = {email: ''}
state = {email: '', stage: 0}
static propTypes = {
stage: PropTypes.number,
actionType: PropTypes.string,
onClose: PropTypes.func.isRequired,
onButtonClick: PropTypes.func.isRequired
suspendUser: PropTypes.func.isRequired
}
componentDidMount() {
@@ -40,12 +40,36 @@ class SuspendUserModal extends Component {
this.setState({email: lang.t('suspenduser.email', about)});
}
/*
* When an admin clicks to suspend a user a dialog is shown, this function
* handles the possible actions for that dialog.
*/
onActionClick = (stage, menuOption) => () => {
const {suspendUser, action} = this.props;
const {stage, email} = this.state;
const cancel = this.props.onClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => suspendUser(action.item_id, lang.t('suspenduser.email_subject'), email);
const suspendModalActions = [
[ cancel, next ],
[ cancel, suspend ]
];
return suspendModalActions[stage][menuOption]();
}
onEmailChange = (e) => this.setState({email: e.target.value})
render () {
const {stage, actionType, onClose, onButtonClick} = this.props;
const {action, onClose} = this.props;
if (!action) {
return null;
}
const {stage} = this.state;
const actionType = action.actionType;
const about = actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username');
return actionType ? <Modal open={actionType} onClose={onClose}>
return <Modal open={true} onClose={onClose}>
<div className={styles.title}>{lang.t(stages[stage].title, about)}</div>
<div className={styles.container}>
<div className={styles.description}>
@@ -66,14 +90,13 @@ class SuspendUserModal extends Component {
}
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={onButtonClick(stage, i)}>
<Button key={i} onClick={this.onActionClick(stage, i)}>
{lang.t(stages[stage].options[key], about)}
</Button>
))}
</div>
</div>
</Modal>
: null;
</Modal>;
}
}
@@ -1,4 +1,5 @@
export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST';
export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS';
export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE';
export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE';
export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS';
@@ -8,7 +8,7 @@ import {
hideBanUserDialog,
fetchModerationQueueComments
} from 'actions/comments';
import {userStatusUpdate} from 'actions/users';
import {userStatusUpdate, sendNotificationEmail} from 'actions/users';
import {fetchSettings} from 'actions/settings';
import ModerationQueue from './ModerationQueue';
@@ -62,7 +62,7 @@ class ModerationContainer extends React.Component {
render () {
const {comments, actions} = this.props;
const premodIds = comments.ids.filter(id => comments.byId[id].status === 'premod');
const rejectedIds = comments.ids.filter(id => comments.byId[id].status === 'rejected');
const flaggedIds = comments.ids.filter(id => comments.byId[id].flagged === true);
@@ -99,6 +99,11 @@ const mapDispatchToProps = dispatch => {
banUser: (userId, commentId) => dispatch(userStatusUpdate('banned', userId, commentId)).then(() => {
dispatch(fetchModerationQueueComments());
}),
userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)),
suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId))
.then(() => dispatch(sendNotificationEmail(userId, subject, text)))
.then(() => dispatch(fetchModerationQueueComments()))
,
updateStatus: (action, comment) => dispatch(updateStatus(action, comment))
};
};
@@ -31,6 +31,8 @@ export default (props) => (
users={props.users.byId}
actionIds={props.userActionIds}
actions={props.actions.byId}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
updateCommentStatus={props.updateStatus}
onClickShowBanDialog={props.showBanUserDialog}
modActions={['reject', 'approve', 'ban']}
@@ -48,6 +50,8 @@ export default (props) => (
isActive={props.activeTab === 'rejected'}
singleView={props.singleView}
commentIds={props.rejectedIds}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
comments={props.comments.byId}
users={props.users.byId}
updateCommentStatus={props.updateStatus}
@@ -61,6 +65,8 @@ export default (props) => (
isActive={props.activeTab === 'rejected'}
singleView={props.singleView}
commentIds={props.flaggedIds}
userStatusUpdate={props.userStatusUpdate}
suspendUser={props.suspendUser}
comments={props.comments.byId}
users={props.users.byId}
updateCommentStatus={props.updateStatus}
+1
View File
@@ -94,6 +94,7 @@
"send": "Send",
"bio": "bio",
"username": "username",
"subject": "Your account has been suspended",
"email": "Another member of the community recently flagged your {0} for review. Because of its content your {0} was rejected. This means you can no longer comment, like, or flag content until you rewrite your {0}. Please e-mail moderator@newsorg.com if you have any questions or concerns.",
"write_message": "Write a message"
},