mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 21:36:00 +08:00
Merge pull request #985 from coralproject/mod-indicator
Moderate Indicator
This commit is contained in:
@@ -6,7 +6,7 @@ import Configure from 'routes/Configure';
|
||||
import Dashboard from 'routes/Dashboard';
|
||||
import Install from 'routes/Install';
|
||||
import Stories from 'routes/Stories';
|
||||
import {CommunityLayout, Community} from 'routes/Community';
|
||||
import Community from 'routes/Community/containers/Community';
|
||||
import {ModerationLayout, Moderation} from 'routes/Moderation';
|
||||
|
||||
import Layout from 'containers/Layout';
|
||||
@@ -22,7 +22,7 @@ const routes = (
|
||||
|
||||
{/* Community Routes */}
|
||||
|
||||
<Route path='community' component={CommunityLayout}>
|
||||
<Route path='community'>
|
||||
<Route path='flagged' components={Community}>
|
||||
<Route path=':id' components={Community} />
|
||||
</Route>
|
||||
|
||||
@@ -6,101 +6,110 @@ 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';
|
||||
|
||||
const CoralHeader = ({
|
||||
handleLogout,
|
||||
showShortcuts = () => {},
|
||||
auth
|
||||
}) => (
|
||||
<Header className={styles.header}>
|
||||
<Logo className={styles.logo} />
|
||||
<div>
|
||||
{
|
||||
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
|
||||
<Navigation className={styles.nav}>
|
||||
<IndexLink
|
||||
id='dashboardNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/dashboard"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.dashboard')}
|
||||
</IndexLink>
|
||||
{
|
||||
can(auth.user, 'MODERATE_COMMENTS') && (
|
||||
<Link
|
||||
id='moderateNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/moderate"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.moderate')}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Link
|
||||
id='streamsNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/stories"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.stories')}
|
||||
</Link>
|
||||
<Link
|
||||
id='communityNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/community"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.community')}
|
||||
</Link>
|
||||
{
|
||||
can(auth.user, 'UPDATE_CONFIG') && (
|
||||
<Link
|
||||
id='configureNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/configure"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.configure')}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</Navigation>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles.rightPanel}>
|
||||
<ul>
|
||||
<li className={styles.settings}>
|
||||
<div>
|
||||
<IconButton name="settings" id="menu-settings"/>
|
||||
<Menu target="menu-settings" align="right">
|
||||
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://github.com/coralproject/talk/releases" target="_blank" rel="noopener noreferrer">
|
||||
View latest version
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank" rel="noopener noreferrer">
|
||||
Report a bug or give feedback
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
{t('configure.sign_out')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
{`v${process.env.VERSION}`}
|
||||
</li>
|
||||
</ul>
|
||||
auth,
|
||||
root
|
||||
}) => {
|
||||
return (
|
||||
<Header className={styles.header}>
|
||||
<Logo className={styles.logo} />
|
||||
<div>
|
||||
{
|
||||
auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ?
|
||||
<Navigation className={styles.nav}>
|
||||
<IndexLink
|
||||
id='dashboardNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/dashboard"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.dashboard')}
|
||||
</IndexLink>
|
||||
{
|
||||
can(auth.user, 'MODERATE_COMMENTS') && (
|
||||
<Link
|
||||
id='moderateNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/moderate"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.moderate')}
|
||||
{(root.premodCount !== 0 || root.reportedCount !== 0) && <Indicator />}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Link
|
||||
id='streamsNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/stories"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.stories')}
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
id='communityNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/community"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.community')}
|
||||
{root.flaggedUsernamesCount !== 0 && <Indicator />}
|
||||
</Link>
|
||||
|
||||
{
|
||||
can(auth.user, 'UPDATE_CONFIG') && (
|
||||
<Link
|
||||
id='configureNav'
|
||||
className={styles.navLink}
|
||||
to="/admin/configure"
|
||||
activeClassName={styles.active}>
|
||||
{t('configure.configure')}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
</Navigation>
|
||||
:
|
||||
null
|
||||
}
|
||||
<div className={styles.rightPanel}>
|
||||
<ul>
|
||||
<li className={styles.settings}>
|
||||
<div>
|
||||
<IconButton name="settings" id="menu-settings"/>
|
||||
<Menu target="menu-settings" align="right">
|
||||
<MenuItem onClick={() => showShortcuts(true)}>{t('configure.shortcuts')}</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://github.com/coralproject/talk/releases" target="_blank" rel="noopener noreferrer">
|
||||
View latest version
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
<a href="https://coralproject.net/contribute.html#other-ideas-and-bug-reports" target="_blank" rel="noopener noreferrer">
|
||||
Report a bug or give feedback
|
||||
</a>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleLogout}>
|
||||
{t('configure.sign_out')}
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
{`v${process.env.VERSION}`}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
);
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
|
||||
CoralHeader.propTypes = {
|
||||
auth: PropTypes.object,
|
||||
showShortcuts: PropTypes.func,
|
||||
handleLogout: PropTypes.func.isRequired
|
||||
handleLogout: PropTypes.func.isRequired,
|
||||
root: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default CoralHeader;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
.indicator {
|
||||
background-color: #E46D59;
|
||||
border-radius: 10px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-top: -4px;
|
||||
margin-left: 7px;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react';
|
||||
import styles from './Indicator.css';
|
||||
|
||||
const Indicator = () =>
|
||||
<span className={styles.indicator}></span>;
|
||||
|
||||
export default Indicator;
|
||||
@@ -1,22 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {Layout as LayoutMDL} from 'react-mdl';
|
||||
import Header from './Header';
|
||||
import Header from '../../containers/Header';
|
||||
import Drawer from './Drawer';
|
||||
import styles from './Layout.css';
|
||||
|
||||
const Layout = ({
|
||||
children,
|
||||
handleLogout = () => {},
|
||||
toggleShortcutModal,
|
||||
toggleShortcutModal = () => {},
|
||||
restricted = false,
|
||||
...props}) => (
|
||||
auth,
|
||||
}) => (
|
||||
<LayoutMDL className={styles.layout} fixedDrawer>
|
||||
<Header
|
||||
handleLogout={handleLogout}
|
||||
showShortcuts={toggleShortcutModal}
|
||||
{...props} />
|
||||
<Drawer handleLogout={handleLogout} restricted={restricted} {...props} />
|
||||
auth={auth}
|
||||
/>
|
||||
<Drawer
|
||||
handleLogout={handleLogout}
|
||||
restricted={restricted}
|
||||
/>
|
||||
<div className={styles.layout}>
|
||||
{children}
|
||||
</div>
|
||||
@@ -24,6 +29,8 @@ const Layout = ({
|
||||
);
|
||||
|
||||
Layout.propTypes = {
|
||||
children: PropTypes.node,
|
||||
auth: PropTypes.object,
|
||||
handleLogout: PropTypes.func,
|
||||
toggleShortcutModal: PropTypes.func,
|
||||
restricted: PropTypes.bool // hide elements from a user that's logged out
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import styles from './Logo.css';
|
||||
import {CoralLogo} from 'coral-ui';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export const Logo = ({className = ''}) => (
|
||||
<div className={`${styles.logo} ${className}`}>
|
||||
@@ -10,3 +11,7 @@ export const Logo = ({className = ''}) => (
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
Logo.propTypes = {
|
||||
className: PropTypes.string
|
||||
};
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {gql} from 'react-apollo';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import Header from '../components/ui/Header';
|
||||
|
||||
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,
|
||||
statuses: [PENDING]
|
||||
})
|
||||
}
|
||||
`, {
|
||||
options: {
|
||||
pollInterval: 5000
|
||||
}
|
||||
})(Header);
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, {Component} from 'react';
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import Layout from '../components/ui/Layout';
|
||||
import {fetchConfig} from '../actions/config';
|
||||
import AdminLogin from '../components/AdminLogin';
|
||||
@@ -10,15 +11,18 @@ import {toggleModal as toggleShortcutModal} from '../actions/moderation';
|
||||
import {checkLogin, handleLogin, requestPasswordReset, logout} from '../actions/auth';
|
||||
import {can} from 'coral-framework/services/perms';
|
||||
import UserDetail from 'coral-admin/src/containers/UserDetail';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
class LayoutContainer extends Component {
|
||||
class LayoutContainer extends React.Component {
|
||||
componentWillMount() {
|
||||
const {checkLogin, fetchConfig} = this.props;
|
||||
|
||||
checkLogin();
|
||||
fetchConfig();
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const {
|
||||
user,
|
||||
loggedIn,
|
||||
@@ -29,13 +33,16 @@ class LayoutContainer extends Component {
|
||||
} = this.props.auth;
|
||||
|
||||
const {
|
||||
handleLogout,
|
||||
children,
|
||||
logout,
|
||||
toggleShortcutModal,
|
||||
TALK_RECAPTCHA_PUBLIC
|
||||
TALK_RECAPTCHA_PUBLIC,
|
||||
} = this.props;
|
||||
|
||||
if (loadingUser) {
|
||||
return <FullLoading />;
|
||||
}
|
||||
|
||||
if (!loggedIn) {
|
||||
return (
|
||||
<AdminLogin
|
||||
@@ -48,17 +55,17 @@ class LayoutContainer extends Component {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (can(user, 'ACCESS_ADMIN') && loggedIn) {
|
||||
return (
|
||||
<Layout
|
||||
handleLogout={handleLogout}
|
||||
handleLogout={logout}
|
||||
toggleShortcutModal={toggleShortcutModal}
|
||||
{...this.props}
|
||||
>
|
||||
auth={this.props.auth} >
|
||||
<BanUserDialog />
|
||||
<SuspendUserDialog />
|
||||
<UserDetail />
|
||||
{this.props.children}
|
||||
{children}
|
||||
</Layout>
|
||||
);
|
||||
} else if (loggedIn) {
|
||||
@@ -72,19 +79,32 @@ class LayoutContainer extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
LayoutContainer.propTypes = {
|
||||
children: PropTypes.node,
|
||||
requestPasswordReset: PropTypes.func,
|
||||
handleLogin: PropTypes.func,
|
||||
auth: PropTypes.object,
|
||||
handleLogout: PropTypes.func,
|
||||
logout: PropTypes.func,
|
||||
toggleShortcutModal: PropTypes.func,
|
||||
TALK_RECAPTCHA_PUBLIC: PropTypes.string,
|
||||
checkLogin: PropTypes.func,
|
||||
fetchConfig: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
TALK_RECAPTCHA_PUBLIC: state.config.data.TALK_RECAPTCHA_PUBLIC,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkLogin: () => dispatch(checkLogin()),
|
||||
fetchConfig: () => dispatch(fetchConfig()),
|
||||
handleLogin: (username, password, recaptchaResponse) =>
|
||||
dispatch(handleLogin(username, password, recaptchaResponse)),
|
||||
requestPasswordReset: (email) => dispatch(requestPasswordReset(email)),
|
||||
toggleShortcutModal: (toggle) => dispatch(toggleShortcutModal(toggle)),
|
||||
handleLogout: () => dispatch(logout())
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
checkLogin,
|
||||
fetchConfig,
|
||||
handleLogin,
|
||||
requestPasswordReset,
|
||||
toggleShortcutModal,
|
||||
logout
|
||||
}, dispatch);
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LayoutContainer);
|
||||
|
||||
@@ -4,7 +4,7 @@ export default {
|
||||
mutations: {
|
||||
SetUserStatus: ({variables: {status, userId}}) => ({
|
||||
updateQueries: {
|
||||
TalkAdmin_FlaggedAccounts: (prev) => {
|
||||
TalkAdmin_Community: (prev) => {
|
||||
if (status !== 'APPROVED') {
|
||||
return prev;
|
||||
}
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
}),
|
||||
RejectUsername: ({variables: {input: {id: userId}}}) => ({
|
||||
updateQueries: {
|
||||
TalkAdmin_FlaggedAccounts: (prev) => {
|
||||
TalkAdmin_Community: (prev) => {
|
||||
const updated = update(prev, {
|
||||
users: {
|
||||
nodes: {$apply: (nodes) => nodes.filter((node) => node.id !== userId)},
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const CommunityLayout = (props) => (
|
||||
<div>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
|
||||
export default CommunityLayout;
|
||||
@@ -1,16 +1,14 @@
|
||||
import React, {Component} from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators} from 'redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import {compose, gql} from 'react-apollo';
|
||||
import withQuery from 'coral-framework/hocs/withQuery';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
|
||||
import FlaggedAccounts from '../containers/FlaggedAccounts';
|
||||
import FlaggedUser from '../containers/FlaggedUser';
|
||||
|
||||
import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations';
|
||||
import {getDefinitionName} from 'coral-framework/utils';
|
||||
|
||||
import {
|
||||
fetchAccounts,
|
||||
updateSorting,
|
||||
@@ -27,7 +25,7 @@ class CommunityContainer extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Community
|
||||
return <Community
|
||||
fetchAccounts={this.props.fetchAccounts}
|
||||
community={this.props.community}
|
||||
hideRejectUsernameDialog={this.props.hideRejectUsernameDialog}
|
||||
@@ -56,28 +54,6 @@ CommunityContainer.propTypes = {
|
||||
root: PropTypes.object
|
||||
};
|
||||
|
||||
const withData = withQuery(gql`
|
||||
query TalkAdmin_FlaggedUsernamesCount {
|
||||
flaggedUsernamesCount: userCount(query: {
|
||||
action_type: FLAG,
|
||||
statuses: [PENDING]
|
||||
})
|
||||
...${getDefinitionName(FlaggedAccounts.fragments.root)}
|
||||
...${getDefinitionName(FlaggedUser.fragments.root)}
|
||||
me {
|
||||
...${getDefinitionName(FlaggedUser.fragments.me)}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
${FlaggedAccounts.fragments.root}
|
||||
${FlaggedUser.fragments.root}
|
||||
${FlaggedUser.fragments.me}
|
||||
`, {
|
||||
options: {
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) =>
|
||||
bindActionCreators({
|
||||
fetchAccounts,
|
||||
@@ -86,9 +62,27 @@ const mapDispatchToProps = (dispatch) =>
|
||||
newPage,
|
||||
}, dispatch);
|
||||
|
||||
const withData = withQuery(gql`
|
||||
query TalkAdmin_Community {
|
||||
flaggedUsernamesCount: userCount(query: {
|
||||
action_type: FLAG,
|
||||
statuses: [PENDING]
|
||||
})
|
||||
...${getDefinitionName(FlaggedAccounts.fragments.root)}
|
||||
...${getDefinitionName(FlaggedUser.fragments.root)}
|
||||
me {
|
||||
...${getDefinitionName(FlaggedUser.fragments.me)}
|
||||
__typename
|
||||
}
|
||||
}
|
||||
${FlaggedAccounts.fragments.root}
|
||||
${FlaggedUser.fragments.root}
|
||||
${FlaggedUser.fragments.me}
|
||||
`);
|
||||
|
||||
export default compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withSetUserStatus,
|
||||
withRejectUsername,
|
||||
withData,
|
||||
withData
|
||||
)(CommunityContainer);
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export {default as Community} from './containers/Community';
|
||||
export {default as CommunityLayout} from './components/CommunityLayout';
|
||||
Reference in New Issue
Block a user