diff --git a/client/coral-admin/src/actions/rejectUsernameDialog.js b/client/coral-admin/src/actions/rejectUsernameDialog.js new file mode 100644 index 000000000..946b36ef7 --- /dev/null +++ b/client/coral-admin/src/actions/rejectUsernameDialog.js @@ -0,0 +1,14 @@ +import { + SHOW_REJECT_USERNAME_DIALOG, + HIDE_REJECT_USERNAME_DIALOG, +} from '../constants/rejectUsernameDialog'; + +export const showRejectUsernameDialog = ({ userId, username }) => ({ + type: SHOW_REJECT_USERNAME_DIALOG, + userId, + username, +}); + +export const hideRejectUsernameDialog = () => ({ + type: HIDE_REJECT_USERNAME_DIALOG, +}); diff --git a/client/coral-admin/src/components/RejectUsernameDialog.css b/client/coral-admin/src/components/RejectUsernameDialog.css new file mode 100644 index 000000000..1c96f509a --- /dev/null +++ b/client/coral-admin/src/components/RejectUsernameDialog.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/components/RejectUsernameDialog.js b/client/coral-admin/src/components/RejectUsernameDialog.js new file mode 100644 index 000000000..cdd211bc4 --- /dev/null +++ b/client/coral-admin/src/components/RejectUsernameDialog.js @@ -0,0 +1,188 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Dialog } from 'coral-ui'; +import { RadioGroup, Radio } from 'react-mdl'; +import styles from './SuspendUserDialog.css'; +import cn from 'classnames'; + +import Button from 'coral-ui/components/Button'; + +import t, { timeago } from 'coral-framework/services/i18n'; +import { dateAdd } from 'coral-framework/utils'; + +const initialState = { step: 0, duration: '3' }; + +function durationsToDate(hours) { + // Add 1 minute more to help `timeago.js` to display the correct duration. + return dateAdd(new Date(), 'minute', hours * 60 + 1); +} + +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 }); + }; + + goToStep1 = () => { + this.setState({ + step: 1, + message: t( + 'suspenduser.email_message_suspend', + this.props.username, + this.props.organizationName, + timeago(durationsToDate(this.state.duration)) + ), + }); + }; + + handlePerform = () => { + this.props.onPerform({ + message: this.state.message, + + // Add 1 minute more to help `timeago.js` to display the correct duration. + until: durationsToDate(this.state.duration), + }); + }; + + renderStep0() { + const { onCancel, username } = this.props; + const { duration } = this.state; + return ( + + {t('suspenduser.title_suspend')} + + {t('suspenduser.description_suspend', username)} + + + + {t('suspenduser.select_duration')} + + + {t('suspenduser.one_hour')} + {t('suspenduser.hours', 3)} + {t('suspenduser.hours', 24)} + {t('suspenduser.days', 7)} + + + + + {t('suspenduser.cancel')} + + + {t('suspenduser.suspend_user')} + + + + ); + } + + renderStep1() { + const { message } = this.state; + const { onCancel, username } = this.props; + return ( + + {t('suspenduser.title_notify')} + + {t('suspenduser.description_notify', username)} + + + + {t('suspenduser.write_message')} + + + + + + {t('suspenduser.cancel')} + + + {t('suspenduser.send')} + + + + ); + } + + render() { + const { open, onCancel } = this.props; + const { step } = this.state; + return ( + + + + × + + + {step === 0 && this.renderStep0()} + {step === 1 && this.renderStep1()} + + ); + } +} + +SuspendUserDialog.propTypes = { + open: PropTypes.bool.isRequired, + onPerform: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + organizationName: PropTypes.string, + username: PropTypes.string, +}; + +export default SuspendUserDialog; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 7a7d7a672..5572c7d71 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -51,6 +51,12 @@ class UserDetail extends React.Component { username: this.props.root.user.username, }); + showRejectUsername = () => + this.props.showBanUserDialog({ + userId: this.props.root.user.id, + username: this.props.root.user.username, + }); + renderLoading() { return ( @@ -230,7 +236,7 @@ class UserDetail extends React.Component { ) : ( this.rejectUsername({ id: user.id })} + onClick={this.showRejectUsername} disabled={me.id === user.id} > {t('user_detail.reject_username')} diff --git a/client/coral-admin/src/constants/rejectUsernameDialog.js b/client/coral-admin/src/constants/rejectUsernameDialog.js new file mode 100644 index 000000000..8ab48c106 --- /dev/null +++ b/client/coral-admin/src/constants/rejectUsernameDialog.js @@ -0,0 +1,2 @@ +export const SHOW_REJECT_USERNAME_DIALOG = 'SHOW_REJECT_USERNAME_DIALOG'; +export const HIDE_REJECT_USERNAME_DIALOG = 'HIDE_REJECT_USERNAME_DIALOG'; diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index 580b20f9b..d8ca11732 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -6,6 +6,7 @@ import Login from '../containers/Login'; import { FullLoading } from '../components/FullLoading'; import BanUserDialog from './BanUserDialog'; import SuspendUserDialog from './SuspendUserDialog'; +import RejectUsernameDialog from './RejectUsernameDialog'; import { toggleModal as toggleShortcutModal } from '../actions/moderation'; import { logout } from 'coral-framework/actions/auth'; import { can } from 'coral-framework/services/perms'; @@ -41,6 +42,7 @@ class LayoutContainer extends React.Component { > + {children} diff --git a/client/coral-admin/src/containers/RejectUsernameDialog.js b/client/coral-admin/src/containers/RejectUsernameDialog.js new file mode 100644 index 000000000..83551200f --- /dev/null +++ b/client/coral-admin/src/containers/RejectUsernameDialog.js @@ -0,0 +1,79 @@ +// import React, { Component } from 'react'; +// import PropTypes from 'prop-types'; +// import { connect } from 'react-redux'; +// import { bindActionCreators } from 'redux'; +// import RejectUsernameDialog from '../components/RejectUsernameDialog'; +// import { hideRejectUsernameDialog } from '../actions/rejectUsernameDialog'; +// import { withRejectUsername } from 'coral-framework/graphql/mutations'; +// import { compose } from 'react-apollo'; +// import t from 'coral-framework/services/i18n'; + +// class RejectUsernameDialogContainer extends Component { +// banUser = async () => { +// const { +// userId, +// commentId, +// commentStatus, +// banUser, +// setCommentStatus, +// hideBanUserDialog, +// } = this.props; +// await banUser({ id: userId, message: '' }); +// 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 ( +// +// ); +// } +// } + +// BanUserDialogContainer.propTypes = { +// banUser: PropTypes.func.isRequired, +// hideBanUserDialog: PropTypes.func, +// open: PropTypes.bool, +// username: PropTypes.string, +// commentStatus: PropTypes.string, +// }; + +// const mapStateToProps = ({ +// banUserDialog: { open, userId, username, commentId, commentStatus }, +// }) => ({ +// open, +// userId, +// username, +// commentId, +// commentStatus, +// }); + +// const mapDispatchToProps = dispatch => ({ +// ...bindActionCreators( +// { +// hideRejectUsernameDialog, +// }, +// dispatch +// ), +// }); + +// export default compose( +// connect(mapStateToProps, mapDispatchToProps), +// withRejectUsername +// )(RejectUsernameDialogContainer); diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index 5ab572afc..03ffee007 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -23,11 +23,13 @@ import { withUnbanUser, withUnsuspendUser, withRejectUsername, + withPostFlag, } from 'coral-framework/graphql/mutations'; import UserDetailComment from './UserDetailComment'; import update from 'immutability-helper'; import { showBanUserDialog } from 'actions/banUserDialog'; import { showSuspendUserDialog } from 'actions/suspendUserDialog'; +import { showRejectUsernameDialog } from 'actions/rejectUsernameDialog'; const commentConnectionFragment = gql` fragment CoralAdmin_UserDetail_CommentConnection on CommentConnection { @@ -279,6 +281,7 @@ const mapDispatchToProps = dispatch => ({ { showBanUserDialog, showSuspendUserDialog, + showRejectUsernameDialog, changeTab, clearUserDetailSelections, toggleSelectCommentInUserDetail, @@ -297,5 +300,6 @@ export default compose( withUnbanUser, withUnsuspendUser, withRejectUsername, + withPostFlag, withRouter )(UserDetailContainer); diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index be0a8a606..e806f5d11 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -5,10 +5,12 @@ import moderation from './moderation'; import install from './install'; import banUserDialog from './banUserDialog'; import suspendUserDialog from './suspendUserDialog'; +import rejectUsernameDialog from './rejectUsernameDialog'; import userDetail from './userDetail'; import ui from './ui'; export default { + rejectUsernameDialog, banUserDialog, configure, suspendUserDialog, diff --git a/client/coral-admin/src/reducers/rejectUsernameDialog.js b/client/coral-admin/src/reducers/rejectUsernameDialog.js new file mode 100644 index 000000000..2ed31066b --- /dev/null +++ b/client/coral-admin/src/reducers/rejectUsernameDialog.js @@ -0,0 +1,29 @@ +import { + SHOW_REJECT_USERNAME_DIALOG, + HIDE_REJECT_USERNAME_DIALOG, +} from '../constants/rejectUsernameDialog'; + +const initialState = { + open: false, + userId: null, + username: '', +}; + +export default function rejectUsernameDialog(state = initialState, action) { + switch (action.type) { + case SHOW_REJECT_USERNAME_DIALOG: + return { + ...state, + open: true, + userId: action.userId, + username: action.username, + }; + case HIDE_REJECT_USERNAME_DIALOG: + return { + ...state, + open: false, + }; + default: + return state; + } +}
+ {t('suspenduser.description_suspend', username)} +
+ {t('suspenduser.description_notify', username)} +