Merge branch 'master' into issue-676-set-domain-whitelist

This commit is contained in:
Kiwi
2017-06-24 00:22:34 +07:00
committed by GitHub
45 changed files with 469 additions and 568 deletions
@@ -0,0 +1,7 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
export const showBanUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_BAN_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideBanUserDialog = () => ({type: HIDE_BAN_USER_DIALOG});
+5 -5
View File
@@ -10,8 +10,8 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/request';
@@ -69,6 +69,6 @@ export const setCommenterStatus = (id, status) => (dispatch) => {
export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user});
export const hideBanUserDialog = () => ({type: HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (user) => ({type: SHOW_SUSPENDUSER_DIALOG, user});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPENDUSER_DIALOG});
// Reject Username Dialog
export const showRejectUsernameDialog = (user) => ({type: SHOW_REJECT_USERNAME_DIALOG, user});
export const hideRejectUsernameDialog = () => ({type: HIDE_REJECT_USERNAME_DIALOG});
@@ -3,16 +3,6 @@ import * as actions from 'constants/moderation';
export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// Ban User Dialog
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG});
// Suspend User Dialog
export const showSuspendUserDialog = (userId, username, commentId, commentStatus) =>
({type: actions.SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: actions.HIDE_SUSPEND_USER_DIALOG});
// hide shortcuts note
export const hideShortcutsNote = () => {
try {
@@ -0,0 +1,7 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog.js';
export const showSuspendUserDialog = ({userId, username, commentId, commentStatus}) =>
({type: SHOW_SUSPEND_USER_DIALOG, userId, username, commentId, commentStatus});
export const hideSuspendUserDialog = () => ({type: HIDE_SUSPEND_USER_DIALOG});
@@ -0,0 +1,44 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, onCancel, onPerform, username, info}) => (
<Dialog
className={styles.dialog}
id="banUserDialog"
open={open}
onCancel={onCancel}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={onCancel}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', username)}</h3>
<i>{info}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={onCancel} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onPerform} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
open: PropTypes.bool,
onPerform: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
username: PropTypes.string,
info: PropTypes.string,
};
export default BanUserDialog;
@@ -8,8 +8,8 @@ import SuspendUserModal from './SuspendUserModal';
// Each action has different meaning and configuration
const menuOptionsMap = {
'reject': {status: 'REJECTED', icon: 'close', key: 'r'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 't'},
'reject': {status: 'REJECTED', icon: 'close', key: 'f'},
'approve': {status: 'ACCEPTED', icon: 'done', key: 'd'},
'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
'ban': {status: 'BANNED', icon: 'not interested'}
};
@@ -49,7 +49,6 @@ class SuspendUserDialog extends React.Component {
handlePerform = () => {
this.props.onPerform({
id: this.props.userId,
message: this.state.message,
// Add 1 minute more to help `timeago.js` to display the correct duration.
@@ -153,11 +152,10 @@ class SuspendUserDialog extends React.Component {
SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string,
userId: PropTypes.string,
onCancel: PropTypes.func.isRequired,
organizationName: PropTypes.string,
username: PropTypes.string,
};
export default SuspendUserDialog;
@@ -0,0 +1,2 @@
export const SHOW_BAN_USER_DIALOG = 'SHOW_BAN_USER_DIALOG';
export const HIDE_BAN_USER_DIALOG = 'HIDE_BAN_USER_DIALOG';
@@ -13,5 +13,5 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILUR
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const SHOW_SUSPENDUSER_DIALOG = 'SHOW_SUSPENDUSER_DIALOG';
export const HIDE_SUSPENDUSER_DIALOG = 'HIDE_SUSPENDUSER_DIALOG';
export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG';
export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG';
@@ -1,10 +1,6 @@
export const TOGGLE_MODAL = 'TOGGLE_MODAL';
export const SINGLE_VIEW = 'SINGLE_VIEW';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
export const HIDE_SHORTCUTS_NOTE = 'HIDE_SHORTCUTS_NOTE';
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL';
export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL';
export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER';
@@ -0,0 +1,2 @@
export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG';
export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG';
@@ -0,0 +1,63 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import BanUserDialog from '../components/BanUserDialog';
import {hideBanUserDialog} from '../actions/banUserDialog';
import {withSetUserStatus, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {compose} from 'react-apollo';
import t from 'coral-framework/services/i18n';
class BanUserDialogContainer extends Component {
banUser = async () => {
const {userId, commentId, commentStatus, setUserStatus, setCommentStatus, hideBanUserDialog} = this.props;
await setUserStatus({userId, status: 'BANNED'});
hideBanUserDialog();
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
await setCommentStatus({commentId, status: 'REJECTED'});
}
}
getInfo() {
let note = t('bandialog.note_ban_user');
if (this.props.commentStatus && this.props.commentStatus !== 'REJECTED') {
note = t('bandialog.note_reject_comment');
}
return t('bandialog.note', note);
}
render() {
return (
<BanUserDialog
open={this.props.open}
onPerform={this.banUser}
onCancel={this.props.hideBanUserDialog}
username={this.props.username}
info={this.getInfo()}
/>
);
}
}
const mapStateToProps = ({banUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideBanUserDialog,
}, dispatch),
});
export default compose(
withSetUserStatus,
withSetCommentStatus,
connect(
mapStateToProps,
mapDispatchToProps,
),
)(BanUserDialogContainer);
+7 -1
View File
@@ -5,6 +5,8 @@ import {fetchConfig} from '../actions/config';
import AdminLogin from '../components/AdminLogin';
import {logout} from 'coral-framework/actions/auth';
import {FullLoading} from '../components/FullLoading';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import {toggleModal as toggleShortcutModal} from '../actions/moderation';
import {checkLogin, handleLogin, requestPasswordReset} from '../actions/auth';
import {can} from 'coral-framework/services/perms';
@@ -52,7 +54,11 @@ class LayoutContainer extends Component {
handleLogout={handleLogout}
toggleShortcutModal={toggleShortcutModal}
{...this.props}
/>
>
<BanUserDialog />
<SuspendUserDialog />
{this.props.children}
</Layout>
);
} else if (loggedIn) {
return (
@@ -0,0 +1,83 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import SuspendUserDialog from '../components/SuspendUserDialog';
import {hideSuspendUserDialog} from '../actions/suspendUserDialog';
import {withSetCommentStatus, withSuspendUser} from 'coral-framework/graphql/mutations';
import {compose, gql} from 'react-apollo';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import withQuery from 'coral-framework/hocs/withQuery';
import get from 'lodash/get';
class SuspendUserDialogContainer extends Component {
suspendUser = async ({message, until}) => {
const {userId, username, commentStatus, commentId, hideSuspendUserDialog, setCommentStatus, suspendUser} = this.props;
hideSuspendUserDialog();
try {
const result = await suspendUser({id: userId, message, until});
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
t('suspenduser.notify_suspend_until', username, timeago(until)),
);
if (commentId && commentStatus && commentStatus !== 'REJECTED') {
return setCommentStatus({commentId, status: 'REJECTED'})
.then((result) => {
if (result.data.setCommentStatus.errors) {
throw result.data.setCommentStatus.errors;
}
});
}
}
catch(err) {
notification.showMutationErrors(err);
}
};
render() {
return (
<SuspendUserDialog
open={this.props.open}
onPerform={this.suspendUser}
onCancel={this.props.hideSuspendUserDialog}
organizationName={get(this.props, 'root.settings.organizationName')}
username={this.props.username}
/>
);
}
}
const withOrganizationName = withQuery(gql`
query CoralAdmin_SuspendUserDialog {
settings {
organizationName
}
}
`);
const mapStateToProps = ({suspendUserDialog: {open, userId, username, commentId, commentStatus}}) => ({
open,
userId,
username,
commentId,
commentStatus,
});
const mapDispatchToProps = (dispatch) => ({
...bindActionCreators({
hideSuspendUserDialog,
}, dispatch),
});
export default compose(
connect(
mapStateToProps,
mapDispatchToProps,
),
withSuspendUser,
withSetCommentStatus,
withOrganizationName,
)(SuspendUserDialogContainer);
@@ -0,0 +1,30 @@
import {SHOW_BAN_USER_DIALOG, HIDE_BAN_USER_DIALOG} from '../constants/banUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function banUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_BAN_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_BAN_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
+7 -7
View File
@@ -9,8 +9,8 @@ import {
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG,
SHOW_SUSPENDUSER_DIALOG,
HIDE_SUSPENDUSER_DIALOG
SHOW_REJECT_USERNAME_DIALOG,
HIDE_REJECT_USERNAME_DIALOG
} from '../constants/community';
const initialState = Map({
@@ -24,7 +24,7 @@ const initialState = Map({
pagePeople: 0,
user: Map({}),
banDialog: false,
suspendDialog: false
rejectUsernameDialog: false
});
export default function community (state = initialState, action) {
@@ -79,14 +79,14 @@ export default function community (state = initialState, action) {
user: Map(action.user),
banDialog: true
});
case HIDE_SUSPENDUSER_DIALOG:
case HIDE_REJECT_USERNAME_DIALOG:
return state
.set('suspendDialog', false);
case SHOW_SUSPENDUSER_DIALOG:
.set('rejectUsernameDialog', false);
case SHOW_REJECT_USERNAME_DIALOG:
return state
.merge({
user: Map(action.user),
suspendDialog: true
rejectUsernameDialog: true
});
default :
return state;
+4
View File
@@ -5,9 +5,13 @@ import community from './community';
import moderation from './moderation';
import install from './install';
import config from './config';
import banUserDialog from './banUserDialog';
import suspendUserDialog from './suspendUserDialog';
export default {
auth,
banUserDialog,
suspendUserDialog,
assets,
settings,
community,
+1 -39
View File
@@ -1,61 +1,23 @@
import {fromJS, Map, Set} from 'immutable';
import {fromJS, Set} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = fromJS({
singleView: false,
modalOpen: false,
user: {},
commentId: null,
commentStatus: null,
userDetailId: null,
userDetailActiveTab: 'all',
userDetailStatuses: ['NONE', 'ACCEPTED', 'REJECTED', 'PREMOD'],
userDetailSelectedIds: new Set(),
banDialog: false,
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
suspendUserDialog: {
show: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
},
});
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.MODERATION_CLEAR_STATE:
return initialState;
case actions.HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false)
.set('commentStatus', null);
case actions.SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
commentId: action.commentId,
commentStatus: action.commentStatus,
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SHOW_SUSPEND_USER_DIALOG:
return state
.mergeDeep({
suspendUserDialog: {
show: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
}
});
case actions.HIDE_SUSPEND_USER_DIALOG:
return state
.setIn(['suspendUserDialog', 'show'], false);
case actions.SET_ACTIVE_TAB:
return state
.set('activeTab', action.activeTab);
@@ -0,0 +1,30 @@
import {SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG} from '../constants/suspendUserDialog';
const initialState = {
open: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
};
export default function suspendUserDialog(state = initialState, action) {
switch (action.type) {
case SHOW_SUSPEND_USER_DIALOG:
return {
...state,
open: true,
userId: action.userId,
username: action.username,
commentId: action.commentId,
commentStatus: action.commentStatus,
};
case HIDE_SUSPEND_USER_DIALOG:
return {
...state,
open: false,
};
default:
return state;
}
}
@@ -1,23 +1,18 @@
import React from 'react';
import styles from './Community.css';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap';
import t from 'coral-framework/services/i18n';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton icon='not interested' user={user} onClick={() => props.showBanUserDialog(user)} />;
}
return (
<Button
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={() => {
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showSuspendUserDialog({user: user});
type === 'APPROVE' ? props.approveUser({userId: user.id}) : props.showRejectUsernameDialog({user: user});
}}
>{t(`modqueue.${menuActionsMap[type].text}`)}</Button>
);
@@ -1,11 +0,0 @@
.banButton {
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
i {
vertical-align: middle;
margin-right: 5px;
font-size: 14px;
}
}
@@ -1,24 +0,0 @@
import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import styles from './BanUserButton.css';
import t from 'coral-framework/services/i18n';
const BanUserButton = ({user, ...props}) => (
<div className={styles.ban}>
<Button cStyle='ban'
className={`ban ${styles.banButton}`}
disabled={user.status === 'BANNED' ? 'disabled' : ''}
onClick={props.onClick}
raised>
<Icon name='not_interested' />
{t('comment.ban_user')}
</Button>
</div>
);
BanUserButton.propTypes = {
onClick: PropTypes.func.isRequired
};
export default BanUserButton;
@@ -1,164 +0,0 @@
.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: 500px;
top: 50%;
transform: translateY(-50%);
height: 184px;
padding: 20px;
h2 {
color: black;
font-size: 1.76em;
font-weight: 500;
margin: 0;
}
h3 {
color: black;
font-size: 1.4em;
font-weight: 500;
margin: 0;
}
}
.textField {
margin-top: 15px;
}
.textField label {
font-size: 1.08em;
font-weight: bold;
margin-bottom: 5px;
}
.textField input {
width: 100%;
display: block;
border: none;
outline: none;
border: 1px solid rgba(0,0,0,.12);
padding: 10px 6px;
box-sizing: border-box;
border-radius: 2px;
margin: 5px auto;
}
.footer {
margin: 20px auto 10px;
text-align: center;
}
.footer span {
display: block;
margin-bottom: 5px;
}
.footer a {
color: #2c69b6;
cursor: pointer;
margin: 0 5px;
}
.socialConnections {
margin-bottom: 20px;
}
.signInButton {
margin-top: 10px;
}
.close {
font-size: 20px;
line-height: 14px;
top: 10px;
right: 10px;
position: absolute;
display: block;
font-weight: bold;
color: #363636;
cursor: pointer;
}
.close:hover {
color: #6b6b6b;
}
input.error{
border: solid 2px #f44336;
}
.errorMsg, .hint {
color: grey;
font-weight: 600;
padding: 3px 0 16px;
}
.alert {
padding: 10px;
margin-bottom: 20px;
border-radius: 2px;
}
.alert--success {
border: solid 1px #1ec00e;
background: #cbf1b8;
color: #006900;
}
.alert--error {
background: #FFEBEE;
color: #B71C1C;
}
.userBox a {
color: #2c69b6;
cursor: pointer;
margin: 0px;
}
.attention {
display: inline-block;
width: 15px;
height: 15px;
background: #B71C1C;
color: #FFEBEE;
font-weight: bolder;
padding: 4px;
vertical-align: middle;
border-radius: 20px;
box-sizing: border-box;
font-size: 9px;
line-height: 7px;
text-align: center;
margin-right: 5px;
}
.action {
margin-top: 15px;
}
.passwordRequestSuccess {
border: 1px solid green;
background-color: lightgreen;
padding: 10px;
}
.passwordRequestFailure {
border: 1px solid orange;
background-color: 1px solid coral;
padding: 10px;
}
.cancel {
margin-right: 10px;
width: 47%;
}
.ban {
width: 47%;
}
.buttons {
margin: 20px 0;
}
@@ -1,51 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('community.are_you_sure', user.username)}</h3>
<i>{t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
onClick={() => {
handleBanUser({userId: user.id}).then(() => {
handleClose();
});
}}
raised>
{t('community.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -1,10 +1,9 @@
import React, {Component} from 'react';
import CommunityMenu from './CommunityMenu';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
import RejectUsernameDialog from './RejectUsernameDialog';
export default class Community extends Component {
@@ -80,19 +79,15 @@ export default class Community extends Component {
<FlaggedAccounts
commenters={users}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
rejectUsername={props.rejectUsername}
showSuspendUserDialog={props.showSuspendUserDialog}
currentUser={this.props.currentUser}
/>
<BanUserDialog
open={community.banDialog}
user={community.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
<SuspendUserDialog
open={community.suspendDialog}
handleClose={props.hideSuspendUserDialog}
<RejectUsernameDialog
open={community.rejectUsernameDialog}
handleClose={props.hideRejectUsernameDialog}
user={community.user}
rejectUsername={props.rejectUsername}
/>
@@ -9,8 +9,6 @@ const FlaggedAccounts = ({...props}) => {
const {commenters} = props;
const hasResults = commenters && !!commenters.length;
// if (commenter.status === 'PENDING' && commenter.actions.length > 0) {
return (
<div className={styles.container}>
<div className={styles.mainFlaggedContent}>
@@ -24,8 +22,9 @@ const FlaggedAccounts = ({...props}) => {
modActionButtons={['APPROVE', 'REJECT']}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
showRejectUsernameDialog={props.showRejectUsernameDialog}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
currentUser={props.currentUser}
/>;
})
: <EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
@@ -1,30 +1,30 @@
import React, {Component, PropTypes} from 'react';
import {Dialog, Button} from 'coral-ui';
import styles from './SuspendUserDialog.css';
import styles from './RejectUsernameDialog.css';
import t from 'coral-framework/services/i18n';
const stages = [
{
title: 'suspenduser.title_reject',
description: 'suspenduser.description_reject',
title: 'reject_username.title_reject',
description: 'reject_username.description_reject',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
'j': 'reject_username.no_cancel',
'k': 'reject_username.yes_suspend'
}
},
{
title: 'suspenduser.title_notify',
description: 'suspenduser.description_notify',
title: 'reject_username.title_notify',
description: 'reject_username.description_notify',
options: {
'j': 'bandialog.cancel',
'k': 'suspenduser.send'
'k': 'reject_username.send'
}
}
];
class SuspendUserDialog extends Component {
class RejectUsernameDialog extends Component {
state = {email: '', stage: 0}
@@ -35,7 +35,7 @@ class SuspendUserDialog extends Component {
}
componentDidMount() {
this.setState({email: t('suspenduser.email_message_reject'), about: t('suspenduser.username')});
this.setState({email: t('reject_username.email_message_reject'), about: t('reject_username.username')});
}
/*
@@ -72,22 +72,22 @@ class SuspendUserDialog extends Component {
return <Dialog
className={styles.suspendDialog}
id="suspendUserDialog"
id="rejectUsernameDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('suspenduser.suspend_user')}>
title={t('reject_username.suspend_user')}>
<div className={styles.title}>
{t(stages[stage].title, t('suspenduser.username'))}
{t(stages[stage].title, t('reject_username.username'))}
</div>
<div className={styles.container}>
<div className={styles.description}>
{t(stages[stage].description, t('suspenduser.username'))}
{t(stages[stage].description, t('reject_username.username'))}
</div>
{
stage === 1 &&
<div className={styles.writeContainer}>
<div className={styles.emailMessage}>{t('suspenduser.write_message')}</div>
<div className={styles.emailMessage}>{t('reject_username.write_message')}</div>
<div className={styles.emailContainer}>
<textarea
rows={5}
@@ -100,7 +100,7 @@ class SuspendUserDialog extends Component {
<div className={styles.modalButtons}>
{Object.keys(stages[stage].options).map((key, i) => (
<Button key={i} onClick={this.onActionClick(stage, i)}>
{t(stages[stage].options[key], t('suspenduser.username'))}
{t(stages[stage].options[key], t('reject_username.username'))}
</Button>
))}
</div>
@@ -109,4 +109,4 @@ class SuspendUserDialog extends Component {
}
}
export default SuspendUserDialog;
export default RejectUsernameDialog;
@@ -2,6 +2,8 @@ import React from 'react';
import styles from './Community.css';
import ActionButton from './ActionButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import t from 'coral-framework/services/i18n';
@@ -18,6 +20,16 @@ const User = (props) => {
const {user, modActionButtons} = props;
let userStatus = user.status;
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: user.id,
username: user.username,
});
const showBanUserDialog = () => props.showBanUserDialog({
userId: user.id,
username: user.username,
});
// Do not display unless the user status is 'pending' or 'banned'.
// This means that they have already been reviewed and approved.
return (userStatus === 'PENDING' || userStatus === 'BANNED') &&
@@ -28,12 +40,19 @@ const User = (props) => {
<span>
{user.username}
</span>
<ActionButton
className={styles.banButton}
type='BAN'
user={user}
showBanUserDialog={props.showBanUserDialog}
/>
{props.currentUser.id !== user.id &&
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showSuspenUserDialog}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={user.status === 'BANNED'}
onClick={showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
}
</div>
</div>
@@ -78,11 +97,10 @@ const User = (props) => {
<div className={`actions ${styles.actions}`}>
{modActionButtons.map((action, i) =>
<ActionButton key={i}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
suspendUser={props.suspendUser}
showSuspendUserDialog={props.showSuspendUserDialog}
type={action.toUpperCase()}
user={user}
approveUser={props.approveUser}
showRejectUsernameDialog={props.showRejectUsernameDialog}
/>
)}
</div>
@@ -7,14 +7,15 @@ import {Spinner} from 'coral-ui';
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {
fetchAccounts,
updateSorting,
newPage,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog
showRejectUsernameDialog,
hideRejectUsernameDialog
} from '../../../actions/community';
import Community from '../components/Community';
@@ -85,16 +86,17 @@ export const withCommunityQuery = withQuery(gql`
});
const mapStateToProps = (state) => ({
community: state.community.toJS()
community: state.community.toJS(),
currentUser: state.auth.toJS().user,
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators({
fetchAccounts,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
showRejectUsernameDialog,
hideRejectUsernameDialog,
updateSorting,
newPage,
}, dispatch);
@@ -165,7 +165,7 @@ const StreamSettings = ({updateSettings, settingsError, settings, errors}) => {
checked={settings.autoCloseStream} />
</div>
<div className={styles.content}>
{t('configure.close_after')}
<div className={styles.settingsHeader}>{t('configure.comment_count_header')}</div>
<br />
<Textfield
type='number'
@@ -1,52 +0,0 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import t from 'coral-framework/services/i18n';
const onBanClick = (userId, commentId, commentStatus, handleBanUser, rejectComment, handleClose) => (e) => {
e.preventDefault();
handleBanUser({userId})
.then(handleClose)
.then(() => commentStatus === 'REJECTED' ? null : rejectComment({commentId}));
};
const BanUserDialog = ({open, handleClose, handleBanUser, rejectComment, user, commentId, commentStatus, showRejectedNote}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={t('bandialog.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{t('bandialog.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{t('bandialog.are_you_sure', user.username)}</h3>
<i>{showRejectedNote && t('bandialog.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{t('bandialog.cancel')}
</Button>
<Button cStyle="black" className={styles.ban} onClick={onBanClick(user.id, commentId, commentStatus, handleBanUser, rejectComment, handleClose)} raised>
{t('bandialog.yes_ban_user')}
</Button>
</div>
</div>
</Dialog>
);
BanUserDialog.propTypes = {
handleBanUser: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
commentId: PropTypes.string,
user: PropTypes.object.isRequired,
};
export default BanUserDialog;
@@ -65,6 +65,20 @@ class Comment extends React.Component {
selectionStateCSS = selected ? 'mdl-shadow--16dp' : 'mdl-shadow--2dp';
}
const showSuspenUserDialog = () => props.showSuspendUserDialog({
userId: comment.user.id,
username: comment.user.username,
commentId: comment.id,
commentStatus: comment.status,
});
const showBanUserDialog = () => props.showBanUserDialog({
userId: comment.user.id,
username: comment.user.username,
commentId: comment.id,
commentStatus: comment.status,
});
return (
<li
tabIndex={props.index}
@@ -102,11 +116,11 @@ class Comment extends React.Component {
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.username, comment.id, comment.status)}>
onClick={showSuspenUserDialog}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
onClick={showBanUserDialog}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
@@ -217,6 +231,7 @@ class Comment extends React.Component {
? <FlagBox
actions={flagActions}
actionSummaries={flagActionSummaries}
viewUserDetail={() => viewUserDetail(comment.user.id)}
/>
: null}
</li>
@@ -67,3 +67,17 @@
color: black;
}
}
.username {
color: #393B44;
text-decoration: none;
cursor: pointer;
font-weight: 600;
padding: 2px 5px;
border-radius: 2px;
margin-left: -5px;
transition: background-color 200ms ease;
&:hover {
background-color: #E0E0E0;
}
}
@@ -33,7 +33,7 @@ class FlagBox extends Component {
}
render() {
const {actionSummaries, actions} = this.props;
const {actionSummaries, actions, viewUserDetail} = this.props;
const {showDetail} = this.state;
return (
@@ -59,9 +59,14 @@ class FlagBox extends Component {
<li key={i}>
{this.reasonMap(summary.reason)} (<strong>{summary.count}</strong>)
<ul>
{
actionList.map((action, j) => <li key={`${i}_${j}`} className={styles.subDetail}><span>{action.user.username}</span> {action.message}</li>)
}
{actionList.map((action, j) =>
<li key={`${i}_${j}`} className={styles.subDetail}>
<a className={styles.username} onClick={viewUserDetail}>
{action.user.username}
</a>
{action.message}
</li>
)}
</ul>
</li>
);
@@ -2,8 +2,6 @@ import React, {Component} from 'react';
import key from 'keymaster';
import styles from './styles.css';
import BanUserDialog from './BanUserDialog';
import SuspendUserDialog from './SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './ModerationMenu';
import ModerationHeader from './ModerationHeader';
@@ -29,8 +27,8 @@ export default class Moderation extends Component {
key('esc', () => toggleModal(false));
key('j', this.select(true));
key('k', this.select(false));
key('r', this.moderate(false));
key('t', this.moderate(true));
key('f', this.moderate(false));
key('d', this.moderate(true));
}
onClose = () => {
@@ -88,8 +86,8 @@ export default class Moderation extends Component {
key.unbind('esc');
key.unbind('j');
key.unbind('k');
key.unbind('r');
key.unbind('t');
key.unbind('f');
key.unbind('d');
}
componentDidUpdate(_, prevState) {
@@ -169,24 +167,6 @@ export default class Moderation extends Component {
viewUserDetail={viewUserDetail}
hideUserDetail={hideUserDetail}
/>
<BanUserDialog
open={moderation.banDialog}
user={moderation.user}
commentId={moderation.commentId}
commentStatus={moderation.commentStatus}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<SuspendUserDialog
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
userId={moderation.suspendUserDialog.userId}
organizationName={root.settings.organizationName}
onCancel={props.hideSuspendUserDialog}
onPerform={this.props.suspendUser}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
@@ -4,23 +4,20 @@ import {bindActionCreators} from 'redux';
import {compose, gql} from 'react-apollo';
import withQuery from 'coral-framework/hocs/withQuery';
import {getDefinitionName} from 'coral-framework/utils';
import * as notification from 'coral-admin/src/services/notification';
import t, {timeago} from 'coral-framework/services/i18n';
import t from 'coral-framework/services/i18n';
import update from 'immutability-helper';
import truncate from 'lodash/truncate';
import NotFoundAsset from '../components/NotFoundAsset';
import {withSetUserStatus, withSuspendUser, withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
import {handleCommentChange} from '../../../graphql/utils';
import {fetchSettings} from 'actions/settings';
import {showBanUserDialog} from 'actions/banUserDialog';
import {showSuspendUserDialog} from 'actions/suspendUserDialog';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
toggleStorySearch,
viewUserDetail,
@@ -138,37 +135,6 @@ class ModerationContainer extends Component {
}
}
suspendUser = async (args) => {
this.props.hideSuspendUserDialog();
try {
const result = await this.props.suspendUser(args);
if (result.data.suspendUser.errors) {
throw result.data.suspendUser.errors;
}
notification.success(
t('suspenduser.notify_suspend_until',
this.props.moderation.suspendUserDialog.username,
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(err) {
notification.showMutationErrors(err);
}
};
banUser = ({userId}) => {
return this.props.setUserStatus({userId, status: 'BANNED'});
}
acceptComment = ({commentId}) => {
return this.props.setCommentStatus({commentId, status: 'ACCEPTED'});
}
@@ -245,10 +211,8 @@ class ModerationContainer extends Component {
return <Moderation
{...this.props}
loadMore={this.loadMore}
banUser={this.banUser}
acceptComment={this.acceptComment}
rejectComment={this.rejectComment}
suspendUser={this.suspendUser}
activeTab={this.activeTab}
/>;
}
@@ -458,11 +422,9 @@ const mapDispatchToProps = (dispatch) => ({
singleView,
fetchSettings,
showBanUserDialog,
hideBanUserDialog,
hideShortcutsNote,
toggleStorySearch,
showSuspendUserDialog,
hideSuspendUserDialog,
viewUserDetail,
hideUserDetail,
setSortOrder,
@@ -474,8 +436,6 @@ const mapDispatchToProps = (dispatch) => ({
export default compose(
connect(mapStateToProps, mapDispatchToProps),
withSetCommentStatus,
withSetUserStatus,
withSuspendUser,
withQueueCountPolling,
withModQueueQuery,
)(ModerationContainer);
@@ -5,9 +5,9 @@ export const actionsMap = {
};
export const menuActionsMap = {
'REJECT': {status: 'REJECTED', text: 'reject', icon: 'close', key: 'r'},
'REJECT': {status: 'REJECTED', text: 'reject', icon: 'close', key: 'f'},
'REJECTED': {status: 'REJECTED', text: 'rejected', icon: 'close'},
'APPROVE': {status: 'ACCEPTED', text: 'approve', icon: 'done', key: 't'},
'APPROVE': {status: 'ACCEPTED', text: 'approve', icon: 'done', key: 'd'},
'FLAGGED': {status: 'FLAGGED', text: 'flag', icon: 'flag', filter: 'Untouched'},
'BAN': {status: 'BANNED', text: 'ban_user', icon: 'not interested'},
'': {icon: 'done'}
+1 -1
View File
@@ -30,7 +30,7 @@ class ResponseError {
* Exports a HOC with the same signature as `graphql`, that will
* apply mutation options registered in the graphRegistry.
*/
export default (document, config) => (WrappedComponent) => {
export default (document, config = {}) => (WrappedComponent) => {
config = {
...config,
options: config.options || {},
+1 -1
View File
@@ -14,7 +14,7 @@ const withSkipOnErrors = (reducer) => (prev, action, ...rest) => {
* Exports a HOC with the same signature as `graphql`, that will
* apply query options registered in the graphRegistry.
*/
export default (document, config) => (WrappedComponent) => {
export default (document, config = {}) => (WrappedComponent) => {
config = {
...config,
options: config.options || {},