Merge pull request #985 from coralproject/mod-indicator

Moderate Indicator
This commit is contained in:
Kim Gardner
2017-09-26 17:03:34 +01:00
committed by GitHub
12 changed files with 218 additions and 153 deletions
+2 -2
View File
@@ -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>
+95 -86
View File
@@ -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;
+12 -5
View File
@@ -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);
+38 -18
View File
@@ -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);
+2 -2
View File
@@ -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';