diff --git a/packages/component-wizard/src/components/AuthorList.js b/packages/component-wizard/src/components/AuthorList.js index 4d7f6281c87ecc90b1c92685a33dedfa1fb9b09c..d3b9896adaa10ba72f65a9a4c5cb7a58e66babdc 100644 --- a/packages/component-wizard/src/components/AuthorList.js +++ b/packages/component-wizard/src/components/AuthorList.js @@ -1,10 +1,16 @@ import React from 'react' -import { get } from 'lodash' +import PropTypes from 'prop-types' import classnames from 'classnames' +import { connect } from 'react-redux' +import { get, debounce } from 'lodash' import { reduxForm } from 'redux-form' -import { compose, withState, withHandlers } from 'recompose' -import { TextField, Menu, Icon, ValidatedField, Button } from '@pubsweet/ui' import { required } from 'xpub-validators' +import { withRouter } from 'react-router-dom' +import { compose, withHandlers, getContext } from 'recompose' +import { TextField, Menu, Icon, ValidatedField, Button } from '@pubsweet/ui' +import { actions } from 'pubsweet-client' + +import { addAuthor, getFragmentAuthors, setAuthors } from '../redux/authors' import classes from './AuthorList.local.scss' import SortableList from './SortableList' @@ -41,33 +47,35 @@ const MenuItem = ({ label, name, options }) => ( /> </div> ) -const AuthorAdder = ({ - author: { firstName, middleName, lastName, email, affiliation, country }, - editAuthor, - addAuthor, - handleSubmit, - ...rest -}) => ( +const AuthorAdder = ({ addAuthor, handleSubmit, ...rest }) => ( <div className={classnames(classes.adder)}> <Button onClick={handleSubmit} primary> + Add author </Button> <span className={classnames(classes.title)}>Author</span> <div className={classnames(classes.row)}> - <ValidatedTextField isRequired label="First name" name="firstName" /> - <ValidatedTextField label="Middle name" name="middleName" /> - <ValidatedTextField isRequired label="Last name" name="lastName" /> + <ValidatedTextField + isRequired + label="First name" + name="author.firstName" + /> + <ValidatedTextField label="Middle name" name="author.middleName" /> + <ValidatedTextField isRequired label="Last name" name="author.lastName" /> </div> <div className={classnames(classes.row)}> <ValidatedTextField isRequired label="Email" - name="email" + name="author.email" validators={[emailValidator]} /> - <ValidatedTextField isRequired label="Affiliation" name="affiliation" /> - <MenuItem label="Country" name="country" options={countries} /> + <ValidatedTextField + isRequired + label="Affiliation" + name="author.affiliation" + /> + <MenuItem label="Country" name="author.country" options={countries} /> </div> </div> ) @@ -129,10 +137,12 @@ const Author = ({ const Adder = compose( reduxForm({ - form: 'new-author', - onSubmit: (values, dispatch, { addAuthor, reset }) => { - addAuthor(values) - reset() + form: 'author', + destroyOnUnmount: false, + onSubmit: (values, dispatch, { addAuthor, reset, match }) => { + const collectionId = get(match, 'params.project') + const fragmentId = get(match, 'params.version') + addAuthor(values.author, collectionId, fragmentId).then(reset) }, })(AuthorAdder), ) @@ -143,12 +153,21 @@ const Authors = ({ moveAuthor, addAuthor, editAuthor, + match, + version, + dropItem, ...rest }) => ( <div> - <Adder addAuthor={addAuthor} author={author} editAuthor={editAuthor} /> + <Adder + addAuthor={addAuthor} + author={author} + editAuthor={editAuthor} + match={match} + /> <SortableList dragHandle={DragHandle} + dropItem={dropItem} items={authors} listItem={Author} moveItem={moveAuthor} @@ -157,33 +176,35 @@ const Authors = ({ </div> ) -const initialAuthor = { - firstName: '', - middleName: '', - lastName: '', - email: '', - affiliation: '', - country: 'ro', -} export default compose( - withState('author', 'changeAuthor', initialAuthor), - withState('authors', 'changeAuthors', []), + withRouter, + connect( + (state, { match: { params: { version } } }) => ({ + authors: getFragmentAuthors(state, version), + }), + { addAuthor, setAuthors, updateFragment: actions.updateFragment }, + ), + getContext({ version: PropTypes.object, project: PropTypes.object }), withHandlers({ + dropItem: ({ updateFragment, authors, project, version }) => + debounce(() => { + updateFragment(project, { + ...version, + authors, + }) + }, 500), countryParser: () => countryCode => countries.find(c => c.value === countryCode).label, - addAuthor: ({ changeAuthors, changeAuthor }) => author => { - changeAuthors(prevAuthors => [author, ...prevAuthors]) - changeAuthor(prev => initialAuthor) - }, - moveAuthor: ({ changeAuthors }) => (dragIndex, hoverIndex) => { - changeAuthors(prev => SortableList.moveItem(prev, dragIndex, hoverIndex)) - }, - editAuthor: ({ changeAuthor }) => field => e => { - const v = get(e, 'target.value') || e - changeAuthor(prev => ({ - ...prev, - [field]: v, - })) + moveAuthor: ({ + authors, + setAuthors, + project, + version, + updateFragment, + match: { params }, + }) => (dragIndex, hoverIndex) => { + const newAuthors = SortableList.moveItem(authors, dragIndex, hoverIndex) + setAuthors(newAuthors, params.version) }, }), )(Authors) diff --git a/packages/component-wizard/src/components/SortableList.js b/packages/component-wizard/src/components/SortableList.js index e7db078bf3b279bd486d425cf647f9d076c537a5..c566f4c63de5337bf218b660660c311efe308b92 100644 --- a/packages/component-wizard/src/components/SortableList.js +++ b/packages/component-wizard/src/components/SortableList.js @@ -36,6 +36,9 @@ const itemTarget = { } monitor.getItem().index = hoverIndex }, + drop({ dropItem }) { + if (dropItem && typeof dropItem === 'function') dropItem() + }, } const Item = ({ diff --git a/packages/component-wizard/src/components/WizardFormStep.js b/packages/component-wizard/src/components/WizardFormStep.js index e031cbc24ad30ce519016da036ffdbdca52b0f1e..ac1a7fc9ea7ed216ed8c70a2045ad04eef643c6e 100644 --- a/packages/component-wizard/src/components/WizardFormStep.js +++ b/packages/component-wizard/src/components/WizardFormStep.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types' import { connect } from 'react-redux' -import { debounce, pick } from 'lodash' +import { debounce, pick, get } from 'lodash' import { actions } from 'pubsweet-client' import { reduxForm, formValueSelector } from 'redux-form' import { compose, getContext, withProps } from 'recompose' @@ -30,7 +30,7 @@ export default compose( }), withProps(({ version, wizard }) => ({ initialValues: pick(version, wizard.formSectionKeys), - readonly: !!version.submitted, + readonly: !!get(version, 'submitted'), })), connect((state, { wizard: { formSectionKeys } }) => ({ formValues: formSectionKeys.reduce( @@ -45,7 +45,11 @@ export default compose( form: 'wizard', destroyOnUnmount: false, forceUnregisterOnUnmount: true, - onSubmit: (values, dispatch, { nextStep, isFinal }) => { + onSubmit: ( + values, + dispatch, + { nextStep, isFinal, formValues, ...rest }, + ) => { if (!isFinal) { nextStep() } diff --git a/packages/component-wizard/src/components/WizardPage.js b/packages/component-wizard/src/components/WizardPage.js index 1e60878690186a6fdb83a0929fad500a7276c140..584d531c3f0c131762dd4fa2101ec67dc40d382c 100644 --- a/packages/component-wizard/src/components/WizardPage.js +++ b/packages/component-wizard/src/components/WizardPage.js @@ -4,9 +4,16 @@ import { actions } from 'pubsweet-client' import { withJournal } from 'xpub-journal' import { ConnectPage } from 'xpub-connect' import { selectCollection, selectFragment } from 'xpub-selectors' -import { compose, withHandlers, withState, withContext } from 'recompose' +import { + compose, + withHandlers, + withState, + withContext, + lifecycle, +} from 'recompose' import Wizard from './Wizard' +import { setAuthors } from '../redux/authors' export default compose( ConnectPage(({ match }) => [ @@ -16,13 +23,22 @@ export default compose( { id: match.params.version }, ), ]), - connect((state, { match }) => { - const project = selectCollection(state, match.params.project) - const version = selectFragment(state, match.params.version) + connect( + (state, { match }) => { + const project = selectCollection(state, match.params.project) + const version = selectFragment(state, match.params.version) - return { project, version } - }), + return { project, version } + }, + { setAuthors }, + ), withJournal, + lifecycle({ + componentDidMount() { + const { version, setAuthors } = this.props + setAuthors(version.authors, version.id) + }, + }), withState('step', 'changeStep', 0), withHandlers({ getSteps: ({ journal: { wizard: { steps } } }) => () => diff --git a/packages/component-wizard/src/components/WizardStep.js b/packages/component-wizard/src/components/WizardStep.js index 672f22d3fca3f1c9f28e85b3accd87251f04e636..fc65ed1a7fd461e2c3d4bb442c28cafc9879893b 100644 --- a/packages/component-wizard/src/components/WizardStep.js +++ b/packages/component-wizard/src/components/WizardStep.js @@ -27,6 +27,7 @@ export default ({ validate, dependsOn, renderComponent: Comp, + type, ...rest }) => { if ( diff --git a/packages/component-wizard/src/index.js b/packages/component-wizard/src/index.js index 0b08c7c2e246189b19c843458de6e02803ab7f56..abfc03ce45939f61cd39ff3ba9ec9ca37011bb01 100644 --- a/packages/component-wizard/src/index.js +++ b/packages/component-wizard/src/index.js @@ -3,6 +3,7 @@ module.exports = { components: [() => require('./components')], reducers: { conversion: () => require('./redux/conversion').default, + authors: () => require('./redux/authors').default, }, }, } diff --git a/packages/component-wizard/src/redux/authors.js b/packages/component-wizard/src/redux/authors.js new file mode 100644 index 0000000000000000000000000000000000000000..2a7fb7c28d265541ce0c92cb75898eb35d52be8e --- /dev/null +++ b/packages/component-wizard/src/redux/authors.js @@ -0,0 +1,37 @@ +import { get } from 'lodash' +import { actions } from 'pubsweet-client' +import * as api from 'pubsweet-client/src/helpers/api' + +// constants +export const SET_AUTHORS = 'authors/SET_AUTHORS' + +// actions +export const setAuthors = (authors, fragmentId) => ({ + type: SET_AUTHORS, + authors, + fragmentId, +}) + +export const addAuthor = (author, collectionId, fragmentId) => dispatch => + api + .create(`/fragments/${fragmentId}/authors`, author) + .then(() => + dispatch(actions.getFragment({ id: collectionId }, { id: fragmentId })), + ) + .then(({ fragment: { authors, id } }) => dispatch(setAuthors(authors, id))) + +// selectors +export const getFragmentAuthors = (state, fragmentId) => + get(state, `authors.${fragmentId}`) || [] + +export default (state = {}, action) => { + switch (action.type) { + case SET_AUTHORS: + return { + ...state, + [action.fragmentId]: action.authors, + } + default: + return state + } +} diff --git a/packages/component-wizard/src/redux/index.js b/packages/component-wizard/src/redux/index.js index 56f29b6f4e05afb38afa4fe6b06b760a401c5360..a0eee2ecd4a1e5776eea3e56ecc701cffe60897f 100644 --- a/packages/component-wizard/src/redux/index.js +++ b/packages/component-wizard/src/redux/index.js @@ -1 +1,2 @@ export { default as conversion } from './conversion' +export { default as authors } from './authors' diff --git a/packages/xpub-faraday-server/src/AuthorBackend.js b/packages/xpub-faraday-server/src/AuthorBackend.js index 5e18d45c1dc1baae93c5b63fdc475745dfb1e623..a7ec49f6ef68ba7031dc1f27c0f701ca1878358c 100644 --- a/packages/xpub-faraday-server/src/AuthorBackend.js +++ b/packages/xpub-faraday-server/src/AuthorBackend.js @@ -44,7 +44,6 @@ const AuthorBackend = app => { fragment = await fragment.save() res.status(200).json(fragment) } catch (e) { - console.log(e) if (e.name === 'NotFoundError') { res.status(e.status).json({ error: 'Fragment not found' }) return diff --git a/packages/xpub-faraday-server/src/AuthorBackend.test.js b/packages/xpub-faraday-server/src/AuthorBackend.test.js index d05c25f4f3691d8361497c9527f9655c7b1a870e..22536c86a7e9e0a41d88acc544291ced7d1a2c0c 100644 --- a/packages/xpub-faraday-server/src/AuthorBackend.test.js +++ b/packages/xpub-faraday-server/src/AuthorBackend.test.js @@ -6,8 +6,6 @@ const express = require('express') const supertest = require('supertest') const component = require('..') -const token = - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InNlYmkiLCJpZCI6IjVlMTRiY2IyLWQ5ZTEtNDZjOS05ZDI0LTM3YTk4MDhmMjFmYiIsImlhdCI6MTUxNjExODAxMSwiZXhwIjoxNTE2MjA0NDExfQ.tqH0Nnpiec2c1FPL2K5fK4krHGN2SrYyMbqVSnYSpog' const author = { first_name: 'marcel', middle_name: 'sss',