diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index 424a53188c3058b27cd8a94af0fb92d34112611a..243136915cb994367d43744fa0c2822263679761 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -366,4 +366,14 @@ export const canMakeRecommendation = (state, collection, fragment = {}) => { const status = get(collection, 'status', 'draft') return isHE && canMakeRecommendationStatuses.includes(status) } + +export const canSubmitRevision = (state, collection, fragment) => { + const userId = get(state, 'currentUser.user.id') + const fragmentAuthors = chain(fragment) + .get('authors', []) + .map(a => a.id) + .value() + + return get(fragment, 'revision', null) && fragmentAuthors.includes(userId) +} // #endregion diff --git a/packages/component-faraday-ui/src/AuthorCard.js b/packages/component-faraday-ui/src/AuthorCard.js index c4dc81df2a9cf981cb0e7d05e4e2bc1b95b07546..2ace7fa985268bb535cdeff598a1a5ac7a030433 100644 --- a/packages/component-faraday-ui/src/AuthorCard.js +++ b/packages/component-faraday-ui/src/AuthorCard.js @@ -39,14 +39,14 @@ const AuthorTitle = ({ formSubmitting, toggleEditMode, isCorresponding, - isAuthorsFetching, }) => ( <Fragment> {!editMode ? ( <Fragment> {!isSubmitting && ( <OpenModal - isFetching={isAuthorsFetching} + isFetching={isFetching} + modalKey="deleteAuthor" onConfirm={deleteAuthor} subtitle={`${get(author, 'firstName', '')} ${get( author, @@ -220,6 +220,7 @@ const EnhancedAuthorEdit = compose( const Author = ({ author, listIndex, + isFetching, isAuthorEdit, deleteAuthor, toggleEditMode, @@ -232,6 +233,7 @@ const Author = ({ isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} isCorresponding={author.isCorresponding} + isFetching={isFetching} isSubmitting={author.isSubmitting} listIndex={listIndex} toggleEditMode={toggleEditMode} @@ -277,6 +279,7 @@ const AuthorCard = ({ editMode={editMode} isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} + isFetching={isFetching} listIndex={index} toggleEditMode={toggleEdit(index)} /> diff --git a/packages/component-faraday-ui/src/FileSection.js b/packages/component-faraday-ui/src/FileSection.js index 0da010405bfdf934b7c65ee88d17d44e89849397..596f65e8436d8362ab1fdf506c1f5b1dd822071c 100644 --- a/packages/component-faraday-ui/src/FileSection.js +++ b/packages/component-faraday-ui/src/FileSection.js @@ -44,7 +44,7 @@ const FileSection = ({ connectDropTarget, allowedFileExtensions, files = [], - onFilePick = () => {}, + onFilePick, onPreview, onDownload, onDelete, diff --git a/packages/component-faraday-ui/src/WizardAuthors.js b/packages/component-faraday-ui/src/WizardAuthors.js index 0136bdde1029aa640cdb4a850fb98500bfb4981e..b9dbdcf9a3f93948130124018c7777d1173b643e 100644 --- a/packages/component-faraday-ui/src/WizardAuthors.js +++ b/packages/component-faraday-ui/src/WizardAuthors.js @@ -58,16 +58,15 @@ const WizardAuthors = ({ error, journal, moveAuthor, + isFetching, authors = [], isAuthorEdit, deleteAuthor, addNewAuthor, setAuthorEdit, saveNewAuthor, - isAuthorsFetching, editExistingAuthor, authorEditorSubmit, - isFetching, }) => ( <Fragment> <Row alignItems="center" justify="flex-start"> @@ -75,6 +74,7 @@ const WizardAuthors = ({ <Label>Authors</Label> <ActionLink data-test-id="submission-add-author" + disabled={isAuthorEdit} icon="plus" onClick={addNewAuthor} > @@ -103,7 +103,6 @@ const WizardAuthors = ({ dragHandle={DragHandle} editExistingAuthor={editExistingAuthor} isAuthorEdit={isAuthorEdit} - isAuthorsFetching={isAuthorsFetching} isFetching={isFetching} itemKey="id" items={authors} @@ -131,11 +130,12 @@ export default compose( setAuthors, setFetching, onAuthorEdit, + formName = 'submission', }) => authors => { setAuthors(authors) setFetching(false) - onAuthorEdit(null) - changeForm('submission', 'authors', authors) + typeof onAuthorEdit === 'function' && onAuthorEdit(null) + changeForm(formName, 'authors', authors) }, }), withHandlers({ @@ -143,7 +143,7 @@ export default compose( if (authors.some(a => a.id === 'newAuthor')) { return } - onAuthorEdit(0) + typeof onAuthorEdit === 'function' && onAuthorEdit(0) setAuthors([ ...authors, { id: 'newAuthor', isCorresponding: false, isSubmitting: false }, @@ -153,7 +153,7 @@ export default compose( setFormAuthors(SortableList.moveItem(authors, dragIndex, hoverIndex)) }, setAuthorEdit: ({ authors, setAuthors, onAuthorEdit }) => index => { - onAuthorEdit(index) + typeof onAuthorEdit === 'function' && onAuthorEdit(index) setAuthors(authors.filter(a => a.id !== 'newAuthor')) }, saveNewAuthor: ({ authors, setFormAuthors }) => author => { @@ -170,41 +170,52 @@ export default compose( }, deleteAuthor: ({ authors, - project, - version, + fragment, + collection, + setFetching, deleteAuthor, setFormAuthors, - }) => author => ({ hideModal, setModalError }) => - deleteAuthor(project.id, version.id, author.id) + }) => author => ({ hideModal, setModalError }) => { + setFetching(true) + return deleteAuthor({ + authorId: author.id, + fragmentId: fragment.id, + collectionId: collection.id, + }) .then(() => { + setFetching(false) const newAuthors = setCorrespondingAuthor( authors.filter(a => a.id !== author.id), ) setFormAuthors(newAuthors) hideModal() }) - .catch(handleError(setModalError)), + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, }), withHandlers({ authorEditorSubmit: ({ authors, - project, - version, + fragment, addAuthor, + collection, setFetching, saveNewAuthor, editExistingAuthor, }) => (values, dispatch, { toggleEditMode }) => { if (values.id === 'newAuthor') { setFetching(true) - return addAuthor( - { + return addAuthor({ + fragmentId: fragment.id, + collectionId: collection.id, + author: { ...omit(values, 'id'), isSubmitting: authors.length === 1, }, - project.id, - version.id, - ).then(saveNewAuthor) + }).then(saveNewAuthor) } editExistingAuthor(values) setTimeout(toggleEditMode, 10) diff --git a/packages/component-faraday-ui/src/WizardFiles.js b/packages/component-faraday-ui/src/WizardFiles.js index 5c08242dd168384602c091f6b982435afd147169..9bd0591a7ff21c8ca020583682a8130d7b9da290 100644 --- a/packages/component-faraday-ui/src/WizardFiles.js +++ b/packages/component-faraday-ui/src/WizardFiles.js @@ -69,19 +69,29 @@ const WizardFiles = ({ </Fragment> ) +const initialFiles = { + manuscripts: [], + coverLetter: [], + supplementary: [], +} + export default compose( withFilePreview, withFileDownload, - withState('files', 'setFiles', ({ files }) => files), + withState('files', 'setFiles', ({ files = initialFiles }) => files), withState('fetching', 'setFilesFetching', { manuscripts: false, coverLetter: false, supplementary: false, }), withHandlers({ - setFormFiles: ({ changeForm, setFiles }) => files => { + setFormFiles: ({ + setFiles, + changeForm, + formName = 'submission', + }) => files => { setFiles(files) - changeForm('submission', 'files', files) + changeForm(formName, 'files', files) }, setFilesFetching: ({ setFilesFetching }) => (type, value) => { setFilesFetching(p => ({ @@ -93,13 +103,13 @@ export default compose( withHandlers({ addFile: ({ files, - version, + fragment, uploadFile, setFormFiles, setFilesFetching, }) => type => file => { setFilesFetching(type, true) - uploadFile({ file, type, fragment: version }) + uploadFile({ file, type, fragment }) .then(f => { const newFiles = { ...files, @@ -112,17 +122,16 @@ export default compose( setFilesFetching(type, false) }) }, - downloadFile: ({ downloadFile, token }) => file => { - downloadFile(file) - }, + previewFile: ({ previewFile }) => previewFile, + downloadFile: ({ downloadFile, token }) => downloadFile, deleteFile: ({ - deleteFile, files, + deleteFile, setFormFiles, setFilesFetching, }) => type => file => { setFilesFetching(type, true) - deleteFile(file.id, type) + deleteFile({ fileId: file.id, type }) .then(() => { const newFiles = { ...files, @@ -142,9 +151,6 @@ export default compose( } setFormFiles(newFiles) }, - previewFile: ({ previewFile }) => file => { - previewFile(file) - }, changeList: ({ files, setFormFiles }) => (from, to, fileId) => { const swappedFile = files[from].find(f => f.id === fileId) diff --git a/packages/component-faraday-ui/src/helpers/withCountries.js b/packages/component-faraday-ui/src/helpers/withCountries.js index bd642eddd9bac3cd647a387b93179b957f9513da..fb72d5997d1270d94f7a11dd8325510a24034e1e 100644 --- a/packages/component-faraday-ui/src/helpers/withCountries.js +++ b/packages/component-faraday-ui/src/helpers/withCountries.js @@ -1,7 +1,7 @@ import { withProps } from 'recompose' import countrylist from 'country-list' -const countryMapper = c => { +const countryMapper = (c = 'GB') => { switch (c) { case 'GB': return 'UK' @@ -14,7 +14,7 @@ const countryMapper = c => { } } -const codeMapper = c => { +const codeMapper = (c = 'UK') => { switch (c) { case 'UK': return 'GB' diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 263820a923f836e555affe74bed8bd51d6ea7b6d..a4feb94b3657fd39ecc004675bf70b84a544b20b 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -47,6 +47,8 @@ export { default as WizardFiles } from './WizardFiles' export { default as TextTooltip } from './TextTooltip' export { default as EditorialReportCard } from './EditorialReportCard' +export { SubmitRevision } from './submissionRevision' + export * from './OverrideAlert' export * from './manuscriptDetails' export * from './contextualBoxes' diff --git a/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js new file mode 100644 index 0000000000000000000000000000000000000000..4b25942909135eec5dc15b08f14fe2d84162bf85 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.js @@ -0,0 +1,100 @@ +import React from 'react' +import { get } from 'lodash' +import { Field } from 'redux-form' +import { required } from 'xpub-validators' +import { + Row, + Item, + Label, + Textarea, + WizardAuthors, + RowOverrideAlert, + ItemOverrideAlert, +} from 'pubsweet-component-faraday-ui' +import { Menu, TextField, ValidatedField } from '@pubsweet/ui' +import { th } from '@pubsweet/ui-toolkit' +import styled from 'styled-components' +import { ContextualBox } from '../' + +const Empty = () => <div /> + +const DetailsAndAuthors = ({ + journal, + fragment, + addAuthor, + collection, + changeForm, + formValues, + isAuthorEdit, + authorsError, + deleteAuthor, + manuscriptTypes, + getTooltipContent, + isAuthorsFetching, + onAuthorEdit, + ...rest +}) => ( + <ContextualBox label="Details and Authors" startExpanded transparent> + <Root> + <Row mb={3}> + <Item data-test-id="submission-title" flex={3} mr={1} vertical> + <Label required>MANUSCRIPT TITLE</Label> + <ValidatedField + component={TextField} + name="metadata.title" + validate={[required]} + /> + </Item> + <ItemOverrideAlert data-test-id="submission-type" vertical> + <Label required>MANUSCRIPT TYPE</Label> + <ValidatedField + component={input => ( + <Menu + options={manuscriptTypes} + {...input} + placeholder="Please select" + /> + )} + name="metadata.type" + validate={[required]} + /> + </ItemOverrideAlert> + </Row> + + <RowOverrideAlert mb={2}> + <Item data-test-id="submission-abstract" vertical> + <Label required>ABSTRACT</Label> + <ValidatedField + component={Textarea} + minHeight={15} + name="metadata.abstract" + validate={[required]} + /> + </Item> + </RowOverrideAlert> + + <Field component={Empty} name="authors" /> + <WizardAuthors + addAuthor={addAuthor} + authors={get(fragment, 'revision.authors', [])} + changeForm={changeForm} + collection={collection} + deleteAuthor={deleteAuthor} + error={authorsError} + formName="revision" + fragment={fragment} + isAuthorEdit={isAuthorEdit} + isAuthorsFetching={isAuthorsFetching} + journal={journal} + onAuthorEdit={onAuthorEdit} + /> + </Root> + </ContextualBox> +) + +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +export default DetailsAndAuthors diff --git a/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md new file mode 100644 index 0000000000000000000000000000000000000000..1ac22499492ee611bb322827d7f5ad34fae164b8 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/DetailsAndAuthors.md @@ -0,0 +1,167 @@ +```js +const { compose, withProps, withHandlers } = require('recompose') +const { connect } = require('react-redux') +const { reduxForm, getFormValues, change } = require('redux-form') + +const Wrapper = compose( + withHandlers({ + onAuthorEdit: props => () => console.log('Edit author'), + }), + withProps({ + manuscriptTypes: [ + { + label: 'Research', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + ], + fragment: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + username: 'mihail.hagiu+re@thinslices.com', + }, + { + id: '96673581-5916-46c5-8a57-d9e69c3e713d', + username: 'admin', + }, + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + collection: { + id: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + type: 'collection', + owners: [ + '96673581-5916-46c5-8a57-d9e69c3e713d', + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + ], + status: 'rejected', + created: 1539245087543, + customId: '5074586', + fragments: ['a9dc38fe-5524-4728-b97f-9495a2eb4bee'], + technicalChecks: {}, + currentVersion: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + '96673581-5916-46c5-8a57-d9e69c3e713d', + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + visibleStatus: 'Rejected', + }, + }), + connect( + state => ({ + formValues: getFormValues('styleguide')(state), + }), + { + changeForm: change, + }, + ), + reduxForm({ + form: 'styleguide', + }), +)(props => console.log('Padadas', props) || <DetailsAndAuthors {...props} />) +;<Wrapper /> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js new file mode 100644 index 0000000000000000000000000000000000000000..5edc9501d7ae2861cce821a8f8294f31b2986c97 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.js @@ -0,0 +1,72 @@ +import React from 'react' +import { get } from 'lodash' +import { Field } from 'redux-form' +import styled from 'styled-components' +import { Icon } from '@pubsweet/ui' +import { Row, Text, WizardFiles } from 'pubsweet-component-faraday-ui' +import { th } from '@pubsweet/ui-toolkit' +import { ContextualBox } from '../' + +const Empty = () => <div /> + +const ManuscriptFiles = ({ + token, + fragment, + formName, + collection, + changeForm, + deleteFile, + uploadFile, + filesError, + previewFile, + getSignedUrl, +}) => ( + <ContextualBox label="Manuscript Files" startExpanded transparent> + <Root> + <Row justify="flex-start" mb={2}> + <Text secondary> + Drag & drop files in the specific section or click{' '} + <CustomIcon secondary size={2}> + plus + </CustomIcon>{' '} + to upload. Use the{' '} + <CustomIcon secondary size={2}> + menu + </CustomIcon>{' '} + icon to reorder or move files to a different type. + </Text> + </Row> + <Field component={Empty} name="files" /> + <WizardFiles + changeForm={changeForm} + collection={collection} + deleteFile={deleteFile} + files={get(fragment, 'revision.files', {})} + formName={formName} + fragment={fragment} + getSignedUrl={getSignedUrl} + previewFile={previewFile} + token={token} + uploadFile={uploadFile} + /> + {filesError && ( + <Row justify="flex-start" mt={1}> + <Text error>{filesError}</Text> + </Row> + )} + </Root> + </ContextualBox> +) + +export default ManuscriptFiles + +// #region styled-components +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +const CustomIcon = styled(Icon)` + vertical-align: sub; +` +// #endregion diff --git a/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md new file mode 100644 index 0000000000000000000000000000000000000000..42a8e212005461b35ce3cedc2926dc0ffff6b7a2 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ManuscriptFiles.md @@ -0,0 +1,21 @@ +```js +const { compose, withHandlers } = require('recompose') +const { connect } = require('react-redux') +const { reduxForm, getFormValues } = require('redux-form') + +const Wrapper = compose( + withHandlers({ + uploadFile: props => file => Promise.resolve(file), + deleteFile: props => file => console.log('Deleted', file), + }), + connect(state => ({ + formValues: getFormValues('styleguide')(state), + })), + reduxForm({ + form: 'styleguide', + }), +)(( props ) => ( + <ManuscriptFiles {...props} /> +)) +;<Wrapper /> +``` \ No newline at end of file diff --git a/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js new file mode 100644 index 0000000000000000000000000000000000000000..f3b6c168fa2da6e0526cdd2d94f8634efa3809b6 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.js @@ -0,0 +1,85 @@ +import React from 'react' +import { isEmpty } from 'lodash' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { FilePicker, Spinner, ValidatedField } from '@pubsweet/ui' + +import { + Row, + Item, + Textarea, + ActionLink, +} from 'pubsweet-component-faraday-ui/src' + +import { + Label, + FileItem, + ContextualBox, + ItemOverrideAlert, + withFilePreview, + withFileDownload, +} from '../' + +const allowedFileExtensions = ['pdf', 'doc', 'docx', 'txt', 'rdf', 'odt'] +const ResponseToReviewer = ({ + file, + onDelete, + onUpload, + isFetching, + previewFile, + downloadFile, +}) => ( + <ContextualBox + label="Response to Reviewer Comments" + startExpanded + transparent + > + <Root> + <Row alignItems="center" justify="space-between"> + <Item> + <Label required>Your Reply</Label> + {isFetching ? ( + <Spinner /> + ) : ( + <FilePicker + allowedFileExtensions={allowedFileExtensions} + disabled={!isEmpty(file)} + onUpload={onUpload} + > + <ActionLink disabled={!isEmpty(file)} icon="plus"> + UPLOAD FILE + </ActionLink> + </FilePicker> + )} + </Item> + </Row> + + <Row> + <ItemOverrideAlert vertical> + <ValidatedField + component={Textarea} + name="responseToReviewers.content" + /> + </ItemOverrideAlert> + </Row> + + {!isEmpty(file) && ( + <Row justify="flex-start" mt={1}> + <FileItem + item={file} + onDelete={onDelete} + onDownload={downloadFile} + onPreview={previewFile} + /> + </Row> + )} + </Root> + </ContextualBox> +) + +const Root = styled.div` + border-left: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')}; + padding-left: calc(${th('gridUnit')} * 1); +` + +export default withFileDownload(withFilePreview(ResponseToReviewer)) diff --git a/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md new file mode 100644 index 0000000000000000000000000000000000000000..e9264445b46902365f4a0d50608d13bd67fde690 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/ResponseToReviewer.md @@ -0,0 +1,7 @@ +```js +const allowedFileExtensions = ['pdf', 'doc', 'docx'] + +const onUpload = (f) => {console.log('Upload', f)} + +<ResponseToReviewer onUpload={onUpload} allowedFileExtensions={allowedFileExtensions}/> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js new file mode 100644 index 0000000000000000000000000000000000000000..be61f16dd096256d2538fdcbbb4cdae27a5eeb80 --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.js @@ -0,0 +1,94 @@ +import React from 'react' +import { Button } from '@pubsweet/ui' +import styled from 'styled-components' +import { reduxForm } from 'redux-form' +import { th } from '@pubsweet/ui-toolkit' +import { Row } from 'pubsweet-component-faraday-ui/src' + +import { ContextualBox, Text } from '../' +import { ManuscriptFiles, DetailsAndAuthors, ResponseToReviewer } from './' + +const SubmitRevision = ({ + journal, + addFile, + fragment, + addAuthor, + deleteFile, + collection, + changeForm, + isFetching, + currentUser, + previewFile, + hasFormError, + deleteAuthor, + getSignedUrl, + handleSubmit, + responseFile, + downloadFile, + fetchingError, + addResponseFile, + deleteResponseFile, + // + onAuthorEdit, + isEditingAuthor, +}) => ( + <ContextualBox highlight label="Submit Revision" mb={2}> + <Root> + <DetailsAndAuthors + addAuthor={addAuthor} + changeForm={changeForm} + collection={collection} + deleteAuthor={deleteAuthor} + fragment={fragment} + isAuthorEdit={isEditingAuthor} + manuscriptTypes={journal.manuscriptTypes} + onAuthorEdit={onAuthorEdit} + /> + + <ManuscriptFiles + changeForm={changeForm} + collection={collection} + deleteFile={deleteFile} + downloadFile={downloadFile} + formName="revision" + fragment={fragment} + getSignedUrl={getSignedUrl} + previewFile={previewFile} + token={currentUser.token} + uploadFile={addFile} + /> + + <ResponseToReviewer + file={responseFile} + getSignedUrl={getSignedUrl} + isFetching={isFetching} + onDelete={deleteResponseFile} + onUpload={addResponseFile} + token={currentUser.token} + /> + + <Row> + {hasFormError && ( + <Text align="center" error mt={1}> + There are some missing required fields. + </Text> + )} + </Row> + + <Row justify="flex-end" mt={1}> + <Button ml={2} onClick={handleSubmit} primary size="medium"> + Submit revision + </Button> + </Row> + </Root> + </ContextualBox> +) + +const Root = styled.div` + background-color: ${th('colorBackgroundHue2')}; + padding: calc(${th('gridUnit')} * 2); +` + +export default reduxForm({ form: 'revision', destroyOnUnmount: false })( + SubmitRevision, +) diff --git a/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md new file mode 100644 index 0000000000000000000000000000000000000000..6c9a0bde8927225e69c55be3fa2ee2cf2f78938c --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/SubmitRevision.md @@ -0,0 +1,156 @@ +```js +const props = { + fragment: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + username: 'mihail.hagiu+re@thinslices.com', + }, + { + id: '96673581-5916-46c5-8a57-d9e69c3e713d', + username: 'admin', + }, + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'RO', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + collection: { + id: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + type: 'collection', + owners: [ + '96673581-5916-46c5-8a57-d9e69c3e713d', + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + ], + status: 'rejected', + created: 1539245087543, + customId: '5074586', + fragments: ['a9dc38fe-5524-4728-b97f-9495a2eb4bee'], + technicalChecks: {}, + currentVersion: { + id: 'a9dc38fe-5524-4728-b97f-9495a2eb4bee', + type: 'fragment', + files: { + coverLetter: [], + manuscripts: [ + { + id: + 'a9dc38fe-5524-4728-b97f-9495a2eb4bee/4a452733-e05d-485a-a0be-7c199c5eb4a1', + name: 'manuscris.pdf', + size: 39973, + originalName: 'manuscris.pdf', + }, + ], + supplementary: [], + }, + owners: [ + '81586d97-d2b4-4423-a1e3-efd228fc67b8', + '96673581-5916-46c5-8a57-d9e69c3e713d', + ], + authors: [ + { + id: '81586d97-d2b4-4423-a1e3-efd228fc67b8', + email: 'mihail.hagiu+re@thinslices.com', + country: 'AX', + lastName: 'ihail', + firstName: 'M', + affiliation: 'TS', + isSubmitting: true, + isCorresponding: true, + }, + ], + created: '2018-10-11T08:04:47.636Z', + version: 1, + metadata: { + type: 'research', + title: 'czxcxzc', + journal: 'Bioinorganic Chemistry and Applications', + abstract: 'xdzczxc', + }, + conflicts: { + hasFunding: '', + hasConflicts: 'no', + hasDataAvailability: '', + }, + submitted: 1539606240257, + collectionId: 'e69cddda-74be-47aa-8f99-c388ef5c8a77', + declarations: { + agree: true, + }, + fragmentType: 'version', + recommendations: [], + }, + visibleStatus: 'Rejected', + }, + journal: { + manuscriptTypes: [ + { + label: 'Research Article', + value: 'research', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Review Article', + value: 'review', + author: true, + peerReview: true, + abstractRequired: true, + }, + { + label: 'Letter to the editor', + value: 'letter-to-editor', + author: true, + peerReview: false, + abstractRequired: false, + }, + ], + }, +} +;<SubmitRevision {...props} /> +``` diff --git a/packages/component-faraday-ui/src/submissionRevision/index.js b/packages/component-faraday-ui/src/submissionRevision/index.js new file mode 100644 index 0000000000000000000000000000000000000000..262a71e3f9707fbb08597535af9a3f1aef39b22f --- /dev/null +++ b/packages/component-faraday-ui/src/submissionRevision/index.js @@ -0,0 +1,4 @@ +export { default as ResponseToReviewer } from './ResponseToReviewer' +export { default as ManuscriptFiles } from './ManuscriptFiles' +export { default as DetailsAndAuthors } from './DetailsAndAuthors' +export { default as SubmitRevision } from './SubmitRevision' diff --git a/packages/component-manuscript-manager/src/routes/collections/get.js b/packages/component-manuscript-manager/src/routes/collections/get.js index ddeeb504d1efee96f4362a6e72455cb7529298f2..1314ffda0107a1e4c80a0edab70561f97db82093 100644 --- a/packages/component-manuscript-manager/src/routes/collections/get.js +++ b/packages/component-manuscript-manager/src/routes/collections/get.js @@ -1,3 +1,8 @@ +const { last, get } = require('lodash') + +const filterDuplicates = collection => + get(collection, 'currentVersion.id') === last(collection.fragments) + const { authsome: authsomeHelper, } = require('pubsweet-component-helper-service') @@ -16,5 +21,5 @@ module.exports = models => async (req, res) => { }) } - res.status(200).json(collections) + res.status(200).json(collections.filter(filterDuplicates)) } diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js index 64f10e4fc62bc24ca139281b489037818d93ec55..ecfadb0a4ae20fb38e8e9511b10120fb33fabfaf 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js +++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/emailCopy.js @@ -5,10 +5,12 @@ const journalName = config.get('journal.name') const getEmailCopy = ({ emailType, titleText, expectedDate, customId }) => { let paragraph const hasLink = true - const hasIntro = true - const hasSignature = true + let hasIntro = true + let hasSignature = true switch (emailType) { case 'he-new-version-submitted': + hasIntro = false + hasSignature = false paragraph = `The authors of ${titleText} have submitted a revised version. <br/><br/> To review this new submission and proceed with the review process, please visit the manuscript details page.` break diff --git a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js index 75be73180ca247e1d78800a3f1ac34587cc85208..7f0b8fd3d3364ddc038385f6424ec53b9578fba3 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js +++ b/packages/component-manuscript-manager/src/routes/fragments/notifications/notifications.js @@ -21,6 +21,7 @@ module.exports = { UserModel, collection, isNewVersion = false, + previousVersion, isTechnicalCheck, isMajorRecommendation, }) { @@ -59,7 +60,6 @@ module.exports = { const heUser = await UserModel.find(handlingEditor.id) sendHandlingEditorEmail({ email, - eicName, baseUrl, customId, title: parsedFragment.title, @@ -73,8 +73,9 @@ module.exports = { baseUrl, customId, UserModel, - fragmentHelper, + previousVersion, title: parsedFragment.title, + heName: collection.handlingEditor.name, }) } @@ -104,7 +105,6 @@ module.exports = { const sendHandlingEditorEmail = ({ email, title, - eicName, baseUrl, customId, handlingEditor, @@ -113,19 +113,17 @@ const sendHandlingEditorEmail = ({ email.toUser = { email: handlingEditor.email, - name: handlingEditor.name, } email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, { id: handlingEditor.id, token: handlingEditor.accessTokens.unsubscribe, }) - email.content.signatureName = eicName email.content.subject = `${customId}: Revision submitted` const { html, text } = email.getNotificationBody({ emailBodyProps: getEmailCopy({ emailType, - titleText: `the manuscript titled ${title}`, + titleText: `the manuscript titled "${title}"`, }), }) email.sendEmail({ html, text }) @@ -134,14 +132,18 @@ const sendHandlingEditorEmail = ({ const sendReviewersEmail = async ({ email, title, + heName, baseUrl, customId, UserModel, - fragmentHelper, + previousVersion, }) => { email.content.subject = `${customId}: A manuscript you reviewed has been revised` + email.content.signatureName = heName + email.fromEmail = `${heName} <${staffEmail}>` const emailType = 'submitted-reviewers-after-revision' + const fragmentHelper = new Fragment({ fragment: previousVersion }) const reviewers = await fragmentHelper.getReviewers({ UserModel, type: 'submitted', @@ -165,7 +167,7 @@ const sendReviewersEmail = async ({ const { html, text } = email.getNotificationBody({ emailBodyProps: getEmailCopy({ emailType, - titleText: `the manuscript titled ${title}`, + titleText: `the manuscript titled "${title}"`, expectedDate: services.getExpectedDate({ daysExpected: 14 }), }), }) diff --git a/packages/component-manuscript-manager/src/routes/fragments/patch.js b/packages/component-manuscript-manager/src/routes/fragments/patch.js index 6f4cd8a9eb18956142727537add44c9ed28035e0..09e3dbc8f106ff8414952801c8b722eb9e0d711b 100644 --- a/packages/component-manuscript-manager/src/routes/fragments/patch.js +++ b/packages/component-manuscript-manager/src/routes/fragments/patch.js @@ -116,10 +116,11 @@ module.exports = models => async (req, res) => { collection.save() notifications.sendNotifications({ - fragment, collection, isNewVersion: true, + fragment: newFragment, UserModel: models.User, + previousVersion: fragment, baseUrl: services.getBaseUrl(req), isMajorRecommendation: heRecommendation.recommendation === 'major', }) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index eb3581a466de4eb6213a08f54cbb7756eac811ba..38ef102fa143ae12a2a5bf78261312211e8d8083 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -12,6 +12,7 @@ import { ManuscriptDetailsTop, ResponseToInvitation, ManuscriptEicDecision, + SubmitRevision, } from 'pubsweet-component-faraday-ui' import ReviewerReportCard from './ReviewReportCard' @@ -68,6 +69,7 @@ const ManuscriptLayout = ({ editorialCommentsExpanded, toggleEditorialComments, onInvitePublonReviewer, + submitRevision, }) => ( <Root pb={30}> {!isEmpty(collection) && !isEmpty(fragment) ? ( @@ -108,6 +110,10 @@ const ManuscriptLayout = ({ /> )} + {get(currentUser, 'permissions.canSubmitRevision', false) && ( + <SubmitRevision {...submitRevision} /> + )} + {submittedOwnRecommendation && ( <ReviewerReportCard getSignedUrl={getSignedUrl} diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 3304d0385603f47ed75cdc1b78bf04a6b3fa7aa7..781fb962155bac6ca78ee9eef5f51f16901d6530 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -42,6 +42,7 @@ import { canMakeRevision, canMakeDecision, isHEToManuscript, + canSubmitRevision, canEditManuscript, canInviteReviewers, pendingHEInvitation, @@ -65,6 +66,7 @@ import { } from 'pubsweet-component-faraday-ui' import ManuscriptLayout from './ManuscriptLayout' +import withSubmitRevision from '../submitRevision/withSubmitRevision' import { parseEicDecision, parseSearchParams, @@ -145,11 +147,11 @@ export default compose( journal, fragment, collection, + isFetching, currentUser, pendingHEInvitation, pendingOwnRecommendation, pendingReviewerInvitation, - isFetching, }, ) => ({ currentUser: { @@ -162,6 +164,7 @@ export default compose( isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')), isHEToManuscript: isHEToManuscript(state, get(collection, 'id', '')), permissions: { + canSubmitRevision: canSubmitRevision(state, collection, fragment), canMakeHERecommendation: canMakeHERecommendation(state, { collection, statuses: get(journal, 'statuses', {}), @@ -192,6 +195,7 @@ export default compose( publonsFetching: isFetching, }, formValues: { + revision: getFormValues('revision')(state), eicDecision: getFormValues('eic-decision')(state), reviewerReport: getFormValues('reviewerReport')(state), responseToInvitation: getFormValues('answer-invitation')(state), @@ -503,6 +507,7 @@ export default compose( get(currentUser, 'isReviewer', false) && isUndefined(submittedOwnRecommendation), })), + withSubmitRevision, lifecycle({ componentDidMount() { const { @@ -519,11 +524,11 @@ export default compose( fetchUpdatedCollection, editorialRecommendations, currentUser: { + isEIC, isInvitedHE, isInvitedToReview, isHEToManuscript, - isEIC, - permissions: { canInviteReviewers }, + permissions: { canInviteReviewers, canSubmitRevision }, }, } = this.props @@ -573,6 +578,10 @@ export default compose( if ((isEIC || isHEToManuscript) && !!editorialRecommendations.length) { this.props.toggleEditorialComments() } + + if (canSubmitRevision) { + this.props.toggleEditorialComments() + } }, componentDidUpdate(prevProps) { const { diff --git a/packages/component-manuscript/src/submitRevision/utils.js b/packages/component-manuscript/src/submitRevision/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..ab28ccc47a0564071bd987022aa022f5f3fe20a8 --- /dev/null +++ b/packages/component-manuscript/src/submitRevision/utils.js @@ -0,0 +1,92 @@ +import { actions } from 'pubsweet-client' +import { get, debounce, omit, set } from 'lodash' +import { handleError } from 'pubsweet-component-faraday-ui' +import { autosaveRequest } from 'pubsweet-component-wizard/src/redux/autosave' +import { submitRevision } from 'pubsweet-component-wizard/src/redux/conversion' + +const parseRevision = (values, fragment) => { + const v = omit(values, 'authorForm') + + return { + ...fragment, + revision: { + ...v, + }, + } +} + +const _onChange = (values, dispatch, { collection, fragment }) => { + dispatch(autosaveRequest()) + dispatch(actions.updateFragment(collection, parseRevision(values, fragment))) +} + +export const onChange = debounce(_onChange, 1000, { maxWait: 5000 }) + +export const onSubmit = ( + values, + dispatch, + { history, fragment, collection, showModal, setFetching, canSubmit }, +) => { + if (!canSubmit) return + + showModal({ + title: 'Ready to submit your revision?', + subtitle: `Once submitted, the submission can't be modified.`, + onConfirm: ({ hideModal, setModalError }) => { + setFetching(true) + return submitRevision({ + fragmentId: fragment.id, + collectionId: collection.id, + }) + .then( + r => { + Promise.all([ + dispatch(actions.getCollection(collection)), + dispatch(actions.getFragments({ id: collection.id })), + ]).then(() => { + setFetching(false) + hideModal() + history.push( + `/projects/${r.collectionId}/versions/${r.id}/details`, + ) + }) + }, + err => { + throw err + }, + ) + .catch(err => { + setFetching(false) + handleError(setModalError)(err) + }) + }, + }) +} + +export const getInitialValues = fragment => ({ + files: get(fragment, 'revision.files', {}), + authors: get(fragment, 'revision.authors', []), + metadata: get(fragment, 'revision.metadata', { + abstract: '', + title: '', + type: '', + }), + responseToReviewers: get(fragment, 'revision.responseToReviewers', { + content: '', + file: null, + }), +}) + +export const validate = ({ responseToReviewers }) => { + const errors = {} + + if (!responseToReviewers.content && !responseToReviewers.file) { + set( + errors, + 'responseToReviewers.content', + 'A reply or a report file is needed.', + ) + } + + return errors +} diff --git a/packages/component-manuscript/src/submitRevision/withSubmitRevision.js b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js new file mode 100644 index 0000000000000000000000000000000000000000..40427a9ceed7074deb17f989c677d9f3224a7279 --- /dev/null +++ b/packages/component-manuscript/src/submitRevision/withSubmitRevision.js @@ -0,0 +1,148 @@ +import { connect } from 'react-redux' +import { get, pick, isNull } from 'lodash' +import { withRouter } from 'react-router-dom' +import { + MultiAction, + handleError, + withFetching, + withFilePreview, + withFileDownload, +} from 'pubsweet-component-faraday-ui' +import { DragDropContext } from 'react-dnd' +import { change as changeForm } from 'redux-form' +import HTML5Backend from 'react-dnd-html5-backend' +import { withModal } from 'pubsweet-component-modal/src/components' +import { + compose, + toClass, + withProps, + withHandlers, + setDisplayName, + withStateHandlers, +} from 'recompose' + +import { + addAuthor, + deleteAuthor, +} from 'pubsweet-components-faraday/src/redux/authors' + +import { + uploadFile, + deleteFile, +} from 'pubsweet-components-faraday/src/redux/files' + +import { onChange, onSubmit, getInitialValues, validate } from './utils' + +export default compose( + withRouter, + withFetching, + withFilePreview, + withFileDownload, + withStateHandlers( + { authorEditIndex: null }, + { + onAuthorEdit: () => index => ({ + authorEditIndex: index, + }), + }, + ), + connect( + state => ({ + canSubmit: !get(state, 'form.revision.syncErrors'), + hasFormError: + get(state, 'form.revision.syncErrors') && + get(state, 'form.revision.anyTouched', false), + }), + { changeForm }, + ), + withProps(() => ({ + modalKey: 'submitRevision', + })), + withModal(({ isFetching }) => ({ + isFetching, + modalComponent: MultiAction, + })), + withHandlers({ + addResponseFile: ({ + setError, + fragment, + changeForm, + setFetching, + }) => file => { + setFetching(true) + return uploadFile({ + file, + fragment, + type: 'responseToReviewers', + }) + .then(f => { + setFetching(false) + changeForm('revision', 'responseToReviewers.file', f) + }) + .catch(err => { + setFetching(false) + handleError(setError)(err) + }) + }, + deleteResponseFile: ({ setError, changeForm, setFetching }) => file => { + setFetching(true) + return deleteFile(file.id, 'responseToReviewers') + .then(r => { + setFetching(false) + changeForm('revision', 'responseToReviewers.file', null) + }) + .catch(err => { + setFetching(false) + handleError(setError)(err) + }) + }, + addFile: () => uploadFile, + addAuthor: () => addAuthor, + deleteFile: () => deleteFile, + deleteAuthor: () => deleteAuthor, + previewFile: ({ previewFile }) => previewFile, + downloadFile: ({ downloadFile }) => downloadFile, + }), + DragDropContext(HTML5Backend), + toClass, + withProps(props => ({ + submitRevision: { + initialValues: getInitialValues(props.fragment), + ...pick(props, [ + 'addFile', + 'journal', + 'history', + 'fragment', + 'canSubmit', + 'addAuthor', + 'showModal', + 'changeForm', + 'collection', + 'isFetching', + 'deleteFile', + 'currentUser', + 'setFetching', + 'previewFile', + 'downloadFile', + 'onAuthorEdit', + 'deleteAuthor', + 'getSignedUrl', + 'hasFormError', + 'fetchingError', + 'addResponseFile', + 'deleteResponseFile', + ]), + isEditingAuthor: !isNull(props.authorEditIndex), + onChange, + onSubmit, + validate, + responseFile: get( + props, + 'formValues.revision.responseToReviewers.file', + null, + ), + }, + })), + + setDisplayName('SubmitRevisionHOC'), +) diff --git a/packages/component-modal/src/components/withModal.js b/packages/component-modal/src/components/withModal.js index 3d7f9913d86dd3fb67526c1b0e81d958c7df2a76..a79c64635bc549950020e9251f5af02dbf82e796 100644 --- a/packages/component-modal/src/components/withModal.js +++ b/packages/component-modal/src/components/withModal.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react' -import { omit } from 'lodash' -import { connect } from 'react-redux' +import { omit, get } from 'lodash' import { compose } from 'recompose' +import { connect } from 'react-redux' import Modal from './Modal' import { showModal, hideModal, setModalError } from '../redux/modal' @@ -15,7 +15,7 @@ const mapState = state => ({ const mapDispatch = (dispatch, props) => ({ hideModal: () => dispatch(hideModal()), showModal: (modalProps = {}) => - dispatch(showModal(props.modalKey, modalProps)), + dispatch(showModal(get(props, 'modalKey'), modalProps)), setModalError: errorMessage => dispatch(setModalError(errorMessage)), }) diff --git a/packages/component-wizard/src/components/StepThree.js b/packages/component-wizard/src/components/StepThree.js index 9a4a67aabed1c0cbe89bded838d20fa29f74fc3c..4b7666551a992a1a389bfdd0e050ee00a8bd3a19 100644 --- a/packages/component-wizard/src/components/StepThree.js +++ b/packages/component-wizard/src/components/StepThree.js @@ -9,8 +9,8 @@ import { Empty } from './' const StepThree = ({ token, - version, - project, + fragment, + collection, changeForm, deleteFile, uploadFile, @@ -36,13 +36,13 @@ const StepThree = ({ <Field component={Empty} name="files" /> <WizardFiles changeForm={changeForm} + collection={collection} deleteFile={deleteFile} - files={get(version, 'files', {})} + files={get(fragment, 'files', {})} + fragment={fragment} getSignedUrl={getSignedUrl} - project={project} token={token} uploadFile={uploadFile} - version={version} /> {filesError && ( <Row justify="flex-start" mt={1}> diff --git a/packages/component-wizard/src/components/StepTwo.js b/packages/component-wizard/src/components/StepTwo.js index 659b4fccc156231e9deaf5baf8f9b042069b9005..4060c434bfab905255b55fddf63e87f9d3548a8d 100644 --- a/packages/component-wizard/src/components/StepTwo.js +++ b/packages/component-wizard/src/components/StepTwo.js @@ -20,10 +20,10 @@ import { H2, Menu, TextField, ValidatedField } from '@pubsweet/ui' import { Empty } from './' const StepTwo = ({ - version, - project, + fragment, journal, addAuthor, + collection, changeForm, formValues, isAuthorEdit, @@ -88,16 +88,16 @@ const StepTwo = ({ <Field component={Empty} name="authors" /> <WizardAuthors addAuthor={addAuthor} - authors={get(version, 'authors', [])} + authors={get(fragment, 'authors', [])} changeForm={changeForm} + collection={collection} deleteAuthor={deleteAuthor} error={authorsError} + fragment={fragment} isAuthorEdit={isAuthorEdit} isAuthorsFetching={isAuthorsFetching} journal={journal} onAuthorEdit={setAuthorEditIndex} - project={project} - version={version} /> {questions.map(q => ( diff --git a/packages/component-wizard/src/components/SubmissionWizard.js b/packages/component-wizard/src/components/SubmissionWizard.js index 25a11e78193a5bacf8728626a67e617c9fd9ea8a..2280a49a7cf5e13c423bd0d32021021b96a06dcc 100644 --- a/packages/component-wizard/src/components/SubmissionWizard.js +++ b/packages/component-wizard/src/components/SubmissionWizard.js @@ -116,19 +116,17 @@ export default compose( (state, { match }) => ({ token: getUserToken(state), isFetching: getAutosaveFetching(state), + isFilesFetching: getFileFetching(state), + reduxAuthorError: getAuthorError(state), formValues: getFormValues('submission')(state), submitFailed: hasSubmitFailed('submission')(state), formSyncErrors: getFormSyncErrors('submission')(state), - version: selectFragment(state, get(match, 'params.version')), - project: selectCollection(state, get(match, 'params.project')), - isFilesFetching: getFileFetching(state), + fragment: selectFragment(state, get(match, 'params.version')), + collection: selectCollection(state, get(match, 'params.project')), isAuthorsFetching: getAuthorFetching(state) || getAutosaveFetching(state), - reduxAuthorError: getAuthorError(state), }), { - addAuthor, changeForm, - deleteAuthor, submitManuscript, updateFragment: actions.updateFragment, }, @@ -156,8 +154,10 @@ export default compose( authorEditIndex, reduxAuthorError, }) => ({ + addAuthor, deleteFile, uploadFile, + deleteAuthor, getSignedUrl, isFirstStep: step === 0, isAuthorEdit: !isNull(authorEditIndex), diff --git a/packages/component-wizard/src/components/utils.js b/packages/component-wizard/src/components/utils.js index 9b922cfc1ee32ab719b35ab431cebafdac41cceb..eb333851dd032ea8341ef8b25144b3d2a0233e0d 100644 --- a/packages/component-wizard/src/components/utils.js +++ b/packages/component-wizard/src/components/utils.js @@ -17,21 +17,21 @@ import { } from '../redux/autosave' import { SubmissionStatement } from './' -export const setInitialValues = ({ version }) => ({ +export const setInitialValues = ({ fragment }) => ({ initialValues: { - files: get(version, 'files', {}), - authors: get(version, 'authors', []), - metadata: get(version, 'metadata', {}), - conflicts: get(version, 'conflicts', { + files: get(fragment, 'files', {}), + authors: get(fragment, 'authors', []), + metadata: get(fragment, 'metadata', {}), + conflicts: get(fragment, 'conflicts', { hasConflicts: 'no', hasDataAvailability: '', hasFunding: '', }), - declarations: get(version, 'declarations', { agree: false }), + declarations: get(fragment, 'declarations', { agree: false }), }, }) -export const validate = (values, props) => { +export const validate = values => { const errors = {} if (!get(values, 'declarations.agree')) { @@ -57,8 +57,12 @@ export const validate = (values, props) => { return errors } -const _onChange = (values, dispatch, { project, version, updateFragment }) => { - const previousValues = pick(version, [ +const _onChange = ( + values, + dispatch, + { collection, fragment, updateFragment }, +) => { + const previousValues = pick(fragment, [ 'files', 'authors', 'metadata', @@ -68,8 +72,8 @@ const _onChange = (values, dispatch, { project, version, updateFragment }) => { const newValues = omit(values, ['agree', 'authorForm']) if (!isEqual(newValues, previousValues)) { dispatch(autosaveRequest()) - updateFragment(project, { - ...version, + updateFragment(collection, { + ...fragment, ...newValues, }).then( () => dispatch(autosaveSuccess()), @@ -92,8 +96,8 @@ export const onSubmit = ( isEditMode, setModalError, submitManuscript, - version: { id: fragmentId }, - project: { id: collectionId, customId }, + fragment: { id: fragmentId }, + collection: { id: collectionId, customId }, }, ) => { if (step !== 2) { @@ -107,14 +111,14 @@ export const onSubmit = ( cancelText: 'BACK TO SUBMISSION', onConfirm: () => { dispatch(autosaveRequest()) - submitManuscript(collectionId, fragmentId) + submitManuscript({ collectionId, fragmentId }) .then(r => { hideModal() dispatch(autosaveSuccess()) history.push('/confirmation-page', { customId, - version: fragmentId, - project: collectionId, + fragment: fragmentId, + collection: collectionId, }) }) .catch(err => { diff --git a/packages/component-wizard/src/redux/autosave.js b/packages/component-wizard/src/redux/autosave.js index 772755f4eb6f3f65900020f484c50bc6ee141f3b..28ec87bc0dc7586c52df6f84cb291d38cbf066af 100644 --- a/packages/component-wizard/src/redux/autosave.js +++ b/packages/component-wizard/src/redux/autosave.js @@ -28,6 +28,8 @@ export const getAutosave = state => get(state, 'autosave', initialState) export const getAutosaveFetching = state => get(state, 'autosave.isFetching', false) +// due to faulty error handing inside the updateFragment action, we have to +// handle error and success here export default (state = initialState, action) => { switch (action.type) { case AUTOSAVE_REQUEST: @@ -35,11 +37,13 @@ export default (state = initialState, action) => { ...initialState, isFetching: true, } + case 'UPDATE_FRAGMENT_FAILURE': case AUTOSAVE_FAILURE: return { ...initialState, error: action.error, } + case 'UPDATE_FRAGMENT_SUCCESS': case AUTOSAVE_SUCCESS: return { ...initialState, diff --git a/packages/component-wizard/src/redux/conversion.js b/packages/component-wizard/src/redux/conversion.js index 16a116a0dd13395cf820675a45f10e091c3afe9c..12a1703c7baaa28ba973ea8ddf3ecb8d3ed2dc31 100644 --- a/packages/component-wizard/src/redux/conversion.js +++ b/packages/component-wizard/src/redux/conversion.js @@ -1,8 +1,8 @@ import moment from 'moment' import { pick, get } from 'lodash' import { actions } from 'pubsweet-client' -import { create, update } from 'pubsweet-client/src/helpers/api' import journalConfig from 'xpub-faraday/app/config/journal' +import { create, update } from 'pubsweet-client/src/helpers/api' /* constants */ export const CREATE_DRAFT_REQUEST = 'CREATE_DRAFT_REQUEST' @@ -90,7 +90,7 @@ export const createDraftSubmission = history => (dispatch, getState) => { }) } -export const submitManuscript = (collectionId, fragmentId) => dispatch => +export const submitManuscript = ({ collectionId, fragmentId }) => dispatch => create(`/collections/${collectionId}/fragments/${fragmentId}/submit`) export const createRevision = ( @@ -122,8 +122,8 @@ export const createRevision = ( }) } -export const submitRevision = (collId, fragId) => dispatch => - update(`/collections/${collId}/fragments/${fragId}/submit`) +export const submitRevision = ({ collectionId, fragmentId }) => + update(`/collections/${collectionId}/fragments/${fragmentId}/submit`) /* reducer */ const initialState = { diff --git a/packages/components-faraday/src/redux/authors.js b/packages/components-faraday/src/redux/authors.js index c867b596751e0c7db39949b1cfa30077156c137c..308d8a6b4b7fe58ec53c093ab3e30b6af1e3c153 100644 --- a/packages/components-faraday/src/redux/authors.js +++ b/packages/components-faraday/src/redux/authors.js @@ -1,8 +1,6 @@ import { get } from 'lodash' import { create, remove, get as apiGet } from 'pubsweet-client/src/helpers/api' -import { handleError } from './utils' - // constants const REQUEST = 'authors/REQUEST' const FAILURE = 'authors/FAILURE' @@ -25,26 +23,17 @@ export const authorSuccess = () => ({ export const getAuthors = (collectionId, fragmentId) => apiGet(`/collections/${collectionId}/fragments/${fragmentId}/users`) -export const addAuthor = (author, collectionId, fragmentId) => dispatch => { - dispatch(authorRequest()) - return create(`/collections/${collectionId}/fragments/${fragmentId}/users`, { +export const addAuthor = ({ author, collectionId, fragmentId }) => + create(`/collections/${collectionId}/fragments/${fragmentId}/users`, { email: author.email, role: 'author', ...author, - }).then(author => { - dispatch(authorSuccess()) - return author - }, handleError(authorFailure, dispatch)) -} + }) -export const deleteAuthor = (collectionId, fragmentId, userId) => dispatch => { - dispatch(authorRequest()) - return remove( - `/collections/${collectionId}/fragments/${fragmentId}/users/${userId}`, +export const deleteAuthor = ({ authorId, fragmentId, collectionId }) => + remove( + `/collections/${collectionId}/fragments/${fragmentId}/users/${authorId}`, ) - .then(() => dispatch(authorSuccess())) - .catch(handleError(authorFailure, dispatch)) -} // selectors export const getFragmentAuthors = (state, fragmentId) => diff --git a/packages/components-faraday/src/redux/files.js b/packages/components-faraday/src/redux/files.js index 02a36e8517af01e2768c6868b75c67467d78e557..4e79e1f65454bfeb0ba21de652bad73557460c03 100644 --- a/packages/components-faraday/src/redux/files.js +++ b/packages/components-faraday/src/redux/files.js @@ -72,7 +72,7 @@ export const uploadFile = ({ file, type, fragment }) => { ) } -export const deleteFile = (fileId, type = 'manuscripts') => +export const deleteFile = ({ fileId, type = 'manuscripts' }) => remove(`/files/${fileId}`) export const getSignedUrl = fileId => apiGet(`/files/${fileId}`) diff --git a/packages/styleguide/styleguide.config.js b/packages/styleguide/styleguide.config.js index 1a6fe7ba50ad201d0bc3ee0a474e8ac2c01d53d9..b59df5cb30351edf8f76fe3d11ed90e15664b03a 100644 --- a/packages/styleguide/styleguide.config.js +++ b/packages/styleguide/styleguide.config.js @@ -22,6 +22,11 @@ module.exports = { sectionDepth: 1, components: ['../component-faraday-ui/src/contextualBoxes/[A-Z]*.js'], }, + { + name: 'Submit Revision', + sectionDepth: 1, + components: ['../component-faraday-ui/src/submissionRevision/[A-Z]*.js'], + }, { name: 'Pending Items', sectionDepth: 1, diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index 978904daf61d9ad44ee03ad187d33d30dc64ac83..cc116eecca70a91cb64c085c77fc0c6b6da39cfd 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -42,7 +42,18 @@ module.exports = { hasFunding: Joi.any().valid(['yes', 'no', '']), fundingMessage: Joi.string().allow(''), }), - commentsToReviewers: Joi.string(), + responseToReviewers: Joi.object({ + file: Joi.object({ + id: Joi.string(), + name: Joi.string().required(), + originalName: Joi.string(), + type: Joi.string(), + size: Joi.number(), + url: Joi.string(), + signedUrl: Joi.string(), + }).allow(null), + content: Joi.string().allow(''), + }), files: Joi.object({ manuscript: Joi.any(), manuscripts: Joi.array().items(