diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 143fbdb4da42e97aaee2fc7e9f49b80199660060..ae7b54ace427f4e6af74297ad94dfcbefe76496a 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -40,16 +40,18 @@ export const currentUserIs = ({ currentUser: { user } }, role) => { } } -export const canInviteReviewers = ({ currentUser: { user } }, project) => { - const status = get(project, 'status') - if (!status || status === 'rejected' || status === 'published') return false +const cannotInviteReviewersStatuses = ['draft', 'rejected', 'published'] +export const canInviteReviewers = (state, collection) => { + if ( + cannotInviteReviewersStatuses.includes(get(collection, 'status', 'draft')) + ) + return false - const handlingEditor = get(project, 'handlingEditor') - const isAdmin = get(user, 'admin') - const isEic = get(user, 'editorInChief') - const isAccepted = get(handlingEditor, 'isAccepted') - const heId = get(handlingEditor, 'id') - return isAccepted && (user.id === heId || isAdmin || isEic) + const user = selectCurrentUser(state) + const isStaff = currentUserIs(state, 'adminEiC') + const { isAccepted, id: heId } = get(collection, 'handlingEditor', {}) + + return isAccepted && (user.id === heId || isStaff) } export const getUserToken = ({ currentUser }) => @@ -203,3 +205,15 @@ export const newestFirstParseDashboard = (state = {}) => .orderBy(['created'], ['desc']) .map(item => parseCollectionDetails(state, item)) .value() + +export const getInvitationsWithReviewersForFragment = (state, fragmentId) => + chain(state) + .get(`fragments.${fragmentId}.invitations`, []) + .filter(invitation => invitation.role === 'reviewer') + .map(invitation => ({ + ...invitation, + person: get(state, 'users.users', []).find( + reviewer => reviewer.id === invitation.userId, + ), + })) + .value() diff --git a/packages/component-faraday-ui/src/ContextualBox.js b/packages/component-faraday-ui/src/ContextualBox.js index 016b6618f4e3bf09e7c92d4c7f246a6ecbfe7a70..32ff2966e40f3884d0069955ea65d619a1e8c689 100644 --- a/packages/component-faraday-ui/src/ContextualBox.js +++ b/packages/component-faraday-ui/src/ContextualBox.js @@ -1,4 +1,4 @@ -import React, { Fragment } from 'react' +import React from 'react' import { has } from 'lodash' import styled from 'styled-components' import { Icon, H3 } from '@pubsweet/ui' @@ -21,12 +21,12 @@ const CustomHeader = ({ onClick={toggle} transparent={transparent} > - <Fragment> + <div> <Icon secondary size={2}> {expanded ? 'minus' : 'plus'} </Icon> <H3>{label}</H3> - </Fragment> + </div> {typeof rightChildren === 'function' ? rightChildren({ ...props, expanded, transparent }) : rightChildren} diff --git a/packages/component-faraday-ui/src/InviteReviewers.js b/packages/component-faraday-ui/src/InviteReviewers.js new file mode 100644 index 0000000000000000000000000000000000000000..d48a219b3ba6d4647be5ccbdbc4ce9bba17d43aa --- /dev/null +++ b/packages/component-faraday-ui/src/InviteReviewers.js @@ -0,0 +1,106 @@ +import React from 'react' +import { compose } from 'recompose' +import styled from 'styled-components' +import { reduxForm } from 'redux-form' +import { th } from '@pubsweet/ui-toolkit' +import { required } from 'xpub-validators' +import { withModal } from 'pubsweet-component-modal/src/components' +import { Button, H4, Menu, TextField, ValidatedField } from '@pubsweet/ui' + +import { + Row, + Item, + Label, + MultiAction, + ItemOverrideAlert, + withFetching, + withCountries, +} from '../' + +const InviteReviewers = ({ countries, handleSubmit, reset }) => ( + <Root> + <Row justify="space-between" mb={2}> + <H4>Invite reviewer</H4> + <Item justify="flex-end"> + <Button onClick={reset} size="small"> + Clear Fields + </Button> + <Button ml={2} onClick={handleSubmit} primary size="small"> + Invite + </Button> + </Item> + </Row> + <Row> + <Item mr={2} vertical> + <Label required>Email</Label> + <ValidatedField + component={TextField} + name="email" + validate={[required]} + /> + </Item> + <Item mr={2} vertical> + <Label required>First Name</Label> + <ValidatedField + component={TextField} + name="firstName" + validate={[required]} + /> + </Item> + <Item mr={2} vertical> + <Label required>Last Name</Label> + <ValidatedField + component={TextField} + name="lastName" + validate={[required]} + /> + </Item> + <Item mr={2} vertical> + <Label required>Affiliation</Label> + <ValidatedField + component={TextField} + name="affiliation" + validate={[required]} + /> + </Item> + + <ItemOverrideAlert vertical> + <Label required>Country</Label> + <ValidatedField + component={input => <Menu options={countries} {...input} />} + name="country" + validate={[required]} + /> + </ItemOverrideAlert> + </Row> + </Root> +) + +export default compose( + withFetching, + withCountries, + withModal(({ isFetching, modalKey }) => ({ + modalKey, + isFetching, + modalComponent: MultiAction, + })), + reduxForm({ + form: 'invite-reviewers-form', + onSubmit: (values, dispatch, { showModal, onInvite, setFetching }) => { + showModal({ + title: 'Send invitation to Review?', + subtitle: values.email, + onConfirm: modalProps => { + onInvite(values, { ...modalProps, setFetching }) + }, + }) + }, + }), +)(InviteReviewers) + +// #region styles +const Root = styled.div` + background-color: ${th('colorBackgroundHue3')}; + padding: calc(${th('gridUnit')} * 2); +` +// #endregion diff --git a/packages/component-faraday-ui/src/InviteReviewers.md b/packages/component-faraday-ui/src/InviteReviewers.md new file mode 100644 index 0000000000000000000000000000000000000000..851bb7ceb0f93de220d32ce6b954cabf6bec40e1 --- /dev/null +++ b/packages/component-faraday-ui/src/InviteReviewers.md @@ -0,0 +1,9 @@ +The invite reviewers form. + +```js +<InviteReviewers + onInvite={(reviewer, props) => { + props.setFetching(true) + }} +/> +``` diff --git a/packages/component-faraday-ui/src/Label.js b/packages/component-faraday-ui/src/Label.js index 7b7a63c2b9287ca571be59a072b1277b7fe67395..5cbdf06966b47836f257ec677c8189a858e99b83 100644 --- a/packages/component-faraday-ui/src/Label.js +++ b/packages/component-faraday-ui/src/Label.js @@ -3,8 +3,10 @@ import { H4 } from '@pubsweet/ui' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' -const Label = ({ children, required = false }) => ( - <Root> +import { marginHelper } from './' + +const Label = ({ children, required = false, ...rest }) => ( + <Root {...rest}> <H4>{children}</H4> {required && <Required>*</Required>} </Root> @@ -22,6 +24,8 @@ const Root = styled.div` align-items: center; display: flex; + ${marginHelper}; + ${H4} { margin: 0; } diff --git a/packages/component-faraday-ui/src/ManuscriptCard.js b/packages/component-faraday-ui/src/ManuscriptCard.js index e9f0e796f79d1b5319c9a2a8faaccb6006619a58..095e959e26d78489305ac9c569adf17342656e72 100644 --- a/packages/component-faraday-ui/src/ManuscriptCard.js +++ b/packages/component-faraday-ui/src/ManuscriptCard.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { Fragment } from 'react' import { get } from 'lodash' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' @@ -11,6 +11,7 @@ import { Text, Row, Item, + Label, IconButton, ActionLink, TextTooltip, @@ -76,7 +77,10 @@ const ManuscriptCard = ({ {get(handlingEditor, 'name', 'Unassigned')} </Text> {handlingEditor && ( - <ReviewerBreakdown fragment={fragment} label="Reviewer Reports" /> + <Fragment> + <Label mr={1}>Reviewers Reports</Label> + <ReviewerBreakdown fragment={fragment} label="Reviewer Reports" /> + </Fragment> )} {canDelete && ( <Item justify="flex-end" onClick={e => e.stopPropagation()}> diff --git a/packages/component-faraday-ui/src/PersonInvitation.js b/packages/component-faraday-ui/src/PersonInvitation.js index 89676c26a54b867889d5f10c65ca33741fcc62ea..5b1da63fab1b7fb1b129b175fce6cd0518f4b146 100644 --- a/packages/component-faraday-ui/src/PersonInvitation.js +++ b/packages/component-faraday-ui/src/PersonInvitation.js @@ -2,15 +2,16 @@ import React, { Fragment } from 'react' import styled from 'styled-components' import { compose, withHandlers, defaultProps, setDisplayName } from 'recompose' -import { Text, OpenModal, IconButton, marginHelper } from './' +import { Text, OpenModal, IconButton, marginHelper, withFetching } from './' const PersonInvitation = ({ + id, withName, hasAnswer, isFetching, - person: { name }, revokeInvitation, resendInvitation, + person: { name, email }, ...rest }) => ( <Root {...rest}> @@ -19,9 +20,12 @@ const PersonInvitation = ({ name !== 'Unassigned' && ( <Fragment> <OpenModal + confirmText="Resend" isFetching={isFetching} + modalKey={`resend-${id}`} onConfirm={resendInvitation} - title="Are you sure you want to resend the invitation?" + subtitle={email} + title="Resend the invitation?" > {showModal => ( <IconButton @@ -34,11 +38,12 @@ const PersonInvitation = ({ )} </OpenModal> <OpenModal - confirmText="Remove invite" + confirmText="Revoke" isFetching={isFetching} + modalKey={`revoke-${id}`} onConfirm={revokeInvitation} - subtitle="Clicking ‘Remove’ will allow you to invite a different Handling Editor" - title="Remove invitation to Handling Editor?" + subtitle={email} + title="Revoke invitation?" > {showModal => ( <IconButton @@ -63,12 +68,19 @@ export default compose( onRevoke: id => {}, onResend: id => {}, }), + withFetching, withHandlers({ - revokeInvitation: ({ id, onRevoke }) => props => { - typeof onRevoke === 'function' && onRevoke(id, props) + revokeInvitation: ({ id, onRevoke, setFetching }) => props => { + typeof onRevoke === 'function' && onRevoke(id, { ...props, setFetching }) }, - resendInvitation: ({ id, onResend, person: { email } }) => props => { - typeof onResend === 'function' && onResend(email, props) + resendInvitation: ({ + id, + onResend, + setFetching, + person: { email }, + }) => props => { + typeof onResend === 'function' && + onResend(email, { ...props, setFetching }) }, }), setDisplayName('PersonInvitation'), diff --git a/packages/component-faraday-ui/src/ReviewerBreakdown.js b/packages/component-faraday-ui/src/ReviewerBreakdown.js index 7c181f90968c9751a32cc48458241fda72bbd246..7d257102a24dc7f39217f22b6c0529d800026247 100644 --- a/packages/component-faraday-ui/src/ReviewerBreakdown.js +++ b/packages/component-faraday-ui/src/ReviewerBreakdown.js @@ -35,7 +35,7 @@ export default compose( submitted: 0, }) return reviewerInvitations.length ? ( - <Row justify="flex-end" {...rest}> + <Row fitContent justify="flex-end" {...rest}> <Text customId mr={1 / 2}> {reviewerInvitations.length} </Text> @@ -57,7 +57,7 @@ export default compose( <Text mr={1 / 2}> submitted</Text> </Row> ) : ( - <Text> {`${reviewerInvitations.length} invited`}</Text> + <Text mr={1}>{`${reviewerInvitations.length} invited`}</Text> ) }, }), diff --git a/packages/component-faraday-ui/src/ReviewersTable.js b/packages/component-faraday-ui/src/ReviewersTable.js index e0bdabb2722aa52c0e0fcf9bd5382c43bcba6bb9..e031cddbe5662310219b6f21d8ee3e8b6781271b 100644 --- a/packages/component-faraday-ui/src/ReviewersTable.js +++ b/packages/component-faraday-ui/src/ReviewersTable.js @@ -1,181 +1,102 @@ -import React from 'react' +import React, { Fragment } from 'react' +import { get } from 'lodash' import styled from 'styled-components' +import { shouldUpdate } from 'recompose' import { th } from '@pubsweet/ui-toolkit' import { DateParser } from '@pubsweet/ui' import { Label, PersonInvitation, Text } from '../' -const invitation = { - id: 'b4305ab6-84e6-48a3-9eb9-fbe0ec80c694', - role: 'handlingEditor', - type: 'invitation', - reason: 'because', - userId: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', - hasAnswer: false, - invitedOn: 713919119, - isAccepted: false, - respondedOn: 1533714034932, - person: { - id: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', - name: 'Toto Schilacci', - }, -} +const ReviewersTable = ({ + invitations, + onResendReviewerInvite, + onRevokeReviewerInvite, +}) => + invitations.length > 0 && ( + <Table> + <thead> + <tr> + <th> + <Label>Full Name</Label> + </th> + <th> + <Label>Invited on</Label> + </th> + <th> + <Label>Responded on</Label> + </th> + <th> + <Label>Submitted on</Label> + </th> + <th> </th> + </tr> + </thead> + <tbody> + {invitations.map((invitation, index) => ( + <TableRow key={invitation.id}> + <td> + <Text>{`${get(invitation, 'person.firstName', '')} ${get( + invitation, + 'person.lastName', + )}`}</Text> + {invitation.isAccepted && ( + <Text customId ml={1}>{`Reviewer ${index + 1}`}</Text> + )} + </td> + <td> + <DateParser timestamp={invitation.invitedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + </td> + <td> + {invitation.respondedOn && ( + <Fragment> + <DateParser timestamp={invitation.respondedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + <Text ml={1} secondary> + ACCEPTED + </Text> + </Fragment> + )} + </td> + <td> + {invitation.respondedOn && ( + <DateParser timestamp={invitation.respondedOn}> + {timestamp => <Text>{timestamp}</Text>} + </DateParser> + )} + </td> + <HiddenCell> + {!invitation.hasAnswer && ( + <PersonInvitation + {...invitation} + onResend={onResendReviewerInvite} + onRevoke={onRevokeReviewerInvite} + /> + )} + </HiddenCell> + </TableRow> + ))} + </tbody> + </Table> + ) -const reviewers = [ - { - id: 1, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 2, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 11, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 21, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 12, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 22, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 13, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 23, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 113, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 213, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 123, - fullName: 'Gica Hagi', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, - { - id: 223, - fullName: 'Cosmin Contra', - invitedOn: Date.now(), - respondedOn: Date.now(), - submittedOn: Date.now() - 3000000000, - }, -] - -const ReviewersTable = () => ( - <Table> - <thead> - <tr> - <th> - <Label>Full Name</Label> - </th> - <th> - <Label>Invited on</Label> - </th> - <th> - <Label>Responded on</Label> - </th> - <th> - <Label>Submitted on</Label> - </th> - <th> </th> - </tr> - </thead> - <tbody> - {reviewers.map((r, index) => ( - <TableRow key={r.id}> - <td> - <Text>{r.fullName}</Text> - <Text customId ml={1}>{`Reviewer ${index + 1}`}</Text> - </td> - <td> - <DateParser timestamp={r.invitedOn}> - {timestamp => <Text>{timestamp}</Text>} - </DateParser> - </td> - <td> - <DateParser timestamp={r.respondedOn}> - {timestamp => <Text>{timestamp}</Text>} - </DateParser> - <Text ml={1} secondary> - ACCEPTED - </Text> - </td> - <td> - <DateParser timestamp={r.submittedOn}> - {timestamp => <Text>{timestamp}</Text>} - </DateParser> - </td> - <HiddenCell> - <PersonInvitation {...invitation} /> - </HiddenCell> - </TableRow> - ))} - </tbody> - </Table> -) - -export default ReviewersTable +export default shouldUpdate(() => false)(ReviewersTable) // #region styles const Table = styled.table` border-collapse: collapse; - padding: ${th('gridUnit')}; & thead { border-bottom: 1px solid ${th('colorBorder')}; + background-color: ${th('colorBackgroundHue2')}; } & th, & td { border: none; - padding-left: ${th('gridUnit')}; + padding-left: calc(${th('gridUnit')} * 2); text-align: start; vertical-align: middle; @@ -186,6 +107,7 @@ const Table = styled.table` const HiddenCell = styled.td` opacity: 0; + padding-top: ${th('gridUnit')}; ` const TableRow = styled.tr` @@ -197,7 +119,7 @@ const TableRow = styled.tr` } &:hover { - background-color: #eeeeee; + background-color: ${th('colorBackgroundHue3')}; ${HiddenCell} { opacity: 1; diff --git a/packages/component-faraday-ui/src/WizardAuthors.js b/packages/component-faraday-ui/src/WizardAuthors.js index 3d951ec7aa7f910d3332e7584172e340a76b4e8e..3be5cad89501e670032295e0efa1937d704af0f7 100644 --- a/packages/component-faraday-ui/src/WizardAuthors.js +++ b/packages/component-faraday-ui/src/WizardAuthors.js @@ -53,7 +53,6 @@ const WizardAuthors = ({ isFetching, }) => ( <Fragment> - {isFetching && <h1>loading...</h1>} <Row alignItems="center" justify="flex-start"> <Item> <Label>Authors</Label> diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js index cca9baae989cda6722630bc6a91e37984aa23b3b..7cf367f7ec049a39afbd533ea780e74b9182e7e7 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.js @@ -1,40 +1,24 @@ import React, { Fragment } from 'react' +import { H4 } from '@pubsweet/ui' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { - Tag, Tabs, - Label, marginHelper, ContextualBox, ReviewersTable, - ReviewerReport, + InviteReviewers, ReviewerBreakdown, } from '../' -const report = { - submittedOn: Date.now(), - recommendation: 'Reject', - report: `Of all of the celestial bodies that capture our attention and - fascination as astronomers, none has a greater influence on life on - planet Earth than it’s own satellite, the moon. When you think about - it, we regard the moon with such powerful significance that unlike the - moons of other planets which we give names, we only refer to our one - and only orbiting orb as THE moon. It is not a moon. To us, it is the - one and only moon.`, - reviewer: { - fullName: 'Kenny Hudson', - reviewerNumber: 1, - }, - confidentialNote: `First 10 pages feel very familiar, you should check for plagarism.`, - files: [ - { id: 'file1', name: 'file1.pdf', size: 12356 }, - { id: 'file2', name: 'file2.pdf', size: 76421 }, - ], -} - -const ReviewerDetails = ({ fragment }) => ( +const ReviewerDetails = ({ + fragment, + invitations, + onInviteReviewer, + onResendReviewerInvite, + onRevokeReviewerInvite, +}) => ( <ContextualBox label="Reviewer details" rightChildren={<ReviewerBreakdown fitContent fragment={fragment} mr={1} />} @@ -50,24 +34,20 @@ const ReviewerDetails = ({ fragment }) => ( onClick={() => changeTab(0)} selected={selectedTab === 0} > - <Label>Reviewer Details</Label> - </TabButton> - <TabButton - ml={1} - mr={1} - onClick={() => changeTab(1)} - selected={selectedTab === 1} - > - <Label>Reviewer Reports</Label> - <Tag ml={1}>12</Tag> + <H4>Reviewer Details</H4> </TabButton> </TabsHeader> - {selectedTab === 0 && <ReviewersTable />} - {selectedTab === 1 && ( + {selectedTab === 0 && ( <Fragment> - <ReviewerReport report={report} /> - <ReviewerReport report={report} /> - <ReviewerReport report={report} /> + <InviteReviewers + modalKey="invite-reviewers" + onInvite={onInviteReviewer} + /> + <ReviewersTable + invitations={invitations} + onResendReviewerInvite={onResendReviewerInvite} + onRevokeReviewerInvite={onRevokeReviewerInvite} + /> </Fragment> )} </Fragment> @@ -92,16 +72,20 @@ const TabButton = styled.div` height: calc(${th('gridUnit')} * 5); ${marginHelper}; + + ${H4} { + padding: 0 ${th('gridUnit')}; + } ` const TabsHeader = styled.div` align-items: center; border-bottom: 1px solid ${th('colorFurniture')}; + background-color: ${th('colorBackgroundHue2')}; box-sizing: border-box; display: flex; justify-content: flex-start; - margin-bottom: ${th('gridUnit')}; padding: 0 calc(${th('gridUnit')} * 3); ` // #endregion diff --git a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md index 49bb54a7d348719cc44c2b822edcaf016d163d5c..8bfbd999d963d485d270504363724999d681034a 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md +++ b/packages/component-faraday-ui/src/contextualBoxes/ReviewerDetails.md @@ -77,7 +77,11 @@ const fragment = { }, ], } -; -<ReviewerDetails fragment={fragment} /> +;<ReviewerDetails + fragment={fragment} + onInviteReviewer={(reviewer, modalProps) => { + modalProps.setFetching(true) + }} +/> ``` diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index b186e2a8a42a0312a827520948661dfa5d57c348..88eb9e4be5129fb6851cb9f74ff01c2e35c24301 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -22,6 +22,7 @@ export { default as Footer } from './Footer' export { default as IconButton } from './IconButton' export { default as IconCard } from './IconCard' export { default as IconTooltip } from './IconTooltip' +export { default as InviteReviewers } from './InviteReviewers' export { default as Label } from './Label' export { default as ManuscriptCard } from './ManuscriptCard' export { default as ReviewerBreakdown } from './ReviewerBreakdown' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.md b/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.md deleted file mode 100644 index 21d5d620c87fbf2b1d69e8ff8c616e4a8cc75013..0000000000000000000000000000000000000000 --- a/packages/component-faraday-ui/src/manuscriptDetails/HandlingEditorAnswer.md +++ /dev/null @@ -1,21 +0,0 @@ -Handling Editor answer invitation. - -```js -const formValues = { - decision: 'decline', -}; - -<RemoteOpener> -{ - ({toggle, expanded}) => - <HandlingEditorAnswer - formValues={formValues} - expanded={expanded} - toggle={toggle} - onResponse={(values, { setFetching }) => { - setFetching(true) - }} - /> -} -</RemoteOpener> -``` diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js index e0511b97b0a7070469d2779023ead0e017deb310..205719b95fb817a3b67b0e0d8b18da2e807ca05d 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptMetadata.js @@ -1,5 +1,6 @@ import React, { Fragment } from 'react' import { isEmpty, get } from 'lodash' +import { withProps } from 'recompose' import { Text, @@ -9,6 +10,7 @@ import { } from 'pubsweet-component-faraday-ui' const ManuscriptMetadata = ({ + filesLabel, getSignedUrl, currentUser: { token }, fragment: { files = {}, conflicts = {}, metadata: { abstract = '' } }, @@ -34,12 +36,7 @@ const ManuscriptMetadata = ({ )} {!isEmpty(files) && ( <Item mb={1}> - <ContextualBox - label={`Files (${files.coverLetter.length + - files.manuscripts.length + - files.supplementary.length})`} - transparent - > + <ContextualBox label={filesLabel} transparent> <ManuscriptFileList files={files} getSignedUrl={getSignedUrl} @@ -51,4 +48,8 @@ const ManuscriptMetadata = ({ </Fragment> ) -export default ManuscriptMetadata +export default withProps(({ fragment: { files } }) => ({ + filesLabel: `Files (${get(files, 'manuscripts', []).length + + get(files, 'coverLetter', []).length + + get(files, 'supplementary', []).length})`, +}))(ManuscriptMetadata) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index 47d39721cccb24282ad928b7bb9e17e0f2dc6144..d5dc9fe1283200362165b066eb17153234ec756b 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -3,6 +3,7 @@ import styled from 'styled-components' import { isEmpty, get, last } from 'lodash' import { Text, + ReviewerDetails, ManuscriptHeader, ManuscriptAssignHE, ManuscriptMetadata, @@ -45,6 +46,10 @@ const ManuscriptLayout = ({ toggleHEResponse, heResponseExpanded, onHEResponse, + onInviteReviewer, + invitationsWithReviewers, + onResendReviewerInvite, + onRevokeReviewerInvite, }) => ( <Root pb={1}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -108,6 +113,16 @@ const ManuscriptLayout = ({ submitDecision={createRecommendation} /> )} + + {get(currentUser, 'permissions.canInviteReviewers', false) && ( + <ReviewerDetails + fragment={fragment} + invitations={invitationsWithReviewers} + onInviteReviewer={onInviteReviewer} + onResendReviewerInvite={onResendReviewerInvite} + onRevokeReviewerInvite={onRevokeReviewerInvite} + /> + )} </Fragment> ) : ( <Text>Loading...</Text> @@ -119,8 +134,8 @@ export default ManuscriptLayout // #region styles const Root = styled.div` - overflow-y: auto; - min-height: 50vh; + overflow-y: visible; + min-height: 70vh; ${paddingHelper}; ` // #endregion diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index c1abbef8a90f4a0203bfc5d4afc5da1021f09d46..b928e99ebce45ed1993264ed16394029992f3f6b 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -22,7 +22,11 @@ import { fromRenderProps, } from 'recompose' import { getSignedUrl } from 'pubsweet-components-faraday/src/redux/files' -import { reviewerDecision } from 'pubsweet-components-faraday/src/redux/reviewers' +import { + inviteReviewer, + revokeReviewer, + reviewerDecision, +} from 'pubsweet-components-faraday/src/redux/reviewers' import { hasManuscriptFailure, clearCustomError, @@ -34,11 +38,13 @@ import { canMakeRevision, canMakeDecision, canEditManuscript, + canInviteReviewers, pendingHEInvitation, currentUserIsReviewer, canMakeRecommendation, parseCollectionDetails, canOverrideTechnicalChecks, + getInvitationsWithReviewersForFragment, } from 'pubsweet-component-faraday-selectors' import { RemoteOpener } from 'pubsweet-component-faraday-ui' @@ -66,6 +72,7 @@ export default compose( ConnectPage(({ match }) => [ actions.getCollection({ id: match.params.project }), actions.getFragments({ id: match.params.project }), + actions.getUsers(), ]), connect( (state, { match }) => ({ @@ -91,6 +98,7 @@ export default compose( assignHandlingEditor, createRecommendation, revokeHandlingEditor, + getUsers: actions.getUsers, getFragment: actions.getFragment, getCollection: actions.getCollection, updateVersion: actions.updateFragment, @@ -110,6 +118,7 @@ export default compose( isInvitedHE: !isUndefined(pendingHEInvitation), permissions: { canAssignHE: canAssignHE(state, match.params.project), + canInviteReviewers: canInviteReviewers(state, collection), canMakeRevision: canMakeRevision(state, collection, fragment), canMakeDecision: canMakeDecision(state, collection, fragment), canEditManuscript: canEditManuscript(state, collection, fragment), @@ -129,6 +138,10 @@ export default compose( eicDecision: getFormValues('eic-decision')(state), heInvitation: getFormValues('he-answer-invitation')(state), }, + invitationsWithReviewers: getInvitationsWithReviewersForFragment( + state, + get(fragment, 'id', ''), + ), }), ), ConnectPage(({ currentUser }) => { @@ -140,12 +153,14 @@ export default compose( withHandlers({ fetchUpdatedCollection: ({ fragment, + getUsers, collection, getFragment, getCollection, }) => () => { getCollection({ id: collection.id }) getFragment(collection, fragment) + getUsers() }, }), withHandlers({ @@ -236,6 +251,71 @@ export default compose( setModalError('Something went wrong...') }) }, + onInviteReviewer: ({ collection, fragment, fetchUpdatedCollection }) => ( + values, + { hideModal, setModalError, setFetching }, + ) => { + setFetching(true) + inviteReviewer({ + reviewerData: values, + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then(() => { + setFetching(false) + hideModal() + fetchUpdatedCollection() + }) + .catch(() => { + setFetching(false) + setModalError('Something went wrong...') + }) + }, + onResendReviewerInvite: ({ + fragment, + collection, + fetchUpdatedCollection, + }) => (email, { hideModal, setFetching, setModalError }) => { + setFetching(true) + inviteReviewer({ + reviewerData: { + email, + role: 'reviewer', + }, + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then(() => { + setFetching(false) + hideModal() + fetchUpdatedCollection() + }) + .catch(() => { + setFetching(false) + setModalError('Something went wrong...') + }) + }, + onRevokeReviewerInvite: ({ + fragment, + collection, + fetchUpdatedCollection, + }) => (invitationId, { hideModal, setFetching, setModalError }) => { + setFetching(true) + revokeReviewer({ + invitationId, + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then(() => { + setFetching(false) + hideModal() + fetchUpdatedCollection() + }) + .catch(() => { + setFetching(false) + setModalError('Something went wrong...') + }) + }, }), fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({ toggleAssignHE: toggle, diff --git a/packages/components-faraday/src/components/Admin/AdminUsers.js b/packages/components-faraday/src/components/Admin/AdminUsers.js index 935f2f686c2a2ed6cabe708429d8002f03203ce9..3bb86eafc038e5db34aa46068648afed53c56e2c 100644 --- a/packages/components-faraday/src/components/Admin/AdminUsers.js +++ b/packages/components-faraday/src/components/Admin/AdminUsers.js @@ -211,7 +211,7 @@ const UserRow = styled.tr` border-bottom: 1px solid ${th('colorBorder')}; &:hover { - background-color: #eeeeee; + background-color: ${th('colorBackgroundHue3')}; ${HiddenCell} { opacity: 1; diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js index 415c8076a7fed3c6ef68c8dc57b8e4125b22d6ce..204911271f2ae06fc53298afd4bbe7912705a493 100644 --- a/packages/components-faraday/src/redux/reviewers.js +++ b/packages/components-faraday/src/redux/reviewers.js @@ -31,19 +31,6 @@ const INVITE_REVIEWER_REQUEST = 'INVITE_REVIEWER_REQUEST' const INVITE_REVIEWER_SUCCESS = 'INVITE_REVIEWER_SUCCESS' const INVITE_REVIEWER_ERROR = 'INVITE_REVIEWER_ERROR' -const inviteRequest = () => ({ - type: INVITE_REVIEWER_REQUEST, -}) - -const inviteSuccess = () => ({ - type: INVITE_REVIEWER_SUCCESS, -}) - -const inviteError = error => ({ - type: INVITE_REVIEWER_ERROR, - error, -}) - // reviewer decision constants and action creators const REVIEWER_DECISION_REQUEST = 'REVIEWER_DECISION_REQUEST' const REVIEWER_DECISION_ERROR = 'REVIEWER_DECISION_ERROR' @@ -118,28 +105,13 @@ export const getCollectionReviewers = ( } // #endregion -// #region Actions - invitations -export const inviteReviewer = ( - reviewerData, - collectionId, - fragmentId, -) => dispatch => { - dispatch(inviteRequest()) - return create( - `/collections/${collectionId}/fragments/${fragmentId}/invitations`, - { - ...reviewerData, - role: 'reviewer', - }, - ).then( - () => dispatch(inviteSuccess()), - err => { - dispatch(inviteError(get(JSON.parse(err.response), 'error'))) - throw err - }, - ) -} +export const inviteReviewer = ({ reviewerData, collectionId, fragmentId }) => + create(`/collections/${collectionId}/fragments/${fragmentId}/invitations`, { + ...reviewerData, + role: 'reviewer', + }) +// #region Actions - invitations export const setReviewerPassword = reviewerBody => dispatch => { dispatch(reviewerDecisionRequest()) return create(`/users/reset-password`, reviewerBody).then(r => { @@ -148,22 +120,10 @@ export const setReviewerPassword = reviewerBody => dispatch => { }) } -export const revokeReviewer = ( - invitationId, - collectionId, - fragmentId, -) => dispatch => { - dispatch(inviteRequest()) - return remove( +export const revokeReviewer = ({ fragmentId, collectionId, invitationId }) => + remove( `/collections/${collectionId}/fragments/${fragmentId}/invitations/${invitationId}`, - ).then( - () => dispatch(inviteSuccess()), - err => { - dispatch(inviteError(get(JSON.parse(err.response), 'error'))) - throw err - }, ) -} // #endregion // #region Actions - decision diff --git a/packages/hindawi-theme/src/elements/Menu.js b/packages/hindawi-theme/src/elements/Menu.js index a35f18829d176d0a214ddf1d14079bf662e6c41d..3f3878c1c89d55ecb6726163feea99d9ef3ea3e9 100644 --- a/packages/hindawi-theme/src/elements/Menu.js +++ b/packages/hindawi-theme/src/elements/Menu.js @@ -6,7 +6,6 @@ export default { background: ${th('colorBackgroundHue')}; height: calc(${th('gridUnit')} * 4); border-radius: ${th('borderRadius')}; - min-width: 120px; `, Value: css` border: none; diff --git a/packages/hindawi-theme/src/index.js b/packages/hindawi-theme/src/index.js index e60cef34f02251ca643b129a64dfc5643f6012bb..d63931498ffdcf1b4a5d451b0b3fb5d1fd56e41e 100644 --- a/packages/hindawi-theme/src/index.js +++ b/packages/hindawi-theme/src/index.js @@ -38,6 +38,7 @@ const hindawiTheme = { colorBorder: '#DBDBDB', colorBackgroundHue: '#FFFFFF', colorBackgroundHue2: '#F6F6F6', + colorBackgroundHue3: '#EEEEEE', colorSuccess: '#63A945', colorError: '#FC6A4B', colorText: '#242424', diff --git a/packages/xpub-faraday/app/FaradayApp.js b/packages/xpub-faraday/app/FaradayApp.js index 2494ed1003798b82ac4c6f075bd04adfc3ee175c..565963319b75d0616677c2bb2a53cbb96b7e6eb0 100644 --- a/packages/xpub-faraday/app/FaradayApp.js +++ b/packages/xpub-faraday/app/FaradayApp.js @@ -118,7 +118,7 @@ const appBarPaddingHelper = props => const MainContainer = styled.div` display: flex; flex-direction: column; - overflow-y: auto; + overflow-y: visible; ${appBarPaddingHelper}; ` // #endregion