mirror of
https://github.com/wassname/talk.git
synced 2026-07-15 11:26:58 +08:00
bulk approve/reject actions for moderators
This commit is contained in:
@@ -20,3 +20,9 @@ export const toggleSelectCommentInUserDetail = (id, active) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const toggleSelectAllCommentInUserDetail = (ids, active) => {
|
||||
return {
|
||||
type: active ? actions.SELECT_ALL_USER_DETAIL_COMMENT : actions.CLEAR_USER_DETAIL_SELECTIONS,
|
||||
ids
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,144 +1,168 @@
|
||||
.copyButton {
|
||||
background-color: white;
|
||||
border: solid 1px;
|
||||
padding: 2px 6px;
|
||||
height: auto;
|
||||
line-height: initial;
|
||||
min-width: auto;
|
||||
letter-spacing: normal;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
background-color: white;
|
||||
border: solid 1px;
|
||||
padding: 2px 6px;
|
||||
height: auto;
|
||||
line-height: initial;
|
||||
min-width: auto;
|
||||
letter-spacing: normal;
|
||||
font-size: 0.9em;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.userDetailList {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.userDetailItem {
|
||||
margin: 0 5px;
|
||||
font-weight: 500;
|
||||
margin: 0 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin: 15px 0 5px;
|
||||
color: #595959;
|
||||
display: flex;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
margin: 15px 0 5px;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.stat {
|
||||
margin-right: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.stat:last-child {
|
||||
margin-right: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.statItem, .statReportResult {
|
||||
padding: 3px 5px;
|
||||
background-color: #D8D8D8;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.4px;
|
||||
min-width: 60px;
|
||||
padding: 3px 5px;
|
||||
background-color: #D8D8D8;
|
||||
border-radius: 3px;
|
||||
font-weight: 500;
|
||||
display: block;
|
||||
font-size: 0.9em;
|
||||
line-height: normal;
|
||||
letter-spacing: 0.4px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.statResult {
|
||||
font-size: 1.5em;
|
||||
padding: 5px 0;
|
||||
display: inline-block;
|
||||
font-size: 1.5em;
|
||||
padding: 5px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.statReportResult {
|
||||
color: white;
|
||||
margin: 5px 0;
|
||||
font-weight: 400;
|
||||
color: white;
|
||||
margin: 5px 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.statReportResult.reliable {
|
||||
background-color: #749C48;
|
||||
background-color: #749C48;
|
||||
}
|
||||
|
||||
.statReportResult.neutral {
|
||||
background-color: #616161;
|
||||
background-color: #616161;
|
||||
}
|
||||
|
||||
.statReportResult.unreliable {
|
||||
background-color: #F44336;
|
||||
background-color: #F44336;
|
||||
}
|
||||
|
||||
.memberSince {
|
||||
clear: both;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.small {
|
||||
color: #888888;
|
||||
font-size: 0.9em;
|
||||
letter-spacing: 0.4px;
|
||||
color: #888888;
|
||||
font-size: 0.9em;
|
||||
letter-spacing: 0.4px;
|
||||
}
|
||||
|
||||
.profileEmail {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
width: calc(100% - 90px);
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
font-size: 16px;
|
||||
outline: none;
|
||||
width: calc(100% - 90px);
|
||||
}
|
||||
|
||||
.commentStatuses {
|
||||
padding: 10px 0 0 0;
|
||||
margin: 0;
|
||||
height: 52px;
|
||||
list-style: none;
|
||||
box-sizing: border-box;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
padding: 0 0 0 10px;
|
||||
margin: 0;
|
||||
align-self: center;
|
||||
list-style: none;
|
||||
box-sizing: border-box;
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 10px;
|
||||
cursor: pointer;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
font-weight: bold;
|
||||
border-bottom: 3px solid #F36451;
|
||||
font-weight: bold;
|
||||
border-bottom: 3px solid #F36451;
|
||||
}
|
||||
|
||||
.bulkActionGroup {
|
||||
height: 52px;
|
||||
background-color: #efefef;
|
||||
height: 52px;
|
||||
background-color: #efefef;
|
||||
padding: 0 0 0 10px;
|
||||
display: flex;
|
||||
i {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 0;
|
||||
}
|
||||
.bulkAction {
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
transform: scale(.7);
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bulkAction {
|
||||
display: inline-block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
transform: scale(.7);
|
||||
min-width: 0;
|
||||
}
|
||||
.bulkAction:last-child {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
.bulkAction:last-child {
|
||||
margin-left: -10px;
|
||||
}
|
||||
span{
|
||||
align-self: center;
|
||||
font-weight: 500;
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.loadMore > button {
|
||||
background-color: #696969;
|
||||
background-color: #696969;
|
||||
|
||||
&:hover {
|
||||
background-color: #404040;
|
||||
color: white;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #404040;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.toggleAll {
|
||||
padding: 0 10px 0 0;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.commentList {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.bulkActionHeader{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 52px;
|
||||
&.selected{
|
||||
background-color: #efefef;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Comment from '../containers/UserDetailComment';
|
||||
import styles from './UserDetail.css';
|
||||
import {Icon, Button, Drawer, Spinner} from 'coral-ui';
|
||||
import {Icon, Drawer, Spinner} from 'coral-ui';
|
||||
import {Slot} from 'coral-framework/components';
|
||||
import ButtonCopyToClipboard from './ButtonCopyToClipboard';
|
||||
import ClickOutside from 'coral-framework/components/ClickOutside';
|
||||
@@ -10,6 +10,8 @@ import LoadMore from '../components/LoadMore';
|
||||
import cn from 'classnames';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import {getReliability} from 'coral-framework/utils/user';
|
||||
import ApproveButton from './ApproveButton';
|
||||
import RejectButton from './RejectButton';
|
||||
|
||||
export default class UserDetail extends React.Component {
|
||||
|
||||
@@ -23,6 +25,7 @@ export default class UserDetail extends React.Component {
|
||||
toggleSelect: PropTypes.func.isRequired,
|
||||
bulkAccept: PropTypes.func.isRequired,
|
||||
bulkReject: PropTypes.func.isRequired,
|
||||
toggleSelectAll: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
rejectThenReload = async (info) => {
|
||||
@@ -47,6 +50,28 @@ export default class UserDetail extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
bulkAcceptThenReload = async () => {
|
||||
try {
|
||||
await this.props.bulkAccept();
|
||||
this.props.data.refetch();
|
||||
} catch (err) {
|
||||
|
||||
// TODO: handle error.
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
bulkRejectThenReload = async () => {
|
||||
try {
|
||||
await this.props.bulkReject();
|
||||
this.props.data.refetch();
|
||||
} catch (err) {
|
||||
|
||||
// TODO: handle error.
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
showAll = () => {
|
||||
this.props.changeStatus('all');
|
||||
}
|
||||
@@ -78,11 +103,10 @@ export default class UserDetail extends React.Component {
|
||||
activeTab,
|
||||
selectedCommentIds,
|
||||
toggleSelect,
|
||||
bulkAccept,
|
||||
bulkReject,
|
||||
hideUserDetail,
|
||||
viewUserDetail,
|
||||
loadMore,
|
||||
toggleSelectAll
|
||||
} = this.props;
|
||||
|
||||
let rejectedPercent = (rejectedComments / totalComments) * 100;
|
||||
@@ -100,14 +124,14 @@ export default class UserDetail extends React.Component {
|
||||
<div>
|
||||
<ul className={styles.userDetailList}>
|
||||
<li>
|
||||
<Icon name="assignment_ind"/>
|
||||
<Icon name="assignment_ind" />
|
||||
<span className={styles.userDetailItem}>Member Since:</span>
|
||||
{new Date(user.created_at).toLocaleString()}
|
||||
</li>
|
||||
|
||||
{user.profiles.map(({id}) =>
|
||||
<li key={id}>
|
||||
<Icon name="email"/>
|
||||
<Icon name="email" />
|
||||
<span className={styles.userDetailItem}>Email:</span>
|
||||
{id} <ButtonCopyToClipboard className={styles.copyButton} icon="content_copy" copyText={id} />
|
||||
</li>
|
||||
@@ -142,35 +166,42 @@ export default class UserDetail extends React.Component {
|
||||
queryData={{root, user}}
|
||||
/>
|
||||
|
||||
<hr/>
|
||||
{
|
||||
selectedCommentIds.length === 0
|
||||
? (
|
||||
<ul className={styles.commentStatuses}>
|
||||
<li className={activeTab === 'all' ? styles.active : ''} onClick={this.showAll}>All</li>
|
||||
<li className={activeTab === 'rejected' ? styles.active : ''} onClick={this.showRejected}>Rejected</li>
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<div className={styles.bulkActionGroup}>
|
||||
<Button
|
||||
onClick={bulkAccept}
|
||||
className={styles.bulkAction}
|
||||
cStyle='approve'
|
||||
icon='done'>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={bulkReject}
|
||||
className={styles.bulkAction}
|
||||
cStyle='reject'
|
||||
icon='close'>
|
||||
</Button>
|
||||
{`${selectedCommentIds.length} comments selected`}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
<div>
|
||||
<hr />
|
||||
<div className={(selectedCommentIds.length > 0) ? cn(styles.bulkActionHeader, styles.selected) : styles.bulkActionHeader}>
|
||||
{
|
||||
selectedCommentIds.length === 0
|
||||
? (
|
||||
<ul className={styles.commentStatuses}>
|
||||
<li className={activeTab === 'all' ? styles.active : ''} onClick={this.showAll}>All</li>
|
||||
<li className={activeTab === 'rejected' ? styles.active : ''} onClick={this.showRejected}>Rejected</li>
|
||||
</ul>
|
||||
)
|
||||
: (
|
||||
<div className={styles.bulkActionGroup}>
|
||||
<ApproveButton
|
||||
onClick={this.bulkAcceptThenReload}
|
||||
minimal
|
||||
/>
|
||||
<RejectButton
|
||||
onClick={this.bulkRejectThenReload}
|
||||
minimal
|
||||
/>
|
||||
<span> {`${selectedCommentIds.length} comments selected`}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div className={styles.toggleAll}>
|
||||
<input
|
||||
type='checkbox'
|
||||
id='toogleAll'
|
||||
checked={selectedCommentIds.length > 0 && selectedCommentIds.length === nodes.length}
|
||||
onChange={(e) => {
|
||||
toggleSelectAll(nodes.map((comment) => comment.id), e.target.checked);
|
||||
}} />
|
||||
<label htmlFor='toogleAll'>Select all</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.commentList}>
|
||||
{
|
||||
nodes.map((comment) => {
|
||||
const selected = selectedCommentIds.indexOf(comment.id) !== -1;
|
||||
@@ -199,7 +230,7 @@ export default class UserDetail extends React.Component {
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
if (this.props.loading) {
|
||||
return this.renderLoading();
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@ export const CHANGE_USER_DETAIL_STATUSES = 'CHANGE_USER_DETAIL_STATUSES';
|
||||
export const SELECT_USER_DETAIL_COMMENT = 'SELECT_USER_DETAIL_COMMENT';
|
||||
export const UNSELECT_USER_DETAIL_COMMENT = 'UNSELECT_USER_DETAIL_COMMENT';
|
||||
export const CLEAR_USER_DETAIL_SELECTIONS = 'CLEAR_USER_DETAIL_SELECTIONS';
|
||||
|
||||
export const SELECT_ALL_USER_DETAIL_COMMENT = 'SELECT_ALL_USER_DETAIL_COMMENT';
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
changeUserDetailStatuses,
|
||||
clearUserDetailSelections,
|
||||
toggleSelectCommentInUserDetail,
|
||||
toggleSelectAllCommentInUserDetail
|
||||
} from 'coral-admin/src/actions/userDetail';
|
||||
import {withSetCommentStatus} from 'coral-framework/graphql/mutations';
|
||||
import UserDetailComment from './UserDetailComment';
|
||||
@@ -120,6 +121,7 @@ class UserDetailContainer extends React.Component {
|
||||
bulkAccept={this.bulkAccept}
|
||||
changeStatus={this.props.changeUserDetailStatuses}
|
||||
toggleSelect={this.props.toggleSelectCommentInUserDetail}
|
||||
toggleSelectAll={this.props.toggleSelectAllCommentInUserDetail}
|
||||
acceptComment={this.acceptComment}
|
||||
rejectComment={this.rejectComment}
|
||||
loading={loading}
|
||||
@@ -188,6 +190,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
toggleSelectCommentInUserDetail,
|
||||
viewUserDetail,
|
||||
hideUserDetail,
|
||||
toggleSelectAllCommentInUserDetail
|
||||
}, dispatch)
|
||||
});
|
||||
|
||||
|
||||
@@ -41,6 +41,11 @@ export default function banUserDialog(state = initialState, action) {
|
||||
...state,
|
||||
selectedCommentIds: state.selectedCommentIds.filter((id) => id !== action.id),
|
||||
};
|
||||
case actions.SELECT_ALL_USER_DETAIL_COMMENT:
|
||||
return {
|
||||
...state,
|
||||
selectedCommentIds: action.ids
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user