Ban Button with graphql mutation.

This commit is contained in:
gaba
2017-03-01 12:57:12 -08:00
parent 6dfd3fc4ef
commit f5ef33d24e
16 changed files with 534 additions and 215 deletions
+2
View File
@@ -12,3 +12,5 @@ dump.rdb
*.cfg
.idea/
coverage/
.tags
.tags1
+7 -1
View File
@@ -7,7 +7,9 @@ import {
SORT_UPDATE,
COMMENTERS_NEW_PAGE,
SET_ROLE,
SET_COMMENTER_STATUS
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG
} from '../constants/community';
import coralApi from '../../../coral-framework/helpers/response';
@@ -56,3 +58,7 @@ export const setCommenterStatus = (id, status) => (dispatch) => {
return dispatch({type: SET_COMMENTER_STATUS, id, status});
});
};
// Ban User Dialog
export const showBanUserDialog = (user) => ({type: SHOW_BANUSER_DIALOG, user});
export const hideBanUserDialog = (showDialog) => ({type: HIDE_BANUSER_DIALOG, showDialog});
@@ -9,3 +9,6 @@ export const SET_COMMENTER_STATUS = 'SET_COMMENTER_STATUS';
export const FETCH_FLAGGED_COMMENTERS_REQUEST = 'FETCH_FLAGGED_COMMENTERS_REQUEST';
export const FETCH_FLAGGED_COMMENTERS_SUCCESS = 'FETCH_FLAGGED_COMMENTERS_SUCCESS';
export const FETCH_FLAGGED_COMMENTERS_FAILURE = 'FETCH_FLAGGED_COMMENTERS_FAILURE';
export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG';
export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG';
@@ -1,3 +1,5 @@
@custom-media --big-viewport (min-width: 780px);
.container {
padding: 10px;
display: flex;
@@ -100,3 +102,188 @@
cursor: pointer;
}
}
.list {
padding: 8px 0;
list-style: none;
display: block;
&.singleView .listItem {
display: none;
}
&.singleView .listItem.activeItem {
display: block;
height: 100%;
font-size: 1.5em;
line-height: 1.5em;
border: none;
.actions {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 16px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
&:last-child {
border-bottom: none;
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
box-sizing: border-box;
}
.itemHeader {
display: inline;
.author {
font-size: 24px;
min-width: 50px;
align-items: left;
margin-bottom: 15px;
}
}
.itemBody {
display: block;
}
.created {
color: #666;
font-size: 13px;
margin-left: 40px;
}
.body {
margin-top: 20px;
flex: 1;
font-size: 0.88em;
color: black;
}
.flagged {
color: rgba(255, 0, 0, .5);
padding-top: 15px;
padding-left: 10px;
}
.flagCount{
font-size: 12px;
color: #d32f2f;
}
}
.empty {
color: #444;
margin-top: 50px;
text-align: center;
}
@media (--big-viewport) {
.listItem {
border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
&.activeItem {
border: 2px solid #333;
}
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.banned {
color: #f00;
text-align: left;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.ban {
display: block;
text-align: center;
margin-top: 5px;
}
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.flaggedByCount {
display: block;
text-align: left;
}
.flaggedBy {
display: inline;
padding: 3px;
}
.flaggedByLabel {
font-weight: bold;
}
@@ -1,26 +1,27 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {compose} from 'react-apollo';
import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries';
import {banUser} from '../../graphql/mutations';
import {
fetchAccounts,
updateSorting,
newPage
newPage,
showBanUserDialog,
hideBanUserDialog
} from '../../actions/community';
import CommunityMenu from './components/CommunityMenu';
import BanUserDialog from './components/BanUserDialog';
import People from './People';
import FlaggedAccounts from './FlaggedAccounts';
class CommunityContainer extends Component {
// static propTypes = {
//
// // list of actions (approve, reject, ban) associated with the users
// modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
// }
constructor(props) {
super(props);
@@ -92,12 +93,23 @@ class CommunityContainer extends Component {
}
return (
<FlaggedAccounts
commenters={data.usersFlagged}
isFetching={data.loading}
error={data.error}
{...this}
/>
<div>
<FlaggedAccounts
commenters={data.usersFlagged}
isFetching={data.loading}
error={data.error}
showBanUserDialog={props.showBanUserDialog}
acceptUser={props.acceptUser}
rejectUser={props.rejectUser}
{...this}
/>
<BanUserDialog
open={community.banDialog}
user={community.user}
handleClose={props.hideBanUserDialog}
handleBanUser={props.banUser}
/>
</div>
);
}
@@ -122,10 +134,13 @@ const mapStateToProps = state => ({
});
const mapDispatchToProps = dispatch => ({
fetchAccounts: query => dispatch(fetchAccounts(query))
fetchAccounts: query => dispatch(fetchAccounts(query)),
showBanUserDialog: (user) => dispatch(showBanUserDialog(user)),
hideBanUserDialog: () => dispatch(hideBanUserDialog(false))
});
export default compose(
connect(mapStateToProps, mapDispatchToProps),
modUserFlaggedQuery
modUserFlaggedQuery,
banUser
)(CommunityContainer);
@@ -39,7 +39,8 @@ const FlaggedAccounts = ({...props}) => {
return <User
user={commenter}
key={index}
index={index} />;
index={index}
showBanUserDialog={props.showBanUserDialog}/>;
})
: <EmptyCard>{lang.t('community.no-flagged-accounts')}</EmptyCard>
}
@@ -1,187 +0,0 @@
@custom-media --big-viewport (min-width: 780px);
.list {
padding: 8px 0;
list-style: none;
display: block;
&.singleView .listItem {
display: none;
}
&.singleView .listItem.activeItem {
display: block;
height: 100%;
font-size: 1.5em;
line-height: 1.5em;
border: none;
.actions {
position: fixed;
bottom: 60px;
left: 25%;
margin: 0 auto;
display: flex;
justify-content: space-around;
width: 50%;
margin: 0;
}
.actionButton {
transform: scale(1.4);
}
}
}
.listItem {
border-bottom: 1px solid #e0e0e0;
font-size: 16px;
width: 100%;
max-width: 660px;
min-width: 400px;
margin: 0 auto;
padding: 16px 14px;
position: relative;
transition: box-shadow 200ms;
&:hover {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
}
&:last-child {
border-bottom: none;
}
.sideActions {
position: absolute;
right: 0;
height: 100%;
top: 0;
padding: 40px 18px;
box-sizing: border-box;
}
.itemHeader {
display: block;
.author {
font-size: 24px;
min-width: 50px;
align-items: left;
margin-bottom: 15px;
}
}
.itemBody {
display: block;
}
.created {
color: #666;
font-size: 13px;
margin-left: 40px;
}
.body {
margin-top: 20px;
flex: 1;
font-size: 0.88em;
color: black;
}
.flagged {
color: rgba(255, 0, 0, .5);
padding-top: 15px;
padding-left: 10px;
}
.flagCount{
font-size: 12px;
color: #d32f2f;
}
}
.empty {
color: #444;
margin-top: 50px;
text-align: center;
}
@media (--big-viewport) {
.listItem {
border: 1px solid #e0e0e0;
margin-bottom: 30px;
&:last-child {
border-bottom: 1px solid #e0e0e0;
}
&.activeItem {
border: 2px solid #333;
}
}
}
.hasLinks {
color: #f00;
text-align: right;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.banned {
color: #f00;
text-align: left;
display: flex;
align-items: center;
i {
margin-right: 5px;
}
}
.ban {
display: block;
text-align: center;
margin-top: 5px;
}
.banButton {
width: 114px;
letter-spacing: 1px;
i {
vertical-align: middle;
margin-right: 10px;
font-size: 14px;
}
}
.actionButton {
transform: scale(.8);
margin: 0;
}
.flaggedByCount {
display: block;
text-align: left;
}
.flaggedBy {
display: inline;
padding: 3px;
}
.flaggedByLabel {
font-weight: bold;
}
@@ -0,0 +1,22 @@
import React from 'react';
import styles from '../Community.css';
import BanUserButton from '../../../components/BanUserButton';
import {FabButton} from 'coral-ui';
import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap';
const ActionButton = ({type = '', user, ...props}) => {
if (type === 'BAN') {
return <BanUserButton user={user} onClick={() => props.showBanUserDialog(user)} />;
}
return (
<FabButton
className={`${type.toLowerCase()} ${styles.actionButton}`}
cStyle={type.toLowerCase()}
icon={menuActionsMap[type].icon}
onClick={type === 'APPROVE' ? props.acceptUser : props.rejectUser}
/>
);
};
export default ActionButton;
@@ -0,0 +1,164 @@
.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;
}
@@ -0,0 +1,53 @@
import React, {PropTypes} from 'react';
import {Dialog} from 'coral-ui';
import styles from './BanUserDialog.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 BanUserDialog = ({open, handleClose, handleBanUser, user}) => (
<Dialog
className={styles.dialog}
id="banuserDialog"
open={open}
onClose={handleClose}
onCancel={handleClose}
title={lang.t('community.ban_user')}>
<span className={styles.close} onClick={handleClose}>×</span>
<div>
<div className={styles.header}>
<h2>{lang.t('community.ban_user')}</h2>
</div>
<div className={styles.separator}>
<h3>{lang.t('community.are_you_sure', user.username)}</h3>
<i>{lang.t('community.note')}</i>
</div>
<div className={styles.buttons}>
<Button cStyle="cancel" className={styles.cancel} onClick={handleClose} raised>
{lang.t('community.cancel')}
</Button>
<Button
cStyle="black" className={styles.ban}
onClick={() => {
handleBanUser({userId: user.id}).then(() => {
handleClose();
});
}}
raised>
{lang.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,5 +1,5 @@
import React from 'react';
import styles from '../UserModerationList.css';
import styles from '../Community.css';
import I18n from 'coral-framework/modules/i18n/i18n';
import translations from '../../../translations.json';
@@ -7,7 +7,7 @@ import translations from '../../../translations.json';
const lang = new I18n(translations);
// import {Icon} from 'react-mdl';
// import ActionButton from './ActionButton';
import ActionButton from './ActionButton';
// Render a single user for the list
const User = props => {
@@ -19,9 +19,13 @@ const User = props => {
return (userStatus === 'PENDING' || userStatus === 'BANNED') &&
<li tabIndex={props.index} className={`mdl-card mdl-shadow--2dp ${styles.listItem} ${props.isActive && !props.hideActive ? styles.activeItem : ''}`}>
<div className={styles.itemHeader}>
<div className={styles.author}>
<span>{user.username}</span>
</div>
<span className={styles.author}>{user.username}</span>
<ActionButton
className={styles.banButton}
type='BAN'
user={user}
showBanUserDialog={props.showBanUserDialog}
/>
</div>
<div className={styles.itemBody}>
<div className={styles.flaggedByCount}>
@@ -9,11 +9,35 @@ export const banUser = graphql(SET_USER_STATUS, {
variables: {
userId,
status: 'BANNED'
}
},
refetchQueries: ['Users']
});
}}),
});
export const setUserStatus = graphql(SET_USER_STATUS, {
props: ({mutate}) => ({
acceptUser: (userId) => {
return mutate({
variables: {
userId,
status: 'APPROVED'
},
refetchQueries: ['modUserFlaggedQuery']
});
},
rejectUser: (userId) => {
return mutate({
variables: {
userId,
status: 'BANNED'
},
refetchQueries: ['modUserFlaggedQuery']
});
}
})
});
export const setCommentStatus = graphql(SET_COMMENT_STATUS, {
props: ({mutate}) => ({
acceptComment: ({commentId}) => {
+15 -2
View File
@@ -6,7 +6,9 @@ import {
FETCH_COMMENTERS_SUCCESS,
SORT_UPDATE,
SET_ROLE,
SET_COMMENTER_STATUS
SET_COMMENTER_STATUS,
SHOW_BANUSER_DIALOG,
HIDE_BANUSER_DIALOG
} from '../constants/community';
const initialState = Map({
@@ -17,7 +19,9 @@ const initialState = Map({
fieldPeople: 'created_at',
ascPeople: false,
totalPagesPeople: 0,
pagePeople: 0
pagePeople: 0,
user: Map({}),
banDialog: false
});
export default function community (state = initialState, action) {
@@ -63,6 +67,15 @@ export default function community (state = initialState, action) {
return state
.set('fieldPeople', action.sort.field)
.set('ascPeople', !state.get('ascPeople'));
case HIDE_BANUSER_DIALOG:
return state
.set('banDialog', false);
case SHOW_BANUSER_DIALOG:
return state
.merge({
user: Map(action.user),
banDialog: true
});
default :
return state;
}
+14 -2
View File
@@ -17,10 +17,16 @@
"flaggedaccounts": "Flagged Usernames",
"people": "People",
"no-flagged-accounts": "The Account Flags queue is currently empty.",
"I don't like this username": "I don't like this username",
"This user is impersonating": "Impersonation",
"This looks like an ad/marketing": "Spam/Ads",
"This username is offensive": "Offensive",
"Other": "Other"
"Other": "Other",
"ban_user": "Ban User?",
"are_you_sure": "Are you sure you would like to ban {0}?",
"note": "Note: Banning this user will not let them edit, comment or remove anything.",
"cancel": "Cancel",
"yes_ban_user": "Yes, Ban User"
},
"modqueue": {
"likes": "likes",
@@ -156,10 +162,16 @@
"flaggedaccounts": "Nombres de Usuario Reportados",
"people": "Gente",
"no-flagged-accounts": "No hay ninguna cuenta reportada.",
"I don't like this username": "No me gusta ese nombre de usuario",
"This user is impersonating": "Suplantación",
"This looks like an ad/marketing": "Spam/Propaganda",
"This username is offensive": "Ofensivo",
"Other": "Otros"
"Other": "Otros",
"ban_user": "Quieres suspender el Usuario?",
"are_you_sure": "Estas segura que quieres suspender a {0}?",
"note": "Nota: Suspender a este usuario no le va a permitir borrar ni editar ni comentar.",
"cancel": "Cancelar",
"yes_ban_user": "Si, Suspendan el usuario"
},
"modqueue": {
"likes": "gustos",