diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 6e1c18975f59a435cf3ae86e109456ba78b6ed3b..60c7b7764b90a9b3f2ba33289f098380f69f75e7 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -1,21 +1,21 @@ -import { get, last } from 'lodash' +import { get, last, chain } from 'lodash' import { selectCurrentUser } from 'xpub-selectors' export const isHEToManuscript = (state, collectionId) => { - const currentUserId = get(state, 'currentUser.user.id') + const currentUserId = get(state, 'currentUser.user.id', '') const collections = get(state, 'collections', []) const collection = collections.find(c => c.id === collectionId) || {} return get(collection, 'handlingEditor.id') === currentUserId } const canMakeRecommendationStatuses = [ - 'reviewCompleted', 'heAssigned', 'underReview', + 'reviewCompleted', ] export const canMakeRecommendation = (state, collection, fragment = {}) => { - if (fragment.id !== last(collection.fragments)) return false - const isHE = isHEToManuscript(state, collection.id) + if (fragment.id !== last(get(collection, 'fragments', []))) return false + const isHE = isHEToManuscript(state, get(collection, 'id', '')) const status = get(collection, 'status', 'draft') return isHE && canMakeRecommendationStatuses.includes(status) } @@ -52,28 +52,27 @@ export const canInviteReviewers = ({ currentUser: { user } }, project) => { return isAccepted && (user.id === heId || isAdmin || isEic) } -export const getUserToken = ({ currentUser }) => get(currentUser, 'user.token') +export const getUserToken = ({ currentUser }) => + get(currentUser, 'user.token', '') export const getHERecommendation = (state, collectionId, fragmentId) => { - const recommendations = get( - state, - `fragments.${fragmentId}.recommendations`, - [], - ) - const collection = get(state, 'collections').find(c => c.id === collectionId) - const heId = get(collection, `handlingEditor.id`) + const heId = chain(state) + .get('collections', []) + .find(c => c.id === collectionId) + .get('hadlingEditor.id', '') + .value() return ( - recommendations.find( - ({ userId, recommendationType }) => - recommendationType === 'editorRecommendation' && userId === heId, - ) || {} + chain(state) + .get(`fragments.${fragmentId}.recommendations`, []) + .find(r => r.rec === 'editorRecommendation' && r.userId === heId) + .value() || {} ) } const canMakeDecisionStatuses = ['submitted', 'pendingApproval'] export const canMakeDecision = (state, collection, fragment = {}) => { - if (fragment.id !== last(collection.fragments)) return false + if (fragment.id !== last(get(collection, 'fragments', []))) return false const status = get(collection, 'status', 'draft') const isEIC = currentUserIs(state, 'adminEiC') @@ -83,7 +82,8 @@ export const canMakeDecision = (state, collection, fragment = {}) => { const canEditManuscriptStatuses = ['draft', 'technicalChecks', 'inQA'] export const canEditManuscript = (state, collection, fragment = {}) => { const isAdmin = currentUserIs(state, 'isAdmin') - if (!isAdmin || fragment.id !== last(collection.fragments)) return false + if (!isAdmin || fragment.id !== last(get(collection, 'fragments', []))) + return false const status = get(collection, 'status', 'draft') return canEditManuscriptStatuses.includes(status) diff --git a/packages/component-faraday-ui/src/PersonInvitation.js b/packages/component-faraday-ui/src/PersonInvitation.js new file mode 100644 index 0000000000000000000000000000000000000000..1ca824bb1354e7e4d00542cb6320fdac9e4b2d61 --- /dev/null +++ b/packages/component-faraday-ui/src/PersonInvitation.js @@ -0,0 +1,77 @@ +import React, { Fragment } from 'react' +import styled from 'styled-components' +import { compose, withHandlers, defaultProps, setDisplayName } from 'recompose' + +import { Text, OpenModal, IconButton, marginHelper } from './' + +const PersonInvitation = ({ + hasAnswer, + person: { name }, + revokeInvitation, + resendInvitation, + ...rest +}) => ( + <Root {...rest}> + <Text>{name}</Text> + {!hasAnswer && ( + <Fragment> + <OpenModal + onConfirm={resendInvitation} + title="Are you sure you want to resend the invitation?" + > + {showModal => ( + <IconButton + icon="refresh-cw" + iconSize={2} + ml={2} + onClick={showModal} + secondary + /> + )} + </OpenModal> + <OpenModal + onConfirm={revokeInvitation} + title="Are you sure you want to revoke the invitation?" + > + {showModal => ( + <IconButton + icon="x-circle" + iconSize={2} + ml={2} + onClick={showModal} + secondary + /> + )} + </OpenModal> + </Fragment> + )} + </Root> +) + +export default compose( + defaultProps({ + person: { + name: 'Unassigned', + }, + onRevoke: id => {}, + onResend: id => {}, + }), + withHandlers({ + revokeInvitation: ({ id, onRevoke }) => () => { + typeof onRevoke === 'function' && onRevoke(id) + }, + resendInvitation: ({ id, onResend, person: { email } }) => () => { + typeof onResend === 'function' && onResend(email) + }, + }), + setDisplayName('PersonInvitation'), +)(PersonInvitation) + +// #region styles +const Root = styled.div` + align-items: center; + display: flex; + + ${marginHelper}; +` +// #endregion diff --git a/packages/component-faraday-ui/src/PersonInvitation.md b/packages/component-faraday-ui/src/PersonInvitation.md new file mode 100644 index 0000000000000000000000000000000000000000..5aff6480f8795faa10c233948e58a2c53f94ac6e --- /dev/null +++ b/packages/component-faraday-ui/src/PersonInvitation.md @@ -0,0 +1,25 @@ +Shows the invited person and the possibility to cancel or resend the invite. + +```js +const invitation = { + id: 'b4305ab6-84e6-48a3-9eb9-fbe0ec80c694', + role: 'handlingEditor', + type: 'invitation', + reason: 'because', + userId: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + hasAnswer: false, + invitedOn: 1533713919119, + isAccepted: false, + respondedOn: 1533714034932, + person: { + id: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + name: 'Toto Schilacci', + }, +}; + +<PersonInvitation + {...invitation} + onResend={id => console.log('resend invitation with id', id)} + onRevoke={id => console.log('revoke invitation with id', id)} + /> +``` diff --git a/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js b/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js index c6a288adb7ba74fb10353db4fcb466c013cef986..4ea572b0b6e24d1d9e153f720137c380236d5394 100644 --- a/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js +++ b/packages/component-faraday-ui/src/contextualBoxes/AssignHE.js @@ -1,8 +1,15 @@ -import React from 'react' +import React, { Fragment } from 'react' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { Button, TextField } from '@pubsweet/ui' -import { compose, withProps, withHandlers, withStateHandlers } from 'recompose' +import { + compose, + withProps, + defaultProps, + withHandlers, + setDisplayName, + withStateHandlers, +} from 'recompose' import { Row, @@ -32,47 +39,60 @@ const AssignHE = ({ top={12} /> </TextContainer> - <Row alignItems="center" height={4} pl={1}> - <Item flex={1}> - <Label>Name</Label> - </Item> - <Item flex={2}> - <Label>Email</Label> - </Item> - <Item flex={1} /> - </Row> - {handlingEditors.map((he, index) => ( - <CustomRow - alignItems="center" - height={4} - isFirst={index === 0} - key={he.id} - pl={1} - > - <Item flex={1}> - <Text secondary>{he.name}</Text> - </Item> - <Item flex={2}> - <Text secondary>{he.email}</Text> - </Item> - <Item flex={1}> - <OpenModal - onConfirm={inviteHandlingEditor(he)} - title="Are you sure you want to invite this HE?" + {handlingEditors.length > 0 && ( + <Fragment> + <Row alignItems="center" height={4} pl={1}> + <Item flex={1}> + <Label>Name</Label> + </Item> + <Item flex={2}> + <Label>Email</Label> + </Item> + <Item flex={1} /> + </Row> + {handlingEditors.map((he, index) => ( + <CustomRow + alignItems="center" + height={4} + isFirst={index === 0} + key={he.id} + pl={1} > - {showModal => ( - <CustomButton onClick={showModal} size="small"> - INVITE - </CustomButton> - )} - </OpenModal> - </Item> - </CustomRow> - ))} + <Item flex={1}> + <Text secondary>{he.name}</Text> + </Item> + <Item flex={2}> + <Text secondary>{he.email}</Text> + </Item> + <Item flex={1}> + <OpenModal + onConfirm={inviteHandlingEditor(he)} + title="Are you sure you want to invite this HE?" + > + {showModal => ( + <CustomButton onClick={showModal} size="small"> + INVITE + </CustomButton> + )} + </OpenModal> + </Item> + </CustomRow> + ))} + </Fragment> + )} </Root> ) export default compose( + defaultProps({ + inviteHandlingEditor: he => {}, + }), + withProps(({ handlingEditors = [] }) => ({ + handlingEditors: handlingEditors.map(he => ({ + ...he, + name: `${he.firstName} ${he.lastName}`, + })), + })), withStateHandlers( { searchValue: '' }, { @@ -90,10 +110,13 @@ export default compose( ), })), withHandlers({ - inviteHandlingEditor: ({ inviteHandlingEditor }) => he => () => { - inviteHandlingEditor(he) + inviteHandlingEditor: ({ inviteHandlingEditor }) => ({ + email = '', + }) => () => { + inviteHandlingEditor(email) }, }), + setDisplayName('AssignHandlingEditor'), )(AssignHE) // #region styles diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 71413c78b0f10310aa085f19d5f8dabbcd6974f5..fd52de8c237374979db60d347a362abaadaec4dc 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -1,3 +1,9 @@ +export * from './styledHelpers' + +// modals +export * from './modals' +export * from './gridItems' + export { default as ActionLink } from './ActionLink' export { default as AuthorWithTooltip } from './AuthorWithTooltip' export { default as AppBar } from './AppBar' @@ -8,6 +14,7 @@ export { default as AuthorTagList } from './AuthorTagList' export { default as AutosaveIndicator } from './AutosaveIndicator' export { default as ContextualBox } from './ContextualBox' export { default as DragHandle } from './DragHandle' +export { default as DownloadZipFiles } from './DownloadZipFiles' export { default as FileItem } from './File' export { default as FileSection } from './FileSection' export { default as IconButton } from './IconButton' @@ -16,19 +23,14 @@ export { default as Logo } from './Logo' export { default as ManuscriptCard } from './ManuscriptCard' export { default as ReviewerBreakdown } from './ReviewerBreakdown' export { default as PersonInfo } from './PersonInfo' +export { default as PersonInvitation } from './PersonInvitation' export { default as SortableList } from './SortableList' export { default as Tag } from './Tag' export { default as Text } from './Text' export { default as WizardAuthors } from './WizardAuthors' export { default as WizardFiles } from './WizardFiles' export { default as PreviewFile } from './PreviewFile' -export { default as DownloadZipFiles } from './DownloadZipFiles' // Manuscript Details export * from './manuscriptDetails' - -export * from './styledHelpers' - -// modals -export * from './modals' -export * from './gridItems' +export * from './contextualBoxes' diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js index 63c356d95c093e71546df8e9a38772491be480eb..005fe0b53b8f3647c62a326dce8b70891777349d 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.js @@ -1,14 +1,27 @@ import React, { Fragment } from 'react' -import { get } from 'lodash' -import { H2, H4, DateParser } from '@pubsweet/ui' -import { compose, withProps } from 'recompose' +import { get, chain } from 'lodash' +import { H2, H4, DateParser, Button } from '@pubsweet/ui' +import { + compose, + withProps, + defaultProps, + withHandlers, + setDisplayName, +} from 'recompose' -import { Tag, Text, Row, AuthorTagList } from 'pubsweet-component-faraday-ui' +import { + Tag, + Row, + Text, + AuthorTagList, + PersonInvitation, +} from 'pubsweet-component-faraday-ui' const ManuscriptHeader = ({ + renderHE, fragment = {}, - editorInChief = 'Unassigned', manuscriptType = {}, + editorInChief = 'Unassigned', collection: { visibleStatus = 'Draft', handlingEditor, customId }, }) => { const { authors = [], metadata = {}, submitted = null } = fragment @@ -48,18 +61,80 @@ const ManuscriptHeader = ({ {editorInChief} </Text> <H4>Handling editor</H4> - <Text ml={1} mr={3}> - {get(handlingEditor, 'name', 'Unassigned')} - </Text> + {renderHE()} </Row> </Fragment> ) } export default compose( - withProps(({ fragment: { metadata }, journal = {} }) => ({ - manuscriptType: get(journal, 'manuscriptTypes', []).find( - t => t.value === get(metadata, 'type', ''), - ), - })), + defaultProps({ + inviteHE: () => {}, + revokeInvitation: () => {}, + resendInvitation: () => {}, + }), + withProps( + ({ + journal = {}, + fragment: { metadata }, + collection: { invitations = [] }, + }) => ({ + manuscriptType: get(journal, 'manuscriptTypes', []).find( + t => t.value === get(metadata, 'type', ''), + ), + heInvitation: invitations.find( + i => i.role === 'handlingEditor' && i.isAccepted, + ), + pendingInvitation: invitations.find( + i => i.role === 'handlingEditor' && !i.hasAnswer, + ), + }), + ), + withHandlers({ + resendInvitation: ({ resendInvitation }) => id => { + resendInvitation(id) + }, + revokeInvitation: ({ revokeInvitation }) => id => { + revokeInvitation(id) + }, + }), + withHandlers({ + renderHE: ({ + inviteHE, + revokeHE, + heInvitation, + resendInvitation, + revokeInvitation, + pendingInvitation, + handlingEditors = [], + collection: { handlingEditor, invitations = [] }, + }) => () => { + if (pendingInvitation) { + const person = chain(handlingEditors) + .filter(he => he.id === pendingInvitation.userId) + .map(he => ({ ...he, name: `${he.firstName} ${he.lastName}` })) + .first() + .value() + + return ( + <PersonInvitation + ml={1} + {...pendingInvitation} + onResend={resendInvitation} + onRevoke={revokeInvitation} + person={person} + /> + ) + } + if (heInvitation) { + return <Text ml={1}>{handlingEditor.name}</Text> + } + return ( + <Button ml={1} onClick={inviteHE} primary size="small"> + Invite + </Button> + ) + }, + }), + setDisplayName('ManuscriptHeader'), )(ManuscriptHeader) diff --git a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.md b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.md index 7122002cd093fe7e23acaf86db687f94389796e3..5c390e8daef5c5d4d06e37bec7da857cddcce12b 100644 --- a/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.md +++ b/packages/component-faraday-ui/src/manuscriptDetails/ManuscriptHeader.md @@ -1,4 +1,4 @@ -Manuscript header +Manuscript header without a HE assigned. ```js const authors = [ @@ -39,6 +39,7 @@ const collection = { id: 'he-1', name: 'Handlington Ignashevici', }, + invitations: [], } const fragment = { @@ -69,11 +70,213 @@ const journal = { abstractRequired: true, }, ], +}; + +<ManuscriptHeader + collection={collection} + fragment={fragment} + journal={journal} + inviteHE={() => console.log('go to invite HE ctx box')} + revokeInvitation={id => console.log('revoke invitation', id)} + resendInvitation={id => console.log('resend invitation', id)} +/> +``` + +Manuscript header with a pending HE invitation. + +```js +const authors = [ + { + email: 'john.doe@gmail.com', + firstName: 'John', + lastName: 'Doe', + isSubmitting: true, + }, + { + email: 'michael.felps@gmail.com', + firstName: 'Michael', + lastName: 'Felps', + isSubmitting: true, + isCorresponding: true, + }, + { + email: 'barrack.obama@gmail.com', + firstName: 'Barrack', + lastName: 'Obama', + }, + { + email: 'barrack.obama@gmail1.com', + firstName: 'Barrack 1', + lastName: 'Obama', + }, + { + email: 'barrack.obama@gmail2.com', + firstName: 'Barrack 2', + lastName: 'Obama', + }, +] + +const collection = { + customId: '55113358', + visibleStatus: 'Pending Approval', + handlingEditor: { + id: 'he-1', + name: 'Handlington Ignashevici', + }, + invitations: [ + { + id: 'b4305ab6-84e6-48a3-9eb9-fbe0ec80c694', + role: 'handlingEditor', + type: 'invitation', + reason: 'because', + userId: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + hasAnswer: false, + invitedOn: 1533713919119, + isAccepted: false, + person: { + id: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + name: 'Toto Schilacci', + }, + }, + ], +} + +const fragment = { + authors, + created: Date.now(), + submitted: Date.now(), + metadata: { + journal: 'Awesomeness', + title: 'A very ok title with many authors', + type: 'research', + }, } -;<ManuscriptHeader +const journal = { + manuscriptTypes: [ + { + label: 'Research', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + ], +}; + +<ManuscriptHeader + collection={collection} + fragment={fragment} + journal={journal} + inviteHE={() => console.log('go to invite HE ctx box')} + revokeInvitation={id => console.log('revoke invitation', id)} + resendInvitation={id => console.log('resend invitation', id)} +/> +``` + +Manuscript header with a pending HE invitation. + +```js +const authors = [ + { + email: 'john.doe@gmail.com', + firstName: 'John', + lastName: 'Doe', + isSubmitting: true, + }, + { + email: 'michael.felps@gmail.com', + firstName: 'Michael', + lastName: 'Felps', + isSubmitting: true, + isCorresponding: true, + }, + { + email: 'barrack.obama@gmail.com', + firstName: 'Barrack', + lastName: 'Obama', + }, + { + email: 'barrack.obama@gmail1.com', + firstName: 'Barrack 1', + lastName: 'Obama', + }, + { + email: 'barrack.obama@gmail2.com', + firstName: 'Barrack 2', + lastName: 'Obama', + }, +] + +const collection = { + customId: '55113358', + visibleStatus: 'Pending Approval', + handlingEditor: { + id: 'he-1', + name: 'Handlington Ignashevici', + }, + invitations: [ + { + id: 'b4305ab6-84e6-48a3-9eb9-fbe0ec80c694', + role: 'handlingEditor', + type: 'invitation', + reason: 'because', + userId: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + hasAnswer: true, + invitedOn: 1533713919119, + isAccepted: true, + respondedOn: 1533714034932, + person: { + id: 'cb7e3e26-6a09-4b79-a6ff-4d1235ee2381', + name: 'Toto Schilacci', + }, + }, + ], +} + +const fragment = { + authors, + created: Date.now(), + submitted: Date.now(), + metadata: { + journal: 'Awesomeness', + title: 'A very ok title with many authors', + type: 'research', + }, +} + +const journal = { + manuscriptTypes: [ + { + label: 'Research', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + ], +}; + +<ManuscriptHeader collection={collection} fragment={fragment} journal={journal} + inviteHE={() => console.log('go to invite HE ctx box')} + revokeInvitation={id => console.log('revoke invitation', id)} + resendInvitation={id => console.log('resend invitation', id)} /> ``` diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index aeca22f0ab50d97a744212cd7a164c07ee16d808..ff0e5b1301733c08d1735aa7b198502739623781 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -4,16 +4,21 @@ import styled from 'styled-components' import { Text, ManuscriptHeader, - ManuscriptDetailsTop, ManuscriptMetadata, + ManuscriptDetailsTop, + ContextualBox, + AssignHE, } from 'pubsweet-component-faraday-ui' const ManuscriptLayout = ({ history, - getSignedUrl, + assignHE, + revokeHE, currentUser, + getSignedUrl, editorInChief, canMakeRevision, + handlingEditors, hasResponseToReviewers, editorialRecommendations, journal = {}, @@ -36,13 +41,24 @@ const ManuscriptLayout = ({ collection={collection} editorInChief={editorInChief} fragment={fragment} + handlingEditors={handlingEditors} journal={journal} + resendInvitation={assignHE} + revokeInvitation={revokeHE} /> <ManuscriptMetadata currentUser={currentUser} fragment={fragment} getSignedUrl={getSignedUrl} /> + {currentUser.canAssignHE && ( + <ContextualBox label="Assign Handling Editor"> + <AssignHE + handlingEditors={handlingEditors} + inviteHandlingEditor={assignHE} + /> + </ContextualBox> + )} </Fragment> ) : ( <Text>Loading...</Text> diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 88f14ae12f0b56c6cf00ef861628351e56b5f41e..cdda8c169507720aa7d44b0f309ccefa0d3a4629 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -26,12 +26,9 @@ import { clearCustomError, } from 'pubsweet-components-faraday/src/redux/errors' import { selectEditorialRecommendations } from 'pubsweet-components-faraday/src/redux/recommendations' -import { - getHandlingEditors, - selectHandlingEditors, -} from 'pubsweet-components-faraday/src/redux/editors' import { getUserToken, + currentUserIs, canMakeRevision, canMakeDecision, canEditManuscript, @@ -42,6 +39,13 @@ import { import ManuscriptLayout from './ManuscriptLayout' import { parseSearchParams, redirectToError } from './utils' +import { + canAssignHE, + getHandlingEditors, + assignHandlingEditor, + revokeHandlingEditor, + selectHandlingEditors, +} from '../redux/editors' export default compose( setDisplayName('ManuscriptPage'), @@ -69,16 +73,20 @@ export default compose( getSignedUrl, clearCustomError, reviewerDecision, + assignHandlingEditor, + revokeHandlingEditor, getFragment: actions.getFragment, getCollection: actions.getCollection, updateVersion: actions.updateFragment, }, ), - connect((state, { currentUser, collection, fragment }) => ({ + connect((state, { currentUser, match, collection, fragment }) => ({ currentUser: { ...currentUser, token: getUserToken(state), + isEIC: currentUserIs(state, 'adminEiC'), isReviewer: currentUserIsReviewer(state), + canAssignHE: canAssignHE(state, match.params.project), }, canMakeRevision: canMakeRevision(state, collection, fragment), permissions: { @@ -89,12 +97,7 @@ export default compose( }, })), ConnectPage(({ currentUser, handlingEditors, collection }) => { - const he = get(collection, 'handlingEditor') - if ( - !he && - !handlingEditors.length && - (get(currentUser, 'admin') || get(currentUser, 'editorInChief')) - ) { + if (currentUser.isEIC) { return [getHandlingEditors()] } return [] @@ -111,6 +114,34 @@ export default compose( setEiC(`${firstName} ${lastName}`) } }, + assignHE: ({ + fragment, + collection, + getFragment, + getCollection, + assignHandlingEditor, + collection: { id: collectionId }, + }) => email => { + assignHandlingEditor({ + email, + collectionId, + }).then(() => { + getCollection({ id: collectionId }) + getFragment(collection, fragment) + }) + }, + revokeHE: ({ + getCollection, + revokeHandlingEditor, + collection: { id: collectionId }, + }) => invitationId => { + revokeHandlingEditor({ + invitationId, + collectionId, + }).then(() => { + getCollection({ id: collectionId }) + }) + }, }), lifecycle({ componentDidMount() { diff --git a/packages/component-manuscript/src/index.js b/packages/component-manuscript/src/index.js index 8d52f148e8c8927db1d86494ca404451da5a917d..01aa93785c2d01ec0fb22a177c2c6a123b2e71b0 100644 --- a/packages/component-manuscript/src/index.js +++ b/packages/component-manuscript/src/index.js @@ -1,5 +1,8 @@ module.exports = { client: { components: [() => require('./components')], + reducers: { + editors: () => require('./redux/editors').default, + }, }, } diff --git a/packages/components-faraday/src/redux/editors.js b/packages/component-manuscript/src/redux/editors.js similarity index 78% rename from packages/components-faraday/src/redux/editors.js rename to packages/component-manuscript/src/redux/editors.js index 17984b255e19f6a3e51ac84481487980774744c1..933b4b23f6f0b54f0f1ea95ebad6ab763870c584 100644 --- a/packages/components-faraday/src/redux/editors.js +++ b/packages/component-manuscript/src/redux/editors.js @@ -1,13 +1,14 @@ -import { get } from 'lodash' +import { get, chain } from 'lodash' import { - get as apiGet, create, remove, update, + get as apiGet, } from 'pubsweet-client/src/helpers/api' +import { currentUserIs } from 'pubsweet-component-faraday-selectors' -const EDITORS_REQUEST = 'EDITORS_REQUEST' const EDITORS_DONE = 'EDITORS_DONE' +const EDITORS_REQUEST = 'EDITORS_REQUEST' const SET_HANDLING_EDITORS = 'SET_HANDLING_EDITORS' const setHandlingEditors = editors => ({ @@ -15,8 +16,19 @@ const setHandlingEditors = editors => ({ payload: { editors }, }) -export const selectFetching = state => get(state, 'editors.isFetching') -export const selectHandlingEditors = state => get(state, 'editors.editors') +export const selectFetching = state => get(state, 'editors.isFetching', false) +export const selectHandlingEditors = state => get(state, 'editors.editors', []) + +export const canAssignHE = (state, collectionId) => { + const isEIC = currentUserIs(state, 'adminEiC') + const hasHE = chain(state) + .get('collections', []) + .find(c => c.id === collectionId) + .get('handlingEditor') + .value() + + return isEIC && !hasHE +} const editorsRequest = () => ({ type: EDITORS_REQUEST }) const editorsDone = () => ({ type: EDITORS_DONE }) @@ -26,7 +38,7 @@ export const getHandlingEditors = () => dispatch => dispatch(setHandlingEditors(res.users)), ) -export const assignHandlingEditor = (email, collectionId) => dispatch => { +export const assignHandlingEditor = ({ email, collectionId }) => dispatch => { dispatch(editorsRequest()) return create(`/collections/${collectionId}/invitations`, { email, @@ -43,10 +55,10 @@ export const assignHandlingEditor = (email, collectionId) => dispatch => { ) } -export const revokeHandlingEditor = ( +export const revokeHandlingEditor = ({ invitationId, collectionId, -) => dispatch => { +}) => dispatch => { dispatch(editorsRequest()) return remove( `/collections/${collectionId}/invitations/${invitationId}`, diff --git a/packages/component-manuscript/src/redux/index.js b/packages/component-manuscript/src/redux/index.js new file mode 100644 index 0000000000000000000000000000000000000000..05f36f6d43441bbc101f5490a0aa3677ecf26a15 --- /dev/null +++ b/packages/component-manuscript/src/redux/index.js @@ -0,0 +1 @@ +export { default as editors } from './editors' diff --git a/packages/components-faraday/src/components/Dashboard/DashboardPage.js b/packages/components-faraday/src/components/Dashboard/DashboardPage.js index 1e9e2825c054b65a057e624b72cc6e839fb8e5d4..ee51f5000102853721063b5c7666f4e7db5365ac 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardPage.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardPage.js @@ -1,4 +1,3 @@ -import { get } from 'lodash' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { actions } from 'pubsweet-client' @@ -15,7 +14,6 @@ import { } from 'pubsweet-component-faraday-selectors' import { Dashboard } from './' -import { getHandlingEditors } from '../../redux/editors' import { priorityFilter, orderFilter, withFiltersHOC } from '../Filters' export default compose( @@ -59,12 +57,6 @@ export default compose( createDraftSubmission: () => dispatch(createDraftSubmission(history)), }), ), - ConnectPage( - ({ currentUser }) => - get(currentUser, 'admin') || get(currentUser, 'editorInChief') - ? [getHandlingEditors()] - : [], - ), withRouter, withJournal, withFiltersHOC({ diff --git a/packages/components-faraday/src/index.js b/packages/components-faraday/src/index.js index be69a5315674890955a7de5fa07fcbfdd22bf9a5..db4b6cd565c1db6e4afba19f44272d8200786c50 100644 --- a/packages/components-faraday/src/index.js +++ b/packages/components-faraday/src/index.js @@ -4,7 +4,6 @@ module.exports = { reducers: { authors: () => require('./redux/authors').default, customError: () => require('./redux/errors').default, - editors: () => require('./redux/editors').default, files: () => require('./redux/files').default, reviewers: () => require('./redux/reviewers').default, technicalCheck: () => require('./redux/technicalCheck').default, diff --git a/packages/components-faraday/src/redux/index.js b/packages/components-faraday/src/redux/index.js index c7fd43a9557173a35047de82ce6f5e64f18a8e03..30c41635ad5620a20b3ab66da9d7e4f8e34dc8df 100644 --- a/packages/components-faraday/src/redux/index.js +++ b/packages/components-faraday/src/redux/index.js @@ -1,7 +1,6 @@ export { default as files } from './files' export { default as errors } from './errors' export { default as authors } from './authors' -export { default as editors } from './editors' export { default as reviewers } from './reviewers' export { default as technicalCheck } from './technicalCheck' export { default as recommendations } from './recommendations' diff --git a/packages/hindawi-theme/src/elements/Button.js b/packages/hindawi-theme/src/elements/Button.js index 34720cd8f3d58e012918c38dd57736cb8e676030..e6f844be07b67b06e48fdbbacbf85942fc5ef2d6 100644 --- a/packages/hindawi-theme/src/elements/Button.js +++ b/packages/hindawi-theme/src/elements/Button.js @@ -12,9 +12,11 @@ const primary = css` background-color: ${lighten('button.primary', 0.1)}; } + &:focus, &:active { background-color: ${th('button.primary')}; border-color: ${th('button.borderActive')}; + outline: none; } &[disabled] {