diff --git a/client/coral-admin/config.json b/client/coral-admin/config.json deleted file mode 100644 index 156740de5..000000000 --- a/client/coral-admin/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "basePath": "admin" -} diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 989316dd9..3bde5f931 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -1,25 +1,20 @@ import React from 'react'; import {Router, Route, IndexRedirect, browserHistory} from 'react-router'; -import Stories from 'containers/Stories/Stories'; -import Configure from 'containers/Configure/Configure'; -import LayoutContainer from 'containers/LayoutContainer'; -import InstallContainer from 'containers/Install/InstallContainer'; +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 {ModerationLayout, Moderation} from 'routes/Moderation'; -import CommunityLayout from 'containers/Community/CommunityLayout'; -import CommunityContainer from 'containers/Community/CommunityContainer'; - -import ModerationLayout from 'containers/ModerationQueue/ModerationLayout'; -import ModerationContainer from 'containers/ModerationQueue/ModerationContainer'; - -import Dashboard from 'containers/Dashboard/Dashboard'; +import Layout from 'containers/Layout'; const routes = (
- - + + - @@ -27,11 +22,11 @@ const routes = ( {/* Community Routes */} - - + + - - + + @@ -39,22 +34,22 @@ const routes = ( {/* Moderation Routes */} - - + + - - + + - - + + - - + + - - + + - + diff --git a/client/coral-admin/src/actions/moderation.js b/client/coral-admin/src/actions/moderation.js index c5e6fc620..4b0b0538a 100644 --- a/client/coral-admin/src/actions/moderation.js +++ b/client/coral-admin/src/actions/moderation.js @@ -5,7 +5,7 @@ export const singleView = () => ({type: actions.SINGLE_VIEW}); // Ban User Dialog export const showBanUserDialog = (user, commentId, commentStatus, showRejectedNote) => ({type: actions.SHOW_BANUSER_DIALOG, user, commentId, commentStatus, showRejectedNote}); -export const hideBanUserDialog = (showDialog) => ({type: actions.HIDE_BANUSER_DIALOG, showDialog}); +export const hideBanUserDialog = () => ({type: actions.HIDE_BANUSER_DIALOG}); // Suspend User Dialog export const showSuspendUserDialog = (userId, username, commentId, commentStatus) => @@ -27,3 +27,9 @@ export const hideShortcutsNote = () => { export const viewUserDetail = (userId) => ({type: actions.VIEW_USER_DETAIL, userId}); export const hideUserDetail = () => ({type: actions.HIDE_USER_DETAIL}); + +export const setSortOrder = (order) => ({ + type: actions.SET_SORT_ORDER, + order +}); + diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js index 42af5db24..e084d8e7a 100644 --- a/client/coral-admin/src/components/ActionButton.js +++ b/client/coral-admin/src/components/ActionButton.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react'; import styles from './ModerationList.css'; import {Button} from 'coral-ui'; -import {menuActionsMap} from '../containers/ModerationQueue/helpers/moderationQueueActionsMap'; +import {menuActionsMap} from '../routes/Moderation/helpers/moderationQueueActionsMap'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/constants/moderation.js b/client/coral-admin/src/constants/moderation.js index b960d95a2..3586b1a75 100644 --- a/client/coral-admin/src/constants/moderation.js +++ b/client/coral-admin/src/constants/moderation.js @@ -7,3 +7,4 @@ export const SHOW_SUSPEND_USER_DIALOG = 'SHOW_SUSPEND_USER_DIALOG'; export const HIDE_SUSPEND_USER_DIALOG = 'HIDE_SUSPEND_USER_DIALOG'; export const VIEW_USER_DETAIL = 'VIEW_USER_DETAIL'; export const HIDE_USER_DETAIL = 'HIDE_USER_DETAIL'; +export const SET_SORT_ORDER = 'MODERATION_SET_SORT_ORDER'; diff --git a/client/coral-admin/src/containers/Community/Loading.js b/client/coral-admin/src/containers/Community/Loading.js deleted file mode 100644 index 72353a619..000000000 --- a/client/coral-admin/src/containers/Community/Loading.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; - -import t from 'coral-framework/services/i18n'; - -const Loading = () => ( -

{t('loading_results')}

-); - -export default Loading; diff --git a/client/coral-admin/src/containers/Community/Table.js b/client/coral-admin/src/containers/Community/Table.js deleted file mode 100644 index e44924bff..000000000 --- a/client/coral-admin/src/containers/Community/Table.js +++ /dev/null @@ -1,79 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {SelectField, Option} from 'react-mdl-selectfield'; -import styles from './Community.css'; -import {setRole, setCommenterStatus} from '../../actions/community'; - -import t from 'coral-framework/services/i18n'; - -class Table extends Component { - - constructor (props) { - super(props); - this.onRoleChange = this.onRoleChange.bind(this); - } - - onRoleChange (id, role) { - this.props.dispatch(setRole(id, role)); - } - - onCommenterStatusChange (id, status) { - this.props.dispatch(setCommenterStatus(id, status)); - } - - render () { - const {headers, commenters, onHeaderClickHandler} = this.props; - - return ( - - - - {headers.map((header, i) =>( - - ))} - - - - {commenters.map((row, i)=> ( - - - - - - - ))} - -
onHeaderClickHandler({field: header.field})}> - {header.title} -
- {row.username} - {row.profiles.map(({id}) => id)} - - {row.created_at} - - this.onCommenterStatusChange(row.id, status)}> - - - - - this.onRoleChange(row.id, role)}> - - - - - -
- ); - } -} - -export default connect((state) => ({commenters: state.community.get('accounts')}))(Table); diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.js b/client/coral-admin/src/containers/Dashboard/Dashboard.js deleted file mode 100644 index e764bf596..000000000 --- a/client/coral-admin/src/containers/Dashboard/Dashboard.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import styles from './Dashboard.css'; -import {compose} from 'react-apollo'; -import {connect} from 'react-redux'; -import {getMetrics} from 'coral-admin/src/graphql/queries'; -import FlagWidget from './FlagWidget'; -import ActivityWidget from './ActivityWidget'; -import CountdownTimer from 'coral-admin/src/components/CountdownTimer'; - -import {Spinner} from 'coral-ui'; - -class Dashboard extends React.Component { - - reloadData = () => { - this.props.data.refetch(); - } - - render () { - - if (this.props.data && this.props.data.loading) { - return ; - } - - const {data: {assetsByActivity, assetsByFlag}} = this.props; - - return ( -
- -
- - -
-
- ); - } -} - -const mapStateToProps = (state) => { - return { - settings: state.settings.toJS(), - moderation: state.moderation.toJS() - }; -}; - -export default compose( - connect(mapStateToProps), - getMetrics -)(Dashboard); diff --git a/client/coral-admin/src/containers/Install/InstallContainer.js b/client/coral-admin/src/containers/Install/InstallContainer.js deleted file mode 100644 index 67f266123..000000000 --- a/client/coral-admin/src/containers/Install/InstallContainer.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import styles from './style.css'; -import {Wizard, WizardNav} from 'coral-ui'; -import Layout from 'coral-admin/src/components/ui/Layout'; - -import { - goToStep, - nextStep, - submitUser, - checkInstall, - previousStep, - finishInstall, - submitSettings, - updateUserFormData, - updateSettingsFormData, - updatePermittedDomains -} from '../../actions/install'; - -import InitialStep from './components/Steps/InitialStep'; -import AddOrganizationName from './components/Steps/AddOrganizationName'; -import CreateYourAccount from './components/Steps/CreateYourAccount'; -import PermittedDomainsStep from './components/Steps/PermittedDomainsStep'; -import FinalStep from './components/Steps/FinalStep'; - -class InstallContainer extends Component { - componentDidMount() { - const {checkInstall} = this.props; - checkInstall(() => { - this.context.router.push('/admin'); - }); - } - - render() { - const {install} = this.props; - - return ( - -
- { - !install.alreadyInstalled ? ( -
-

Welcome to the Coral Project

- { install.step !== 0 ? : null } - - - - - - - -
- ) : ( -
Talk is already installed
- ) - } -
-
- ); - } -} - -InstallContainer.contextTypes = { - router: React.PropTypes.object -}; - -const mapStateToProps = (state) => ({ - install: state.install.toJS() -}); - -const mapDispatchToProps = (dispatch) => ({ - nextStep: () => dispatch(nextStep()), - goToStep: (step) => dispatch(goToStep(step)), - previousStep: () => dispatch(previousStep()), - finishInstall: () => dispatch(finishInstall()), - checkInstall: (next) => dispatch(checkInstall(next)), - handleDomainsChange: (value) => { - dispatch(updatePermittedDomains(value)); - }, - handleSettingsChange: (e) => { - const {name, value} = e.currentTarget; - dispatch(updateSettingsFormData(name, value)); - }, - handleUserChange: (e) => { - const {name, value} = e.currentTarget; - dispatch(updateUserFormData(name, value)); - }, - handleSettingsSubmit: (e) => { - e.preventDefault(); - dispatch(submitSettings()); - }, - handleUserSubmit: (e) => { - e.preventDefault(); - dispatch(submitUser()); - } -}); - -export default connect( - mapStateToProps, - mapDispatchToProps -)(InstallContainer); diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/Layout.js similarity index 100% rename from client/coral-admin/src/containers/LayoutContainer.js rename to client/coral-admin/src/containers/Layout.js diff --git a/client/coral-admin/src/containers/ModerationQueue/UserDetail.js b/client/coral-admin/src/containers/ModerationQueue/UserDetail.js deleted file mode 100644 index cabd8bf9f..000000000 --- a/client/coral-admin/src/containers/ModerationQueue/UserDetail.js +++ /dev/null @@ -1,114 +0,0 @@ -import React, {PropTypes} from 'react'; -import {Button, Drawer} from 'coral-ui'; -import styles from './UserDetail.css'; -import {compose} from 'react-apollo'; -import {getUserDetail} from 'coral-admin/src/graphql/queries'; -import Slot from 'coral-framework/components/Slot'; -import Comment from './components/Comment'; -import {actionsMap} from './helpers/moderationQueueActionsMap'; - -class UserDetail extends React.Component { - static propTypes = { - id: PropTypes.string.isRequired, - bannedWords: PropTypes.array.isRequired, - suspectWords: PropTypes.array.isRequired, - showBanUserDialog: PropTypes.func.isRequired, - showSuspendUserDialog: PropTypes.func.isRequired, - acceptComment: PropTypes.func.isRequired, - rejectComment: PropTypes.func.isRequired, - hideUserDetail: PropTypes.func.isRequired - } - - copyPermalink = () => { - this.profile.select(); - try { - document.execCommand('copy'); - } catch (e) { - - /* nothing */ - } - } - - render () { - const { - data, - hideUserDetail, - bannedWords, - suspectWords, - showBanUserDialog, - showSuspendUserDialog, - acceptComment, - rejectComment - } = this.props; - - if (!('user' in data)) { - return null; - } - - const {user, comments, totalComments, rejectedComments} = data; - const localProfile = user.profiles.find((p) => p.provider === 'local'); - let profile; - if (localProfile) { - profile = localProfile.id; - } - - let rejectedPercent = rejectedComments / totalComments; - if (rejectedPercent === Infinity || isNaN(rejectedPercent)) { - - // if totalComments is 0, you're dividing by zero, which is naughty - rejectedPercent = 0; - } - - return ( - -

{user.username}

- - {profile && this.profile = ref} value={profile} />} - -

Member since {new Date(user.created_at).toLocaleString()}

-
-

- Account summary -
Data represents the last six months of activity -

-
-
-

Total Comments

-

{totalComments}

-
-
-

Reject Rate

-

{`${(rejectedPercent).toFixed(1)}%`}

-
-
-
- { - comments.map((comment, i) => { - const status = comment.action_summaries ? 'FLAGGED' : comment.status; - return {}} - actions={actionsMap[status]} - showBanUserDialog={showBanUserDialog} - showSuspendUserDialog={showSuspendUserDialog} - acceptComment={acceptComment} - rejectComment={rejectComment} - currentAsset={null} - currentUserId={this.props.id} - minimal={true} />; - }) - } -
-
- ); - } -} - -export default compose( - getUserDetail -)(UserDetail); diff --git a/client/coral-admin/src/containers/Streams/Stories.js b/client/coral-admin/src/containers/Streams/Stories.js deleted file mode 100644 index 86db633b2..000000000 --- a/client/coral-admin/src/containers/Streams/Stories.js +++ /dev/null @@ -1,185 +0,0 @@ -import React, {Component} from 'react'; -import styles from './Stories.css'; -import {connect} from 'react-redux'; -import t from 'coral-framework/services/i18n'; -import {fetchAssets, updateAssetState} from '../../actions/assets'; - -import {Link} from 'react-router'; - -import {Pager, Icon} from 'coral-ui'; -import {DataTable, TableHeader, RadioGroup, Radio} from 'react-mdl'; -import EmptyCard from 'coral-admin/src/components/EmptyCard'; - -const limit = 25; - -class Stories extends Component { - - state = { - search: '', - sort: 'desc', - filter: 'all', - statusMenus: {}, - timer: null, - page: 0 - } - - componentDidMount () { - this.props.fetchAssets(0, limit, '', this.state.sortBy); - } - - onSettingChange = (setting) => (e) => { - let options = this.state; - this.setState({[setting]: e.target.value}); - options[setting] = e.target.value; - this.props.fetchAssets(0, limit, options.search, options.sort, options.filter); - } - - onSearchChange = (e) => { - const search = e.target.value; - this.setState((prevState) => { - prevState.search = search; - clearTimeout(prevState.timer); - const fetchAssets = this.props.fetchAssets; - prevState.timer = setTimeout(() => { - fetchAssets(0, limit, search, this.state.sort, this.state.filter); - }, 350); - return prevState; - }); - } - - renderDate = (date) => { - const d = new Date(date); - return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; - } - - onStatusClick = (closeStream, id, statusMenuOpen) => () => { - if (statusMenuOpen) { - this.setState((prev) => { - prev.statusMenus[id] = false; - return prev; - }); - this.props.updateAssetState(id, closeStream ? Date.now() : null) - .then(() => { - const {search, sort, filter, page} = this.state; - this.props.fetchAssets(page, limit, search, sort, filter); - }); - } else { - this.setState((prev) => { - prev.statusMenus[id] = true; - return prev; - }); - } - } - - renderTitle = (title, {id}) => {title} - - renderStatus = (closedAt, {id}) => { - const closed = closedAt && new Date(closedAt).getTime() < Date.now(); - const statusMenuOpen = this.state.statusMenus[id]; - return
-
- {!statusMenuOpen && } - {closed ? t('streams.closed') : t('streams.open')} -
- { - statusMenuOpen && -
- {!closed ? t('streams.closed') : t('streams.open')} -
- } -
; - } - - onPageClick = (page) => { - this.setState({page}); - const {search, sort, filter} = this.state; - this.props.fetchAssets((page - 1) * limit, limit, search, sort, filter); - } - - render () { - const {search, sort, filter} = this.state; - const {assets} = this.props; - - const assetsIds = assets.ids.map((id) => assets.byId[id]); - - return ( -
-
-
- - -
-
{t('streams.filter_streams')}
-
{t('streams.stream_status')}
- - {t('streams.all')} - {t('streams.open')} - {t('streams.closed')} - -
{t('streams.sort_by')}
- - {t('streams.newest')} - {t('streams.oldest')} - -
- { - assetsIds.length - ?
- - {t('streams.article')} - - {t('streams.pubdate')} - - - {t('streams.status')} - - - -
- : {t('streams.empty_result')} - } -
- ); - } -} - -const mapStateToProps = ({assets}) => { - return { - assets: assets.toJS() - }; -}; - -const mapDispatchToProps = (dispatch) => { - return { - fetchAssets: (...args) => { - dispatch(fetchAssets.apply(this, args)); - }, - updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(Stories); diff --git a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql b/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql deleted file mode 100644 index 726194143..000000000 --- a/client/coral-admin/src/graphql/fragments/assetMetricsView.graphql +++ /dev/null @@ -1,12 +0,0 @@ -fragment metrics on Asset { - id - title - url - author - created_at - commentCount - action_summaries { - actionCount - actionableItemCount - } -} diff --git a/client/coral-admin/src/graphql/fragments/commentView.graphql b/client/coral-admin/src/graphql/fragments/commentView.graphql deleted file mode 100644 index 1f3b8b1d3..000000000 --- a/client/coral-admin/src/graphql/fragments/commentView.graphql +++ /dev/null @@ -1,31 +0,0 @@ -fragment commentView on Comment { - id - body - created_at - status - user { - id - name: username - status - } - asset { - id - title - url - } - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - actions { - ... on FlagAction { - reason - message - user { - username - } - } - } -} diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js new file mode 100644 index 000000000..740af20a1 --- /dev/null +++ b/client/coral-admin/src/graphql/index.js @@ -0,0 +1,68 @@ +import {add} from 'coral-framework/services/graphqlRegistry'; +const queues = ['all', 'premod', 'flagged', 'accepted', 'rejected']; + +const extension = { + mutations: { + SetUserStatus: () => ({ + refetchQueries: ['CoralAdmin_Community'], + }), + RejectUsername: () => ({ + refetchQueries: ['CoralAdmin_Community'], + }), + SetCommentStatus: ({variables: {commentId, status}}) => ({ + updateQueries: { + CoralAdmin_Moderation: (oldData) => { + const comment = queues.reduce((comment, queue) => { + return comment ? comment : oldData[queue].find((c) => c.id === commentId); + }, null); + + let accepted = oldData.accepted; + let acceptedCount = oldData.acceptedCount; + let rejected = oldData.rejected; + let rejectedCount = oldData.rejectedCount; + + if (status !== comment.status) { + if (status === 'ACCEPTED') { + comment.status = 'ACCEPTED'; + acceptedCount++; + accepted = [comment, ...accepted]; + } + else if (status === 'REJECTED') { + comment.status = 'REJECTED'; + rejectedCount++; + rejected = [comment, ...rejected]; + } + } + + const premod = oldData.premod.filter((c) => c.id !== commentId); + const flagged = oldData.flagged.filter((c) => c.id !== commentId); + const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; + const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; + + if (status === 'REJECTED') { + accepted = oldData.accepted.filter((c) => c.id !== commentId); + acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount; + } + else if (status === 'ACCEPTED') { + rejected = oldData.rejected.filter((c) => c.id !== commentId); + rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount; + } + + return { + ...oldData, + premodCount: Math.max(0, premodCount), + flaggedCount: Math.max(0, flaggedCount), + acceptedCount: Math.max(0, acceptedCount), + rejectedCount: Math.max(0, rejectedCount), + premod, + flagged, + accepted, + rejected, + }; + } + } + }), + }, +}; + +add(extension); diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js deleted file mode 100644 index 3aef68ebc..000000000 --- a/client/coral-admin/src/graphql/mutations/index.js +++ /dev/null @@ -1,153 +0,0 @@ -import {graphql} from 'react-apollo'; -import SET_USER_STATUS from './setUserStatus.graphql'; -import SET_COMMENT_STATUS from './setCommentStatus.graphql'; -import SUSPEND_USER from './suspendUser.graphql'; -import REJECT_USERNAME from './rejectUsername.graphql'; - -export const banUser = graphql(SET_USER_STATUS, { - props: ({mutate}) => ({ - banUser: ({userId}) => { - return mutate({ - variables: { - userId, - status: 'BANNED' - }, - refetchQueries: ['Users'] - }); - }}), -}); - -export const setUserStatus = graphql(SET_USER_STATUS, { - props: ({mutate}) => ({ - approveUser: ({userId}) => { - return mutate({ - variables: { - userId, - status: 'APPROVED' - }, - refetchQueries: ['Users'] - }); - } - }) -}); - -export const suspendUser = graphql(SUSPEND_USER, { - props: ({mutate}) => ({ - suspendUser: (input) => { - return mutate({ - variables: { - input, - }, - }); - } - }) -}); - -export const rejectUsername = graphql(REJECT_USERNAME, { - props: ({mutate}) => ({ - rejectUsername: (input) => { - return mutate({ - variables: { - input, - }, - refetchQueries: ['Users'] - }); - } - }) -}); - -const views = ['all', 'premod', 'flagged', 'accepted', 'rejected']; -export const setCommentStatus = graphql(SET_COMMENT_STATUS, { - props: ({mutate}) => ({ - acceptComment: ({commentId}) => { - return mutate({ - variables: { - commentId, - status: 'ACCEPTED' - }, - updateQueries: { - ModQueue: (oldData) => { - const comment = views.reduce((comment, view) => { - return comment ? comment : oldData[view].find((c) => c.id === commentId); - }, null); - let accepted; - let acceptedCount = oldData.acceptedCount; - - // if the comment was already in the Approved queue, don't re-add it - if (comment.status === 'ACCEPTED') { - accepted = [...oldData.accepted]; - } else { - comment.status = 'ACCEPTED'; - acceptedCount++; - accepted = [comment, ...oldData.accepted]; - } - - const premod = oldData.premod.filter((c) => c.id !== commentId); - const flagged = oldData.flagged.filter((c) => c.id !== commentId); - const rejected = oldData.rejected.filter((c) => c.id !== commentId); - const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; - const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; - const rejectedCount = rejected.length < oldData.rejected.length ? oldData.rejectedCount - 1 : oldData.rejectedCount; - - return { - ...oldData, - premodCount: Math.max(0, premodCount), - flaggedCount: Math.max(0, flaggedCount), - acceptedCount: Math.max(0, acceptedCount), - rejectedCount: Math.max(0, rejectedCount), - premod, - flagged, - accepted, - rejected, - }; - } - } - }); - }, - rejectComment: ({commentId}) => { - return mutate({ - variables: { - commentId, - status: 'REJECTED' - }, - updateQueries: { - ModQueue: (oldData) => { - const comment = views.reduce((comment, view) => { - return comment ? comment : oldData[view].find((c) => c.id === commentId); - }, null); - let rejected; - let rejectedCount = oldData.rejectedCount; - - // if the item was already in the Rejected queue, don't put it in again - if (comment.status === 'REJECTED') { - rejected = oldData.rejected; - } else { - comment.status = 'REJECTED'; - rejectedCount++; - rejected = [comment, ...oldData.rejected]; - } - - const premod = oldData.premod.filter((c) => c.id !== commentId); - const flagged = oldData.flagged.filter((c) => c.id !== commentId); - const accepted = oldData.accepted.filter((c) => c.id !== commentId); - const premodCount = premod.length < oldData.premod.length ? oldData.premodCount - 1 : oldData.premodCount; - const flaggedCount = flagged.length < oldData.flagged.length ? oldData.flaggedCount - 1 : oldData.flaggedCount; - const acceptedCount = accepted.length < oldData.accepted.length ? oldData.acceptedCount - 1 : oldData.acceptedCount; - - return { - ...oldData, - premodCount: Math.max(0, premodCount), - flaggedCount: Math.max(0, flaggedCount), - acceptedCount: Math.max(0, acceptedCount), - rejectedCount: Math.max(0, rejectedCount), - premod, - flagged, - accepted, - rejected - }; - } - } - }); - } - }) -}); diff --git a/client/coral-admin/src/graphql/mutations/rejectUsername.graphql b/client/coral-admin/src/graphql/mutations/rejectUsername.graphql deleted file mode 100644 index c07887649..000000000 --- a/client/coral-admin/src/graphql/mutations/rejectUsername.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation rejectUsername($input: RejectUsernameInput!) { - rejectUsername(input: $input) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql b/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql deleted file mode 100644 index 7ff6173a8..000000000 --- a/client/coral-admin/src/graphql/mutations/setCommentStatus.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation setCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ - setCommentStatus(id: $commentId, status: $status) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql b/client/coral-admin/src/graphql/mutations/setUserStatus.graphql deleted file mode 100644 index 32fcf7e20..000000000 --- a/client/coral-admin/src/graphql/mutations/setUserStatus.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation setUserStatus($userId: ID!, $status: USER_STATUS!) { - setUserStatus(id: $userId, status: $status) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/mutations/suspendUser.graphql b/client/coral-admin/src/graphql/mutations/suspendUser.graphql deleted file mode 100644 index f6ab2426f..000000000 --- a/client/coral-admin/src/graphql/mutations/suspendUser.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation suspendUser($input: SuspendUserInput!) { - suspendUser(input: $input) { - errors { - translation_key - } - } -} diff --git a/client/coral-admin/src/graphql/queries/assetsQuery.graphql b/client/coral-admin/src/graphql/queries/assetsQuery.graphql deleted file mode 100644 index 37950692d..000000000 --- a/client/coral-admin/src/graphql/queries/assetsQuery.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query Assets { - assets { - id - title - } -} diff --git a/client/coral-admin/src/graphql/queries/getQueueCounts.graphql b/client/coral-admin/src/graphql/queries/getQueueCounts.graphql deleted file mode 100644 index 4a7c00d87..000000000 --- a/client/coral-admin/src/graphql/queries/getQueueCounts.graphql +++ /dev/null @@ -1,22 +0,0 @@ -query Counts ($asset_id: ID) { - allCount: commentCount(query: { - asset_id: $asset_id - }) - acceptedCount: commentCount(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) - premodCount: commentCount(query: { - statuses: [PREMOD], - asset_id: $asset_id - }) - rejectedCount: commentCount(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) - flaggedCount: commentCount(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD] - }) -} diff --git a/client/coral-admin/src/graphql/queries/index.js b/client/coral-admin/src/graphql/queries/index.js deleted file mode 100644 index ae94d70c3..000000000 --- a/client/coral-admin/src/graphql/queries/index.js +++ /dev/null @@ -1,116 +0,0 @@ -import {graphql} from 'react-apollo'; - -import MOD_QUEUE_QUERY from './modQueueQuery.graphql'; -import MOD_QUEUE_LOAD_MORE from './loadMore.graphql'; -import MOD_USER_FLAGGED_QUERY from './modUserFlaggedQuery.graphql'; -import METRICS from './metricsQuery.graphql'; -import USER_DETAIL from './userDetail.graphql'; -import GET_QUEUE_COUNTS from './getQueueCounts.graphql'; - -export const modQueueQuery = graphql(MOD_QUEUE_QUERY, { - options: ({params: {id = null}}) => { - return { - variables: { - asset_id: id, - sort: 'REVERSE_CHRONOLOGICAL' - } - }; - }, - props: ({ownProps: {params: {id = null}}, data}) => ({ - data, - modQueueResort: modQueueResort(id, data.fetchMore), - loadMore: loadMore(data.fetchMore) - }) -}); - -export const getMetrics = graphql(METRICS, { - options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => { - - return { - variables: { - from: dashboardWindowStart, - to: dashboardWindowEnd - } - }; - } -}); - -export const loadMore = (fetchMore) => ({limit = 10, cursor, sort, tab, asset_id}) => { - let variables = { - limit, - cursor, - sort, - asset_id - }; - switch(tab) { - case 'all': - variables.statuses = null; - break; - case 'accepted': - variables.statuses = ['ACCEPTED']; - break; - case 'premod': - variables.statuses = ['PREMOD']; - break; - case 'flagged': - variables.statuses = ['NONE', 'PREMOD']; - variables.action_type = 'FLAG'; - break; - case 'rejected': - variables.statuses = ['REJECTED']; - break; - } - return fetchMore({ - query: MOD_QUEUE_LOAD_MORE, - variables, - updateQuery: (oldData, {fetchMoreResult:{comments}}) => { - return { - ...oldData, - [tab]: [ - ...oldData[tab], - ...comments - ] - }; - } - }); -}; - -export const modUserFlaggedQuery = graphql(MOD_USER_FLAGGED_QUERY, { - options: ({params: {action_type = 'FLAG'}}) => { - return { - variables: { - action_type: action_type - } - }; - } -}); - -export const modQueueResort = (id, fetchMore) => (sort) => { - return fetchMore({ - query: MOD_QUEUE_QUERY, - variables: { - asset_id: id, - sort - }, - updateQuery: (oldData, {fetchMoreResult:{data}}) => data - }); -}; - -export const getUserDetail = graphql(USER_DETAIL, { - options: ({id}) => { - return { - variables: {author_id: id} - }; - } -}); - -export const getQueueCounts = graphql(GET_QUEUE_COUNTS, { - options: ({params: {id = null}}) => { - return { - pollInterval: 5000, - variables: { - asset_id: id - } - }; - } -}); diff --git a/client/coral-admin/src/graphql/queries/loadMore.graphql b/client/coral-admin/src/graphql/queries/loadMore.graphql deleted file mode 100644 index a42c7b2bf..000000000 --- a/client/coral-admin/src/graphql/queries/loadMore.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "../fragments/commentView.graphql" - -query LoadMoreModQueue($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { - comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) { - ...commentView - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - } -} diff --git a/client/coral-admin/src/graphql/queries/metricsQuery.graphql b/client/coral-admin/src/graphql/queries/metricsQuery.graphql deleted file mode 100644 index 17fac3034..000000000 --- a/client/coral-admin/src/graphql/queries/metricsQuery.graphql +++ /dev/null @@ -1,10 +0,0 @@ -#import "../fragments/assetMetricsView.graphql" - -query Metrics ($from: Date!, $to: Date!) { - assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) { - ...metrics - } - assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { - ...metrics - } -} diff --git a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql b/client/coral-admin/src/graphql/queries/modQueueQuery.graphql deleted file mode 100644 index 6bfcf81ec..000000000 --- a/client/coral-admin/src/graphql/queries/modQueueQuery.graphql +++ /dev/null @@ -1,68 +0,0 @@ -#import "../fragments/commentView.graphql" - -query ModQueue ($asset_id: ID, $sort: SORT_ORDER) { - all: comments(query: { - statuses: [NONE, PREMOD, ACCEPTED, REJECTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - accepted: comments(query: { - statuses: [ACCEPTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - premod: comments(query: { - statuses: [PREMOD], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - flagged: comments(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD], - sort: $sort - }) { - ...commentView - } - rejected: comments(query: { - statuses: [REJECTED], - asset_id: $asset_id, - sort: $sort - }) { - ...commentView - } - assets: assets { - id - title - url - } - allCount: commentCount(query: { - asset_id: $asset_id - }) - acceptedCount: commentCount(query: { - statuses: [ACCEPTED], - asset_id: $asset_id - }) - premodCount: commentCount(query: { - statuses: [PREMOD], - asset_id: $asset_id - }) - rejectedCount: commentCount(query: { - statuses: [REJECTED], - asset_id: $asset_id - }) - flaggedCount: commentCount(query: { - action_type: FLAG, - asset_id: $asset_id, - statuses: [NONE, PREMOD] - }) - settings { - organizationName - } -} diff --git a/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql b/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql deleted file mode 100644 index c99d94583..000000000 --- a/client/coral-admin/src/graphql/queries/modUserFlaggedQuery.graphql +++ /dev/null @@ -1,26 +0,0 @@ -query Users ($action_type: ACTION_TYPE) { - users (query:{action_type: $action_type}){ - id - username - status - roles - actions{ - id - created_at - ... on FlagAction { - reason - message - user { - id - username - } - } - } - action_summaries { - count - ... on FlagActionSummary { - reason - } - } - } -} diff --git a/client/coral-admin/src/graphql/queries/userDetail.graphql b/client/coral-admin/src/graphql/queries/userDetail.graphql deleted file mode 100644 index c72b104d0..000000000 --- a/client/coral-admin/src/graphql/queries/userDetail.graphql +++ /dev/null @@ -1,21 +0,0 @@ -#import "../fragments/commentView.graphql" - -query UserDetail ($author_id: ID!) { - user(id: $author_id) { - id - username - created_at - profiles { - id - provider - } - } - totalComments: commentCount(query: {author_id: $author_id}) - rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]}) - comments: comments(query: { - author_id: $author_id, - statuses: [NONE, PREMOD, ACCEPTED, REJECTED] - }) { - ...commentView - } -} diff --git a/client/coral-admin/src/index.js b/client/coral-admin/src/index.js index 0af9a4a5a..d0dfc5f9a 100644 --- a/client/coral-admin/src/index.js +++ b/client/coral-admin/src/index.js @@ -8,6 +8,7 @@ import store from './services/store'; import App from './components/App'; import 'react-mdl/extra/material.js'; +import './graphql'; import {loadPluginsTranslations} from 'coral-framework/helpers/plugins'; loadPluginsTranslations(); diff --git a/client/coral-admin/src/reducers/moderation.js b/client/coral-admin/src/reducers/moderation.js index 4bcd82d47..10cbe0eca 100644 --- a/client/coral-admin/src/reducers/moderation.js +++ b/client/coral-admin/src/reducers/moderation.js @@ -10,6 +10,7 @@ const initialState = fromJS({ userDetailId: null, banDialog: false, shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show', + sortOrder: 'REVERSE_CHRONOLOGICAL', suspendUserDialog: { show: false, userId: null, @@ -64,6 +65,8 @@ export default function moderation (state = initialState, action) { return state.set('userDetailId', action.userId); case actions.HIDE_USER_DETAIL: return state.set('userDetailId', null); + case actions.SET_SORT_ORDER: + return state.set('sortOrder', action.order); default : return state; } diff --git a/client/coral-admin/src/containers/Community/components/ActionButton.js b/client/coral-admin/src/routes/Community/components/ActionButton.js similarity index 84% rename from client/coral-admin/src/containers/Community/components/ActionButton.js rename to client/coral-admin/src/routes/Community/components/ActionButton.js index 34eb852e4..67f16acad 100644 --- a/client/coral-admin/src/containers/Community/components/ActionButton.js +++ b/client/coral-admin/src/routes/Community/components/ActionButton.js @@ -1,8 +1,8 @@ import React from 'react'; -import styles from '../Community.css'; +import styles from './Community.css'; import BanUserButton from './BanUserButton'; import {Button} from 'coral-ui'; -import {menuActionsMap} from '../../../containers/ModerationQueue/helpers/moderationQueueActionsMap'; +import {menuActionsMap} from '../../Moderation/helpers/moderationQueueActionsMap'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/containers/Community/components/BanUserButton.css b/client/coral-admin/src/routes/Community/components/BanUserButton.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserButton.css rename to client/coral-admin/src/routes/Community/components/BanUserButton.css diff --git a/client/coral-admin/src/containers/Community/components/BanUserButton.js b/client/coral-admin/src/routes/Community/components/BanUserButton.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserButton.js rename to client/coral-admin/src/routes/Community/components/BanUserButton.js diff --git a/client/coral-admin/src/containers/Community/components/BanUserDialog.css b/client/coral-admin/src/routes/Community/components/BanUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserDialog.css rename to client/coral-admin/src/routes/Community/components/BanUserDialog.css diff --git a/client/coral-admin/src/containers/Community/components/BanUserDialog.js b/client/coral-admin/src/routes/Community/components/BanUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/BanUserDialog.js rename to client/coral-admin/src/routes/Community/components/BanUserDialog.js diff --git a/client/coral-admin/src/containers/Community/Community.css b/client/coral-admin/src/routes/Community/components/Community.css similarity index 100% rename from client/coral-admin/src/containers/Community/Community.css rename to client/coral-admin/src/routes/Community/components/Community.css diff --git a/client/coral-admin/src/containers/Community/CommunityContainer.js b/client/coral-admin/src/routes/Community/components/Community.js similarity index 54% rename from client/coral-admin/src/containers/Community/CommunityContainer.js rename to client/coral-admin/src/routes/Community/components/Community.js index 7c0051477..f6544b0ac 100644 --- a/client/coral-admin/src/containers/Community/CommunityContainer.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -1,55 +1,26 @@ import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import {compose} from 'react-apollo'; - -import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries'; -import {banUser, setUserStatus, rejectUsername} from 'coral-admin/src/graphql/mutations'; - -import { - fetchAccounts, - updateSorting, - newPage, - showBanUserDialog, - hideBanUserDialog, - showSuspendUserDialog, - hideSuspendUserDialog -} from '../../actions/community'; - -import CommunityMenu from './components/CommunityMenu'; -import BanUserDialog from './components/BanUserDialog'; -import SuspendUserDialog from './components/SuspendUserDialog'; +import CommunityMenu from './CommunityMenu'; +import BanUserDialog from './BanUserDialog'; +import SuspendUserDialog from './SuspendUserDialog'; import People from './People'; import FlaggedAccounts from './FlaggedAccounts'; -class CommunityContainer extends Component { +export default class Community extends Component { - constructor(props) { - super(props); + state = { + searchValue: '', + timer: null + }; - this.state = { - searchValue: '', - timer: null - }; - - this.onKeyDownHandler = this.onKeyDownHandler.bind(this); - this.onSearchChange = this.onSearchChange.bind(this); - this.onHeaderClickHandler = this.onHeaderClickHandler.bind(this); - this.onNewPageHandler = this.onNewPageHandler.bind(this); - } - - componentWillMount() { - this.props.fetchAccounts({}); - } - - onKeyDownHandler(e) { + onKeyDownHandler = (e) => { if (e.key === 'Enter') { e.preventDefault(); this.search(); } } - onSearchChange(e) { + onSearchChange = (e) => { const value = e.target.value; this.setState((prevState) => { prevState.searchValue = value; @@ -62,6 +33,16 @@ class CommunityContainer extends Component { }); } + onHeaderClickHandler = (sort) => { + this.props.updateSorting(sort); + this.search(); + } + + onNewPageHandler = (page) => { + this.props.newPage(page); + this.search({page}); + } + search(query = {}) { const {community} = this.props; @@ -71,21 +52,10 @@ class CommunityContainer extends Component { asc: community.ascPeople, ...query }); - - } - - onHeaderClickHandler(sort) { - this.props.dispatch(updateSorting(sort)); - this.search(); - } - - onNewPageHandler(page) { - this.props.dispatch(newPage(page)); - this.search({page}); } getTabContent(searchValue, props) { - const {community, data} = props; + const {community, root: {users}} = props; const activeTab = props.route.path === ':id' ? 'flagged' : props.route.path; if (activeTab === 'people') { @@ -108,9 +78,7 @@ class CommunityContainer extends Component { return (
({ - community: state.community.toJS() -}); - -const mapDispatchToProps = (dispatch) => ({ - fetchAccounts: (query) => dispatch(fetchAccounts(query)), - showBanUserDialog: (user) => dispatch(showBanUserDialog(user)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - showSuspendUserDialog: (user) => dispatch(showSuspendUserDialog(user)), - hideSuspendUserDialog: () => dispatch(hideSuspendUserDialog()) -}); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - modUserFlaggedQuery, - banUser, - setUserStatus, - rejectUsername -)(CommunityContainer); diff --git a/client/coral-admin/src/containers/Community/CommunityLayout.js b/client/coral-admin/src/routes/Community/components/CommunityLayout.js similarity index 100% rename from client/coral-admin/src/containers/Community/CommunityLayout.js rename to client/coral-admin/src/routes/Community/components/CommunityLayout.js diff --git a/client/coral-admin/src/containers/Community/components/CommunityMenu.js b/client/coral-admin/src/routes/Community/components/CommunityMenu.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/CommunityMenu.js rename to client/coral-admin/src/routes/Community/components/CommunityMenu.js diff --git a/client/coral-admin/src/containers/Community/FlaggedAccounts.js b/client/coral-admin/src/routes/Community/components/FlaggedAccounts.js similarity index 82% rename from client/coral-admin/src/containers/Community/FlaggedAccounts.js rename to client/coral-admin/src/routes/Community/components/FlaggedAccounts.js index 222ace8bb..df6368844 100644 --- a/client/coral-admin/src/containers/Community/FlaggedAccounts.js +++ b/client/coral-admin/src/routes/Community/components/FlaggedAccounts.js @@ -2,20 +2,18 @@ import React from 'react'; import t from 'coral-framework/services/i18n'; import styles from './Community.css'; -import Loading from './Loading'; import EmptyCard from 'coral-admin/src/components/EmptyCard'; -import User from './components/User'; +import User from './User'; const FlaggedAccounts = ({...props}) => { - const {commenters, isFetching} = props; - const hasResults = !isFetching && commenters && !!commenters.length; + const {commenters} = props; + const hasResults = commenters && !!commenters.length; // if (commenter.status === 'PENDING' && commenter.actions.length > 0) { return (
- { isFetching && } { hasResults ? commenters.map((commenter, index) => { diff --git a/client/coral-admin/src/containers/Community/People.js b/client/coral-admin/src/routes/Community/components/People.js similarity index 94% rename from client/coral-admin/src/containers/Community/People.js rename to client/coral-admin/src/routes/Community/components/People.js index 4afbe387f..464f3bc5b 100644 --- a/client/coral-admin/src/containers/Community/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -1,9 +1,9 @@ import React from 'react'; import styles from './Community.css'; -import Table from './Table'; +import Table from '../containers/Table'; import {Pager, Icon} from 'coral-ui'; -import EmptyCard from '../../components/EmptyCard'; +import EmptyCard from '../../../components/EmptyCard'; import t from 'coral-framework/services/i18n'; const tableHeaders = [ diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.css b/client/coral-admin/src/routes/Community/components/SuspendUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/SuspendUserDialog.css rename to client/coral-admin/src/routes/Community/components/SuspendUserDialog.css diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js b/client/coral-admin/src/routes/Community/components/SuspendUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/Community/components/SuspendUserDialog.js rename to client/coral-admin/src/routes/Community/components/SuspendUserDialog.js diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js new file mode 100644 index 000000000..dac97b1ad --- /dev/null +++ b/client/coral-admin/src/routes/Community/components/Table.js @@ -0,0 +1,54 @@ +import React from 'react'; +import {SelectField, Option} from 'react-mdl-selectfield'; +import styles from '../components/Community.css'; +import t from 'coral-framework/services/i18n'; + +export default ({headers, commenters, onHeaderClickHandler, onRoleChange, onCommenterStatusChange}) => ( + + + + {headers.map((header, i) =>( + + ))} + + + + {commenters.map((row, i)=> ( + + + + + + + ))} + +
onHeaderClickHandler({field: header.field})}> + {header.title} +
+ {row.username} + {row.profiles.map(({id}) => id)} + + {row.created_at} + + onCommenterStatusChange(row.id, status)}> + + + + + onRoleChange(row.id, role)}> + + + + + +
+); diff --git a/client/coral-admin/src/containers/Community/components/User.js b/client/coral-admin/src/routes/Community/components/User.js similarity index 98% rename from client/coral-admin/src/containers/Community/components/User.js rename to client/coral-admin/src/routes/Community/components/User.js index 05224170b..84d30e5f3 100644 --- a/client/coral-admin/src/containers/Community/components/User.js +++ b/client/coral-admin/src/routes/Community/components/User.js @@ -1,5 +1,5 @@ import React from 'react'; -import styles from '../Community.css'; +import styles from './Community.css'; import ActionButton from './ActionButton'; diff --git a/client/coral-admin/src/containers/Community/components/styles.css b/client/coral-admin/src/routes/Community/components/styles.css similarity index 100% rename from client/coral-admin/src/containers/Community/components/styles.css rename to client/coral-admin/src/routes/Community/components/styles.css diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js new file mode 100644 index 000000000..8c5de95d4 --- /dev/null +++ b/client/coral-admin/src/routes/Community/containers/Community.js @@ -0,0 +1,107 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose, gql} from 'react-apollo'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {Spinner} from 'coral-ui'; + +import {withSetUserStatus, withRejectUsername} from 'coral-framework/graphql/mutations'; + +import { + fetchAccounts, + updateSorting, + newPage, + showBanUserDialog, + hideBanUserDialog, + showSuspendUserDialog, + hideSuspendUserDialog +} from '../../../actions/community'; + +import Community from '../components/Community'; + +class CommunityContainer extends Component { + + componentWillMount() { + this.props.fetchAccounts({}); + } + + approveUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'APPROVED'}); + } + + banUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'BANNED'}); + } + + render() { + if (this.props.data.error) { + return
{this.props.data.error.message}
; + } + + if (!('users' in this.props.root)) { + return
; + } + return ( + + ); + } +} + +export const withCommunityQuery = withQuery(gql` + query CoralAdmin_Community($action_type: ACTION_TYPE) { + users(query:{action_type: $action_type}){ + id + username + status + roles + actions{ + id + created_at + ... on FlagAction { + reason + message + user { + id + username + } + } + } + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + } + } +`, { + options: ({params: {action_type = 'FLAG'}}) => { + return { + variables: { + action_type: action_type + } + }; + } +}); + +const mapStateToProps = (state) => ({ + community: state.community.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchAccounts, + showBanUserDialog, + hideBanUserDialog, + showSuspendUserDialog, + hideSuspendUserDialog, + updateSorting, + newPage, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withCommunityQuery, + withSetUserStatus, + withRejectUsername, +)(CommunityContainer); diff --git a/client/coral-admin/src/routes/Community/containers/Table.js b/client/coral-admin/src/routes/Community/containers/Table.js new file mode 100644 index 000000000..499e0718a --- /dev/null +++ b/client/coral-admin/src/routes/Community/containers/Table.js @@ -0,0 +1,37 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import {setRole, setCommenterStatus} from '../../../actions/community'; +import Table from '../components/Table'; + +class TableContainer extends Component { + + constructor (props) { + super(props); + } + + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + commenters: state.community.get('accounts'), +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + setCommenterStatus, + setRole, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(TableContainer); + diff --git a/client/coral-admin/src/routes/Community/index.js b/client/coral-admin/src/routes/Community/index.js new file mode 100644 index 000000000..528fe63cd --- /dev/null +++ b/client/coral-admin/src/routes/Community/index.js @@ -0,0 +1,2 @@ +export {default as Community} from './containers/Community'; +export {default as CommunityLayout} from './components/CommunityLayout'; diff --git a/client/coral-admin/src/containers/Configure/Configure.css b/client/coral-admin/src/routes/Configure/components/Configure.css similarity index 100% rename from client/coral-admin/src/containers/Configure/Configure.css rename to client/coral-admin/src/routes/Configure/components/Configure.css diff --git a/client/coral-admin/src/containers/Configure/Configure.js b/client/coral-admin/src/routes/Configure/components/Configure.js similarity index 82% rename from client/coral-admin/src/containers/Configure/Configure.js rename to client/coral-admin/src/routes/Configure/components/Configure.js index 4eb046df4..84e47dfb7 100644 --- a/client/coral-admin/src/containers/Configure/Configure.js +++ b/client/coral-admin/src/routes/Configure/components/Configure.js @@ -1,12 +1,4 @@ import React, {Component} from 'react'; -import {connect} from 'react-redux'; -import { - fetchSettings, - updateSettings, - saveSettingsToServer, - updateWordlist, - updateDomainlist -} from '../../actions/settings'; import {Button, List, Item, Card, Spinner} from 'coral-ui'; import styles from './Configure.css'; @@ -16,45 +8,36 @@ import TechSettings from './TechSettings'; import t from 'coral-framework/services/i18n'; import {can} from 'coral-framework/services/perms'; -class Configure extends Component { - constructor (props) { - super(props); +export default class Configure extends Component { - this.state = { - activeSection: 'stream', - changed: false, - errors: {} - }; - - this.changeSection = this.changeSection.bind(this); - } - - componentWillMount = () => { - this.props.dispatch(fetchSettings()); - } + state = { + activeSection: 'stream', + changed: false, + errors: {} + }; saveSettings = () => { - this.props.dispatch(saveSettingsToServer()); + this.props.saveSettingsToServer(); this.setState({changed: false}); } - changeSection(activeSection) { + changeSection = (activeSection) => { this.setState({activeSection}); } onChangeWordlist = (listName, list) => { this.setState({changed: true}); - this.props.dispatch(updateWordlist(listName, list)); + this.props.updateWordlist(listName, list); } onChangeDomainlist = (listName, list) => { this.setState({changed: true}); - this.props.dispatch(updateDomainlist(listName, list)); + this.props.updateDomainlist(listName, list); } onSettingUpdate = (setting) => { this.setState({changed: true}); - this.props.dispatch(updateSettings(setting)); + this.props.updateSettings(setting); } // Sets an arbitrary error string and a boolean state. @@ -175,9 +158,3 @@ class Configure extends Component { ); } } - -const mapStateToProps = (state) => ({ - auth: state.auth.toJS(), - settings: state.settings.toJS() -}); -export default connect(mapStateToProps)(Configure); diff --git a/client/coral-admin/src/containers/Configure/Domainlist.js b/client/coral-admin/src/routes/Configure/components/Domainlist.js similarity index 100% rename from client/coral-admin/src/containers/Configure/Domainlist.js rename to client/coral-admin/src/routes/Configure/components/Domainlist.js diff --git a/client/coral-admin/src/containers/Configure/EmbedLink.js b/client/coral-admin/src/routes/Configure/components/EmbedLink.js similarity index 99% rename from client/coral-admin/src/containers/Configure/EmbedLink.js rename to client/coral-admin/src/routes/Configure/components/EmbedLink.js index 7ff348b50..1cd48cdc5 100644 --- a/client/coral-admin/src/containers/Configure/EmbedLink.js +++ b/client/coral-admin/src/routes/Configure/components/EmbedLink.js @@ -1,5 +1,4 @@ import React, {Component} from 'react'; - import t from 'coral-framework/services/i18n'; import styles from './Configure.css'; import {Button, Card} from 'coral-ui'; diff --git a/client/coral-admin/src/containers/Configure/ModerationSettings.js b/client/coral-admin/src/routes/Configure/components/ModerationSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/ModerationSettings.js rename to client/coral-admin/src/routes/Configure/components/ModerationSettings.js diff --git a/client/coral-admin/src/containers/Configure/StreamSettings.js b/client/coral-admin/src/routes/Configure/components/StreamSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/StreamSettings.js rename to client/coral-admin/src/routes/Configure/components/StreamSettings.js diff --git a/client/coral-admin/src/containers/Configure/TechSettings.js b/client/coral-admin/src/routes/Configure/components/TechSettings.js similarity index 100% rename from client/coral-admin/src/containers/Configure/TechSettings.js rename to client/coral-admin/src/routes/Configure/components/TechSettings.js diff --git a/client/coral-admin/src/containers/Configure/Wordlist.js b/client/coral-admin/src/routes/Configure/components/Wordlist.js similarity index 100% rename from client/coral-admin/src/containers/Configure/Wordlist.js rename to client/coral-admin/src/routes/Configure/components/Wordlist.js diff --git a/client/coral-admin/src/routes/Configure/containers/Configure.js b/client/coral-admin/src/routes/Configure/containers/Configure.js new file mode 100644 index 000000000..c68b39e0c --- /dev/null +++ b/client/coral-admin/src/routes/Configure/containers/Configure.js @@ -0,0 +1,42 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import { + fetchSettings, + updateSettings, + saveSettingsToServer, + updateWordlist, + updateDomainlist +} from '../../../actions/settings'; + +import Configure from '../components/Configure'; + +class ConfigureContainer extends Component { + componentWillMount = () => { + this.props.fetchSettings(); + } + + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + auth: state.auth.toJS(), + settings: state.settings.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchSettings, + updateSettings, + saveSettingsToServer, + updateWordlist, + updateDomainlist + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(ConfigureContainer); + diff --git a/client/coral-admin/src/routes/Configure/index.js b/client/coral-admin/src/routes/Configure/index.js new file mode 100644 index 000000000..d548be931 --- /dev/null +++ b/client/coral-admin/src/routes/Configure/index.js @@ -0,0 +1 @@ +export {default} from './containers/Configure'; diff --git a/client/coral-admin/src/containers/Dashboard/ActivityWidget.js b/client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/ActivityWidget.js rename to client/coral-admin/src/routes/Dashboard/components/ActivityWidget.js diff --git a/client/coral-admin/src/components/CountdownTimer.js b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js similarity index 96% rename from client/coral-admin/src/components/CountdownTimer.js rename to client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js index a1bf32769..9e1995b7f 100644 --- a/client/coral-admin/src/components/CountdownTimer.js +++ b/client/coral-admin/src/routes/Dashboard/components/CountdownTimer.js @@ -1,5 +1,5 @@ import React, {PropTypes} from 'react'; -import styles from 'coral-admin/src/containers/Dashboard/Dashboard.css'; +import styles from './Dashboard.css'; import {Icon} from 'coral-ui'; import t from 'coral-framework/services/i18n'; diff --git a/client/coral-admin/src/containers/Dashboard/Dashboard.css b/client/coral-admin/src/routes/Dashboard/components/Dashboard.css similarity index 100% rename from client/coral-admin/src/containers/Dashboard/Dashboard.css rename to client/coral-admin/src/routes/Dashboard/components/Dashboard.css diff --git a/client/coral-admin/src/routes/Dashboard/components/Dashboard.js b/client/coral-admin/src/routes/Dashboard/components/Dashboard.js new file mode 100644 index 000000000..ba224a3b9 --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/components/Dashboard.js @@ -0,0 +1,15 @@ +import React from 'react'; +import FlagWidget from './FlagWidget'; +import ActivityWidget from './ActivityWidget'; +import CountdownTimer from './CountdownTimer'; +import styles from './Dashboard.css'; + +export default ({root: {assetsByActivity, assetsByFlag}, reloadData}) => ( +
+ +
+ + +
+
+); diff --git a/client/coral-admin/src/containers/Dashboard/FlagWidget.js b/client/coral-admin/src/routes/Dashboard/components/FlagWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/FlagWidget.js rename to client/coral-admin/src/routes/Dashboard/components/FlagWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/LikeWidget.js b/client/coral-admin/src/routes/Dashboard/components/LikeWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/LikeWidget.js rename to client/coral-admin/src/routes/Dashboard/components/LikeWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js b/client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js similarity index 100% rename from client/coral-admin/src/containers/Dashboard/MostLikedCommentsWidget.js rename to client/coral-admin/src/routes/Dashboard/components/MostLikedCommentsWidget.js diff --git a/client/coral-admin/src/containers/Dashboard/Widget.css b/client/coral-admin/src/routes/Dashboard/components/Widget.css similarity index 100% rename from client/coral-admin/src/containers/Dashboard/Widget.css rename to client/coral-admin/src/routes/Dashboard/components/Widget.css diff --git a/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js new file mode 100644 index 000000000..ea1fb2b6c --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/containers/Dashboard.js @@ -0,0 +1,65 @@ +import React from 'react'; +import {connect} from 'react-redux'; +import Dashboard from '../components/Dashboard'; +import {compose, gql} from 'react-apollo'; +import withQuery from 'coral-framework/hocs/withQuery'; + +import {Spinner} from 'coral-ui'; + +class DashboardContainer extends React.Component { + + reloadData = () => { + this.props.data.refetch(); + } + + render () { + if (this.props.data.loading) { + return ; + } + return ; + } +} + +export const witDashboardQuery = withQuery(gql` + query CoralAdmin_Dashboard($from: Date!, $to: Date!) { + assetsByFlag: assetMetrics(from: $from, to: $to, sort: FLAG) { + ...CoralAdmin_Metrics + } + assetsByActivity: assetMetrics(from: $from, to: $to, sort: ACTIVITY) { + ...CoralAdmin_Metrics + } + } + fragment CoralAdmin_Metrics on Asset { + id + title + url + author + created_at + commentCount + action_summaries { + actionCount + actionableItemCount + } + } +`, { + options: ({settings: {dashboardWindowStart, dashboardWindowEnd}}) => { + return { + variables: { + from: dashboardWindowStart, + to: dashboardWindowEnd + } + }; + } +}); + +const mapStateToProps = (state) => { + return { + settings: state.settings.toJS(), + moderation: state.moderation.toJS() + }; +}; + +export default compose( + connect(mapStateToProps), + witDashboardQuery, +)(DashboardContainer); diff --git a/client/coral-admin/src/routes/Dashboard/index.js b/client/coral-admin/src/routes/Dashboard/index.js new file mode 100644 index 000000000..5bbb476e2 --- /dev/null +++ b/client/coral-admin/src/routes/Dashboard/index.js @@ -0,0 +1 @@ +export {default} from './containers/Dashboard.js'; diff --git a/client/coral-admin/src/routes/Install/components/Install.js b/client/coral-admin/src/routes/Install/components/Install.js new file mode 100644 index 000000000..f7069d67c --- /dev/null +++ b/client/coral-admin/src/routes/Install/components/Install.js @@ -0,0 +1,79 @@ +import React, {Component} from 'react'; +import styles from './style.css'; +import {Wizard, WizardNav} from 'coral-ui'; +import Layout from 'coral-admin/src/components/ui/Layout'; + +import InitialStep from './Steps/InitialStep'; +import AddOrganizationName from './Steps/AddOrganizationName'; +import CreateYourAccount from './Steps/CreateYourAccount'; +import PermittedDomainsStep from './Steps/PermittedDomainsStep'; +import FinalStep from './Steps/FinalStep'; + +export default class Install extends Component { + handleDomainsChange = (value) => { + this.props.updatePermittedDomains(value); + }; + + handleSettingsChange = ({currentTarget: {name, value}}) => { + this.props.updateSettingsFormData(name, value); + }; + + handleUserChange = ({currentTarget: {name, value}}) => { + this.props.updateUserFormData(name, value); + }; + + handleSettingsSubmit = (e) => { + e.preventDefault(); + this.props.submitSettings(); + }; + + handleUserSubmit = (e) => { + e.preventDefault(); + this.props.submitUser(); + } + + render() { + const {install} = this.props; + + return ( + +
+ { + !install.alreadyInstalled ? ( +
+

Welcome to the Coral Project

+ { install.step !== 0 ? : null } + + + + + + + +
+ ) : ( +
Talk is already installed
+ ) + } +
+
+ ); + } +} diff --git a/client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js b/client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/AddOrganizationName.js rename to client/coral-admin/src/routes/Install/components/Steps/AddOrganizationName.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js b/client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/CreateYourAccount.js rename to client/coral-admin/src/routes/Install/components/Steps/CreateYourAccount.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/FinalStep.js b/client/coral-admin/src/routes/Install/components/Steps/FinalStep.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/FinalStep.js rename to client/coral-admin/src/routes/Install/components/Steps/FinalStep.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/InitialStep.js b/client/coral-admin/src/routes/Install/components/Steps/InitialStep.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/InitialStep.js rename to client/coral-admin/src/routes/Install/components/Steps/InitialStep.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js b/client/coral-admin/src/routes/Install/components/Steps/InviteTeamMembers.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/InviteTeamMembers.js rename to client/coral-admin/src/routes/Install/components/Steps/InviteTeamMembers.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js b/client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/PermittedDomainsStep.js rename to client/coral-admin/src/routes/Install/components/Steps/PermittedDomainsStep.js diff --git a/client/coral-admin/src/containers/Install/components/Steps/style.css b/client/coral-admin/src/routes/Install/components/Steps/style.css similarity index 100% rename from client/coral-admin/src/containers/Install/components/Steps/style.css rename to client/coral-admin/src/routes/Install/components/Steps/style.css diff --git a/client/coral-admin/src/containers/Install/style.css b/client/coral-admin/src/routes/Install/components/style.css similarity index 100% rename from client/coral-admin/src/containers/Install/style.css rename to client/coral-admin/src/routes/Install/components/style.css diff --git a/client/coral-admin/src/routes/Install/containers/Install.js b/client/coral-admin/src/routes/Install/containers/Install.js new file mode 100644 index 000000000..5dcb35705 --- /dev/null +++ b/client/coral-admin/src/routes/Install/containers/Install.js @@ -0,0 +1,57 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import Install from '../components/Install'; + +import { + goToStep, + nextStep, + submitUser, + checkInstall, + previousStep, + finishInstall, + submitSettings, + updateUserFormData, + updateSettingsFormData, + updatePermittedDomains +} from '../../../actions/install'; + +class InstallContainer extends Component { + componentDidMount() { + const {checkInstall} = this.props; + checkInstall(() => { + this.context.router.push('/admin'); + }); + } + + render() { + return ; + } +} + +InstallContainer.contextTypes = { + router: React.PropTypes.object +}; + +const mapStateToProps = (state) => ({ + install: state.install.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + goToStep, + nextStep, + submitUser, + checkInstall, + previousStep, + finishInstall, + submitSettings, + updateUserFormData, + updateSettingsFormData, + updatePermittedDomains + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(InstallContainer); diff --git a/client/coral-admin/src/routes/Install/index.js b/client/coral-admin/src/routes/Install/index.js new file mode 100644 index 000000000..94c7bf93f --- /dev/null +++ b/client/coral-admin/src/routes/Install/index.js @@ -0,0 +1 @@ +export {default} from './containers/Install.js'; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css b/client/coral-admin/src/routes/Moderation/components/BanUserDialog.css similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.css rename to client/coral-admin/src/routes/Moderation/components/BanUserDialog.css diff --git a/client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js b/client/coral-admin/src/routes/Moderation/components/BanUserDialog.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/BanUserDialog.js rename to client/coral-admin/src/routes/Moderation/components/BanUserDialog.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/Comment.css rename to client/coral-admin/src/routes/Moderation/components/Comment.css diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/routes/Moderation/components/Comment.js similarity index 89% rename from client/coral-admin/src/containers/ModerationQueue/components/Comment.js rename to client/coral-admin/src/routes/Moderation/components/Comment.js index fe41f4d34..46f6a40c8 100644 --- a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js +++ b/client/coral-admin/src/routes/Moderation/components/Comment.js @@ -40,8 +40,8 @@ const Comment = ({ } // since words are checked against word boundaries on the backend, - // this should be the behavior on the front end as well. - // currently the highlighter plugin does not support this out of the box. + // should be the behavior on the front end as well. + // currently the highlighter plugin does not support out of the box. const searchWords = [...suspectWords, ...bannedWords] .filter((w) => { return new RegExp(`(^|\\s)${w}(\\s|$)`).test(comment.body); @@ -83,7 +83,12 @@ const Comment = ({ {t('comment.banned_user')} : null} - +
Story: {comment.asset.title} @@ -105,7 +110,12 @@ const Comment = ({ {t('comment.view_context')}

- +
{links ? @@ -137,12 +147,22 @@ const Comment = ({ ); })}
- +
- +
{flagActions && flagActions.length ? singleView()); key('shift+/', () => toggleModal(true)); key('esc', () => toggleModal(false)); @@ -54,6 +28,10 @@ class ModerationContainer extends Component { key('t', this.moderate(true)); } + onClose = () => { + this.toggleModal(false); + } + moderate = (accept) => () => { const {acceptComment, rejectComment} = this.props; const {selectedIndex} = this.state; @@ -69,9 +47,9 @@ class ModerationContainer extends Component { } getComments = () => { - const {data, route} = this.props; + const {root, route} = this.props; const activeTab = route.path === ':id' ? 'premod' : route.path; - return data[activeTab]; + return root[activeTab]; } select = (next) => () => { @@ -92,38 +70,6 @@ class ModerationContainer extends Component { } } - selectSort = (sort) => { - this.setState({sort}); - this.props.modQueueResort(sort); - } - - suspendUser = async (args) => { - this.props.hideSuspendUserDialog(); - try { - const result = await this.props.suspendUser(args); - if (result.data.suspendUser.errors) { - throw result.data.suspendUser.errors; - } - notification.success( - t('suspenduser.notify_suspend_until', - this.props.moderation.suspendUserDialog.username, - timeago(args.until)), - ); - const {commentStatus, commentId} = this.props.moderation.suspendUserDialog; - if (commentStatus !== 'REJECTED') { - return this.props.rejectComment({commentId}) - .then((result) => { - if (result.data.setCommentStatus.errors) { - throw result.data.setCommentStatus.errors; - } - }); - } - } - catch(err) { - notification.showMutationErrors(err); - } - }; - componentWillUnmount() { key.unbind('s'); key.unbind('shift+/'); @@ -145,28 +91,13 @@ class ModerationContainer extends Component { } } - componentWillReceiveProps(nextProps) { - const {updateAssets} = this.props; - if(!isEqual(nextProps.data.assets, this.props.data.assets)) { - updateAssets(nextProps.data.assets); - } - } - render () { - const {data, moderation, settings, assets, onClose, viewUserDetail, hideUserDetail, ...props} = this.props; + const {root, moderation, settings, assets, viewUserDetail, hideUserDetail, ...props} = this.props; const providedAssetId = this.props.params.id; const activeTab = this.props.route.path === ':id' ? 'premod' : this.props.route.path; let asset; - if (!('premodCount' in data)) { - return
; - } - - if (data.error) { - return
Error
; - } - if (providedAssetId) { asset = assets.find((asset) => asset.id === this.props.params.id); @@ -175,23 +106,23 @@ class ModerationContainer extends Component { } } - const comments = data[activeTab]; + const comments = root[activeTab]; let activeTabCount; switch(activeTab) { case 'all': - activeTabCount = data.allCount; + activeTabCount = root.allCount; break; case 'accepted': - activeTabCount = data.acceptedCount; + activeTabCount = root.acceptedCount; break; case 'premod': - activeTabCount = data.premodCount; + activeTabCount = root.premodCount; break; case 'flagged': - activeTabCount = data.flaggedCount; + activeTabCount = root.flaggedCount; break; case 'rejected': - activeTabCount = data.rejectedCount; + activeTabCount = root.rejectedCount; break; } @@ -200,15 +131,17 @@ class ModerationContainer extends Component { + onClose={this.onClose}/> {moderation.userDetailId && ( ({ - moderation: state.moderation.toJS(), - settings: state.settings.toJS(), - auth: state.auth.toJS(), - assets: state.assets.get('assets') -}); - -const mapDispatchToProps = (dispatch) => ({ - onClose: () => dispatch(toggleModal(false)), - hideBanUserDialog: () => dispatch(hideBanUserDialog(false)), - ...bindActionCreators({ - toggleModal, - singleView, - updateAssets, - fetchSettings, - showBanUserDialog, - hideShortcutsNote, - showSuspendUserDialog, - hideSuspendUserDialog, - viewUserDetail, - hideUserDetail, - }, dispatch), -}); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - setCommentStatus, - getQueueCounts, - banUser, - suspendUser, - modQueueQuery, -)(ModerationContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js b/client/coral-admin/src/routes/Moderation/components/ModerationHeader.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationHeader.js rename to client/coral-admin/src/routes/Moderation/components/ModerationHeader.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationLayout.js b/client/coral-admin/src/routes/Moderation/components/ModerationLayout.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/ModerationLayout.js rename to client/coral-admin/src/routes/Moderation/components/ModerationLayout.js diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js b/client/coral-admin/src/routes/Moderation/components/ModerationMenu.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/components/ModerationMenu.js rename to client/coral-admin/src/routes/Moderation/components/ModerationMenu.js diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js similarity index 89% rename from client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js rename to client/coral-admin/src/routes/Moderation/components/ModerationQueue.js index bdcf82eef..234c9cbcb 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/routes/Moderation/components/ModerationQueue.js @@ -1,13 +1,12 @@ import React, {PropTypes} from 'react'; -import Comment from './components/Comment'; -import styles from './components/styles.css'; -import EmptyCard from '../../components/EmptyCard'; -import {actionsMap} from './helpers/moderationQueueActionsMap'; +import Comment from '../containers/Comment'; +import styles from './styles.css'; +import EmptyCard from '../../../components/EmptyCard'; +import {actionsMap} from '../helpers/moderationQueueActionsMap'; +import LoadMore from './LoadMore'; import t from 'coral-framework/services/i18n'; -import LoadMore from './components/LoadMore'; - class ModerationQueue extends React.Component { static propTypes = { @@ -54,6 +53,8 @@ class ModerationQueue extends React.Component { ? comments.map((comment, i) => { const status = comment.action_summaries ? 'FLAGGED' : comment.status; return { + this.props.hideSuspendUserDialog(); + try { + const result = await this.props.suspendUser(args); + if (result.data.suspendUser.errors) { + throw result.data.suspendUser.errors; + } + notification.success( + t('suspenduser.notify_suspend_until', + this.props.moderation.suspendUserDialog.username, + timeago(args.until)), + ); + const {commentStatus, commentId} = this.props.moderation.suspendUserDialog; + if (commentStatus !== 'REJECTED') { + return this.props.rejectComment({commentId}) + .then((result) => { + if (result.data.setCommentStatus.errors) { + throw result.data.setCommentStatus.errors; + } + }); + } + } + catch(err) { + notification.showMutationErrors(err); + } + }; + + banUser = ({userId}) => { + return this.props.setUserStatus({userId, status: 'BANNED'}); + } + + acceptComment = ({commentId}) => { + return this.props.setCommentStatus({commentId, status: 'ACCEPTED'}); + } + + rejectComment = ({commentId}) => { + return this.props.setCommentStatus({commentId, status: 'REJECTED'}); + } + + loadMore = ({limit = 10, cursor, sort, tab, asset_id}) => { + let variables = { + limit, + cursor, + sort, + asset_id + }; + switch(tab) { + case 'all': + variables.statuses = null; + break; + case 'accepted': + variables.statuses = ['ACCEPTED']; + break; + case 'premod': + variables.statuses = ['PREMOD']; + break; + case 'flagged': + variables.statuses = ['NONE', 'PREMOD']; + variables.action_type = 'FLAG'; + break; + case 'rejected': + variables.statuses = ['REJECTED']; + break; + } + return this.props.data.fetchMore({ + query: LOAD_MORE_QUERY, + variables, + updateQuery: (oldData, {fetchMoreResult:{comments}}) => { + return { + ...oldData, + [tab]: [ + ...oldData[tab], + ...comments + ] + }; + } + }); + }; + + render () { + const {root, data} = this.props; + + if (data.error) { + return
Error
; + } + + if (!('premodCount' in root)) { + return
; + } + + return ; + } +} + +const LOAD_MORE_QUERY = gql` + query CoralAdmin_Moderation_LoadMore($limit: Int = 10, $cursor: Date, $sort: SORT_ORDER, $asset_id: ID, $statuses:[COMMENT_STATUS!], $action_type: ACTION_TYPE) { + comments(query: {limit: $limit, cursor: $cursor, asset_id: $asset_id, statuses: $statuses, sort: $sort, action_type: $action_type}) { + ...${getDefinitionName(Comment.fragments.comment)} + action_summaries { + count + ... on FlagActionSummary { + reason + } + } + } + } + ${Comment.fragments.comment} +`; + +const withModQueueQuery = withQuery(gql` + query CoralAdmin_Moderation($asset_id: ID, $sort: SORT_ORDER) { + all: comments(query: { + statuses: [NONE, PREMOD, ACCEPTED, REJECTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + accepted: comments(query: { + statuses: [ACCEPTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + premod: comments(query: { + statuses: [PREMOD], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + flagged: comments(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD], + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + rejected: comments(query: { + statuses: [REJECTED], + asset_id: $asset_id, + sort: $sort + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + assets: assets { + id + title + url + } + allCount: commentCount(query: { + asset_id: $asset_id + }) + acceptedCount: commentCount(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) + settings { + organizationName + } + } + ${Comment.fragments.comment} +`, { + options: ({params: {id = null}, moderation: {sortOrder}}) => { + return { + variables: { + asset_id: id, + sort: sortOrder, + } + }; + }, +}); + +const withQueueCountPolling = withQuery(gql` + query CoralAdmin_ModerationCountPoll($asset_id: ID) { + allCount: commentCount(query: { + asset_id: $asset_id + }) + acceptedCount: commentCount(query: { + statuses: [ACCEPTED], + asset_id: $asset_id + }) + premodCount: commentCount(query: { + statuses: [PREMOD], + asset_id: $asset_id + }) + rejectedCount: commentCount(query: { + statuses: [REJECTED], + asset_id: $asset_id + }) + flaggedCount: commentCount(query: { + action_type: FLAG, + asset_id: $asset_id, + statuses: [NONE, PREMOD] + }) + } +`, { + options: ({params: {id = null}}) => { + return { + pollInterval: 5000, + variables: { + asset_id: id + } + }; + } +}); + +const mapStateToProps = (state) => ({ + moderation: state.moderation.toJS(), + settings: state.settings.toJS(), + auth: state.auth.toJS(), + assets: state.assets.get('assets') +}); + +const mapDispatchToProps = (dispatch) => ({ + ...bindActionCreators({ + toggleModal, + singleView, + updateAssets, + fetchSettings, + showBanUserDialog, + hideBanUserDialog, + hideShortcutsNote, + showSuspendUserDialog, + hideSuspendUserDialog, + viewUserDetail, + hideUserDetail, + setSortOrder, + }, dispatch), +}); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withSetCommentStatus, + withSetUserStatus, + withSuspendUser, + withQueueCountPolling, + withModQueueQuery, +)(ModerationContainer); diff --git a/client/coral-admin/src/routes/Moderation/containers/UserDetail.js b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js new file mode 100644 index 000000000..d6ab06af6 --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/containers/UserDetail.js @@ -0,0 +1,62 @@ +import React, {PropTypes} from 'react'; +import {compose, gql} from 'react-apollo'; +import UserDetail from '../components/UserDetail'; +import withQuery from 'coral-framework/hocs/withQuery'; +import {getSlotsFragments} from 'coral-framework/helpers/plugins'; +import {getDefinitionName} from 'coral-framework/utils'; +import Comment from './Comment'; + +const pluginFragments = getSlotsFragments([ + 'userProfile', +]); + +class UserDetailContainer extends React.Component { + static propTypes = { + id: PropTypes.string.isRequired, + hideUserDetail: PropTypes.func.isRequired + } + + render () { + if (!('user' in this.props.root)) { + return null; + } + + return ; + } +} + +export const withUserDetailQuery = withQuery(gql` + query CoralAdmin_UserDetail($author_id: ID!) { + user(id: $author_id) { + id + username + created_at + profiles { + id + provider + } + ${pluginFragments.spreads('user')} + } + totalComments: commentCount(query: {author_id: $author_id}) + rejectedComments: commentCount(query: {author_id: $author_id, statuses: [REJECTED]}) + comments: comments(query: { + author_id: $author_id, + statuses: [NONE, PREMOD, ACCEPTED, REJECTED] + }) { + ...${getDefinitionName(Comment.fragments.comment)} + } + ${pluginFragments.spreads('root')} + } + ${pluginFragments.definitions('user')} + ${pluginFragments.definitions('root')} +`, { + options: ({id}) => { + return { + variables: {author_id: id} + }; + } +}); + +export default compose( + withUserDetailQuery, +)(UserDetailContainer); diff --git a/client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js b/client/coral-admin/src/routes/Moderation/helpers/moderationQueueActionsMap.js similarity index 100% rename from client/coral-admin/src/containers/ModerationQueue/helpers/moderationQueueActionsMap.js rename to client/coral-admin/src/routes/Moderation/helpers/moderationQueueActionsMap.js diff --git a/client/coral-admin/src/routes/Moderation/index.js b/client/coral-admin/src/routes/Moderation/index.js new file mode 100644 index 000000000..e6bef5ef3 --- /dev/null +++ b/client/coral-admin/src/routes/Moderation/index.js @@ -0,0 +1,2 @@ +export {default as Moderation} from './containers/Moderation'; +export {default as ModerationLayout} from './components/ModerationLayout'; diff --git a/client/coral-admin/src/containers/Stories/Stories.css b/client/coral-admin/src/routes/Stories/components/Stories.css similarity index 100% rename from client/coral-admin/src/containers/Stories/Stories.css rename to client/coral-admin/src/routes/Stories/components/Stories.css diff --git a/client/coral-admin/src/containers/Stories/Stories.js b/client/coral-admin/src/routes/Stories/components/Stories.js similarity index 91% rename from client/coral-admin/src/containers/Stories/Stories.js rename to client/coral-admin/src/routes/Stories/components/Stories.js index 292eaeaba..7f9098fba 100644 --- a/client/coral-admin/src/containers/Stories/Stories.js +++ b/client/coral-admin/src/routes/Stories/components/Stories.js @@ -1,8 +1,6 @@ import React, {Component} from 'react'; import styles from './Stories.css'; -import {connect} from 'react-redux'; import t from 'coral-framework/services/i18n'; -import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; import {Link} from 'react-router'; import {Pager, Icon} from 'coral-ui'; @@ -12,7 +10,7 @@ import sortBy from 'lodash/sortBy'; const limit = 25; -class Stories extends Component { +export default class Stories extends Component { state = { search: '', @@ -171,19 +169,3 @@ class Stories extends Component { } } -const mapStateToProps = ({assets}) => { - return { - assets: assets.toJS() - }; -}; - -const mapDispatchToProps = (dispatch) => { - return { - fetchAssets: (...args) => { - dispatch(fetchAssets.apply(this, args)); - }, - updateAssetState: (...args) => dispatch(updateAssetState.apply(this, args)) - }; -}; - -export default connect(mapStateToProps, mapDispatchToProps)(Stories); diff --git a/client/coral-admin/src/routes/Stories/containers/Stories.js b/client/coral-admin/src/routes/Stories/containers/Stories.js new file mode 100644 index 000000000..450efcf74 --- /dev/null +++ b/client/coral-admin/src/routes/Stories/containers/Stories.js @@ -0,0 +1,27 @@ +import React, {Component} from 'react'; +import {connect} from 'react-redux'; +import {bindActionCreators} from 'redux'; +import {compose} from 'react-apollo'; +import {fetchAssets, updateAssetState} from 'coral-admin/src/actions/assets'; +import Stories from '../components/Stories'; + +class StoriesContainer extends Component { + render () { + return ; + } +} + +const mapStateToProps = (state) => ({ + assets: state.assets.toJS() +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators({ + fetchAssets, + updateAssetState, + }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), +)(StoriesContainer); + diff --git a/client/coral-admin/src/routes/Stories/index.js b/client/coral-admin/src/routes/Stories/index.js new file mode 100644 index 000000000..5b2999b21 --- /dev/null +++ b/client/coral-admin/src/routes/Stories/index.js @@ -0,0 +1 @@ +export {default} from './containers/Stories.js'; diff --git a/client/coral-admin/src/services/PymConnection.js b/client/coral-admin/src/services/PymConnection.js deleted file mode 100644 index ca592b824..000000000 --- a/client/coral-admin/src/services/PymConnection.js +++ /dev/null @@ -1,9 +0,0 @@ -import Pym from '../../node_modules/pym.js'; - -const pym = new Pym.Child({polling: 100}); -export default pym; - -export const link = (url) => (e) => { - e.preventDefault(); - pym.sendMessage('navigate', url); -}; diff --git a/client/coral-admin/src/services/client.js b/client/coral-admin/src/services/client.js index ff8216373..df97066bc 100644 --- a/client/coral-admin/src/services/client.js +++ b/client/coral-admin/src/services/client.js @@ -4,13 +4,17 @@ import fragmentMatcher from './fragmentMatcher'; export const client = new ApolloClient({ fragmentMatcher, + connectToDevTools: true, addTypename: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle - return result.__typename + result.id; // eslint-disable-line no-underscore-dangle + return `${result.__typename}_${result.id}`; // eslint-disable-line no-underscore-dangle } return null; }, networkInterface }); + +export default client; + diff --git a/client/coral-admin/src/services/config.js b/client/coral-admin/src/services/config.js deleted file mode 100644 index 05cf0ecb8..000000000 --- a/client/coral-admin/src/services/config.js +++ /dev/null @@ -1,14 +0,0 @@ - -/** - * Just load the root config file and throw an - * error message if not present - */ - -try { - module.exports = require('../../config.json'); -} catch (error) { - const message = `The config.json file under the root directory is missing -or invalid Please add one to use this app. You can use config.sample.json as a guide.`; - window.alert(message); - throw new Error(message); -} diff --git a/client/coral-admin/src/services/notification.js b/client/coral-admin/src/services/notification.js index cc25b0756..5e8673060 100644 --- a/client/coral-admin/src/services/notification.js +++ b/client/coral-admin/src/services/notification.js @@ -13,13 +13,14 @@ export function info(msg) { return toast(msg, {type: 'info'}); } -export function showMutationErrors(err) { - const errors = Array.isArray(err) ? err : [err]; - errors.forEach((err) => { - console.error(err); - toast( - err.translation_key ? t(`error.${err.translation_key}`) : err, - {type: 'error'} - ); - }); +export function showMutationErrors(error) { + console.error(error); + if (error.errors) { + error.errors.forEach((err) => { + toast( + err.translation_key ? t(`error.${err.translation_key}`) : err, + {type: 'error'} + ); + }); + } } diff --git a/client/coral-admin/src/services/store.js b/client/coral-admin/src/services/store.js index bf2735e45..2ab9d3750 100644 --- a/client/coral-admin/src/services/store.js +++ b/client/coral-admin/src/services/store.js @@ -14,7 +14,7 @@ if (window.devToolsExtension) { middlewares.push(window.devToolsExtension()); } -export default createStore( +const store = createStore( combineReducers({ ...mainReducer, apollo: client.reducer() @@ -22,3 +22,6 @@ export default createStore( {}, compose(...middlewares) ); + +window.coralStore = store; +export default store; diff --git a/client/coral-admin/src/services/subscriptions.js b/client/coral-admin/src/services/subscriptions.js deleted file mode 100644 index f514ea18b..000000000 --- a/client/coral-admin/src/services/subscriptions.js +++ /dev/null @@ -1,14 +0,0 @@ -import {print} from 'graphql-tag/printer'; - -// quick way to add the subscribe and unsubscribe functions to the network interface -const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, { - subscribe: (request, handler) => wsClient.subscribe({ - query: print(request.query), - variables: request.variables, - }, handler), - unsubscribe: (id) => { - wsClient.unsubscribe(id); - }, -}); - -export default addGraphQLSubscriptions; diff --git a/client/coral-embed-stream/src/containers/Comment.js b/client/coral-embed-stream/src/containers/Comment.js index 72a12c92e..8122b1900 100644 --- a/client/coral-embed-stream/src/containers/Comment.js +++ b/client/coral-embed-stream/src/containers/Comment.js @@ -15,14 +15,14 @@ const pluginFragments = getSlotsFragments([ export default withFragments({ root: gql` - fragment Comment_root on RootQuery { + fragment CoralEmbedStream_Comment_root on RootQuery { __typename ${pluginFragments.spreads('root')} } ${pluginFragments.definitions('root')} `, comment: gql` - fragment Comment_comment on Comment { + fragment CoralEmbedStream_Comment_comment on Comment { id body created_at diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 7309965a4..05dd9fb91 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -60,7 +60,7 @@ class EmbedContainer extends React.Component { } const EMBED_QUERY = gql` - query EmbedQuery($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) { + query CoralEmbedStream_Embed($assetId: ID, $assetUrl: String, $commentId: ID!, $hasComment: Boolean!, $excludeIgnored: Boolean) { asset(id: $assetId, url: $assetUrl) { totalCommentCount(excludeIgnored: $excludeIgnored) } diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js index dddfd3c16..f3cb1bfe9 100644 --- a/client/coral-embed-stream/src/containers/Stream.js +++ b/client/coral-embed-stream/src/containers/Stream.js @@ -132,7 +132,7 @@ class StreamContainer extends React.Component { } const LOAD_COMMENT_COUNTS_QUERY = gql` - query LoadCommentCounts($assetUrl: String, $assetId: ID, $excludeIgnored: Boolean) { + query CoralEmbedStream_LoadCommentCounts($assetUrl: String, $assetId: ID, $excludeIgnored: Boolean) { asset(id: $assetId, url: $assetUrl) { id commentCount(excludeIgnored: $excludeIgnored) @@ -145,7 +145,7 @@ const LOAD_COMMENT_COUNTS_QUERY = gql` `; const LOAD_MORE_QUERY = gql` - query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { + query CoralEmbedStream_LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $excludeIgnored: Boolean) { new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, excludeIgnored: $excludeIgnored}) { ...${getDefinitionName(Comment.fragments.comment)} replyCount(excludeIgnored: $excludeIgnored) @@ -159,7 +159,7 @@ const LOAD_MORE_QUERY = gql` const fragments = { root: gql` - fragment Stream_root on RootQuery { + fragment CoralEmbedStream_Stream_root on RootQuery { comment(id: $commentId) @include(if: $hasComment) { ...${getDefinitionName(Comment.fragments.comment)} replyCount(excludeIgnored: $excludeIgnored) diff --git a/client/coral-embed-stream/src/graphql/index.js b/client/coral-embed-stream/src/graphql/index.js index 548834a94..3b40e0028 100644 --- a/client/coral-embed-stream/src/graphql/index.js +++ b/client/coral-embed-stream/src/graphql/index.js @@ -9,23 +9,6 @@ const extension = { comment { status } - errors { - translation_key - } - } - `, - StopIgnoringUserResponse: gql` - fragment CoralEmbedStream_StopIgnoringUserResponse on StopIgnoringUserResponse { - errors { - translation_key - } - } - `, - IgnoreUserResponse: gql` - fragment CoralEmbedStream_IgnoreUserResponse on IgnoreUserResponse { - errors { - translation_key - } } `, RemoveCommentTagResponse: gql` @@ -36,9 +19,6 @@ const extension = { name } } - errors { - translation_key - } } `, AddCommentTagResponse: gql` @@ -49,16 +29,6 @@ const extension = { name } } - errors { - translation_key - } - } - `, - DeleteActionResponse: gql` - fragment CoralEmbedStream_DeleteActionResponse on DeleteActionResponse { - errors { - translation_key - } } `, CreateFlagResponse: gql` @@ -66,9 +36,6 @@ const extension = { flag { id } - errors { - translation_key - } } `, CreateDontAgreeResponse : gql` @@ -76,9 +43,6 @@ const extension = { dontagree { id } - errors { - translation_key - } } `, CreateCommentResponse: gql` @@ -89,9 +53,6 @@ const extension = { ...CoralEmbedStream_CreateCommentResponse_Comment } } - errors { - translation_key - } } fragment CoralEmbedStream_CreateCommentResponse_Comment on Comment { @@ -124,7 +85,7 @@ const extension = { mutations: { IgnoreUser: ({variables}) => ({ updateQueries: { - EmbedQuery: (previousData) => { + CoralEmbedStream_Embed: (previousData) => { const ignoredUserId = variables.id; const updated = update(previousData, {me: {ignoredUsers: {$push: [{ id: ignoredUserId, @@ -136,7 +97,7 @@ const extension = { }), StopIgnoringUser: ({variables}) => ({ updateQueries: { - EmbedStreamProfileQuery: (previousData) => { + CoralEmbedStream_Profile: (previousData) => { const noLongerIgnoredUserId = variables.id; // remove noLongerIgnoredUserId from ignoredUsers @@ -172,7 +133,7 @@ const extension = { } }, updateQueries: { - EmbedQuery: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => { + CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {createComment: {comment}}}}) => { if (previousData.asset.settings.moderation === 'PRE' || comment.status === 'PREMOD' || comment.status === 'REJECTED') { return previousData; } @@ -213,7 +174,7 @@ const extension = { variables: {id, edit}, }) => ({ updateQueries: { - EmbedQuery: (previousData, {mutationResult: {data: {editComment: {comment}}}}) => { + CoralEmbedStream_Embed: (previousData, {mutationResult: {data: {editComment: {comment}}}}) => { const {status} = comment; const updateCommentWithEdit = (comment, edit) => { const {body} = edit; diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 5e7c66555..06d52649a 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -8,11 +8,13 @@ import './graphql'; import {addExternalConfig} from 'coral-embed-stream/src/actions/config'; import reducers from './reducers'; -import store, {injectReducers} from 'coral-framework/services/store'; +import {getStore, injectReducers} from 'coral-framework/services/store'; import AppRouter from './AppRouter'; import {pym} from 'coral-framework'; import {loadPluginsTranslations} from 'coral-framework/helpers/plugins'; +const store = getStore(); + loadPluginsTranslations(); injectReducers(reducers); diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index f4948ed05..9c396cbaa 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -1,2 +1,23 @@ +import {gql} from 'react-apollo'; +import * as mutations from './mutations'; + +function createDefaultResponseFragments() { + const names = Object.keys(mutations).map((key) => key.replace('with', '')); + const result = {}; + names.forEach((name) => { + const response = `${name}Response`; + result[response] = gql` + fragment Coral_${response} on ${response} { + errors { + translation_key + } + } + `; + }); + return result; +} + // fragments defined here are automatically registered. -export default {}; +export default { + ...createDefaultResponseFragments() +}; diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 0744df35b..d9c3cfe45 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -1,6 +1,84 @@ import {gql} from 'react-apollo'; import withMutation from '../hocs/withMutation'; +export const withSetCommentStatus = withMutation( + gql` + mutation SetCommentStatus($commentId: ID!, $status: COMMENT_STATUS!){ + setCommentStatus(id: $commentId, status: $status) { + ...SetCommentStatusResponse + } + } + `, { + props: ({mutate}) => ({ + setCommentStatus: ({commentId, status}) => { + return mutate({ + variables: { + commentId, + status, + }, + }); + } + }) + }); + +export const withSuspendUser = withMutation( + gql` + mutation SuspendUser($input: SuspendUserInput!) { + suspendUser(input: $input) { + ...SuspendUserResponse + } + } + `, { + props: ({mutate}) => ({ + suspendUser: (input) => { + return mutate({ + variables: { + input, + }, + }); + } + }) + }); + +export const withRejectUsername = withMutation( + gql` + mutation RejectUsername($input: RejectUsernameInput!) { + rejectUsername(input: $input) { + ...RejectUsernameResponse + } + } + `, { + props: ({mutate}) => ({ + rejectUsername: (input) => { + return mutate({ + variables: { + input, + }, + }); + } + }) + }); + +export const withSetUserStatus = withMutation( + gql` + mutation SetUserStatus($userId: ID!, $status: USER_STATUS!) { + setUserStatus(id: $userId, status: $status) { + ...SetUserStatusResponse + } + } + `, { + props: ({mutate}) => ({ + setUserStatus: ({userId, status}) => { + return mutate({ + variables: { + userId, + status + }, + }); + } + }), + }); + export const withPostComment = withMutation( gql` mutation PostComment($comment: CreateCommentInput!) { diff --git a/client/coral-framework/hocs/withMutation.js b/client/coral-framework/hocs/withMutation.js index 285932372..5cccaa062 100644 --- a/client/coral-framework/hocs/withMutation.js +++ b/client/coral-framework/hocs/withMutation.js @@ -5,7 +5,7 @@ import uniq from 'lodash/uniq'; import flatten from 'lodash/flatten'; import isEmpty from 'lodash/isEmpty'; import {getMutationOptions, resolveFragments} from 'coral-framework/services/graphqlRegistry'; -import {store} from 'coral-framework/services/store'; +import {getStore} from 'coral-framework/services/store'; import {getDefinitionName, getResponseErrors} from '../utils'; import t from 'coral-framework/services/i18n'; @@ -41,7 +41,7 @@ export default (document, config) => (WrappedComponent) => { const callbacks = getMutationOptions(name); const mutate = (base) => { const variables = base.variables || config.options.variables; - const configs = callbacks.map((cb) => cb({variables, state: store.getState()})); + const configs = callbacks.map((cb) => cb({variables, state: getStore().getState()})); const optimisticResponse = merge( base.optimisticResponse || config.options.optimisticResponse, diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js index 47ea9c352..c77bc9848 100644 --- a/client/coral-framework/services/client.js +++ b/client/coral-framework/services/client.js @@ -13,6 +13,7 @@ import {networkInterface} from './transport'; // ); export const client = new ApolloClient({ connectToDevTools: true, + addTypename: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle @@ -24,3 +25,4 @@ export const client = new ApolloClient({ }); export default client; + diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index 22e126d75..0530d8c28 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -24,22 +24,29 @@ if (window.devToolsExtension) { middlewares.push(window.devToolsExtension()); } -let storeReducers = { - ...mainReducer, - apollo: client.reducer() -}; - -export const store = createStore( - combineReducers(storeReducers), - {}, - compose(...middlewares) -); - -export default store; - export function injectReducers(reducers) { - storeReducers = {...storeReducers, ...reducers}; - store.replaceReducer(combineReducers(storeReducers)); + const store = getStore(); + store.coralReducers = {...store.coralReducers, ...reducers}; + store.replaceReducer(combineReducers(store.coralReducers)); } -window.coralStore = store; +export function getStore() { + if (window.coralStore) { + return window.coralStore; + } + + const coralReducers = { + ...mainReducer, + apollo: client.reducer() + }; + + window.coralStore = createStore( + combineReducers(coralReducers), + {}, + compose(...middlewares) + ); + + window.coralStore.coralReducers = coralReducers; + + return window.coralStore; +} diff --git a/client/coral-framework/utils/index.js b/client/coral-framework/utils/index.js index c8e68517a..ca2dd5ab2 100644 --- a/client/coral-framework/utils/index.js +++ b/client/coral-framework/utils/index.js @@ -55,11 +55,12 @@ export function separateDataAndRoot( subscribeToMore, updateQuery, variables, + error, ...root, }) { return { data: {fetchMore, loading, networkStatus, refetch, startPolling, - stopPolling, subscribeToMore, updateQuery, variables}, + stopPolling, subscribeToMore, updateQuery, variables, error}, root, }; } diff --git a/client/coral-settings/containers/ProfileContainer.js b/client/coral-settings/containers/ProfileContainer.js index 245780f0f..d95a50f13 100644 --- a/client/coral-settings/containers/ProfileContainer.js +++ b/client/coral-settings/containers/ProfileContainer.js @@ -82,7 +82,7 @@ class ProfileContainer extends Component { const withQuery = graphql( gql` - query EmbedStreamProfileQuery { + query CoralEmbedStream_Profile { me { id ignoredUsers { diff --git a/services/users.js b/services/users.js index f7f70b99e..09ddd8ce9 100644 --- a/services/users.js +++ b/services/users.js @@ -381,15 +381,18 @@ module.exports = class UsersService { * @param {Function} done callback after the operation is complete */ static addRoleToUser(id, role) { + const roles = []; // Check to see if the user role is in the allowable set of roles. - if (USER_ROLES.indexOf(role) === -1) { + if (role && USER_ROLES.indexOf(role) === -1) { // User role is not supported! Error out here. return Promise.reject(new Error(`role ${role} is not supported`)); + } else if(role) { + roles.push(role); } - return UserModel.update({id}, {$set: {roles: [role]}}); + return UserModel.update({id}, {$set: {roles}}); } /**