diff --git a/client/coral-admin/src/components/ActionsMenu.css b/client/coral-admin/src/components/ActionsMenu.css index 5d263ca21..c8638423f 100644 --- a/client/coral-admin/src/components/ActionsMenu.css +++ b/client/coral-admin/src/components/ActionsMenu.css @@ -44,6 +44,7 @@ } &:hover, &:active, &:focus { background-color: #e2e2e2; + border-color: #616161; } &[disabled], &[disabled]:hover, &[disabled]:focus, &[disabled]:active { background-color: #262626; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index d1e7327d3..a7b0c495b 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -9,14 +9,13 @@ import ApproveButton from './ApproveButton'; import AccountHistory from './AccountHistory'; import {Slot} from 'coral-framework/components'; import UserDetailCommentList from '../components/UserDetailCommentList'; -import {getReliability} from 'coral-framework/utils/user'; +import {getReliability, isSuspended, isBanned} from 'coral-framework/utils/user'; import ButtonCopyToClipboard from './ButtonCopyToClipboard'; import ClickOutside from 'coral-framework/components/ClickOutside'; import {Icon, Drawer, Spinner, TabBar, Tab, TabContent, TabPane} from 'coral-ui'; import ActionsMenu from 'coral-admin/src/components/ActionsMenu'; import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; import UserInfoTooltip from './UserInfoTooltip'; -import get from 'lodash/get'; class UserDetail extends React.Component { @@ -91,12 +90,9 @@ class UserDetail extends React.Component { getActionMenuLabel() { const {root: {user}} = this.props; - const banned = get(user, 'state.status.banned.status'); - const suspensionUntil = get(user, 'state.status.suspension.until'); - - if (banned) { + if (isBanned(user)) { return 'Banned'; - } else if (suspensionUntil && new Date(suspensionUntil) > new Date()) { + } else if (isSuspended(user)) { return 'Suspended'; } @@ -132,13 +128,8 @@ class UserDetail extends React.Component { rejectedPercent = 0; } - const banned = get(user, 'state.status.banned.status'); - const suspensionUntil = get(user, 'state.status.suspension.until'); - - const suspended = - user && - suspensionUntil && - new Date(suspensionUntil) > new Date(); + const banned = isBanned(user); + const suspended = isSuspended(user); return ( diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 9cc65b1a5..d5ada0557 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -3,8 +3,48 @@ import {mapLeaves} from 'coral-framework/utils'; export default { mutations: { + BanUser: ({variables: {input: {id: userId}}}) => ({ + updateQueries: { + TalkAdmin_Community: (prev) => { + + const updated = update(prev, { + users: { + nodes: { + $apply: (nodes) => nodes.map((node) => { + if (node.id === userId) { + node.state.status.banned.status = true; + } + + return node; + }) + }, + }, + }); + + return updated; + } + } + }), UnBanUser: ({variables: {input: {id: userId}}}) => ({ updateQueries: { + TalkAdmin_Community: (prev) => { + + const updated = update(prev, { + users: { + nodes: { + $apply: (nodes) => nodes.map((node) => { + if (node.id === userId) { + node.state.status.banned.status = false; + } + + return node; + }) + }, + }, + }); + + return updated; + }, CoralAdmin_Moderation: (prev) => { const updated = update(prev, { diff --git a/client/coral-admin/src/routes/Community/components/Community.js b/client/coral-admin/src/routes/Community/components/Community.js index 3b60b357a..ef0e6bb78 100644 --- a/client/coral-admin/src/routes/Community/components/Community.js +++ b/client/coral-admin/src/routes/Community/components/Community.js @@ -12,7 +12,11 @@ class Community extends Component { const activeTab = route.path === ':id' ? 'flagged' : route.path; if (activeTab === 'people') { - return ; + return ; } return ( diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index 467175977..941173773 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -1,27 +1,60 @@ import React from 'react'; import cn from 'classnames'; import styles from './styles.css'; -import Table from './Table'; -import {Icon} from 'coral-ui'; +import {Icon, Dropdown, Option} from 'coral-ui'; import EmptyCard from '../../../components/EmptyCard'; import t from 'coral-framework/services/i18n'; - +import LoadMore from '../../../components/LoadMore'; import PropTypes from 'prop-types'; +import ActionsMenu from 'coral-admin/src/components/ActionsMenu'; +import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; +import {isSuspended, isBanned} from 'coral-framework/utils/user'; +import moment from 'moment'; + +const headers = [ + { + title: t('community.username_and_email'), + field: 'username' + }, + { + title: t('community.account_creation_date'), + field: 'created_at' + }, + { + title: t('community.status'), + field: 'status' + }, + { + title: t('community.newsroom_role'), + field: 'role' + } +]; + +const getActionMenuLabel = (user) => { + + if (isBanned(user)) { + return 'Banned'; + } else if (isSuspended(user)) { + return 'Suspended'; + } + + return ''; +}; const People = (props) => { const { - users = [], - searchValue, onSearchChange, - onHeaderClickHandler, - onPageChange, - totalPages, - page, + users = [], setUserRole, viewUserDetail, + loadMore, + unSuspendUser, + unBanUser, + showSuspendUserDialog, + showBanUserDialog, } = props; - const hasResults = !!users.length; + const hasResults = !!users.nodes.length; return (
@@ -32,7 +65,7 @@ const People = (props) => { id="commenters-search" type="text" className={styles.searchBoxInput} - value={searchValue} + defaultValue='' onChange={onSearchChange} placeholder={t('streams.search')} /> @@ -41,19 +74,88 @@ const People = (props) => {
{ hasResults - ? + ?
+
+
+ + + {headers.map((header, i) =>( + + ))} + + + + {users.nodes.map((user, i)=> ( + + + + + + + ))} + +
+ {header.title} +
+ + {user.profiles.map(({id}) => id)} + + {moment(new Date(user.created_at)).format('MMMM Do YYYY, h:mm:ss a')} + + + + + {isSuspended(user) ? unSuspendUser({id: user.id})}> + Remove Suspension + : showSuspendUserDialog({ + userId: user.id, + username: user.username, + })}> + Suspend User + } + + {isBanned(user) ? unBanUser({id: user.id})}> + Remove Ban + : showBanUserDialog({ + userId: user.id, + username: user.username, + })}> + Ban User + } + + + + setUserRole(user.id, role)}> + +
+
+ +
: {t('community.no_results')} } @@ -62,19 +164,15 @@ const People = (props) => { }; People.propTypes = { - onHeaderClickHandler: PropTypes.func, - users: PropTypes.array, - page: PropTypes.number.isRequired, - searchValue: PropTypes.string, - onSearchChange: PropTypes.func, - totalPages: PropTypes.number, - onPageChange: PropTypes.func, + users: PropTypes.object.isRequired, + onSearchChange: PropTypes.func.isRequired, setUserRole: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, unBanUser: PropTypes.func.isRequired, unSuspendUser: PropTypes.func.isRequired, showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, + loadMore: PropTypes.func.isRequired, }; export default People; diff --git a/client/coral-admin/src/routes/Community/components/Table.css b/client/coral-admin/src/routes/Community/components/Table.css deleted file mode 100644 index 4071d9aa9..000000000 --- a/client/coral-admin/src/routes/Community/components/Table.css +++ /dev/null @@ -1,69 +0,0 @@ -.dataTable { - width: 100%; - border-left: none; - border-right: none; -} - -th.header { - font-size: 1.1em; -} - -th.header:hover { - cursor: pointer; -} - -th.header:nth-child(2), th.header:nth-child(3) { - width: 100px; -} - -.button { - composes: buttonReset from 'coral-framework/styles/reset.css'; - &:hover { - background-color: #D0D0D0; - } -} - -.username, .email { - max-width: 215px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.email { - display: block; -} - -.selectField { - position: relative; - width: 150px; - height: 36px; - background: #2c2c2c; - padding: 10px 15px; - box-sizing: border-box; - color: white; - border-radius: 2px; - box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); - - > div { - padding: 0; - } - - i { - position: absolute; - top: 7px; - right: 7px; - } - - input { - padding: 0; - font-size: 13px; - letter-spacing: 0.7px; - font-weight: 400; - } - - &:hover { - cursor: pointer; - } -} - diff --git a/client/coral-admin/src/routes/Community/components/Table.js b/client/coral-admin/src/routes/Community/components/Table.js deleted file mode 100644 index 6d40b5d28..000000000 --- a/client/coral-admin/src/routes/Community/components/Table.js +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react'; -import styles from './Table.css'; -import t from 'coral-framework/services/i18n'; -import PropTypes from 'prop-types'; -import {Paginate, Dropdown, Option} from 'coral-ui'; -import cn from 'classnames'; -import ActionsMenu from 'coral-admin/src/components/ActionsMenu'; -import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; -import {isSuspended, isBanned} from 'coral-framework/utils/user'; - -const headers = [ - { - title: t('community.username_and_email'), - field: 'username' - }, - { - title: t('community.account_creation_date'), - field: 'created_at' - }, - { - title: t('community.status'), - field: 'status' - }, - { - title: t('community.newsroom_role'), - field: 'role' - } -]; - -const Table = (props) => { - - const { - users, - setUserRole, - onHeaderClickHandler, - viewUserDetail, - pageCount, - page, - onPageChange, - unSuspendUser, - unBanUser, - showSuspendUserDialog, - showBanUserDialog, - } = props; - - return ( -
- - - - {headers.map((header, i) =>( - - ))} - - - - {users.map((user, i)=> ( - - - - - - - ))} - -
onHeaderClickHandler({field: header.field})}> - {header.title} -
- - {user.profiles.map(({id}) => id)} - - {user.created_at} - - - - - {isSuspended(user) ? unSuspendUser({id: user.id})}> - Remove Suspension - : showSuspendUserDialog({ - userId: user.id, - username: user.username, - })}> - Suspend User - } - - {isBanned(user) ? unBanUser({id: user.id})}> - Remove Ban - : showBanUserDialog({ - userId: user.id, - username: user.username, - })}> - Ban User - } - - - - setUserRole(user.id, role)}> - -
- -
- ); -}; - -Table.propTypes = { - users: PropTypes.array, - onHeaderClickHandler: PropTypes.func.isRequired, - setUserRole: PropTypes.func.isRequired, - viewUserDetail: PropTypes.func.isRequired, - pageCount: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, - showSuspendUserDialog: PropTypes.func, - showBanUserDialog: PropTypes.func, - unBanUser: PropTypes.func.isRequired, - unSuspendUser: PropTypes.func.isRequired, -}; - -export default Table; diff --git a/client/coral-admin/src/routes/Community/components/styles.css b/client/coral-admin/src/routes/Community/components/styles.css index fc94717e0..093ee3e7f 100644 --- a/client/coral-admin/src/routes/Community/components/styles.css +++ b/client/coral-admin/src/routes/Community/components/styles.css @@ -240,3 +240,97 @@ } } +/* ========================================================================== */ +/* TABLE */ +/* ========================================================================== */ + +.dataTable { + width: 100%; + border-left: none; + border-right: none; +} + +th.header { + font-size: 1.1em; +} + +th.header:hover { + cursor: pointer; +} + +th.header:nth-child(2), th.header:nth-child(3) { + width: 100px; +} + +.button { + composes: buttonReset from 'coral-framework/styles/reset.css'; + &:hover { + background-color: #D0D0D0; + } +} + +.username, .email { + max-width: 215px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.email { + display: block; +} + +.selectField { + position: relative; + width: 150px; + height: 36px; + background: #2c2c2c; + padding: 10px 15px; + box-sizing: border-box; + color: white; + border-radius: 2px; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + + > div { + padding: 0; + } + + i { + position: absolute; + top: 7px; + right: 7px; + } + + input { + padding: 0; + font-size: 13px; + letter-spacing: 0.7px; + font-weight: 400; + } + + &:hover { + cursor: pointer; + } +} + +.actionsMenu { + font-size: 1em; +} + +.actionsMenu .actionsMenuButton { + -webkit-transform: scale(1); + transform: scale(1); + font-size: 0.98em; +} + +.actionsMenuSuspended { + background-color: #F29336; + border-color: #F29336; + color: white; +} + +.actionsMenuBanned { + background-color: #E45241; + border-color: #E45241; + color: white; +} diff --git a/client/coral-admin/src/routes/Community/containers/Community.js b/client/coral-admin/src/routes/Community/containers/Community.js index 9b34a5264..d8737c938 100644 --- a/client/coral-admin/src/routes/Community/containers/Community.js +++ b/client/coral-admin/src/routes/Community/containers/Community.js @@ -39,9 +39,9 @@ const withData = withQuery(gql` __typename } } + ${People.fragments.root} ${FlaggedAccounts.fragments.root} ${FlaggedUser.fragments.root} - ${People.fragments.root} ${FlaggedUser.fragments.me} `, { options: { diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 4a0a3c6fb..f6f84ec69 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -9,59 +9,29 @@ import {withUnBanUser, withUnSuspendUser, withSetUserRole} from 'coral-framework import {showBanUserDialog} from 'actions/banUserDialog'; import {showSuspendUserDialog} from 'actions/suspendUserDialog'; import {viewUserDetail} from '../../../actions/userDetail'; - -import { - fetchUsers, - updateSorting, - setPage, - hideRejectUsernameDialog, - setSearchValue, -} from '../../../actions/community'; +import {appendNewNodes} from 'plugin-api/beta/client/utils'; +import update from 'immutability-helper'; +import {Spinner} from 'coral-ui'; class PeopleContainer extends React.Component { timer = null; - fetchUsers = (query = {}) => { - const {community} = this.props; - - this.props.fetchUsers({ - value: community.searchValue, - field: community.fieldPeople, - asc: community.ascPeople, - ...query - }); - } - - componentWillMount() { - this.fetchUsers(); - } - onKeyDownHandler = (e) => { if (e.key === 'Enter') { e.preventDefault(); - this.fetchUsers(); + // this.fetchUsers(); } } onSearchChange = (e) => { const value = e.target.value; + console.log(value); - this.props.setSearchValue(value); - clearTimeout(this.timer); - this.timer = setTimeout(() => { - this.fetchUsers({value}); - }, 350); - } + // clearTimeout(this.timer); - onHeaderClickHandler = (sort) => { - this.props.updateSorting(sort); - this.fetchUsers(); - } - - onPageChange = ({selected}) => { - const page = selected + 1; - this.props.setPage(page); - this.fetchUsers({page}); + // this.timer = setTimeout(() => { + // // this.fetchUsers({value}); + // }, 350); } setUserRole = async (id, role) => { @@ -69,51 +39,92 @@ class PeopleContainer extends React.Component { await this.fetchUsers(); } + loadMore = () => { + return this.props.data.fetchMore({ + query: LOAD_MORE_QUERY, + variables: { + limit: 5, + cursor: this.props.root.users.endCursor, + }, + updateQuery: (previous, {fetchMoreResult:{users}}) => { + const updated = update(previous, { + flaggedUsers: { + nodes: { + $apply: (nodes) => appendNewNodes(nodes, users.nodes), + }, + hasNextPage: {$set: users.hasNextPage}, + endCursor: {$set: users.endCursor}, + }, + }); + return updated; + }, + }); + }; + render() { + + if (this.props.data.error) { + return
{this.props.data.error.message}
; + } + + if (this.props.data.loading) { + return
; + } + return ; } } PeopleContainer.propTypes = { - setPage: PropTypes.func, - fetchUsers: PropTypes.func, - updateSorting: PropTypes.func, + community: PropTypes.object, setUserRole: PropTypes.func.isRequired, unBanUser: PropTypes.func.isRequired, unSuspendUser: PropTypes.func.isRequired, - setSearchValue: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, - community: PropTypes.object, showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, + data: PropTypes.object, + root: PropTypes.object, }; const mapDispatchToProps = (dispatch) => bindActionCreators({ - setPage, - fetchUsers, - updateSorting, - hideRejectUsernameDialog, viewUserDetail, - setSearchValue, showSuspendUserDialog, showBanUserDialog, }, dispatch); +const LOAD_MORE_QUERY = gql` + query TalkAdminCommunity_People_LoadMoreUsers($limit: Int, $cursor: Cursor) { + users(query: {}){ + hasNextPage + endCursor + nodes { + __typename + id + username + created_at + profiles { + id + provider + } + } + } + } +`; + export default compose( connect(null, mapDispatchToProps), withSetUserRole, @@ -128,10 +139,26 @@ export default compose( nodes { __typename id + username + role + created_at + profiles { + id + provider + } + state { + status { + banned { + status + } + suspension { + until + } + } + } } } } - `, }), )(PeopleContainer); diff --git a/client/coral-ui/components/Button.css b/client/coral-ui/components/Button.css index ce2cff350..f60e2c15e 100644 --- a/client/coral-ui/components/Button.css +++ b/client/coral-ui/components/Button.css @@ -150,6 +150,7 @@ box-shadow: none; color: white; background-color: #616161; + border-color: transparent; } > .icon { diff --git a/client/coral-ui/components/Dropdown.css b/client/coral-ui/components/Dropdown.css index 1d548bc18..794955220 100644 --- a/client/coral-ui/components/Dropdown.css +++ b/client/coral-ui/components/Dropdown.css @@ -1,19 +1,19 @@ .dropdown { position: relative; width: 150px; - height: 36px; + height: 34px; background: #2c2c2c; box-sizing: border-box; color: white; - border-radius: 2px; + border-radius: 3px; box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); - line-height: 1.4; - font-size: 13px; + font-size: 0.98em; + border-radius: 3px; } .toggle { - padding: 10px 15px; + padding: 8px 15px; cursor: pointer; outline: none; @@ -58,5 +58,5 @@ right: 15px; font-size: 1.2rem; vertical-align: middle; - top: 13px; + top: 11px; }