diff --git a/client/coral-admin/src/components/ActionButton.css b/client/coral-admin/src/components/ActionButton.css
new file mode 100644
index 000000000..226006c6b
--- /dev/null
+++ b/client/coral-admin/src/components/ActionButton.css
@@ -0,0 +1,24 @@
+.actionButton {
+ transform: scale(.8);
+ margin: 0;
+}
+
+.minimal {
+ width: 45px;
+ min-width: 0;
+}
+
+.approve__active {
+ box-shadow: none;
+ color: white;
+ background-color: #519954;
+ cursor: not-allowed;
+}
+
+.reject__active, .rejected__active {
+ color: white;
+ background-color: #D03235;
+ box-shadow: none;
+ cursor: not-allowed;
+}
+
diff --git a/client/coral-admin/src/components/ActionButton.js b/client/coral-admin/src/components/ActionButton.js
index 79fa2f182..77456b939 100644
--- a/client/coral-admin/src/components/ActionButton.js
+++ b/client/coral-admin/src/components/ActionButton.js
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import styles from './ModerationList.css';
+import styles from './ActionButton.css';
import {Button} from 'coral-ui';
import {menuActionsMap} from '../utils/moderationQueueActionsMap';
diff --git a/client/coral-admin/src/components/ModerationList.css b/client/coral-admin/src/components/ModerationList.css
deleted file mode 100644
index 4ac894290..000000000
--- a/client/coral-admin/src/components/ModerationList.css
+++ /dev/null
@@ -1,208 +0,0 @@
-
-@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;
- }
-
- .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;
- }
-}
-
-.selected {
- border-radius: 10px;
-}
-
-
-.actionButton {
- transform: scale(.8);
- margin: 0;
-}
-
-.minimal {
- width: 45px;
- min-width: 0;
-}
-
-.approve__active {
- box-shadow: none;
- color: white;
- background-color: #519954;
- cursor: not-allowed;
-}
-
-.reject__active, .rejected__active {
- color: white;
- background-color: #D03235;
- box-shadow: none;
- cursor: not-allowed;
-}
diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js
deleted file mode 100644
index adfe63051..000000000
--- a/client/coral-admin/src/components/ModerationList.js
+++ /dev/null
@@ -1,228 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import styles from './ModerationList.css';
-import key from 'keymaster';
-import Hammer from 'hammerjs';
-import Comment from './Comment';
-import User from './User';
-import SuspendUserModal from './SuspendUserModal';
-
-// Each action has different meaning and configuration
-const menuOptionsMap = {
- 'reject': {status: 'REJECTED', icon: 'close', key: 'f'},
- 'approve': {status: 'ACCEPTED', icon: 'done', key: 'd'},
- 'flag': {status: 'FLAGGED', icon: 'flag', filter: 'Untouched'},
- 'ban': {status: 'BANNED', icon: 'not interested'}
-};
-
-// Renders a comment list and allow performing actions
-export default class ModerationList extends React.Component {
- static propTypes = {
- isActive: PropTypes.bool,
- singleView: PropTypes.bool,
- commentIds: PropTypes.arrayOf(PropTypes.string),
- actionIds: PropTypes.arrayOf(PropTypes.string),
- comments: PropTypes.object,
- users: PropTypes.object.isRequired,
- actions: PropTypes.object,
- userStatusUpdate: PropTypes.func.isRequired,
- suspendUser: PropTypes.func.isRequired,
-
- // list of actions (flags, etc) associated with the comments
- modActions: PropTypes.arrayOf(PropTypes.string).isRequired,
- loading: PropTypes.bool,
-
- suspectWords: PropTypes.arrayOf(PropTypes.string).isRequired
- }
-
- state = {active: null, suspendUserModal: null, email: null};
-
- // remove key handlers before leaving
- componentWillUnmount () {
- this.unbindKeyHandlers();
- }
-
- // add key handlers and gestures
- componentDidMount () {
- this.bindKeyHandlers();
-
- // this.bindGestures() // need to check whether we're on a mobile device or this throws an Error
- }
-
- // If entering to singleview and no active, active is the first eleement
- componentWillReceiveProps (nextProps) {
- if (nextProps.singleView && !this.state.active) {
- this.setState({active: nextProps.commentIds[0]});
- }
- }
-
- // Add swipe to approve or reject
- bindGestures () {
- const {modActions} = this.props;
- this._hammer = new Hammer(this.base);
- this._hammer.get('swipe').set({direction: Hammer.DIRECTION_HORIZONTAL});
-
- if (modActions.indexOf('reject') !== -1) {
- this._hammer.on('swipeleft', () => this.props.singleView && this.actionKeyHandler('Rejected'));
- }
- if (modActions.indexOf('approve') !== -1) {
- this._hammer.on('swiperight', () => this.props.singleView && this.actionKeyHandler('Approved'));
- }
- }
-
- // Add key handlers. Each action has one and added j/k for moving around
- bindKeyHandlers () {
- const {modActions, isActive} = this.props;
- modActions.filter((action) => menuOptionsMap[action].key).forEach((action) => {
- key(menuOptionsMap[action].key, 'moderationList', () => isActive && this.actionKeyHandler(menuOptionsMap[action].status));
- });
- key('j', 'moderationList', () => isActive && this.moveKeyHandler('down'));
- key('k', 'moderationList', () => isActive && this.moveKeyHandler('up'));
- key.setScope('moderationList');
- }
-
- // Perform an action using the keys only if the comment is active
- actionKeyHandler (action) {
- if (this.props.isActive && this.state.active) {
- this.onClickAction(action, this.state.active);
- }
- }
-
- // move around with j/k
- moveKeyHandler (direction) {
- if (!this.props.isActive) {
- return;
- }
-
- const {commentIds} = this.props;
- const {active} = this.state;
-
- // check boundaries
- if (active === null || !commentIds.length) {
- this.setState({active: commentIds[0]});
- } else if (direction === 'up' && active !== commentIds[0]) {
- this.setState({active: commentIds[commentIds.indexOf(active) - 1]});
- } else if (direction === 'down' && active !== commentIds[commentIds.length - 1]) {
- this.setState({active: commentIds[commentIds.indexOf(active) + 1]});
- }
-
- // scroll to the position
- const index = Math.max(commentIds.indexOf(this.state.active), 0);
- this.base.childNodes[index] && this.base.childNodes[index].focus();
- }
-
- unbindKeyHandlers () {
- key.deleteScope('moderationList');
- }
-
- // If we are performing an action over a comment (aka removing from the list) we need to select a new active.
- // TODO: In the future this can be improved and look at the actual state to
- // resolve since the content of the list could change externally. For now it works as expected
- onClickAction = (menuOption, id, action) => {
-
- // activate the next comment
- if (id === this.state.active) {
- const moderationIds = this.getModerationIds();
- if (moderationIds[moderationIds.length - 1] === this.state.active) {
- this.setState({active: moderationIds[moderationIds.length - 2]});
- } else {
- this.setState({active: moderationIds[Math.min(moderationIds.indexOf(this.state.active) + 1, moderationIds.length - 1)]});
- }
- }
-
- // Update the status right away if this is a comment
- if (action.item_type === 'COMMENTS') {
- this.props.updateCommentStatus(menuOption, id);
- } else if (action.item_type === 'USERS') {
-
- // If a user bio or name is rejected, bring up a dialog before suspending them.
- if (menuOption === 'REJECTED') {
- this.setState({suspendUserModal: action});
- } else if (menuOption === 'ACCEPTED') {
- this.props.userStatusUpdate('APPROVED', action.item_id);
- }
- }
- }
-
- onClickShowBanDialog = (userId, userName, commentId) => {
- this.props.onClickShowBanDialog(userId, userName, commentId);
- }
-
- mapModItems = (itemId, index) => {
-
- const {comments = {}, users, actions = {}, modActions, suspectWords, hideActive} = this.props;
- const {active} = this.state;
-
- // Because ids are unique, the id will either appear as an action or as a comment.
-
- const item = comments[itemId] || actions[itemId];
- let modItem;
-
- if (item.body) {
-
- // If the item is a comment...
- const author = users[item.author_id];
- modItem =