import React from 'react' import { get, isEqual } from 'lodash' import { connect } from 'react-redux' import { actions } from 'pubsweet-client' import { ConnectPage } from 'xpub-connect' import { withJournal } from 'xpub-journal' import { Icon, th } from '@pubsweet/ui' import { withRouter, Link } from 'react-router-dom' import styled, { withTheme } from 'styled-components' import { compose, withState, withHandlers } from 'recompose' import { withModal, ConfirmationModal, } from 'pubsweet-component-modal/src/components' import { Pagination } from './' import { updateUserStatusModal } from './utils' const TableRow = ({ id, email, firstName = '', lastName = '', isActive = true, affiliation, editorInChief, handlingEditor, admin, toggleUserStatus, getStatusLabel, }) => ( <Row> <td>{`${firstName} ${lastName}`}</td> <td>{email}</td> <td>{affiliation}</td> <td> <Role>{`Author${isEqual(editorInChief, true) ? ', Editor in Chief' : ''}${ isEqual(handlingEditor, true) ? ', Handling Editor' : '' }${isEqual(admin, true) ? ', Admin' : ''}`}</Role> </td> <td> <Tag>{getStatusLabel()}</Tag> </td> <TD> <Action to={`/admin/users/edit/${id}`}>Edit</Action> <ActionButton onClick={toggleUserStatus}> {isActive ? 'Deactivate' : 'Reactivate'} </ActionButton> </TD> </Row> ) const Users = ({ page, users, theme, history, journal, itemsPerPage, incrementPage, decrementPage, getStatusLabel, toggleUserStatus, }) => { const slicedUsers = users.slice( page * itemsPerPage, itemsPerPage * (page + 1), ) return ( <div> <Header> <BreadCrumbs> <span>Admin Dashboard</span> <span>Users</span> </BreadCrumbs> <AddButton data-test="button-add-user" onClick={() => history.push('/admin/users/add')} > <Icon color={theme.colorPrimary} size={3}> plus-circle </Icon> Add User </AddButton> </Header> <SubHeader> <Pagination decrementPage={decrementPage} hasMore={itemsPerPage * (page + 1) < users.length} incrementPage={incrementPage} itemsPerPage={itemsPerPage} maxLength={users.length} page={page} /> </SubHeader> <Table> <thead> <tr> <td>Full name</td> <td>Email</td> <td>Affiliation</td> <td width="220">Roles</td> <td>Status</td> <td width="125" /> </tr> </thead> <tbody> {slicedUsers.map(u => ( <TableRow key={u.id} {...u} getStatusLabel={getStatusLabel(u)} roleOptions={journal.roles} toggleUserStatus={toggleUserStatus(u)} /> ))} </tbody> </Table> </div> ) } export default compose( ConnectPage(() => [actions.getUsers()]), withRouter, withJournal, withTheme, withModal(props => ({ modalComponent: ConfirmationModal, })), connect(state => ({ users: get(state, 'users.users') })), withState('itemsPerPage', 'setItemsPerPage', 20), withState('page', 'setPage', 0), withHandlers({ incrementPage: ({ setPage, page, itemsPerPage, users }) => () => { if (page * itemsPerPage + itemsPerPage < users.length) { setPage(p => p + 1) } }, decrementPage: ({ setPage }) => () => { setPage(p => (p > 0 ? p - 1 : p)) }, getStatusLabel: () => ({ isConfirmed, isActive = true }) => () => { if (!isActive) { return 'Inactive' } return isConfirmed ? 'Active' : 'Invited' }, toggleUserStatus: ({ dispatch, showModal, hideModal, setModalError, }) => user => () => { updateUserStatusModal({ dispatch, showModal, hideModal, setModalError, user, }) }, }), )(Users) // #region styled-components const AddButton = styled.button` align-items: center; background-color: ${th('backgroundColor')}; border: none; cursor: pointer; color: ${th('colorPrimary')}; display: flex; font-family: ${th('fontInterface')}; font-size: ${th('fontSizeBaseSmall')}; text-align: left; &:active, &:focus { outline: none; } &:hover { opacity: 0.7; } ` const Header = styled.div` align-items: center; display: flex; flex-direction: row; ` const BreadCrumbs = styled.div` & span { color: ${th('colorPrimary')}; cursor: pointer; font-size: ${th('fontSizeBase')}; margin-left: calc(${th('subGridUnit')}*2); text-align: left; &:after { content: '>'; padding: 0 calc(${th('subGridUnit')}*2); } &:last-child { font-size: ${th('fontSizeBase')}; font-weight: bold; &:after { content: ''; } } ` const SubHeader = styled.div` align-items: center; border-bottom: ${th('borderDefault')}; display: flex; flex-direction: row; justify-content: space-between; margin-top: ${th('gridUnit')}; padding-bottom: calc(${th('subGridUnit')}*2); > div:first-child { align-items: center; display: flex; > div { margin-right: calc(${th('subGridUnit')}); } } span { color: ${th('colorPrimary')}; font-family: ${th('fontReading')}; font-size: ${th('fontSizeBaseSmall')}; margin-right: calc(${th('subGridUnit')}); text-align: left; } ` const Table = styled.table` border-spacing: 0; border-collapse: collapse; width: 100%; & thead tr { border-bottom: ${th('borderDefault')}; color: ${th('colorPrimary')}; font-family: ${th('fontReading')}; font-size: ${th('fontSizeBaseSmall')}; font-weight: bold; height: 40px; text-align: left; } ` const Row = styled.tr` border-bottom: ${th('borderDefault')}; color: ${th('colorPrimary')}; font-family: ${th('fontReading')}; font-size: ${th('fontSizeBaseSmall')}; height: 40px; text-align: left; &:hover { background-color: ${th('backgroundColorReverse')}; a, i { display: block; } } ` const Tag = styled.span` border: solid 1px #667080; color: ${th('colorPrimary')}; font-family: ${th('fontReading')}; font-size: ${th('fontSizeBaseSmall')}; font-weight: bold; margin-right: calc(${th('subGridUnit')}); padding: 2px calc(${th('subGridUnit')}*2); text-align: left; text-transform: uppercase; ` const Role = styled.div` color: ${th('colorPrimary')}; font-size: ${th('fontSizeBaseSmall')}; line-height: 1.5; text-align: left; text-transform: uppercase; ` const Action = styled(Link)` color: ${th('colorPrimary')}; display: none; ` const TD = styled.td` display: inline-flex; width: 100%; min-height: 20px; ` const ActionButton = styled.i` cursor: pointer; display: none; font-style: unset; text-decoration: underline; margin: 0 calc(${th('subGridUnit')} * 2); ` // #endregion