diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index cd1a3e59f..9968f25d3 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -29,7 +29,6 @@ class ModerationContainer extends Component { componentWillMount() { const {toggleModal, singleView} = this.props; - this.props.fetchModerationQueueComments(); this.props.fetchSettings(); key('s', () => singleView()); @@ -54,7 +53,7 @@ class ModerationContainer extends Component { } render () { - const {data, moderation} = this.props; + const {data, moderation, settings} = this.props; if (data.loading) { return
; @@ -66,27 +65,16 @@ class ModerationContainer extends Component { ); } } -// ModerationContainer.contextTypes = { -// router: React.PropTypes.func.isRequired -// }; - const mapStateToProps = state => ({ moderation: state.moderation.toJS(), - comments: state.comments.toJS(), - settings: state.settings.toJS(), - users: state.users.toJS(), - actions: state.actions.toJS() + settings: state.settings.toJS() }); const mapDispatchToProps = dispatch => ({ diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js index 961642e84..f1f3514ea 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationQueue.js @@ -1,22 +1,29 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; -// import Comment from '../../components/Comment'; +import Comment from './components/Comment'; -export default (props) => { +const ModerationQueue = props => { return (
- {/* */} +
    { - props.data[props.activeTab].map((comment, i) => -
    - {comment.body} -
    - ) + props.data[props.activeTab].map((comment, i) => { + console.log(comment); + return ; + }) } +
); }; + +ModerationQueue.propTypes = { + data: PropTypes.object.isRequired +}; + +export default ModerationQueue; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/Comment.js b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js new file mode 100644 index 000000000..a06f052e4 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/Comment.js @@ -0,0 +1,76 @@ +import React, {PropTypes} from 'react'; +import timeago from 'timeago.js'; +import Linkify from 'react-linkify'; +import Highlighter from 'react-highlight-words'; + +import styles from './styles.css'; + +import {Icon} from 'coral-ui'; + +// import ActionButton from '../components/ActionButton'; + +import I18n from 'coral-framework/modules/i18n/i18n'; +import translations from '../../../translations.json'; + +const Comment = props => { + const links = linkify.getMatches(props.body); + + return ( +
  • +
    +
    + {props.user.name} + + {timeago().format(props.created_at || (Date.now() - props.index * 60 * 1000), lang.getLocale().replace('-', '_'))} + + {props.flagged ?

    {lang.t('comment.flagged')}

    : null} +
    +
    + {links ? Contains Link : null} +
    + {/* {props.modActions.map(*/} + {/* (action, i) =>*/} + {/* */} + {/* )}*/} +
    + {props.user.banned === 'banned' ? + + + {lang.t('comment.banned_user')} + + : null} +
    +
    +
    + + + + + +
    +
  • + ); +}; + +const linkStyles = { + backgroundColor: 'rgb(255, 219, 135)', + padding: '1px 2px' +}; + +const linkify = new Linkify(); +const lang = new I18n(translations); + +Comment.propTypes = { + user: PropTypes.object.isRequired +}; + +export default Comment; diff --git a/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js b/client/coral-admin/src/containers/ModerationQueue/components/ModerationList.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-admin/src/containers/ModerationQueue/components/styles.css b/client/coral-admin/src/containers/ModerationQueue/components/styles.css new file mode 100644 index 000000000..fbcbce7d7 --- /dev/null +++ b/client/coral-admin/src/containers/ModerationQueue/components/styles.css @@ -0,0 +1,184 @@ + +@custom-media --big-viewport (min-width: 780px); + +.list { + padding: 8px 0; + list-style: none; + display: block; + + &.singleView .listItem { + display: none; + } + + &.singleView .listItem.activeItem { + display: block; + height: 100%; + font-size: 1.5em; + line-height: 1.5em; + border: none; + + .actions { + position: fixed; + bottom: 60px; + left: 25%; + margin: 0 auto; + display: flex; + justify-content: space-around; + width: 50%; + margin: 0; + } + + .actionButton { + transform: scale(1.4); + } + } +} + +.listItem { + border-bottom: 1px solid #e0e0e0; + font-size: 16px; + width: 100%; + max-width: 660px; + min-width: 400px; + margin: 0 auto; + padding: 16px 14px; + position: relative; + transition: box-shadow 200ms; + + + &:hover { + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); + } + + &:last-child { + border-bottom: none; + } + + .sideActions { + position: absolute; + right: 0; + height: 100%; + top: 0; + padding: 40px 18px; + box-sizing: border-box; + } + + .itemHeader { + display: flex; + align-items: center; + justify-content: space-between; + + .author { + min-width: 230px; + display: flex; + align-items: center; + } + } + + .itemBody { + display: flex; + justify-content: space-between; + } + + .avatar { + margin-right: 16px; + height: 40px; + width: 40px; + border-radius: 50%; + background-color: #757575; + font-size: 40px; + color: #fff; + } + + .created { + color: #666; + font-size: 13px; + margin-left: 40px; + } + + .actionButton { + transform: scale(.8); + margin: 0; + } + + .body { + margin-top: 20px; + flex: 1; + font-size: 0.88em; + color: black; + } + + .flagged { + color: rgba(255, 0, 0, .5); + padding-top: 15px; + padding-left: 10px; + } + + .flagCount{ + font-size: 12px; + color: #d32f2f; + } + +} + +.empty { + color: #444; + margin-top: 50px; + text-align: center; +} + + +@media (--big-viewport) { + .listItem { + border: 1px solid #e0e0e0; + margin-bottom: 30px; + + &:last-child { + border-bottom: 1px solid #e0e0e0; + } + + &.activeItem { + border: 2px solid #333; + } + } + +} + +.hasLinks { + color: #f00; + text-align: right; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.banned { + color: #f00; + text-align: left; + display: flex; + align-items: center; + + i { + margin-right: 5px; + } +} + +.ban { + display: block; + text-align: center; + margin-top: 5px; +} + +.banButton { + width: 114px; + letter-spacing: 1px; + + i { + vertical-align: middle; + margin-right: 10px; + font-size: 14px; + } +} diff --git a/client/coral-admin/src/reducers/actions.js b/client/coral-admin/src/reducers/actions.js deleted file mode 100644 index 284e41c72..000000000 --- a/client/coral-admin/src/reducers/actions.js +++ /dev/null @@ -1,27 +0,0 @@ -import {Map, Set, fromJS} from 'immutable'; -import * as types from '../constants/actions'; - -const initialState = Map({ - ids: Set(), - byId: Map() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case types.ACTIONS_MODERATION_QUEUE_FETCH_SUCCESS: return addActions(state, action); - default: - return state; - } -}; - -const addActions = (state, action) => { - - // Make ids that are unique by item_id and by action type - const typeId = (action) => `${action.action_type}_${action.item_id}`; - const ids = action.actions.map(action => typeId(action)); - const map = action.actions.reduce((memo, action) => { - memo[typeId(action)] = action; - return memo; - }, {}); - return state.set('byId', fromJS(map)).set('ids', new Set(ids)); -}; diff --git a/client/coral-admin/src/reducers/comments.js b/client/coral-admin/src/reducers/comments.js deleted file mode 100644 index 034016088..000000000 --- a/client/coral-admin/src/reducers/comments.js +++ /dev/null @@ -1,77 +0,0 @@ -import * as actions from '../constants/comments'; -import * as userActions from '../constants/users'; -import {Map, List, fromJS} from 'immutable'; - -/** - * Comments state is stored using 2 structures: - * - byId is a Map holding the comments using the item_id property as keys - * - ids is a List of item_id, this allows us to order and iterate easily - * since maps are unordered and some times we just need a list of things - */ - -const initialState = Map({ - byId: Map(), - ids: List(), - loading: false, - showBanUserDialog: false, - banUser: { - 'userName': '', - 'userId': '', - 'commentId': '' - } -}); - -// Handle the comment actions -export default (state = initialState, action) => { - switch (action.type) { - case actions.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST: return state.set('loading', true); - case actions.COMMENTS_MODERATION_QUEUE_FETCH_SUCCESS: return replaceComments(action, state); - case actions.COMMENTS_MODERATION_QUEUE_FAILED: return state.set('loading', false); - case actions.COMMENT_STATUS_UPDATE_REQUEST: return updateStatus(state, action); - case actions.COMMENT_FLAG: return flag(state, action); - case actions.COMMENT_CREATE_SUCCESS: return addComment(state, action); - case actions.COMMENT_STREAM_FETCH_SUCCESS: return replaceComments(action, state); - case actions.SHOW_BANUSER_DIALOG: return setBanUser(state, true, action); - case actions.HIDE_BANUSER_DIALOG: return setBanUser(state, false, action); - case actions.USER_BAN_SUCCESS: return setBanUser(state, false, action); - case userActions.UPDATE_STATUS_SUCCESS: return setBanUser(state, false, action); - default: return state; - } -}; - -// hide or show the UI for the dialog confirming the ban -// set the user that is going to set and the comment that is the reason -const setBanUser = (state, showBanUser, action) => { - const banUser = {'userName': action.userName, 'userId': action.userId, 'commentId': action.commentId}; - return state.set('showBanUserDialog', showBanUser) - .set('banUser', banUser); -}; - -// Update a comment status -const updateStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.id, data)); -}; - -// Flag a comment -const flag = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.id).set('flagged', true); - const comment = byId.get(action.id).set('data', data); - return state.set('byId', byId.set(action.id, comment)); -}; - -// Replace the comment list with a new one -const replaceComments = (action, state) => { - const comments = fromJS(action.comments.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', comments).set('loading', false) - .set('ids', List(comments.keys())); -}; - -// Add a new comment -const addComment = (state, action) => { - const comment = fromJS(action.comment); - return state.set('byId', state.get('byId').set(comment.get('item_id'), comment)) - .set('ids', state.get('ids').unshift(comment.get('item_id'))); -}; diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index 53256fac5..e58f5bf5a 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -1,19 +1,13 @@ import auth from './auth'; -import users from './users'; import assets from './assets'; -import actions from './actions'; -import comments from './comments'; import settings from './settings'; import community from './community'; import moderation from './moderation'; export default { auth, - users, assets, - actions, settings, - comments, community, moderation }; diff --git a/client/coral-admin/src/reducers/settings.js b/client/coral-admin/src/reducers/settings.js index 12b16d9ad..fa728b3ed 100644 --- a/client/coral-admin/src/reducers/settings.js +++ b/client/coral-admin/src/reducers/settings.js @@ -1,5 +1,5 @@ import {Map, List} from 'immutable'; -import * as types from '../actions/settings'; +import * as actions from '../actions/settings'; const initialState = Map({ settings: Map({ @@ -13,43 +13,46 @@ const initialState = Map({ fetchingSettings: false }); -// Handle the comment actions -export default (state = initialState, action) => { +export default function settings (state = initialState, action) { switch (action.type) { - case types.SETTINGS_LOADING: return state.set('fetchingSettings', true).set('fetchSettingsError', null); - case types.SETTINGS_RECEIVED: return updateSettings(state, action); - case types.SETTINGS_FETCH_ERROR: return settingsFetchFailed(state, action); - case types.SETTINGS_UPDATED: return updateSettings(state, action); - case types.SAVE_SETTINGS_LOADING: return state.set('fetchingSettings', true).set('saveSettingsError', null); - case types.SAVE_SETTINGS_SUCCESS: return saveComplete(state, action); - case types.SAVE_SETTINGS_FAILED: return settingsSaveFailed(state, action); - case types.WORDLIST_UPDATED: return updateWordlist(state, action); - default: return state; + case actions.SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('fetchSettingsError', null); + case actions.SETTINGS_RECEIVED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SETTINGS_FETCH_ERROR: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.SETTINGS_UPDATED: + return state.merge({ + fetchingSettings: null, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_LOADING: + return state + .set('fetchingSettings', true) + .set('saveSettingsError', null); + case actions.SAVE_SETTINGS_SUCCESS: + return state.merge({ + fetchingSettings: false, + fetchSettingsError: null, + ...action.settings + }); + case actions.SAVE_SETTINGS_FAILED: + return state + .set('fetchingSettings', false) + .set('fetchSettingsError', action.error); + case actions.WORDLIST_UPDATED: + return state + .setIn(['settings', 'wordlist', action.listName], action.list); + default: + return state; } -}; - -// only for updating top-level settings -const updateSettings = (state, action) => { - const s = state.set('fetchingSettings', false).set('fetchSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -// any nested settings must have a specialized setter -const updateWordlist = (state, action) => { - return state.setIn(['settings', 'wordlist', action.listName], action.list); -}; - -const saveComplete = (state, action) => { - const s = state.set('fetchingSettings', false).set('saveSettingsError', null); - const settings = s.get('settings').merge(action.settings); - return s.set('settings', settings); -}; - -const settingsFetchFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; - -const settingsSaveFailed = (state, action) => { - return state.set('fetchingSettings', false).set('fetchSettingsError', action.error); -}; +} diff --git a/client/coral-admin/src/reducers/users.js b/client/coral-admin/src/reducers/users.js deleted file mode 100644 index ef589c155..000000000 --- a/client/coral-admin/src/reducers/users.js +++ /dev/null @@ -1,28 +0,0 @@ -import {Map, List, fromJS} from 'immutable'; - -const initialState = Map({ - byId: Map(), - ids: List() -}); - -export default (state = initialState, action) => { - switch (action.type) { - case 'USERS_MODERATION_QUEUE_FETCH_SUCCESS': return replaceUsers(action, state); - case 'USER_STATUS_UPDATE': return updateUserStatus(state, action); - default: return state; - } -}; - -// Replace the comment list with a new one -const replaceUsers = (action, state) => { - const users = fromJS(action.users.reduce((prev, curr) => { prev[curr.id] = curr; return prev; }, {})); - return state.set('byId', users) - .set('ids', List(users.keys())); -}; - -// Update a user status -const updateUserStatus = (state, action) => { - const byId = state.get('byId'); - const data = byId.get(action.author_id).set('status', action.status.toLowerCase()); - return state.set('byId', byId.set(action.author_id, data)); -}; diff --git a/client/coral-admin/src/services/client.js b/client/coral-admin/src/services/client.js index b4a7a38df..40a539634 100644 --- a/client/coral-admin/src/services/client.js +++ b/client/coral-admin/src/services/client.js @@ -2,7 +2,6 @@ import ApolloClient, {addTypename} from 'apollo-client'; import getNetworkInterface from './transport'; export const client = new ApolloClient({ - connectToDevTools: true, queryTransformer: addTypename, dataIdFromObject: (result) => { if (result.id && result.__typename) { // eslint-disable-line no-underscore-dangle