From 63fa3c4de79e5fb0c04eeac11966f909239adfa2 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 15 May 2017 22:28:04 +0700 Subject: [PATCH 01/15] Implement suspend user components --- client/coral-admin/src/actions/moderation.js | 6 + .../src/components/ActionsMenu.css | 53 ++++ .../coral-admin/src/components/ActionsMenu.js | 64 +++++ .../src/components/ActionsMenuItem.js | 9 + client/coral-admin/src/components/App.js | 10 +- .../src/components/ToastContainer.css | 226 ++++++++++++++++++ .../src/components/ToastContainer.js | 7 + .../coral-admin/src/constants/moderation.js | 2 + .../Community/components/ActionButton.js | 2 +- .../Community}/components/BanUserButton.css | 0 .../Community}/components/BanUserButton.js | 0 .../Community/components/SuspendUserDialog.js | 10 +- .../ModerationQueue/ModerationContainer.js | 38 ++- .../ModerationQueue/ModerationQueue.js | 2 + .../components/BanUserDialog.css | 7 +- .../components/BanUserDialog.js | 2 +- .../ModerationQueue/components/Comment.js | 19 +- .../components/SuspendUserDialog.css | 90 +++++++ .../components/SuspendUserDialog.js | 147 ++++++++++++ client/coral-admin/src/reducers/moderation.js | 28 ++- client/coral-admin/src/translations.json | 41 ++-- client/coral-ui/components/Button.css | 36 ++- client/coral-ui/components/Button.js | 2 +- package.json | 5 +- yarn.lock | 24 ++ 25 files changed, 770 insertions(+), 60 deletions(-) create mode 100644 client/coral-admin/src/components/ActionsMenu.css create mode 100644 client/coral-admin/src/components/ActionsMenu.js create mode 100644 client/coral-admin/src/components/ActionsMenuItem.js create mode 100644 client/coral-admin/src/components/ToastContainer.css create mode 100644 client/coral-admin/src/components/ToastContainer.js rename client/coral-admin/src/{ => containers/Community}/components/BanUserButton.css (100%) rename client/coral-admin/src/{ => containers/Community}/components/BanUserButton.js (100%) rename client/coral-admin/src/{ => containers/ModerationQueue}/components/BanUserDialog.css (97%) rename client/coral-admin/src/{ => containers/ModerationQueue}/components/BanUserDialog.js (97%) create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.css create mode 100644 client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index c93dc0c76..52d5134a5 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -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 { diff --git a/client/coral-admin/src/components/ActionsMenu.css b/client/coral-admin/src/components/ActionsMenu.css new file mode 100644 index 000000000..1d2f91f74 --- /dev/null +++ b/client/coral-admin/src/components/ActionsMenu.css @@ -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); + } +} + diff --git a/client/coral-admin/src/components/ActionsMenu.js b/client/coral-admin/src/components/ActionsMenu.js new file mode 100644 index 000000000..eb173eb81 --- /dev/null +++ b/client/coral-admin/src/components/ActionsMenu.js @@ -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 ( +
+ + + {this.props.children} + +
+ ); + } +} + +ActionsMenu.propTypes = { + icon: PropTypes.string, +}; + +export default ActionsMenu; diff --git a/client/coral-admin/src/components/ActionsMenuItem.js b/client/coral-admin/src/components/ActionsMenuItem.js new file mode 100644 index 000000000..82dab96f0 --- /dev/null +++ b/client/coral-admin/src/components/ActionsMenuItem.js @@ -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) => + ; + +export default ActionsMenuItem; diff --git a/client/coral-admin/src/components/App.js b/client/coral-admin/src/components/App.js index 3c6e88a14..1a15c72c3 100644 --- a/client/coral-admin/src/components/App.js +++ b/client/coral-admin/src/components/App.js @@ -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 ( - - - +
+ + +
); } } diff --git a/client/coral-admin/src/components/ToastContainer.css b/client/coral-admin/src/components/ToastContainer.css new file mode 100644 index 000000000..319872ceb --- /dev/null +++ b/client/coral-admin/src/components/ToastContainer.css @@ -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; + } +} diff --git a/client/coral-admin/src/components/ToastContainer.js b/client/coral-admin/src/components/ToastContainer.js new file mode 100644 index 000000000..a751d2714 --- /dev/null +++ b/client/coral-admin/src/components/ToastContainer.js @@ -0,0 +1,7 @@ +import './ToastContainer.css'; +import {defaultProps} from 'recompose'; +import {ToastContainer} from 'react-toastify'; + +export default defaultProps({ + autoClose: 5000, +})(ToastContainer); diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 14672146e..7e918ed4a 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -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'; diff --git a/client/coral-admin/src/containers/Community/components/ActionButton.js b/client/coral-admin/src/containers/Community/components/ActionButton.js index 6352480fd..ad5346ac5 100644 --- a/client/coral-admin/src/containers/Community/components/ActionButton.js +++ b/client/coral-admin/src/containers/Community/components/ActionButton.js @@ -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'; diff --git a/client/coral-admin/src/components/BanUserButton.css b/client/coral-admin/src/containers/Community/components/BanUserButton.css similarity index 100% rename from client/coral-admin/src/components/BanUserButton.css rename to client/coral-admin/src/containers/Community/components/BanUserButton.css diff --git a/client/coral-admin/src/components/BanUserButton.js b/client/coral-admin/src/containers/Community/components/BanUserButton.js similarity index 100% rename from client/coral-admin/src/components/BanUserButton.js rename to client/coral-admin/src/containers/Community/components/BanUserButton.js diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js b/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js index 20e221c48..827352c42 100644 --- a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js +++ b/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js @@ -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')}); } /* diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 9606be543..e8535f854 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -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} /> + toast('User admin has been suspended for 24 hours. this suspension will automatically end after 24 hours.', {type: 'success'}) && props.hideSuspendUserDialog()} + /> ({ }); 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( diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index c4bf0f85c..b93f185f0 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -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} diff --git a/client/coral-admin/src/components/BanUserDialog.css b/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css similarity index 97% rename from client/coral-admin/src/components/BanUserDialog.css rename to client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css index a46b9da32..f13f0e6aa 100644 --- a/client/coral-admin/src/components/BanUserDialog.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css @@ -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; } diff --git a/client/coral-admin/src/components/BanUserDialog.js b/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js similarity index 97% rename from client/coral-admin/src/components/BanUserDialog.js rename to client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js index b259012f3..87a5fe55a 100644 --- a/client/coral-admin/src/components/BanUserDialog.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js @@ -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) => { diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index ffd4db2a6..2e940d0d8 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -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}) = {timeago().format(comment.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} - props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')} /> + + + props.showSuspendUserDialog(comment.user.id, comment.user.name, comment.id, comment.status)}> + Suspend User + props.showBanUserDialog(comment.user, comment.id, comment.status, comment.status !== 'REJECTED')}> + Ban User + + + {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, diff --git a/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.css b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.css new file mode 100644 index 000000000..1c96f509a --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.css @@ -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; +} diff --git a/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js new file mode 100644 index 000000000..0ce0a43d1 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/SuspendUserDialog.js @@ -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 ( +
+

+ {lang.t('suspenduser.title_suspend')} +

+

+ {lang.t('suspenduser.description_suspend', username)} +

+
+ {lang.t('suspenduser.select_duration')} + + {lang.t('suspenduser.hours', 3)} + {lang.t('suspenduser.hours', 24)} + {lang.t('suspenduser.days', 7)} + +
+
+ + +
+
+ ); + } + + renderStep1() { + const {onCancel, username} = this.props; + const {message} = this.state; + return ( +
+

+ {lang.t('suspenduser.title_notify')} +

+

+ {lang.t('suspenduser.description_notify', username)} +

+
+ {lang.t('suspenduser.write_message')} +