diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index d50aeb3f6..d8e1b5377 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -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 */} - + diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 74dc85a17..30c5fa7e6 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -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 -}) => ( -
- -
- { - auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? - - - {t('configure.dashboard')} - - { - can(auth.user, 'MODERATE_COMMENTS') && ( - - {t('configure.moderate')} - - ) - } - - {t('configure.stories')} - - - {t('configure.community')} - - { - can(auth.user, 'UPDATE_CONFIG') && ( - - {t('configure.configure')} - - ) - } - - : - null - } -
- + auth, + root +}) => { + return ( +
+ +
+ { + auth && auth.user && can(auth.user, 'ACCESS_ADMIN') ? + + + {t('configure.dashboard')} + + { + can(auth.user, 'MODERATE_COMMENTS') && ( + + {t('configure.moderate')} + {(root.premodCount !== 0 || root.reportedCount !== 0) && } + + ) + } + + {t('configure.stories')} + + + + {t('configure.community')} + {root.flaggedUsernamesCount !== 0 && } + + + { + can(auth.user, 'UPDATE_CONFIG') && ( + + {t('configure.configure')} + + ) + } + + : + null + } +
+ +
-
-
-); + + ); +}; CoralHeader.propTypes = { auth: PropTypes.object, showShortcuts: PropTypes.func, - handleLogout: PropTypes.func.isRequired + handleLogout: PropTypes.func.isRequired, + root: PropTypes.object.isRequired }; export default CoralHeader; diff --git a/client/coral-admin/src/components/ui/Indicator.css b/client/coral-admin/src/components/ui/Indicator.css new file mode 100644 index 000000000..ec84c7027 --- /dev/null +++ b/client/coral-admin/src/components/ui/Indicator.css @@ -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; +} diff --git a/client/coral-admin/src/components/ui/Indicator.js b/client/coral-admin/src/components/ui/Indicator.js new file mode 100644 index 000000000..dd1d36202 --- /dev/null +++ b/client/coral-admin/src/components/ui/Indicator.js @@ -0,0 +1,7 @@ +import React from 'react'; +import styles from './Indicator.css'; + +const Indicator = () => + ; + +export default Indicator; diff --git a/client/coral-admin/src/components/ui/Layout.js b/client/coral-admin/src/components/ui/Layout.js index 3aa056d9e..9ee9fda9f 100644 --- a/client/coral-admin/src/components/ui/Layout.js +++ b/client/coral-admin/src/components/ui/Layout.js @@ -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, +}) => (
- + auth={auth} + /> +
{children}
@@ -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 diff --git a/client/coral-admin/src/components/ui/Logo.js b/client/coral-admin/src/components/ui/Logo.js index 8c57cde11..2538a0716 100644 --- a/client/coral-admin/src/components/ui/Logo.js +++ b/client/coral-admin/src/components/ui/Logo.js @@ -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 = ''}) => (
@@ -10,3 +11,7 @@ export const Logo = ({className = ''}) => (
); + +Logo.propTypes = { + className: PropTypes.string +}; diff --git a/client/coral-admin/src/containers/Header.js b/client/coral-admin/src/containers/Header.js new file mode 100644 index 000000000..a5c1f0afc --- /dev/null +++ b/client/coral-admin/src/containers/Header.js @@ -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); diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index f2d71c93b..b90016102 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -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 ; } + if (!loggedIn) { return ( ); } + if (can(user, 'ACCESS_ADMIN') && loggedIn) { return ( + auth={this.props.auth} > - {this.props.children} + {children} ); } 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); diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 8fedf6ebe..6b8aa7456 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -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)}, diff --git a/client/coral-admin/src/routes/Community/components/CommunityLayout.js b/client/coral-admin/src/routes/Community/components/CommunityLayout.js deleted file mode 100644 index 0665b967d..000000000 --- a/client/coral-admin/src/routes/Community/components/CommunityLayout.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -const CommunityLayout = (props) => ( -
- {props.children} -
-); - -export default CommunityLayout; diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js index 445b7f413..4e60c64d7 100644 --- a/client/coral-admin/src/routes/Community/containers/Community.js +++ b/client/coral-admin/src/routes/Community/containers/Community.js @@ -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 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); diff --git a/client/coral-admin/src/routes/Community/index.js b/client/coral-admin/src/routes/Community/index.js deleted file mode 100644 index 528fe63cd..000000000 --- a/client/coral-admin/src/routes/Community/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export {default as Community} from './containers/Community'; -export {default as CommunityLayout} from './components/CommunityLayout';