Implement suspend user components

This commit is contained in:
Chi Vinh Le
2017-05-15 22:28:04 +07:00
parent d8e7b25d0e
commit 63fa3c4de7
25 changed files with 770 additions and 60 deletions
@@ -7,6 +7,12 @@ export const singleView = () => ({type: actions.SINGLE_VIEW});
export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote});
export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog});
// 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,53 @@
.button {
-webkit-transform: scale(.8);
transform: scale(.8);
margin: 0;
}
.root {
color: black;
> :global(.mdl-menu__container) {
margin-left: 10px;
> :global(.mdl-menu__outline) {
box-shadow: none;
}
}
}
.buttonOpen {
box-shadow: none;
color: white;
background-color: #616161;
}
.arrowIcon {
margin-left: 6px;
margin-right: 0;
vertical-align: middle;
margin-right: 0;
font-size: 14px;
}
.menu {
padding: 0;
}
.menuItem {
background-color: #2a2a2a;
color: white;
&:first-child {
margin-bottom: 1px;
border-radius: 2px 2px 0px 0px;
}
&:last-child {
border-radius: 0px 0px 2px 2px;
}
&:hover, &:active, &:focus {
background-color: #767676;
}
&[disabled], &[disabled]:hover, &[disabled]:focus, &[disabled]:active {
background-color: #262626;
color: rgba(255, 255, 255, 0.5);
}
}
@@ -0,0 +1,64 @@
import React, {PropTypes} from 'react';
import {Button, Icon} from 'coral-ui';
import {Menu} from 'react-mdl';
import cn from 'classnames';
import {findDOMNode} from 'react-dom';
import styles from './ActionsMenu.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from 'coral-admin/src/translations.json';
const lang = new I18n(translations);
let count = 0;
class ActionsMenu extends React.Component {
id = `actions-dropdown-${count++}`;
menu = null;
state = {open: false};
timeout = null;
componentWillUnmount() {
clearTimeout(this.timeout);
}
handleRef = (ref) => {
this.menu = ref ? findDOMNode(ref).parentNode : null;
}
syncOpenState = () => {
clearTimeout(this.timeout);
this.timeout = setTimeout(() => {
this.setState({open: this.menu.className.indexOf('is-visible') >= 0});
}, 150);
};
render() {
return (
<div className={styles.root} onBlur={this.syncOpenState} >
<Button
cStyle='actions'
className={cn(styles.button, {[styles.buttonOpen]: this.state.open})}
disabled={false}
id={this.id}
onClick={this.syncOpenState}
icon={this.props.icon}
raised>
{lang.t('modqueue.actions')}
<Icon
name={this.state.open ? 'keyboard_arrow_up' : 'keyboard_arrow_down'}
className={styles.arrowIcon}
/>
</Button>
<Menu target={this.id} className={styles.menu} ref={this.handleRef}>
{this.props.children}
</Menu>
</div>
);
}
}
ActionsMenu.propTypes = {
icon: PropTypes.string,
};
export default ActionsMenu;
@@ -0,0 +1,9 @@
import React from 'react';
import cn from 'classnames';
import {MenuItem} from 'react-mdl';
import styles from './ActionsMenu.css';
const ActionsMenuItem = (props) =>
<MenuItem className={cn(styles.menuItem, props.className)} {...props} />;
export default ActionsMenuItem;
+5 -5
View File
@@ -1,16 +1,16 @@
import React from 'react';
import {Provider} from 'react-redux';
import ToastContainer from './ToastContainer';
import 'material-design-lite';
import store from 'services/store';
import AppRouter from '../AppRouter';
export default class App extends React.Component {
render () {
return (
<Provider store={store}>
<AppRouter store={store} />
</Provider>
<div>
<ToastContainer />
<AppRouter />
</div>
);
}
}
@@ -0,0 +1,226 @@
@keyframes :global(bounceInRight) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
from {
opacity: 0;
transform: translate3d(3000px, 0, 0); }
60% {
opacity: 1;
transform: translate3d(-25px, 0, 0); }
75% {
transform: translate3d(10px, 0, 0); }
90% {
transform: translate3d(-5px, 0, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutRight) {
20% {
opacity: 1;
transform: translate3d(-20px, 0, 0); }
to {
opacity: 0;
transform: translate3d(2000px, 0, 0); } }
@keyframes :global(bounceInLeft) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
0% {
opacity: 0;
transform: translate3d(-3000px, 0, 0); }
60% {
opacity: 1;
transform: translate3d(25px, 0, 0); }
75% {
transform: translate3d(-10px, 0, 0); }
90% {
transform: translate3d(5px, 0, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutLeft) {
20% {
opacity: 1;
transform: translate3d(20px, 0, 0); }
to {
opacity: 0;
transform: translate3d(-2000px, 0, 0); } }
@keyframes :global(bounceInUp) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
from {
opacity: 0;
transform: translate3d(0, 3000px, 0); }
60% {
opacity: 1;
transform: translate3d(0, -20px, 0); }
75% {
transform: translate3d(0, 10px, 0); }
90% {
transform: translate3d(0, -5px, 0); }
to {
transform: translate3d(0, 0, 0); } }
@keyframes :global(bounceOutUp) {
20% {
transform: translate3d(0, -10px, 0); }
40%, 45% {
opacity: 1;
transform: translate3d(0, 20px, 0); }
to {
opacity: 0;
transform: translate3d(0, -2000px, 0); } }
@keyframes :global(bounceInDown) {
from, 60%, 75%, 90%, to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); }
0% {
opacity: 0;
transform: translate3d(0, -3000px, 0); }
60% {
opacity: 1;
transform: translate3d(0, 25px, 0); }
75% {
transform: translate3d(0, -10px, 0); }
90% {
transform: translate3d(0, 5px, 0); }
to {
transform: none; } }
@keyframes :global(bounceOutDown) {
20% {
transform: translate3d(0, 10px, 0); }
40%, 45% {
opacity: 1;
transform: translate3d(0, -20px, 0); }
to {
opacity: 0;
transform: translate3d(0, 2000px, 0); } }
@keyframes :global(track-progress) {
0% {
width: 100%; }
100% {
width: 0; } }
:global {
.bounceOutRight, .toast-exit--top-right, .toast-exit--bottom-right {
animation-name: bounceOutRight; }
.bounceInRight, .toast-enter--top-right, .toast-enter--bottom-right {
animation-name: bounceInRight; }
.bounceInLeft, .toast-enter--top-left, .toast-enter--bottom-left {
animation-name: bounceInLeft; }
.bounceOutLeft, .toast-exit--top-left, .toast-exit--bottom-left {
animation-name: bounceOutLeft; }
.bounceInUp, .toast-enter--bottom-center {
animation-name: bounceInUp; }
.bounceOutUp, .toast-exit--top-center {
animation-name: bounceOutUp; }
.bounceInDown, .toast-enter--top-center {
animation-name: bounceInDown; }
.bounceOutDown, .toast-exit--bottom-center {
animation-name: bounceOutDown; }
.animated {
animation-duration: 0.75s;
animation-fill-mode: both; }
.toastify {
z-index: 999;
position: fixed;
padding: 4px;
width: 350px;
max-width: 98%;
color: #999;
box-sizing: border-box; }
.toastify--top-left {
top: 1em;
left: 1em; }
.toastify--top-center {
top: 1em;
left: 50%;
margin-left: -175px; }
.toastify--top-right {
top: 1em;
right: 2em; }
.toastify--bottom-left {
bottom: 1em;
left: 1em; }
.toastify--bottom-center {
bottom: 1em;
left: 50%;
margin-left: -175px; }
.toastify--bottom-right {
bottom: 1em;
right: 2em; }
.toastify__img {
float: left;
margin-right: 8px;
vertical-align: middle; }
.toastify__close {
position: absolute;
top: 18px;
left: 12px;
width: 20px;
height: 16px;
padding: 0;
text-align: center;
text-decoration: none;
color: white;
font-weight: bold;
font-size: 14px;
background: transparent;
outline: none;
border: none;
cursor: pointer;
opacity: 0.8;
transition: .3s ease; }
.toastify__close:hover, .toastify__close:focus {
opacity: 1;
}
.toastify-content {
position: relative;
width: 100%;
margin-bottom: 12px;
padding: 18px 24px 20px 48px;
box-sizing: border-box;
background: #404040;
border-radius: 2px;
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1), 0 3px 20px 0 rgba(0, 0, 0, 0.05); }
.toastify-content--info {
background: #2488cb; }
.toastify-content--success {
background: #008577; }
.toastify-content--warning {
background: #ef6c2b; }
.toastify-content--error {
background: #ef342b; }
.toastify__body {
color: white;
font-size: 15px;
font-weight: 400;
}
.toastify__progress {
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 4px;
z-index: 999;
opacity: 0.8;
border-radius: 2px;
animation: track-progress linear 1;
background-color: white;
}
}
@@ -0,0 +1,7 @@
import './ToastContainer.css';
import {defaultProps} from 'recompose';
import {ToastContainer} from 'react-toastify';
export default defaultProps({
autoClose: 5000,
})(ToastContainer);
@@ -3,3 +3,5 @@ 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';
@@ -1,6 +1,6 @@
import React from 'react';
import styles from '../Community.css';
import BanUserButton from '../../../components/BanUserButton';
import BanUserButton from './BanUserButton';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
@@ -10,16 +10,16 @@ const lang = new I18n(translations);
const stages = [
{
title: 'suspenduser.title_0',
description: 'suspenduser.description_0',
title: 'suspenduser.title_reject',
description: 'suspenduser.description_reject',
options: {
'j': 'suspenduser.no_cancel',
'k': 'suspenduser.yes_suspend'
}
},
{
title: 'suspenduser.title_1',
description: 'suspenduser.description_1',
title: 'suspenduser.title_notify',
description: 'suspenduser.description_notify',
options: {
'j': 'bandialog.cancel',
'k': 'suspenduser.send'
@@ -38,7 +38,7 @@ class SuspendUserDialog extends Component {
}
componentDidMount() {
this.setState({email: lang.t('suspenduser.email'), about: lang.t('suspenduser.username')});
this.setState({email: lang.t('suspenduser.email_message_reject'), about: lang.t('suspenduser.username')});
}
/*
@@ -1,6 +1,8 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {compose} from 'react-apollo';
import {toast} from 'react-toastify';
import key from 'keymaster';
import isEqual from 'lodash/isEqual';
import styles from './components/styles.css';
@@ -10,10 +12,19 @@ import {banUser, setCommentStatus} from '../../graphql/mutations';
import {fetchSettings} from 'actions/settings';
import {updateAssets} from 'actions/assets';
import {toggleModal, singleView, showBanUserDialog, hideBanUserDialog, hideShortcutsNote} from 'actions/moderation';
import {
toggleModal,
singleView,
showBanUserDialog,
hideBanUserDialog,
showSuspendUserDialog,
hideSuspendUserDialog,
hideShortcutsNote,
} from 'actions/moderation';
import {Spinner} from 'coral-ui';
import BanUserDialog from '../../components/BanUserDialog';
import BanUserDialog from './components/BanUserDialog';
import SuspendUserDialog from './components/SuspendUserDialog';
import ModerationQueue from './ModerationQueue';
import ModerationMenu from './components/ModerationMenu';
import ModerationHeader from './components/ModerationHeader';
@@ -175,6 +186,7 @@ class ModerationContainer extends Component {
bannedWords={settings.wordlist.banned}
suspectWords={settings.wordlist.suspect}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
loadMore={props.loadMore}
@@ -192,6 +204,12 @@ class ModerationContainer extends Component {
showRejectedNote={moderation.showRejectedNote}
rejectComment={props.rejectComment}
/>
<SuspendUserDialog
open={moderation.suspendUserDialog.show}
username={moderation.suspendUserDialog.username}
onCancel={props.hideSuspendUserDialog}
onPerform={() => toast('User admin has been suspended for 24 hours. this suspension will automatically end after 24 hours.', {type: 'success'}) && props.hideSuspendUserDialog()}
/>
<ModerationKeysModal
hideShortcutsNote={props.hideShortcutsNote}
shortcutsNoteVisible={moderation.shortcutsNoteVisible}
@@ -209,14 +227,18 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
toggleModal: toggle => dispatch(toggleModal(toggle)),
onClose: () => dispatch(toggleModal(false)),
singleView: () => dispatch(singleView()),
updateAssets: assets => dispatch(updateAssets(assets)),
fetchSettings: () => dispatch(fetchSettings()),
showBanUserDialog: (user, commentId, commentStatus, showRejectedNote) => dispatch(showBanUserDialog(user, commentId, commentStatus, showRejectedNote)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false)),
hideShortcutsNote: () => dispatch(hideShortcutsNote()),
...bindActionCreators({
toggleModal,
singleView,
updateAssets,
fetchSettings,
showBanUserDialog,
hideShortcutsNote,
showSuspendUserDialog,
hideSuspendUserDialog,
}, dispatch),
});
export default compose(
@@ -16,6 +16,7 @@ class ModerationQueue extends React.Component {
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
rejectComment: PropTypes.func.isRequired,
acceptComment: PropTypes.func.isRequired,
comments: PropTypes.array.isRequired
@@ -51,6 +52,7 @@ class ModerationQueue extends React.Component {
bannedWords={props.bannedWords}
actions={actionsMap[status]}
showBanUserDialog={props.showBanUserDialog}
showSuspendUserDialog={props.showSuspendUserDialog}
acceptComment={props.acceptComment}
rejectComment={props.rejectComment}
currentAsset={props.currentAsset}
@@ -152,13 +152,14 @@ input.error{
.cancel {
margin-right: 10px;
width: 47%;
width: 48%;
}
.ban {
width: 47%;
width: 48%;
}
.buttons {
margin: 20px 0;
margin: 20px;
text-align: center;
}
@@ -5,7 +5,7 @@ import styles from './BanUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../translations';
import translations from '../../../translations';
const lang = new I18n(translations);
const onBanClick = (userId, commentId, commentStatus, handleBanUser, rejectComment, handleClose) => (e) => {
@@ -9,7 +9,8 @@ import {Icon} from 'coral-ui';
import FlagBox from './FlagBox';
import CommentType from './CommentType';
import ActionButton from 'coral-admin/src/components/ActionButton';
import BanUserButton from 'coral-admin/src/components/BanUserButton';
import ActionsMenu from 'coral-admin/src/components/ActionsMenu';
import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem';
import {getActionSummary} from 'coral-framework/utils';
const linkify = new Linkify();
@@ -48,7 +49,19 @@ const Comment = ({actions = [], comment, suspectWords, bannedWords, ...props}) =
<span className={styles.created}>
{timeago().format(comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))}
</span>
<BanUserButton user={comment.user} onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')} />
<ActionsMenu icon="not_interested">
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}>
Suspend User</ActionsMenuItem>
<ActionsMenuItem
disabled={comment.user.status === 'BANNED'}
onClick={() => props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}>
Ban User
</ActionsMenuItem>
</ActionsMenu>
<CommentType type={commentType} />
</div>
{comment.user.status === 'banned' ?
@@ -103,6 +116,8 @@ Comment.propTypes = {
suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired,
bannedWords: PropTypes.arrayOf(PropTypes.string).isRequired,
currentAsset: PropTypes.object,
showBanUserDialog: PropTypes.func.isRequired,
showSuspendUserDialog: PropTypes.func.isRequired,
comment: PropTypes.shape({
body: PropTypes.string.isRequired,
action_summaries: PropTypes.array,
@@ -0,0 +1,90 @@
.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;
}
.close {
display: block;
position: absolute;
top: 24px;
right: 20px;
}
.closeButton {
userSelect: none;
outline: none;
border: none;
touchAction: manipulation;
&::-moz-focus-inner: {
border: 0;
}
background: 0;
padding: 0;
font-size: 24px;
line-height: 14px;
cursor: pointer;
color: #363636;
&:hover {
color: #6b6b6b;
}
}
.legend {
padding: 0;
font-weight: bold;
}
div.radioGroup {
margin-top: 6px;
}
label.radioGroup {
&:global(.is-checked) > :global(.mdl-radio__outer-circle),
> :global(.mdl-radio__outer-circle) {
border-color: #212121;
}
> :global(.mdl-radio__inner-circle) {
background: #212121;
}
> :global(.mdl-radio__label) {
font-size: 14px;
line-height: 20px;
}
}
.messageInput {
border-radius: 3px;
width: 100%;
padding: 10px;
font-size: 14px;
box-sizing: border-box;
}
.cancel {
margin-right: 5px;
}
.perform {
min-width: 90px;
}
.buttons {
margin-top: 8px;
margin-bottom: 6px;
text-align: right;
}
@@ -0,0 +1,147 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import {RadioGroup, Radio} from 'react-mdl';
import styles from './SuspendUserDialog.css';
import Button from 'coral-ui/components/Button';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations';
const lang = new I18n(translations);
const initialState = {step: 0, duration: '3', message: lang.t('suspenduser.email_message_suspend')};
class SuspendUserDialog extends React.Component {
state = initialState;
componentWillReceiveProps(next) {
if (this.props.open && !next.open) {
this.setState(initialState);
}
}
handleDurationChange = (event) => {
this.setState({duration: event.target.value});
}
handleMessageChange = (event) => {
this.setState({message: event.target.value});
}
increaseStep = () => {
this.setState({step: this.state.step + 1});
}
resetStep = () => {
this.setState({step: 0});
}
handlePerform = () => {
this.props.onPerform({
duration: this.state.duration,
message: this.state.message,
});
};
renderStep0() {
const {onCancel, username} = this.props;
const {duration} = this.state;
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_suspend')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_suspend', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.select_duration')}</legend>
<RadioGroup
name='status filter'
value={duration}
childContainer='div'
onChange={this.handleDurationChange}
className={styles.radioGroup}
>
<Radio value='3'>{lang.t('suspenduser.hours', 3)}</Radio>
<Radio value='24'>{lang.t('suspenduser.hours', 24)}</Radio>
<Radio value='168'>{lang.t('suspenduser.days', 7)}</Radio>
</RadioGroup>
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
</Button>
<Button cStyle="black" className={styles.perform} onClick={this.increaseStep} raised>
{lang.t('suspenduser.suspend_user')}
</Button>
</div>
</section>
);
}
renderStep1() {
const {onCancel, username} = this.props;
const {message} = this.state;
return (
<section>
<h1 className={styles.header}>
{lang.t('suspenduser.title_notify')}
</h1>
<p className={styles.description}>
{lang.t('suspenduser.description_notify', username)}
</p>
<fieldset>
<legend className={styles.legend}>{lang.t('suspenduser.write_message')}</legend>
<textarea
rows={5}
className={styles.messageInput}
value={message}
onChange={this.handleMessageChange} />
</fieldset>
<div className={styles.buttons}>
<Button cStyle="white" className={styles.cancel} onClick={onCancel} raised>
{lang.t('suspenduser.cancel')}
</Button>
<Button
cStyle="black"
className={styles.perform}
onClick={this.handlePerform}
disabled={this.state.message.length === 0}
raised
>
{lang.t('suspenduser.send')}
</Button>
</div>
</section>
);
}
render() {
const {open, onCancel} = this.props;
const {step} = this.state;
return (
<Dialog
className={styles.dialog}
onCancel={onCancel}
open={open}
>
<div className={styles.close}>
<button aria-label="Close" onClick={onCancel} className={styles.closeButton}>×</button>
</div>
{step === 0 && this.renderStep0()}
{step === 1 && this.renderStep1()}
</Dialog>
);
}
}
SuspendUserDialog.propTypes = {
open: PropTypes.bool.isRequired,
onCancel: PropTypes.func.isRequired,
onPerform: PropTypes.func.isRequired,
username: PropTypes.string.isRequired,
};
export default SuspendUserDialog;
+24 -4
View File
@@ -1,14 +1,21 @@
import {Map} from 'immutable';
import {fromJS, Map} from 'immutable';
import * as actions from '../constants/moderation';
const initialState = Map({
const initialState = fromJS({
singleView: false,
modalOpen: false,
user: Map({}),
user: {},
commentId: null,
commentStatus: null,
banDialog: false,
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show'
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
suspendUserDialog: {
show: false,
userId: null,
username: '',
commentId: null,
commentStatus: '',
},
});
export default function moderation (state = initialState, action) {
@@ -26,6 +33,19 @@ export default function moderation (state = initialState, action) {
showRejectedNote: action.showRejectedNote,
banDialog: true
});
case actions.SHOW_SUSPEND_USER_DIALOG:
return state
.mergeDeep({
suspendUserDialog: {
show: true,
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);
+27 -14
View File
@@ -140,19 +140,28 @@
"yes_ban_user": "Yes, Ban User"
},
"suspenduser": {
"title": "Suspend a user",
"title_0": "We noticed you rejected a username",
"description_0": "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}.",
"title_1": "Notify the user of their temporary suspension",
"description_1": "Suspending this user will temporarily disable their account and hide all of their comments on the site.",
"title_suspend": "Suspend User",
"description_suspend": "You are suspending {0}. This comment will go to the Rejected queue, and {0} will not be allowed to like, report, reply or post until the suspension time is complete.",
"select_duration": "Select suspension duration",
"title_reject": "We noticed you rejected a username",
"description_reject": "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}.",
"title_notify": "Notify the user of their temporary suspension",
"description_notify": "Suspending this user will temporarily disable their account and hide all of their comments on the site.",
"no_cancel": "No, cancel",
"yes_suspend": "Yes, suspend",
"send": "Send",
"bio": "bio",
"username": "username",
"email_subject": "Your account has been suspended",
"email": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
"write_message": "Write a message"
"email_message_reject": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
"email_message_suspend": "Your user account has been suspended. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.",
"write_message": "Write a message",
"hours": "{0} hours",
"days": "{0} days",
"suspend_user": "Suspend User",
"cancel": "Cancel",
"error_email_message_empty": "You must specify an E-Mail message."
},
"dashboard": {
"next-update": "{0} minutes until next update.",
@@ -217,19 +226,23 @@
"yes_ban_user": "Si, Suspendan el usuario"
},
"suspenduser": {
"title": "Suspendiendo un usuario",
"title_0": "Esta queriendo suspender un usuario?",
"description_0": "Le gustaria suspender a esta usuaria temporarianmente por su nombre de usuario? Si lo hace sus comentarios serán escondidos temporariamente hasta que puedan reescribir su nombre de usuario.",
"title_1": "Enviarle una nota al usuario sobre su cuenta suspendida",
"description_1": "Si suspende a este usuario, su cuenta va a ser deshabilitada y todos sus comentarios escondidos del sitio.",
"title_suspend": "Suspender Usuario",
"title_reject": "Esta queriendo suspender un usuario?",
"description_reject": "Le gustaria suspender a esta usuaria temporarianmente por su nombre de usuario? Si lo hace sus comentarios serán escondidos temporariamente hasta que puedan reescribir su nombre de usuario.",
"title_notify": "Enviarle una nota al usuario sobre su cuenta suspendida",
"description_notify": "Si suspende a este usuario, su cuenta va a ser deshabilitada y todos sus comentarios escondidos del sitio.",
"no_cancel": "No, cancelar",
"yes_suspend": "Si, suspender",
"send": "Enviar",
"username": "nombre de usuario",
"email_subject": "Su cuenta ha sido suspendida temporariamente",
"email": "Otra persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo a moderator@newsorg.com si tiene alguna pregunta o preocupación",
"email_message_reject": "Otra persona de la comunidad recientemente marcó su nombre de usuario para ser revisado. Por su contenido, el nombre de usuario ha sido rechazado. Esto quiere decir que no puede comentar, gustar o marcar contenido hasta que modifique su nombre de usuario. Por favor, envienos un correo a moderator@newsorg.com si tiene alguna pregunta o preocupación",
"write_message": "Escribir un mensaje",
"loading": "Cargando resultados"
"loading": "Cargando resultados",
"hours": "{0} horas",
"days": "{0} días",
"suspend_user": "Suspender",
"cancel": "Cancelar"
},
"modqueue": {
"all": "todos",
+23 -13
View File
@@ -25,12 +25,6 @@
letter-spacing: 0.7px;
font-weight: 400;
i {
margin-right: 13px;
font-size: 18px;
vertical-align: middle;
}
&:disabled {
background: #E0E0E0;
color: #4f5c67;
@@ -38,11 +32,22 @@
}
}
.icon {
margin-right: 13px;
font-size: 18px;
vertical-align: middle;
}
.type--black {
color: #E0E0E0;
color: white;
background: #212121;
}
.type--white {
color: #212121;
background: white;
}
.type--local {
background: #E0E0E0;
color: #212121;
@@ -163,7 +168,7 @@
cursor: not-allowed;
}
.type--ban {
.type--ban, .type--actions {
display: block;
color: #616161;
border: solid 1px rgba(97, 97, 97, 0.77);
@@ -179,11 +184,16 @@
font-size: 14px;
width: auto;
&:hover {
box-shadow: none;
color: white;
background-color: #616161;
}
&:hover {
box-shadow: none;
color: white;
background-color: #616161;
}
> .icon {
margin-right: 5px;
font-size: 14px;
}
}
.full {
+1 -1
View File
@@ -13,7 +13,7 @@ const Button = ({cStyle = 'local', children, className, raised = false, full = f
`}
{...props}
>
{icon && <Icon name={icon} />}
{icon && <Icon name={icon} className={styles.icon} />}
{children}
</button>
);
+2 -3
View File
@@ -96,14 +96,13 @@
"prop-types": "^15.5.8",
"react-apollo": "^1.1.0",
"react-recaptcha": "^2.2.6",
"react-toastify": "^1.5.0",
"recompose": "^0.23.1",
"redis": "^2.7.1",
"uuid": "^3.0.1",
"simplemde": "^1.11.2",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"resolve": "^1.3.2",
"semver": "^5.3.0",
"simplemde": "^1.11.2",
"subscriptions-transport-ws": "^0.5.5-alpha.0",
"uuid": "^3.0.1"
},
"devDependencies": {
+24
View File
@@ -1476,6 +1476,10 @@ chai@^3.5.0:
deep-eql "^0.1.3"
type-detect "^1.0.0"
chain-function@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/chain-function/-/chain-function-1.0.0.tgz#0d4ab37e7e18ead0bdc47b920764118ce58733dc"
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -2422,6 +2426,10 @@ doctypes@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9"
dom-helpers@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.2.1.tgz#3203e07fed217bd1f424b019735582fc37b2825a"
dom-serializer@0, dom-serializer@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82"
@@ -6817,6 +6825,22 @@ react-tagsinput@^3.14.0:
version "3.16.1"
resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.16.1.tgz#dfb3bcbe5fc4430f60c145716c17cdc2613ce117"
react-toastify@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-1.5.0.tgz#e9857e0b5d640064e5ba6caf7a96bb1578273de7"
dependencies:
prop-types "^15.5.8"
react-transition-group "^1.1.2"
react-transition-group@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.1.3.tgz#5e02cf6e44a863314ff3c68a0c826c2d9d70b221"
dependencies:
chain-function "^1.0.0"
dom-helpers "^3.2.0"
prop-types "^15.5.6"
warning "^3.0.0"
react@^15.3.1, react@^15.4.2:
version "15.5.4"
resolved "https://registry.yarnpkg.com/react/-/react-15.5.4.tgz#fa83eb01506ab237cdc1c8c3b1cea8de012bf047"