diff --git a/client/coral-admin/src/actions/assets.js b/client/coral-admin/src/actions/assets.js index f431f1ad6..a4a3513a8 100644 --- a/client/coral-admin/src/actions/assets.js +++ b/client/coral-admin/src/actions/assets.js @@ -4,8 +4,10 @@ import { FETCH_ASSETS_FAILURE, UPDATE_ASSET_STATE_REQUEST, UPDATE_ASSET_STATE_SUCCESS, - UPDATE_ASSET_STATE_FAILURE + UPDATE_ASSET_STATE_FAILURE, + UPDATE_ASSETS } from '../constants/assets'; + import coralApi from '../../../coral-framework/helpers/response'; /** @@ -34,3 +36,7 @@ export const updateAssetState = (id, closedAt) => (dispatch) => { dispatch({type: UPDATE_ASSET_STATE_SUCCESS})) .catch(error => dispatch({type: UPDATE_ASSET_STATE_FAILURE, error})); }; + +export const updateAssets = assets => dispatch => { + dispatch({type: UPDATE_ASSETS, assets}); +}; diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index ac7af12fd..14f33bf36 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -101,12 +101,3 @@ export const flagComment = id => (dispatch, getState) => { dispatch({type: commentTypes.COMMENT_FLAG, id}); dispatch({type: 'COMMENT_UPDATE', comment: getState().comments.get('byId').get(id)}); }; - -// Dialog Actions -export const showBanUserDialog = (userId, userName, commentId) => { - return {type: commentTypes.SHOW_BANUSER_DIALOG, userId, userName, commentId}; -}; - -export const hideBanUserDialog = (showDialog) => { - return {type: commentTypes.HIDE_BANUSER_DIALOG, showDialog}; -}; diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index 425628abe..7d34a14ed 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -3,3 +3,12 @@ import * as actions from 'constants/moderation'; export const setActiveTab = activeTab => ({type: actions.SET_ACTIVE_TAB, activeTab}); export const toggleModal = open => ({type: actions.TOGGLE_MODAL, open}); export const singleView = () => ({type: actions.SINGLE_VIEW}); + +// Ban User Dialog +export const showBanUserDialog = (userId, userName, commentId) => { + return {type: actions.SHOW_BANUSER_DIALOG, userId, userName, commentId}; +}; + +export const hideBanUserDialog = (showDialog) => { + return {type: actions.HIDE_BANUSER_DIALOG, showDialog}; +}; diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index 8fbdedc0a..e6cf85d23 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -16,6 +16,7 @@ .rightPanel { position: absolute; + top: 0; right: 0; width: 170px; height: 100%; diff --git a/client/coral-admin/src/constants/assets.js b/client/coral-admin/src/constants/assets.js index 0a2ecf73c..20ec0a9c3 100644 --- a/client/coral-admin/src/constants/assets.js +++ b/client/coral-admin/src/constants/assets.js @@ -1,6 +1,9 @@ export const FETCH_ASSETS_REQUEST = 'FETCH_ASSETS_REQUEST'; export const FETCH_ASSETS_SUCCESS = 'FETCH_ASSETS_SUCCESS'; export const FETCH_ASSETS_FAILURE = 'FETCH_ASSETS_FAILURE'; + export const UPDATE_ASSET_STATE_REQUEST = 'UPDATE_ASSET_STATE_REQUEST'; export const UPDATE_ASSET_STATE_SUCCESS = 'UPDATE_ASSET_STATE_SUCCESS'; export const UPDATE_ASSET_STATE_FAILURE = 'UPDATE_ASSET_STATE_FAILURE'; + +export const UPDATE_ASSETS = 'UPDATE_ASSETS'; diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index 0fa51dc7b..10c6a7c4c 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -1,3 +1,5 @@ export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB'; export const TOGGLE_MODAL = 'TOGGLE_MODAL'; export const SINGLE_VIEW = 'SINGLE_VIEW'; +export const SHOW_BANUSER_DIALOG = 'SHOW_BANUSER_DIALOG'; +export const HIDE_BANUSER_DIALOG = 'HIDE_BANUSER_DIALOG'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index 277a0344b..ae78d1b8e 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -1,28 +1,20 @@ import React, {Component} from 'react'; -import key from 'keymaster'; import {connect} from 'react-redux'; import {compose} from 'react-apollo'; -import {Spinner} from 'coral-ui'; -import {withRouter} from 'react-router'; +import key from 'keymaster'; +import isEqual from 'lodash/isEqual'; import {modQueueQuery} from '../../graphql/queries'; -import { - updateStatus, - showBanUserDialog, - hideBanUserDialog, - fetchPremodQueue, - fetchRejectedQueue, - fetchFlaggedQueue, - fetchModerationQueueComments, -} from 'actions/comments'; - import {fetchSettings} from 'actions/settings'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {updateAssets} from 'actions/assets'; import {setActiveTab, toggleModal, singleView} from 'actions/moderation'; +import {Spinner} from 'coral-ui'; import ModerationQueue from './ModerationQueue'; -import ModerationQueueHeader from './components/ModerationQueueHeader'; +import ModerationMenu from './components/ModerationMenu'; +import ModerationHeader from './components/ModerationHeader'; +import NotFoundAsset from './components/NotFoundAsset'; class ModerationContainer extends Component { @@ -42,29 +34,37 @@ class ModerationContainer extends Component { key.unbind('esc'); } - onTabClick = (activeTab) => { - const {setActiveTab} = this.props; - setActiveTab(activeTab); - } - - onClose = () => { - const {toggleModal} = this.props; - toggleModal(false); + componentWillReceiveProps(nextProps) { + const {updateAssets} = this.props; + if(!isEqual(nextProps.data.assets, this.props.data.assets)) { + updateAssets(nextProps.data.assets); + } } render () { - const {data, moderation, settings} = this.props; + const {data, moderation, settings, assets} = this.props; + const providedAssetId = this.props.params.id; + let asset; if (data.loading) { return
; } - const enablePremodTab = data.premod.length; + if (providedAssetId) { + asset = assets.find(asset => asset.id === this.props.params.id); + + if (!asset) { + return ; + } + } + + const enablePremodTab = !!data.premod.length; return (
- + ({ moderation: state.moderation.toJS(), - settings: state.settings.toJS() + settings: state.settings.toJS(), + assets: state.assets.get('assets') }); const mapDispatchToProps = dispatch => ({ - setActiveTab: tab => dispatch(setActiveTab(tab)), - toggleModal: open => dispatch(toggleModal(open)), + onTabClick: activeTab => dispatch(setActiveTab(activeTab)), + toggleModal: toggle => dispatch(toggleModal(toggle)), + onClose: () => dispatch(toggleModal(false)), singleView: () => dispatch(singleView()), - - fetchSettings: () => dispatch(fetchSettings()), - fetchModerationQueueComments: () => dispatch(fetchModerationQueueComments()), - fetchPremodQueue: () => dispatch(fetchPremodQueue()), - fetchRejectedQueue: () => dispatch(fetchRejectedQueue()), - fetchFlaggedQueue: () => dispatch(fetchFlaggedQueue()), - showBanUserDialog: (userId, userName, commentId) => dispatch(showBanUserDialog(userId, userName, commentId)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { - dispatch(fetchModerationQueueComments()); - }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) - .then(() => dispatch(sendNotificationEmail(userId, subject, text))) - .then(() => dispatch(fetchModerationQueueComments())) - , - updateStatus: (action, comment) => dispatch(updateStatus(action, comment)) + updateAssets: assets => dispatch(updateAssets(assets)), + fetchSettings: () => dispatch(fetchSettings()) }); export default compose( connect(mapStateToProps, mapDispatchToProps), - withRouter, modQueueQuery )(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js index 771d6fe55..881675d6a 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -14,7 +14,6 @@ import translations from '../../../translations.json'; const Comment = props => { const links = linkify.getMatches(props.body); - return (
  • @@ -50,14 +49,24 @@ const Comment = props => { : null}
  • + + {/*
    */} + {/* Article title */} + {/* Moderate this Article */} + {/*
    */} +
    - +

    - +

    - View context + + {/* */} + {/* View context */} + {/* */} + ); }; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js new file mode 100644 index 000000000..474d8fd27 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js @@ -0,0 +1,25 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const ModerationHeader = props => ( +
    +
    + { + props.asset ? +
    + All Streams + {props.asset.title} + Select Stream +
    + : +
    + + All Streams + Select Stream +
    + } +
    +
    +); +export default ModerationHeader; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js similarity index 80% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js rename to client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js index 06540537b..803f62ecd 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/ModerationQueueHeader.js +++ b/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js @@ -5,7 +5,7 @@ import translations from '../../../translations.json'; const lang = new I18n(translations); -const ModerationQueueHeader = (props) => ( +const ModerationMenu = (props) => (
    ( : null } - { - e.preventDefault(); - props.onTabClick('account'); - }} - className={`mdl-tabs__tab ${styles.tab} ${props.activeTab === 'account' ? styles.active : ''}`}> - {lang.t('modqueue.account')} - { e.preventDefault(); @@ -59,9 +51,9 @@ const ModerationQueueHeader = (props) => (
    ); -ModerationQueueHeader.propTypes = { +ModerationMenu.propTypes = { activeTab: PropTypes.string.isRequired, enablePremodTab: PropTypes.bool }; -export default ModerationQueueHeader; +export default ModerationMenu; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js new file mode 100644 index 000000000..ffa1adfcd --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/NotFoundAsset.js @@ -0,0 +1,14 @@ +import React from 'react'; +import {Link} from 'react-router'; +import styles from './styles.css'; + +const NotFound = props => ( +
    +

    + The provided asset id {props.assetId} does not exist. + Go to Streams +

    +
    +); + +export default NotFound; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css index a1e918459..cc40b03a3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/styles.css +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -1,3 +1,62 @@ +.notFound { + position: relative; + margin: 20px auto; + text-align: center; + padding: 68px 45px; + vertical-align: middle; + min-width: 500px; + + a { + color: rgb(244, 126, 107); + font-weight: 500; + + &.goToStreams { + position: absolute; + right: 10px; + bottom: 10px; + } + } +} + +.header { + background-color: #3949AB; + color: white; + margin-bottom: -1px; + + .moderateAsset { + a { + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + color: white; + text-transform: capitalize; + font-weight: 500; + font-size: 15px; + letter-spacing: 1px; + transition: opacity 200ms; + opacity: 1; + + &:hover { + opacity: .8; + cursor: pointer; + } + + &:first-child { + text-align: left; + } + + &:nth-child(2) { + text-align: center; + } + + &:last-child { + text-align: right; + } + } + } +} + + @custom-media --big-viewport (min-width: 780px); .list { diff --git a/client/coral-admin/src/containers/Streams/Streams.css b/client/coral-admin/src/containers/Streams/Streams.css index 26fb4f74f..d8a75a4ce 100644 --- a/client/coral-admin/src/containers/Streams/Streams.css +++ b/client/coral-admin/src/containers/Streams/Streams.css @@ -55,6 +55,12 @@ border-left: none; border-right: none; + a { + color: rgb(44, 44, 44); + font-weight: 500; + text-decoration: none; + } + th { font-size: 1.1em; } diff --git a/client/coral-admin/src/containers/Streams/Streams.js b/client/coral-admin/src/containers/Streams/Streams.js index 1937f82c0..62f2f009e 100644 --- a/client/coral-admin/src/containers/Streams/Streams.js +++ b/client/coral-admin/src/containers/Streams/Streams.js @@ -4,14 +4,10 @@ import {connect} from 'react-redux'; import I18n from 'coral-framework/modules/i18n/i18n'; import {fetchAssets, updateAssetState} from '../../actions/assets'; import translations from '../../translations.json'; -import { - RadioGroup, - Radio, - Icon, - DataTable, - TableHeader -} from 'react-mdl'; -import Pager from 'coral-ui/components/Pager'; +import {Link} from 'react-router'; + +import {Pager, Icon} from 'coral-ui'; +import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; const limit = 25; @@ -74,6 +70,8 @@ class Streams extends Component { } } + renderTitle = (title, {id}) => {title} + renderStatus = (closedAt, {id}) => { const closed = closedAt && new Date(closedAt).getTime() < Date.now(); const statusMenuOpen = this.state.statusMenus[id]; @@ -104,6 +102,9 @@ class Streams extends Component { render () { const {search, sort, filter} = this.state; const {assets} = this.props; + + const assetsIds = assets.ids.map((id) => assets.byId[id]); + return (
    @@ -142,16 +143,14 @@ class Streams extends Component {
    - assets.byId[id])}> - {lang.t('streams.article')} - - {lang.t('streams.pubdate')} - - - {lang.t('streams.status')} - + + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + + {lang.t('streams.status')} + { assets: assets.toJS() }; }; + const mapDispatchToProps = (dispatch) => { return { fetchAssets: (...args) => { diff --git a/client/coral-admin/src/containers/Streams/StreamsTable.js b/client/coral-admin/src/containers/Streams/StreamsTable.js new file mode 100644 index 000000000..a801a3f7b --- /dev/null +++ b/client/coral-admin/src/containers/Streams/StreamsTable.js @@ -0,0 +1,39 @@ +import React from 'react'; +import translations from '../../translations.json'; +import I18n from 'coral-framework/modules/i18n/i18n'; +const lang = new I18n(translations); + +const StreamsTable = props => ( + + + + + + + + + + {props.rows.map((row, i)=> ( + + + + + + ))} + +
    + {lang.t('streams.article')} + + {lang.t('streams.pubdate')} + + {lang.t('streams.status')} +
    + {row.title} + + {row.publication_date} + + {lang.t('streams.status')} +
    +); + +export default StreamsTable; diff --git a/client/coral-admin/src/graphql/queries/assetsQuery.graphql b/client/coral-admin/src/graphql/queries/assetsQuery.graphql new file mode 100644 index 000000000..37950692d --- /dev/null +++ b/client/coral-admin/src/graphql/queries/assetsQuery.graphql @@ -0,0 +1,6 @@ +query Assets { + assets { + id + title + } +} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql index 5d69f6324..a22a6c808 100644 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql @@ -1,6 +1,6 @@ #import "../fragments/commentView.graphql" -query ($asset_id: ID!) { +query ModQueue ($asset_id: ID!) { all: comments(query: { statuses: [ACCEPTED, REJECTED, PREMOD], asset_id: $asset_id @@ -25,10 +25,8 @@ query ($asset_id: ID!) { }) { ...commentView } - account: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) { - ...commentView + assets: assets { + id + title } } diff --git a/client/coral-admin/src/reducers/assets.js b/client/coral-admin/src/reducers/assets.js index 77d0bf081..c9a82f1c5 100644 --- a/client/coral-admin/src/reducers/assets.js +++ b/client/coral-admin/src/reducers/assets.js @@ -1,20 +1,26 @@ import {Map, List, fromJS} from 'immutable'; -import {FETCH_ASSETS_SUCCESS, UPDATE_ASSET_STATE_REQUEST} from '../constants/assets'; +import * as actions from '../constants/assets'; const initialState = Map({ byId: Map(), - ids: List() + ids: List(), + assets: List() }); -export default (state = initialState, action) => { +export default function assets (state = initialState, action) { switch (action.type) { - case FETCH_ASSETS_SUCCESS: + case actions.FETCH_ASSETS_SUCCESS: return replaceAssets(action, state); - case UPDATE_ASSET_STATE_REQUEST: - return state.setIn(['byId', action.id, 'closedAt'], action.closedAt); - default: return state; + case actions.UPDATE_ASSET_STATE_REQUEST: + return state + .setIn(['byId', action.id, 'closedAt'], action.closedAt); + case actions.UPDATE_ASSETS: + return state + .set('assets', List(action.assets)); + default: + return state; } -}; +} const replaceAssets = (action, state) => { const assets = fromJS(action.assets.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); diff --git a/graph/mutators/index.js b/graph/mutators/index.js index 58d0ed62c..b799cf83d 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const Comment = require('./comment'); const Action = require('./action'); +const User = require('./user'); module.exports = (context) => { @@ -9,6 +10,7 @@ module.exports = (context) => { return _.merge(...[ Comment, Action, + User, ].map((mutators) => { // Each set of mutators is a function which takes the context. diff --git a/graph/mutators/user.js b/graph/mutators/user.js new file mode 100644 index 000000000..f693195d6 --- /dev/null +++ b/graph/mutators/user.js @@ -0,0 +1,34 @@ +const UsersService = require('../../services/users'); + +const setUserStatus = ({user}, {id, status}) => { + console.log('------as-d-asd-a-sads-a-sad-dsa-----'); + console.log('user', user); + console.log('id', id); + console.log('status', status); + + return UsersService.setStatus(id, status) + .then((user) => { + console.log('result', user); + return user; + }); +}; + +module.exports = (context) => { + + // TODO: refactor to something that'll return an error in the event an attempt + // is made to mutate state while not logged in. There's got to be a better way + // to do this. + if (context.user && context.user.can('mutation:setUserStatus')) { + return { + User: { + setUserStatus: (action) => setUserStatus(context, action) + } + }; + } + + return { + User: { + setUserStatus: () => {}, + } + }; +}; diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ef46b839a..a1d25189b 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -8,6 +8,9 @@ const RootMutation = { deleteAction(_, {id}, {mutators: {Action}}) { return Action.delete({id}); }, + setUserStatus(_, {id, status}, {mutators: {User}}) { + return User.setUserStatus({id, status}); + } }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 8369b93c6..a655a77f3 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -61,6 +61,9 @@ type User { # returns all comments based on a query. comments(query: CommentsQuery): [Comment] + + # returns user status + status: USER_STATUS } type Comment { @@ -177,6 +180,13 @@ enum COMMENT_STATUS { PREMOD } +enum USER_STATUS { + ACTIVE + BANNED + PENDING + APPROVED +} + type RootQuery { # retrieves site wide settings and defaults. @@ -216,6 +226,8 @@ type RootMutation { # delete an action based on the action id. deleteAction(id: ID!): Boolean + # sets user status + setUserStatus(id: ID!, status: USER_STATUS!): Boolean } schema { diff --git a/models/user.js b/models/user.js index 06dc3c7d1..0194ba5f3 100644 --- a/models/user.js +++ b/models/user.js @@ -147,7 +147,8 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createComment', 'mutation:createAction', 'mutation:deleteAction', - 'mutation:editName' + 'mutation:editName', + 'mutation:setUserStatus' ]; /**