mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 05:03:47 +08:00
Merge branch 'master' into moderation-refactor
This commit is contained in:
@@ -5,7 +5,7 @@ import { Router, Route, IndexRedirect, IndexRoute } from 'react-router';
|
||||
import Configure from 'routes/Configure';
|
||||
import Install from 'routes/Install';
|
||||
import Stories from 'routes/Stories';
|
||||
import Community from 'routes/Community/containers/Community';
|
||||
import Community from 'routes/Community';
|
||||
import { ModerationLayout, Moderation } from 'routes/Moderation';
|
||||
|
||||
import Layout from 'containers/Layout';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Layout from 'coral-admin/src/components/ui/Layout';
|
||||
import Layout from 'coral-admin/src/components/Layout';
|
||||
import styles from './NotFound.css';
|
||||
import { Button, TextField, Alert, Success } from 'coral-ui';
|
||||
import Recaptcha from 'react-recaptcha';
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
transform: scale(.8);
|
||||
margin: 0;
|
||||
|
||||
&:hover {
|
||||
&:not(:disabled):hover {
|
||||
box-shadow: none;
|
||||
color: white;
|
||||
background-color: #519954;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Icon } from 'coral-ui';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const ApproveButton = ({ active, minimal, onClick, className }) => {
|
||||
const ApproveButton = ({ active, minimal, onClick, className, disabled }) => {
|
||||
const text = active ? t('modqueue.approved') : t('modqueue.approve');
|
||||
return (
|
||||
<button
|
||||
@@ -17,6 +17,7 @@ const ApproveButton = ({ active, minimal, onClick, className }) => {
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
disabled={disabled || active}
|
||||
>
|
||||
<Icon name={'done'} className={styles.icon} />
|
||||
{!minimal && text}
|
||||
@@ -28,6 +29,7 @@ ApproveButton.propTypes = {
|
||||
className: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
+4
-5
@@ -7,7 +7,8 @@ import styles from './Header.css';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { Logo } from './Logo';
|
||||
import { can } from 'coral-framework/services/perms';
|
||||
import Indicator from './Indicator';
|
||||
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
|
||||
import CommunityIndicator from '../routes/Community/containers/Indicator';
|
||||
|
||||
const CoralHeader = ({
|
||||
handleLogout,
|
||||
@@ -30,9 +31,7 @@ const CoralHeader = ({
|
||||
activeClassName={styles.active}
|
||||
>
|
||||
{t('configure.moderate')}
|
||||
{(root.premodCount !== 0 || root.reportedCount !== 0) && (
|
||||
<Indicator />
|
||||
)}
|
||||
<ModerationIndicator root={root} />
|
||||
</IndexLink>
|
||||
)}
|
||||
<Link
|
||||
@@ -51,7 +50,7 @@ const CoralHeader = ({
|
||||
activeClassName={styles.active}
|
||||
>
|
||||
{t('configure.community')}
|
||||
{root.flaggedUsernamesCount !== 0 && <Indicator />}
|
||||
<CommunityIndicator root={root} />
|
||||
</Link>
|
||||
|
||||
{can(auth.user, 'UPDATE_CONFIG') && (
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Layout as LayoutMDL } from 'react-mdl';
|
||||
import Header from '../../containers/Header';
|
||||
import Drawer from '../Drawer';
|
||||
import Header from '../containers/Header';
|
||||
import Drawer from './Drawer';
|
||||
import styles from './Layout.css';
|
||||
|
||||
const Layout = ({
|
||||
@@ -16,12 +16,16 @@
|
||||
transform: scale(.8);
|
||||
margin: 0;
|
||||
|
||||
&:hover {
|
||||
&:not(:disabled):hover {
|
||||
color: white;
|
||||
background-color: #D03235;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Icon } from 'coral-ui';
|
||||
|
||||
import t from 'coral-framework/services/i18n';
|
||||
|
||||
const RejectButton = ({ active, minimal, onClick, className }) => {
|
||||
const RejectButton = ({ active, minimal, onClick, className, disabled }) => {
|
||||
const text = active ? t('modqueue.rejected') : t('modqueue.reject');
|
||||
return (
|
||||
<button
|
||||
@@ -17,6 +17,7 @@ const RejectButton = ({ active, minimal, onClick, className }) => {
|
||||
className
|
||||
)}
|
||||
onClick={onClick}
|
||||
disabled={disabled || active}
|
||||
>
|
||||
<Icon name={'close'} className={styles.icon} />
|
||||
{!minimal && text}
|
||||
@@ -29,6 +30,7 @@ RejectButton.propTypes = {
|
||||
active: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default RejectButton;
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
import { gql } from 'react-apollo';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import Header from '../components/ui/Header';
|
||||
import Header from '../components/Header';
|
||||
import CommunityIndicator from '../routes/Community/containers/Indicator';
|
||||
import ModerationIndicator from '../routes/Moderation/containers/Indicator';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
|
||||
export default withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Header {
|
||||
__typename
|
||||
premodCount: commentCount(query: { statuses: [PREMOD] })
|
||||
reportedCount: commentCount(
|
||||
query: { statuses: [NONE, PREMOD, SYSTEM_WITHHELD], action_type: FLAG }
|
||||
)
|
||||
flaggedUsernamesCount: userCount(
|
||||
query: {
|
||||
action_type: FLAG
|
||||
state: { status: { username: [SET, CHANGED] } }
|
||||
}
|
||||
)
|
||||
...${getDefinitionName(ModerationIndicator.fragments.root)}
|
||||
...${getDefinitionName(CommunityIndicator.fragments.root)}
|
||||
}
|
||||
${ModerationIndicator.fragments.root}
|
||||
${CommunityIndicator.fragments.root}
|
||||
`,
|
||||
{
|
||||
options: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import Layout from '../components/ui/Layout';
|
||||
import Layout from '../components/Layout';
|
||||
import { fetchConfig } from '../actions/config';
|
||||
import AdminLogin from '../components/AdminLogin';
|
||||
import { FullLoading } from '../components/FullLoading';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import update from 'immutability-helper';
|
||||
import { mapLeaves } from 'coral-framework/utils';
|
||||
import { gql } from 'react-apollo';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const userStatusFragment = gql`
|
||||
fragment Talk_UpdateUserStatus on User {
|
||||
@@ -163,9 +164,22 @@ export default {
|
||||
},
|
||||
}),
|
||||
ApproveUsername: ({ variables: { id } }) => ({
|
||||
optimisticResponse: {
|
||||
approveUsername: {
|
||||
__typename: 'ApproveUsernameResponse',
|
||||
errors: null,
|
||||
isOptimistic: true,
|
||||
},
|
||||
},
|
||||
updateQueries: {
|
||||
TalkAdmin_Community: prev => {
|
||||
TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => {
|
||||
// Remove from list after the mutation was "really" completed.
|
||||
if (get(mutationResult, 'data.approveUsername.isOptimistic')) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const updated = update(prev, {
|
||||
flaggedUsernamesCount: { $apply: count => count - 1 },
|
||||
flaggedUsers: {
|
||||
nodes: { $apply: nodes => nodes.filter(node => node.id !== id) },
|
||||
},
|
||||
@@ -173,20 +187,91 @@ export default {
|
||||
return updated;
|
||||
},
|
||||
},
|
||||
update: proxy => {
|
||||
proxy.writeFragment({
|
||||
fragment: gql`
|
||||
fragment Talk_ApproveUsername on User {
|
||||
state {
|
||||
status {
|
||||
username {
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
id: `User_${id}`,
|
||||
data: {
|
||||
__typename: 'User',
|
||||
state: {
|
||||
__typename: 'UserState',
|
||||
status: {
|
||||
__typename: 'UserStatus',
|
||||
username: {
|
||||
__typename: 'UsernameStatus',
|
||||
status: 'APPROVED',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
RejectUsername: ({ variables: { id: userId } }) => ({
|
||||
RejectUsername: ({ variables: { id } }) => ({
|
||||
optimisticResponse: {
|
||||
rejectUsername: {
|
||||
__typename: 'RejectUsernameResponse',
|
||||
errors: null,
|
||||
isOptimistic: true,
|
||||
},
|
||||
},
|
||||
updateQueries: {
|
||||
TalkAdmin_Community: prev => {
|
||||
TalkAdmin_Community_FlaggedAccounts: (prev, { mutationResult }) => {
|
||||
// Remove from list after the mutation was "really" completed.
|
||||
if (get(mutationResult, 'data.rejectUsername.isOptimistic')) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const updated = update(prev, {
|
||||
flaggedUsernamesCount: { $apply: count => count - 1 },
|
||||
flaggedUsers: {
|
||||
nodes: {
|
||||
$apply: nodes => nodes.filter(node => node.id !== userId),
|
||||
$apply: nodes => nodes.filter(node => node.id !== id),
|
||||
},
|
||||
},
|
||||
});
|
||||
return updated;
|
||||
},
|
||||
},
|
||||
update: proxy => {
|
||||
proxy.writeFragment({
|
||||
fragment: gql`
|
||||
fragment Talk_RejectUsername on User {
|
||||
state {
|
||||
status {
|
||||
username {
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
id: `User_${id}`,
|
||||
data: {
|
||||
__typename: 'User',
|
||||
state: {
|
||||
__typename: 'UserState',
|
||||
status: {
|
||||
__typename: 'UserStatus',
|
||||
username: {
|
||||
__typename: 'UsernameStatus',
|
||||
status: 'REJECTED',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
}),
|
||||
UpdateSettings: ({ variables: { input } }) => ({
|
||||
updateQueries: {
|
||||
|
||||
@@ -2,44 +2,31 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import styles from './Community.css';
|
||||
import People from '../containers/People';
|
||||
import CommunityMenu from './CommunityMenu';
|
||||
import RejectUsernameDialog from './RejectUsernameDialog';
|
||||
import CommunityMenu from '../containers/CommunityMenu';
|
||||
import RejectUsernameDialog from '../containers/RejectUsernameDialog';
|
||||
import FlaggedAccounts from '../containers/FlaggedAccounts';
|
||||
|
||||
class Community extends Component {
|
||||
renderTab() {
|
||||
const { route, community, ...props } = this.props;
|
||||
const { route } = this.props;
|
||||
const activeTab = route.path === ':id' ? 'flagged' : route.path;
|
||||
|
||||
if (activeTab === 'people') {
|
||||
return (
|
||||
<People
|
||||
community={community}
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
/>
|
||||
);
|
||||
return <People />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FlaggedAccounts data={this.props.data} root={this.props.root} />
|
||||
<RejectUsernameDialog
|
||||
user={community.user}
|
||||
open={community.rejectUsernameDialog}
|
||||
rejectUsername={props.rejectUsername}
|
||||
handleClose={props.hideRejectUsernameDialog}
|
||||
/>
|
||||
<FlaggedAccounts />
|
||||
<RejectUsernameDialog />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { root: { flaggedUsernamesCount } } = this.props;
|
||||
|
||||
return (
|
||||
<div className="talk-admin-community">
|
||||
<CommunityMenu flaggedUsernamesCount={flaggedUsernamesCount} />
|
||||
<CommunityMenu />
|
||||
<div className={styles.container}>{this.renderTab()}</div>
|
||||
</div>
|
||||
);
|
||||
@@ -48,11 +35,6 @@ class Community extends Component {
|
||||
|
||||
Community.propTypes = {
|
||||
route: PropTypes.object,
|
||||
community: PropTypes.object,
|
||||
rejectUsername: PropTypes.func.isRequired,
|
||||
hideRejectUsernameDialog: PropTypes.func.isRequired,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
};
|
||||
|
||||
export default Community;
|
||||
|
||||
@@ -17,6 +17,7 @@ class FlaggedAccounts extends React.Component {
|
||||
approveUser,
|
||||
me,
|
||||
viewUserDetail,
|
||||
hasMore,
|
||||
} = this.props;
|
||||
|
||||
const hasResults = users.nodes && !!users.nodes.length;
|
||||
@@ -60,7 +61,7 @@ class FlaggedAccounts extends React.Component {
|
||||
) : (
|
||||
<EmptyCard>{t('community.no_flagged_accounts')}</EmptyCard>
|
||||
)}
|
||||
<LoadMore loadMore={loadMore} showLoadMore={users.hasNextPage} />
|
||||
<LoadMore loadMore={loadMore} showLoadMore={hasMore} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -70,6 +71,7 @@ class FlaggedAccounts extends React.Component {
|
||||
FlaggedAccounts.propTypes = {
|
||||
users: PropTypes.object,
|
||||
loadMore: PropTypes.func,
|
||||
hasMore: PropTypes.bool,
|
||||
showRejectUsernameDialog: PropTypes.func,
|
||||
approveUser: PropTypes.func,
|
||||
me: PropTypes.object,
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
min-width: 400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
transition: box-shadow 200ms, margin-bottom 200ms;
|
||||
transition: background 200ms, box-shadow 200ms, margin-bottom 200ms;
|
||||
padding: 10px 0 0;
|
||||
min-height: 220px;
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.dangling {
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
.rootSelected {
|
||||
composes: mdl-shadow--16dp from global;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import t from 'coral-framework/services/i18n';
|
||||
import { username } from 'talk-plugin-flags/helpers/flagReasons';
|
||||
import ApproveButton from 'coral-admin/src/components/ApproveButton';
|
||||
import RejectButton from 'coral-admin/src/components/RejectButton';
|
||||
import { isFlaggedUserDangling } from '../utils';
|
||||
|
||||
const shortReasons = {
|
||||
[username.other]: t('community.other'),
|
||||
@@ -28,12 +29,14 @@ class User extends React.Component {
|
||||
|
||||
render() {
|
||||
const { user, viewUserDetail, selected, className } = this.props;
|
||||
const dangling = isFlaggedUserDangling(user);
|
||||
|
||||
return (
|
||||
<li
|
||||
tabIndex={0}
|
||||
className={cn(className, styles.root, {
|
||||
[styles.rootSelected]: selected,
|
||||
[styles.dangling]: dangling,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
@@ -110,11 +113,15 @@ class User extends React.Component {
|
||||
<div className={styles.actions}>
|
||||
<ApproveButton
|
||||
className="talk-admin-flagged-user-approve-button"
|
||||
active={user.state.status.username.status === 'APPROVED'}
|
||||
onClick={this.approveUser}
|
||||
disabled={dangling}
|
||||
/>
|
||||
<RejectButton
|
||||
className="talk-admin-flagged-user-reject-button"
|
||||
active={user.state.status.username.status === 'REJECTED'}
|
||||
onClick={this.showRejectUsernameDialog}
|
||||
disabled={dangling}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@ class RejectUsernameDialog extends Component {
|
||||
|
||||
{/* {
|
||||
|
||||
// Suspension Message: This functionality it's not entirely done on the BE - It will be released soon.
|
||||
// Suspension Message: This functionality it's not entirely done on the BE - It will be released soon.
|
||||
stage === 1 &&
|
||||
<div className={styles.writeContainer}>
|
||||
<div className={styles.emailMessage}>{t('reject_username.write_message')}</div>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
import { withRejectUsername } from 'coral-framework/graphql/mutations';
|
||||
import FlaggedAccounts from '../containers/FlaggedAccounts';
|
||||
import FlaggedUser from '../containers/FlaggedUser';
|
||||
import People from '../containers/People';
|
||||
import { hideRejectUsernameDialog } from '../../../actions/community';
|
||||
import Community from '../components/Community';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
community: state.community,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
hideRejectUsernameDialog,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
const withData = withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Community {
|
||||
flaggedUsernamesCount: userCount(
|
||||
query:{
|
||||
action_type: FLAG,
|
||||
state: {
|
||||
status: {
|
||||
username: [SET, CHANGED]
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
...${getDefinitionName(FlaggedAccounts.fragments.root)}
|
||||
...${getDefinitionName(FlaggedUser.fragments.root)}
|
||||
...${getDefinitionName(People.fragments.root)}
|
||||
me {
|
||||
...${getDefinitionName(FlaggedUser.fragments.me)}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
${People.fragments.root}
|
||||
${FlaggedAccounts.fragments.root}
|
||||
${FlaggedUser.fragments.root}
|
||||
${FlaggedUser.fragments.me}
|
||||
`,
|
||||
{
|
||||
options: {
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withRejectUsername,
|
||||
withData
|
||||
)(Community);
|
||||
@@ -0,0 +1,4 @@
|
||||
import withCachedFlaggedUsernamesCount from '../hocs/withCachedFlaggedUsernamesCount';
|
||||
import CommunityMenu from '../components/CommunityMenu';
|
||||
|
||||
export default withCachedFlaggedUsernamesCount(CommunityMenu);
|
||||
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import { Spinner } from 'coral-ui';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withApproveUsername } from 'coral-framework/graphql/mutations';
|
||||
@@ -11,15 +11,127 @@ import { viewUserDetail } from '../../../actions/userDetail';
|
||||
import { getDefinitionName } from 'coral-framework/utils';
|
||||
import { appendNewNodes } from 'plugin-api/beta/client/utils';
|
||||
import update from 'immutability-helper';
|
||||
import { handleFlaggedUsernameChange } from '../graphql';
|
||||
import { notify } from 'coral-framework/actions/notification';
|
||||
import { isFlaggedUserDangling } from '../utils';
|
||||
import t from 'coral-framework/services/i18n';
|
||||
import { notifyOnMutationError } from 'coral-framework/hocs';
|
||||
|
||||
import FlaggedAccounts from '../components/FlaggedAccounts';
|
||||
import FlaggedUser from '../containers/FlaggedUser';
|
||||
|
||||
function whoChangedTheStatus(statusObject) {
|
||||
return statusObject.history[statusObject.history.length - 1].assigned_by
|
||||
.username;
|
||||
}
|
||||
|
||||
function whoFlagged(user) {
|
||||
return user.actions[user.actions.length - 1].user.username;
|
||||
}
|
||||
|
||||
class FlaggedAccountsContainer extends Component {
|
||||
subscriptions = [];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
getCountWithoutDangling() {
|
||||
return this.props.root.flaggedUsers.nodes.filter(
|
||||
node => !isFlaggedUserDangling(node)
|
||||
).length;
|
||||
}
|
||||
|
||||
subscribeToUpdates() {
|
||||
const parameters = [
|
||||
{
|
||||
document: USERNAME_FLAGGED_SUBSCRIPTION,
|
||||
updateQuery: (
|
||||
prev,
|
||||
{ subscriptionData: { data: { usernameFlagged: user } } }
|
||||
) => {
|
||||
return handleFlaggedUsernameChange(prev, user, () => {
|
||||
const msg = t(
|
||||
'flagged_usernames.notify_flagged',
|
||||
whoFlagged(user),
|
||||
user.username
|
||||
);
|
||||
this.props.notify('info', msg);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
document: USERNAME_APPROVED_SUBSCRIPTION,
|
||||
updateQuery: (
|
||||
prev,
|
||||
{ subscriptionData: { data: { usernameApproved: user } } }
|
||||
) => {
|
||||
return handleFlaggedUsernameChange(prev, user, () => {
|
||||
const msg = t(
|
||||
'flagged_usernames.notify_approved',
|
||||
whoChangedTheStatus(user.state.status.username),
|
||||
user.username
|
||||
);
|
||||
this.props.notify('info', msg);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
document: USERNAME_REJECTED_SUBSCRIPTION,
|
||||
updateQuery: (
|
||||
prev,
|
||||
{ subscriptionData: { data: { usernameRejected: user } } }
|
||||
) => {
|
||||
return handleFlaggedUsernameChange(prev, user, () => {
|
||||
const msg = t(
|
||||
'flagged_usernames.notify_rejected',
|
||||
whoChangedTheStatus(user.state.status.username),
|
||||
user.username
|
||||
);
|
||||
this.props.notify('info', msg);
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
document: USERNAME_CHANGED_SUBSCRIPTION,
|
||||
updateQuery: (
|
||||
prev,
|
||||
{
|
||||
subscriptionData: {
|
||||
data: { usernameChanged: { previousUsername, user } },
|
||||
},
|
||||
}
|
||||
) => {
|
||||
return handleFlaggedUsernameChange(prev, user, () => {
|
||||
const msg = t(
|
||||
'flagged_usernames.notify_changed',
|
||||
previousUsername,
|
||||
user.username
|
||||
);
|
||||
this.props.notify('info', msg);
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
this.subscriptions = parameters.map(param =>
|
||||
this.props.data.subscribeToMore(param)
|
||||
);
|
||||
}
|
||||
|
||||
unsubscribe() {
|
||||
this.subscriptions.forEach(unsubscribe => unsubscribe());
|
||||
this.subscriptions = [];
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.subscribeToUpdates();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
approveUser = ({ userId: id }) => {
|
||||
return this.props.approveUsername(id);
|
||||
};
|
||||
@@ -68,6 +180,9 @@ class FlaggedAccountsContainer extends Component {
|
||||
data={this.props.data}
|
||||
root={this.props.root}
|
||||
users={this.props.root.flaggedUsers}
|
||||
hasMore={
|
||||
this.getCountWithoutDangling() < this.props.root.flaggedUsernamesCount
|
||||
}
|
||||
me={this.props.root.me}
|
||||
/>
|
||||
);
|
||||
@@ -77,6 +192,8 @@ class FlaggedAccountsContainer extends Component {
|
||||
FlaggedAccountsContainer.propTypes = {
|
||||
showRejectUsernameDialog: PropTypes.func,
|
||||
viewUserDetail: PropTypes.func,
|
||||
notify: PropTypes.func,
|
||||
flaggedUsernamesCount: PropTypes.number,
|
||||
approveUsername: PropTypes.func,
|
||||
data: PropTypes.object,
|
||||
root: PropTypes.object,
|
||||
@@ -105,11 +222,72 @@ const LOAD_MORE_QUERY = gql`
|
||||
${FlaggedUser.fragments.user}
|
||||
`;
|
||||
|
||||
const historyFields = `
|
||||
state {
|
||||
status {
|
||||
username {
|
||||
history {
|
||||
status
|
||||
assigned_by {
|
||||
id
|
||||
username
|
||||
}
|
||||
created_at
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const USERNAME_FLAGGED_SUBSCRIPTION = gql`
|
||||
subscription TalkAdmin_UsernameFlagged {
|
||||
usernameFlagged {
|
||||
...${getDefinitionName(FlaggedUser.fragments.user)}
|
||||
${historyFields}
|
||||
}
|
||||
}
|
||||
${FlaggedUser.fragments.user}
|
||||
`;
|
||||
|
||||
const USERNAME_APPROVED_SUBSCRIPTION = gql`
|
||||
subscription TalkAdmin_UsernameApproved {
|
||||
usernameApproved {
|
||||
...${getDefinitionName(FlaggedUser.fragments.user)}
|
||||
${historyFields}
|
||||
}
|
||||
}
|
||||
${FlaggedUser.fragments.user}
|
||||
`;
|
||||
|
||||
const USERNAME_REJECTED_SUBSCRIPTION = gql`
|
||||
subscription TalkAdmin_UsernameRejected {
|
||||
usernameRejected {
|
||||
...${getDefinitionName(FlaggedUser.fragments.user)}
|
||||
${historyFields}
|
||||
}
|
||||
}
|
||||
${FlaggedUser.fragments.user}
|
||||
`;
|
||||
|
||||
const USERNAME_CHANGED_SUBSCRIPTION = gql`
|
||||
subscription TalkAdmin_UsernameChanged {
|
||||
usernameChanged {
|
||||
previousUsername
|
||||
user {
|
||||
...${getDefinitionName(FlaggedUser.fragments.user)}
|
||||
${historyFields}
|
||||
}
|
||||
}
|
||||
}
|
||||
${FlaggedUser.fragments.user}
|
||||
`;
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
showRejectUsernameDialog,
|
||||
viewUserDetail,
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
@@ -117,9 +295,16 @@ const mapDispatchToProps = dispatch =>
|
||||
export default compose(
|
||||
connect(null, mapDispatchToProps),
|
||||
withApproveUsername,
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkAdminCommunity_FlaggedAccounts_root on RootQuery {
|
||||
notifyOnMutationError(['approveUsername']),
|
||||
withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Community_FlaggedAccounts {
|
||||
flaggedUsernamesCount: userCount(
|
||||
query: {
|
||||
action_type: FLAG
|
||||
state: { status: { username: [SET, CHANGED] } }
|
||||
}
|
||||
)
|
||||
flaggedUsers: users(query:{
|
||||
action_type: FLAG,
|
||||
state: {
|
||||
@@ -138,9 +323,16 @@ export default compose(
|
||||
}
|
||||
me {
|
||||
id
|
||||
...${getDefinitionName(FlaggedUser.fragments.me)}
|
||||
}
|
||||
}
|
||||
${FlaggedUser.fragments.user}
|
||||
${FlaggedUser.fragments.me}
|
||||
`,
|
||||
})
|
||||
{
|
||||
options: {
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
}
|
||||
)
|
||||
)(FlaggedAccountsContainer);
|
||||
|
||||
@@ -4,19 +4,20 @@ import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
|
||||
export default withFragments({
|
||||
root: gql`
|
||||
fragment TalkAdminCommunity_FlaggedUser_root on RootQuery {
|
||||
fragment TalkAdmin_Community_FlaggedUser_root on RootQuery {
|
||||
__typename
|
||||
}
|
||||
`,
|
||||
me: gql`
|
||||
fragment TalkAdminCommunity_FlaggedUser_me on User {
|
||||
fragment TalkAdmin_Community_FlaggedUser_me on User {
|
||||
id
|
||||
}
|
||||
`,
|
||||
user: gql`
|
||||
fragment TalkAdminCommunity_FlaggedUser_user on User {
|
||||
fragment TalkAdmin_Community_FlaggedUser_user on User {
|
||||
id
|
||||
username
|
||||
created_at
|
||||
state {
|
||||
status {
|
||||
username {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Indicator from '../../../components/Indicator';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import { branch, renderNothing } from 'recompose';
|
||||
|
||||
const hideIfNoData = hasNoData => branch(hasNoData, renderNothing);
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkAdmin_Community_Indicator_root on RootQuery {
|
||||
flaggedUsernamesCount: userCount(
|
||||
query: {
|
||||
action_type: FLAG
|
||||
state: { status: { username: [SET, CHANGED] } }
|
||||
}
|
||||
)
|
||||
}
|
||||
`,
|
||||
}),
|
||||
hideIfNoData(props => !props.root.flaggedUsernamesCount)
|
||||
);
|
||||
|
||||
export default enhance(Indicator);
|
||||
@@ -4,7 +4,6 @@ import { bindActionCreators } from 'redux';
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import People from '../components/People';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import {
|
||||
withUnbanUser,
|
||||
withUnsuspendUser,
|
||||
@@ -16,6 +15,7 @@ import { viewUserDetail } from '../../../actions/userDetail';
|
||||
import { appendNewNodes } from 'plugin-api/beta/client/utils';
|
||||
import update from 'immutability-helper';
|
||||
import { Spinner } from 'coral-ui';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
|
||||
class PeopleContainer extends React.Component {
|
||||
timer = null;
|
||||
@@ -39,7 +39,7 @@ class PeopleContainer extends React.Component {
|
||||
query: SEARCH_QUERY,
|
||||
variables: {
|
||||
value,
|
||||
limit: 5,
|
||||
limit: 10,
|
||||
},
|
||||
updateQuery: (previous, { fetchMoreResult: { users } }) => {
|
||||
const updated = update(previous, {
|
||||
@@ -115,7 +115,6 @@ class PeopleContainer extends React.Component {
|
||||
}
|
||||
|
||||
PeopleContainer.propTypes = {
|
||||
community: PropTypes.object,
|
||||
setUserRole: PropTypes.func.isRequired,
|
||||
unbanUser: PropTypes.func.isRequired,
|
||||
unsuspendUser: PropTypes.func.isRequired,
|
||||
@@ -137,7 +136,7 @@ const mapDispatchToProps = dispatch =>
|
||||
);
|
||||
|
||||
const LOAD_MORE_QUERY = gql`
|
||||
query TalkAdminCommunity_People_LoadMoreUsers(
|
||||
query TalkAdmin_Community_People_LoadMoreUsers(
|
||||
$limit: Int
|
||||
$cursor: Cursor
|
||||
$value: String
|
||||
@@ -171,7 +170,7 @@ const LOAD_MORE_QUERY = gql`
|
||||
`;
|
||||
|
||||
const SEARCH_QUERY = gql`
|
||||
query TalkAdminCommunity_People_SearchUsers($value: String, $limit: Int) {
|
||||
query TalkAdmin_Community_People_SearchUsers($value: String, $limit: Int) {
|
||||
users(query: { value: $value, limit: $limit }) {
|
||||
hasNextPage
|
||||
endCursor
|
||||
@@ -205,9 +204,9 @@ export default compose(
|
||||
withSetUserRole,
|
||||
withUnsuspendUser,
|
||||
withUnbanUser,
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkAdminCommunity_People_root on RootQuery {
|
||||
withQuery(
|
||||
gql`
|
||||
query TalkAdmin_Community_People {
|
||||
users(query: {}) {
|
||||
hasNextPage
|
||||
endCursor
|
||||
@@ -235,5 +234,10 @@ export default compose(
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
{
|
||||
options: {
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
}
|
||||
)
|
||||
)(PeopleContainer);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import RejectUsernameDialog from '../components/RejectUsernameDialog';
|
||||
import { withRejectUsername } from 'coral-framework/graphql/mutations';
|
||||
import { hideRejectUsernameDialog } from '../../../actions/community';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { notify } from 'coral-framework/actions/notification';
|
||||
import { notifyOnMutationError } from 'coral-framework/hocs';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
user: state.community.user,
|
||||
open: state.community.rejectUsernameDialog,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
handleClose: hideRejectUsernameDialog,
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
);
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withRejectUsername,
|
||||
notifyOnMutationError(['rejectUsername'])
|
||||
)(RejectUsernameDialog);
|
||||
@@ -0,0 +1,107 @@
|
||||
import update from 'immutability-helper';
|
||||
|
||||
function shouldAddFlaggedUser(root, user) {
|
||||
const isEmpty = !root.flaggedUsers.nodes.length;
|
||||
if (isEmpty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasFlaggedUser(root, user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldest = root.flaggedUsers.nodes.reduce((cur, node) => {
|
||||
const createdAt = new Date(node.created_at);
|
||||
return createdAt < cur ? createdAt : cur;
|
||||
}, new Date());
|
||||
|
||||
return new Date(user.created_at) >= oldest;
|
||||
}
|
||||
|
||||
function hasFlaggedUser(root, user) {
|
||||
return root.flaggedUsers.nodes.find(u => u.id === user.id);
|
||||
}
|
||||
|
||||
function applyUserChanges(root, user) {
|
||||
const index = root.flaggedUsers.nodes.findIndex(({ id }) => id === user.id);
|
||||
if (index > -1) {
|
||||
return update(root, {
|
||||
flaggedUsers: {
|
||||
nodes: {
|
||||
[index]: { $merge: user },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
function incrementFlaggedUserCount(root) {
|
||||
return update(root, {
|
||||
flaggedUsernamesCount: { $apply: count => count + 1 },
|
||||
});
|
||||
}
|
||||
|
||||
function decrementFlaggedUserCount(root) {
|
||||
return update(root, {
|
||||
flaggedUsernamesCount: { $apply: count => count - 1 },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Assimilate flagged user changes into current store.
|
||||
* @param {Object} root current state of the store
|
||||
* @param {Object} user user that was changed
|
||||
* @param {function} notify callback to show notification
|
||||
* @return {Object} next state of the store
|
||||
*/
|
||||
export function handleFlaggedUsernameChange(root, user, notify) {
|
||||
if (user.state.status.username.status !== 'SET') {
|
||||
// Check if change came from current user, if so ignore it.
|
||||
const lastChange =
|
||||
user.state.status.username.history[
|
||||
user.state.status.username.history.length - 1
|
||||
];
|
||||
if (lastChange.assigned_by.id === root.me.id) {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasFlaggedUser(root, user)) {
|
||||
switch (user.state.status.username.status) {
|
||||
case 'SET':
|
||||
case 'CHANGED':
|
||||
root = incrementFlaggedUserCount(root);
|
||||
|
||||
if (!shouldAddFlaggedUser(root, user)) {
|
||||
return root;
|
||||
}
|
||||
|
||||
notify();
|
||||
|
||||
return update(root, {
|
||||
flaggedUsers: {
|
||||
nodes: { $push: [user] },
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'APPROVED':
|
||||
case 'REJECTED':
|
||||
return root;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if (hasFlaggedUser(root, user)) {
|
||||
switch (user.state.status.username.status) {
|
||||
case 'SET':
|
||||
case 'CHANGED':
|
||||
return root;
|
||||
case 'APPROVED':
|
||||
case 'REJECTED':
|
||||
notify();
|
||||
return applyUserChanges(decrementFlaggedUserCount(root), user);
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import { mapProps } from 'recompose';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import get from 'lodash/get';
|
||||
|
||||
const withData = withQuery(
|
||||
gql`
|
||||
query TalkAdmin_CommunityMenu {
|
||||
flaggedUsernamesCount: userCount(
|
||||
query: {
|
||||
action_type: FLAG
|
||||
state: { status: { username: [SET, CHANGED] } }
|
||||
}
|
||||
)
|
||||
}
|
||||
`,
|
||||
{
|
||||
options: {
|
||||
fetchPolicy: 'cache-only',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withData,
|
||||
mapProps(ownProps => ({
|
||||
flaggedUsernamesCount: get(ownProps, 'root.flaggedUsernamesCount'),
|
||||
}))
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './components/Community';
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isFlaggedUserDangling(user) {
|
||||
return ['APPROVED', 'REJECTED'].includes(user.state.status.username.status);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import styles from './Install.css';
|
||||
import { Wizard, WizardNav } from 'coral-ui';
|
||||
import Layout from 'coral-admin/src/components/ui/Layout';
|
||||
import Layout from 'coral-admin/src/components/Layout';
|
||||
|
||||
import InitialStep from './Steps/InitialStep';
|
||||
import AddOrganizationName from './Steps/AddOrganizationName';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { compose, gql } from 'react-apollo';
|
||||
import Indicator from '../../../components/Indicator';
|
||||
import { withFragments } from 'plugin-api/beta/client/hocs';
|
||||
import { branch, renderNothing } from 'recompose';
|
||||
|
||||
const hideIfNoData = hasNoData => branch(hasNoData, renderNothing);
|
||||
|
||||
const enhance = compose(
|
||||
withFragments({
|
||||
root: gql`
|
||||
fragment TalkAdmin_Moderation_Indicator_root on RootQuery {
|
||||
premodCount: commentCount(query: { statuses: [PREMOD] })
|
||||
reportedCount: commentCount(
|
||||
query: {
|
||||
statuses: [NONE, PREMOD, SYSTEM_WITHHELD]
|
||||
action_type: FLAG
|
||||
}
|
||||
)
|
||||
}
|
||||
`,
|
||||
}),
|
||||
hideIfNoData(props => !props.root.premodCount && !props.root.reportedCount)
|
||||
);
|
||||
|
||||
export default enhance(Indicator);
|
||||
@@ -6,3 +6,4 @@ export { default as withEmit } from './withEmit';
|
||||
export { default as excludeIf } from './excludeIf';
|
||||
export { default as connect } from './connect';
|
||||
export { default as withMergedSettings } from './withMergedSettings';
|
||||
export { default as notifyOnMutationError } from './notifyOnMutationError';
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { compose } from 'react-apollo';
|
||||
import { notify } from 'coral-framework/actions/notification';
|
||||
import { forEachError } from 'coral-framework/utils';
|
||||
import { withProps } from 'recompose';
|
||||
|
||||
const notifyOnMutationError = keys =>
|
||||
compose(
|
||||
connect(null, dispatch =>
|
||||
bindActionCreators(
|
||||
{
|
||||
notify,
|
||||
},
|
||||
dispatch
|
||||
)
|
||||
),
|
||||
withProps(ownProps =>
|
||||
keys.reduce((props, key) => {
|
||||
props[key] = async (...args) => {
|
||||
try {
|
||||
return await ownProps[key](...args);
|
||||
} catch (e) {
|
||||
forEachError(e, ({ msg }) => {
|
||||
ownProps.notify('error', msg);
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
return props;
|
||||
}, {})
|
||||
)
|
||||
);
|
||||
|
||||
export default notifyOnMutationError;
|
||||
@@ -73,10 +73,20 @@ const createAction = async (
|
||||
metadata,
|
||||
});
|
||||
|
||||
if (action_type === 'FLAG' && item_type === 'COMMENTS') {
|
||||
// The item is a comment, and this is a flag. Push that the comment was
|
||||
// flagged, don't wait for it to finish.
|
||||
pubsub.publish('commentFlagged', item);
|
||||
if (action_type === 'FLAG') {
|
||||
switch (item_type) {
|
||||
case 'COMMENTS':
|
||||
// The item is a comment, and this is a flag. Push that the comment was
|
||||
// flagged, don't wait for it to finish.
|
||||
pubsub.publish('commentFlagged', item);
|
||||
break;
|
||||
case 'USERS':
|
||||
// The item is a user, and this is a flag. Push that the user was
|
||||
// flagged, don't wait for it to finish.
|
||||
pubsub.publish('usernameFlagged', item);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
|
||||
@@ -56,7 +56,10 @@ const stopIgnoringUser = ({ user }, userToStopIgnoring) => {
|
||||
};
|
||||
|
||||
const changeUsername = async (ctx, id, username) => {
|
||||
return UsersService.changeUsername(id, username, ctx.user.id);
|
||||
const user = await UsersService.changeUsername(id, username, ctx.user.id);
|
||||
const previousUsername = ctx.user.username;
|
||||
ctx.pubsub.publish('usernameChanged', { previousUsername, user });
|
||||
return user;
|
||||
};
|
||||
|
||||
const setUsername = async (ctx, id, username) => {
|
||||
|
||||
@@ -23,9 +23,18 @@ const Subscription = {
|
||||
userSuspended(user) {
|
||||
return user;
|
||||
},
|
||||
usernameApproved(user) {
|
||||
return user;
|
||||
},
|
||||
usernameRejected(user) {
|
||||
return user;
|
||||
},
|
||||
usernameFlagged(user) {
|
||||
return user;
|
||||
},
|
||||
usernameChanged(payload) {
|
||||
return payload;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = Subscription;
|
||||
|
||||
@@ -9,6 +9,8 @@ const {
|
||||
SUBSCRIBE_ALL_USER_BANNED,
|
||||
SUBSCRIBE_ALL_USERNAME_REJECTED,
|
||||
SUBSCRIBE_ALL_USERNAME_APPROVED,
|
||||
SUBSCRIBE_ALL_USERNAME_FLAGGED,
|
||||
SUBSCRIBE_ALL_USERNAME_CHANGED,
|
||||
} = require('../perms/constants');
|
||||
|
||||
const merge = require('lodash/merge');
|
||||
@@ -97,6 +99,16 @@ const setupFunctions = {
|
||||
}
|
||||
return !args.user_id || user.id === args.user_id;
|
||||
},
|
||||
usernameFlagged: (options, args, user, context) => {
|
||||
if (
|
||||
!context.user ||
|
||||
(args.user_id !== user.id &&
|
||||
!context.user.can(SUBSCRIBE_ALL_USERNAME_FLAGGED))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return !args.user_id || user.id === args.user_id;
|
||||
},
|
||||
usernameRejected: (options, args, user, context) => {
|
||||
if (
|
||||
!context.user ||
|
||||
@@ -117,6 +129,16 @@ const setupFunctions = {
|
||||
}
|
||||
return !args.user_id || user.id === args.user_id;
|
||||
},
|
||||
usernameChanged: (options, args, { user }, context) => {
|
||||
if (
|
||||
!context.user ||
|
||||
(args.user_id !== user.id &&
|
||||
!context.user.can(SUBSCRIBE_ALL_USERNAME_CHANGED))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return !args.user_id || user.id === args.user_id;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+15
-1
@@ -1476,6 +1476,11 @@ type RootMutation {
|
||||
stopIgnoringUser(id: ID!): StopIgnoringUserResponse
|
||||
}
|
||||
|
||||
type UsernameChangedPayload {
|
||||
previousUsername: String
|
||||
user: User
|
||||
}
|
||||
|
||||
################################################################################
|
||||
## Subscriptions
|
||||
################################################################################
|
||||
@@ -1517,14 +1522,23 @@ type Subscription {
|
||||
# users with the `ADMIN` or `MODERATOR` role.
|
||||
userBanned(user_id: ID): User
|
||||
|
||||
# Get an update whenever a username was flagged.
|
||||
# `user_id` must match id of current user except for
|
||||
# users with the `ADMIN` or `MODERATOR` role.
|
||||
usernameFlagged(user_id: ID): User
|
||||
|
||||
# Get an update whenever a username has been rejected.
|
||||
# `user_id` must match id of current user except for
|
||||
# users with the `ADMIN` or `MODERATOR` role.
|
||||
usernameRejected(user_id: ID): User
|
||||
|
||||
# Gen an update whenever a username has been approved. `user_id` must match id
|
||||
# Get an update whenever a username has been approved. `user_id` must match id
|
||||
# of current user except for users with the `ADMIN` or `MODERATOR` role.
|
||||
usernameApproved(user_id: ID): User
|
||||
|
||||
# Get an update whenever a username has been changed. `user_id` must match id
|
||||
# of current user except for users with the `ADMIN` or `MODERATOR` role.
|
||||
usernameChanged(user_id: ID): UsernameChangedPayload
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
@@ -61,6 +61,11 @@ en:
|
||||
reaction: 'reaction'
|
||||
reactions: 'reactions'
|
||||
story: 'Story'
|
||||
flagged_usernames:
|
||||
notify_approved: '{0} approved username {1}'
|
||||
notify_rejected: '{0} rejected username {1}'
|
||||
notify_flagged: '{0} reported username {1}'
|
||||
notify_changed: 'user {0} changed his username to {1}'
|
||||
community:
|
||||
account_creation_date: "Account Creation Date"
|
||||
active: Active
|
||||
|
||||
@@ -9,4 +9,6 @@ module.exports = {
|
||||
SUBSCRIBE_ALL_USER_BANNED: 'SUBSCRIBE_ALL_USER_BANNED',
|
||||
SUBSCRIBE_ALL_USERNAME_REJECTED: 'SUBSCRIBE_ALL_USERNAME_REJECTED',
|
||||
SUBSCRIBE_ALL_USERNAME_APPROVED: 'SUBSCRIBE_ALL_USERNAME_APPROVED',
|
||||
SUBSCRIBE_ALL_USERNAME_FLAGGED: 'SUBSCRIBE_ALL_USERNAME_FLAGGED',
|
||||
SUBSCRIBE_ALL_USERNAME_CHANGED: 'SUBSCRIBE_ALL_USERNAME_CHANGED',
|
||||
};
|
||||
|
||||
@@ -13,6 +13,8 @@ module.exports = (user, perm) => {
|
||||
case types.SUBSCRIBE_ALL_USER_BANNED:
|
||||
case types.SUBSCRIBE_ALL_USERNAME_REJECTED:
|
||||
case types.SUBSCRIBE_ALL_USERNAME_APPROVED:
|
||||
case types.SUBSCRIBE_ALL_USERNAME_FLAGGED:
|
||||
case types.SUBSCRIBE_ALL_USERNAME_CHANGED:
|
||||
return check(user, ['ADMIN', 'MODERATOR']);
|
||||
default:
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user