diff --git a/.gitignore b/.gitignore index 3817278106349f7af0064d689534d7a52d9704df..a0f8767dc054599383f2ac91e7bc471b5b267175 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ api/db/* logs/* log.txt config.json +packages/**/_build +packages/**/api/**/ \ No newline at end of file diff --git a/packages/component-dashboard/src/components/Dashboard.js b/packages/component-dashboard/src/components/Dashboard.js index 4cb225f0c1fabaccf9e0923957ef590f51730c7a..9805128060c9f8623199a5291014d75f101eeef5 100644 --- a/packages/component-dashboard/src/components/Dashboard.js +++ b/packages/component-dashboard/src/components/Dashboard.js @@ -18,6 +18,7 @@ const Dashboard = ({ deleteProject, reviewerResponse, uploadManuscript, + createDraftSubmission, }) => ( <div className={classes.root}> <div className={classes.upload}> @@ -27,6 +28,11 @@ const Dashboard = ({ /> </div> + <div> + Create new Submission + <button onClick={createDraftSubmission}>CREATE</button> + </div> + {!dashboard.owner.length && !dashboard.reviewer.length && !dashboard.editor.length && ( diff --git a/packages/component-dashboard/src/components/DashboardPage.js b/packages/component-dashboard/src/components/DashboardPage.js index e8efaac3c80fbda1ddd7a9bd0a646e89b57f4505..452238f0455cc679466db76f7a5b500ccdb4a610 100644 --- a/packages/component-dashboard/src/components/DashboardPage.js +++ b/packages/component-dashboard/src/components/DashboardPage.js @@ -4,7 +4,7 @@ import { withRouter } from 'react-router-dom' import { actions } from 'pubsweet-client' import { newestFirst, selectCurrentUser } from 'xpub-selectors' import { ConnectPage } from 'xpub-connect' -import { uploadManuscript } from '../redux/conversion' +import { uploadManuscript, createDraftSubmission } from '../redux/conversion' import Dashboard from './Dashboard' import AssignEditorContainer from './AssignEditorContainer' @@ -94,6 +94,7 @@ export default compose( dispatch(reviewerResponse(project, version, reviewer, status)), uploadManuscript: acceptedFiles => dispatch(uploadManuscript(acceptedFiles, history)), + createDraftSubmission: () => dispatch(createDraftSubmission(history)), }), ), withProps({ diff --git a/packages/component-dashboard/src/redux/conversion.js b/packages/component-dashboard/src/redux/conversion.js index 575cdbfb94d71a97e16c2e61aa03b068fba7bc7e..993e1026d334d67d1f1b8141f9d2d8ade947cc75 100644 --- a/packages/component-dashboard/src/redux/conversion.js +++ b/packages/component-dashboard/src/redux/conversion.js @@ -9,6 +9,10 @@ export const UPLOAD_MANUSCRIPT_REQUEST = 'UPLOAD_MANUSCRIPT_REQUEST' export const UPLOAD_MANUSCRIPT_SUCCESS = 'UPLOAD_MANUSCRIPT_SUCCESS' export const UPLOAD_MANUSCRIPT_FAILURE = 'UPLOAD_MANUSCRIPT_FAILURE' +// +export const CREATE_DRAFT_REQUEST = 'CREATE_DRAFT_REQUEST' +export const CREATE_DRAFT_SUCCESS = 'CREATE_DRAFT_SUCCESS' + /* actions */ export const uploadManuscriptRequest = () => ({ @@ -102,6 +106,45 @@ export const uploadManuscript = (acceptedFiles, history) => dispatch => { }) } +// faraday stuff +export const createDraftRequest = () => ({ + type: CREATE_DRAFT_REQUEST, +}) + +export const createDraftSuccess = draft => ({ + type: CREATE_DRAFT_SUCCESS, + draft, +}) + +export const createDraftSubmission = history => dispatch => + dispatch(actions.createCollection()).then(({ collection }) => { + if (!collection.id) { + throw new Error('Failed to create a project') + } + + // TODO: rethrow errors so they can be caught here + return dispatch( + actions.createFragment(collection, { + created: new Date(), // TODO: set on server + files: { + supplementary: [], + }, + fragmentType: 'version', + metadata: {}, + version: 1, + }), + ).then(({ fragment }) => { + dispatch(uploadManuscriptSuccess(collection, fragment)) + + const route = `/projects/${collection.id}/versions/${fragment.id}/submit` + + // redirect after a short delay + window.setTimeout(() => { + history.push(route) + }, 1000) + }) + }) + /* reducer */ const initialState = { @@ -131,7 +174,6 @@ export default (state = initialState, action) => { converting: false, error: action.error, } - default: return state } diff --git a/packages/component-wizard/package.json b/packages/component-wizard/package.json new file mode 100644 index 0000000000000000000000000000000000000000..59358018eb9dcf132d02035272ef0a66a4b887c9 --- /dev/null +++ b/packages/component-wizard/package.json @@ -0,0 +1,25 @@ +{ + "name": "pubsweet-component-wizard", + "version": "0.0.1", + "main": "src", + "license": "MIT", + "dependencies": { + "@pubsweet/ui": "^0.1.1", + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-dnd": "^2.5.4", + "react-dnd-html5-backend": "^2.5.4", + "react-dom": "^15.6.1", + "react-router-dom": "^4.2.2", + "recompose": "^0.26.0", + "redux": "^3.6.0", + "redux-form": "^7.0.3" + }, + "peerDependencies": { + "prop-types": "^15.5.10", + "react": "^15.6.1", + "react-dom": "^15.6.1", + "react-redux": "^5.0.2", + "react-router-dom": "^4.2.2" + } +} diff --git a/packages/component-wizard/src/components/Dropdown.js b/packages/component-wizard/src/components/Dropdown.js new file mode 100644 index 0000000000000000000000000000000000000000..74aa2b0ae4d1f4f467c711ca4825bee90efcf5d8 --- /dev/null +++ b/packages/component-wizard/src/components/Dropdown.js @@ -0,0 +1,76 @@ +import React from 'react' +import { compose, withState, withHandlers } from 'recompose' +import classnames from 'classnames' + +import classes from './Dropdown.local.scss' + +const DropdownOptions = ({ visible, options, onSelect }) => + visible ? ( + <div className={classes.options}> + {options.map(opt => ( + <div key={opt.value} onClick={onSelect(opt)}> + {opt.label} + </div> + ))} + </div> + ) : null + +const Dropdown = ({ + label, + showDropdown, + inputValue, + changeInput, + defaultValue = null, + options, + onChange, + setDropdown, + selectValue, +}) => ( + <div className={classes.container}> + <span className={classes.label}>{label}</span> + <div className={classes.inputContainer}> + <input + onChange={changeInput} + onFocus={setDropdown(true)} + placeholder="Type or select from dropdown" + type="text" + value={inputValue.label} + /> + <span className={classnames({ [classes.downarrow]: showDropdown })}> + ► + </span> + </div> + <DropdownOptions + onSelect={selectValue} + options={options.filter(opt => opt.label.indexOf(inputValue.label) > -1)} + visible={showDropdown} + /> + </div> +) + +export default compose( + withState( + 'inputValue', + 'setInputValue', + ({ defaultValue }) => defaultValue || { label: '' }, + ), + withState('showDropdown', 'setDropdownVisibility', false), + withHandlers({ + changeInput: ({ setInputValue }) => e => { + e.persist() + setInputValue(prev => ({ ...prev, label: e.target.value })) + }, + setDropdown: ({ setDropdownVisibility }) => visibilty => e => { + setDropdownVisibility(visibilty) + }, + selectValue: ({ + setInputValue, + setDropdownVisibility, + onSelect, + }) => value => e => { + setInputValue(value) + onSelect && onSelect(value) + setDropdownVisibility(false) + }, + }), +)(Dropdown) diff --git a/packages/component-wizard/src/components/Dropdown.local.scss b/packages/component-wizard/src/components/Dropdown.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..ffe230262dd06dedaf989acfa41d5ff495244767 --- /dev/null +++ b/packages/component-wizard/src/components/Dropdown.local.scss @@ -0,0 +1,57 @@ +.container { + display: flex; + flex-direction: column; + margin: 5px 0; + position: relative; +} + +.label { + font-size: 16px; + font-weight: 400; + margin: 5px 0; +} + +.inputContainer { + align-items: center; + display: flex; + position: relative; + + input { + border: 1px solid #888; + border-radius: 2px; + display: flex; + flex: 1; + height: 20px; + padding: 0 2px; + + &:focus { + outline: none; + } + } + + span { + position: absolute; + right: 10px; + top: 1px; + transform: rotateZ(90deg); + transition: transform 0.2s linear; + } +} + +.options { + background-color: #eee; + cursor: pointer; + left: 0; + position: absolute; + top: 50px; + width: 100%; + z-index: 1000; + + :hover { + background-color: #aaa; + } +} + +.downarrow { + transform: rotateZ(270deg) !important; +} diff --git a/packages/component-wizard/src/components/SortableList.js b/packages/component-wizard/src/components/SortableList.js new file mode 100644 index 0000000000000000000000000000000000000000..1273b9e12bf1ef63d4b4747a1f000f0e79791986 --- /dev/null +++ b/packages/component-wizard/src/components/SortableList.js @@ -0,0 +1,87 @@ +import React from 'react' +import { compose } from 'recompose' +import { findDOMNode } from 'react-dom' +import { DragSource, DropTarget } from 'react-dnd' + +const itemSource = { + beginDrag(props) { + return { index: props.index } + }, +} + +const itemTarget = { + hover({ moveItem, index }, monitor, component) { + const dragIndex = monitor.getItem().index + const hoverIndex = index + + // Don't replace items with themselves + if (dragIndex === hoverIndex) { + return + } + + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect() // eslint-disable-line + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2 + const clientOffset = monitor.getClientOffset() + const hoverClientY = clientOffset.y - hoverBoundingRect.top + + if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) { + return + } + + if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) { + return + } + moveItem(dragIndex, hoverIndex) + monitor.getItem().index = hoverIndex + }, +} + +const Item = ({ connectDragSource, connectDropTarget, listItem, ...rest }) => + connectDragSource( + connectDropTarget(<div>{React.createElement(listItem, rest)}</div>), + ) + +const DecoratedItem = compose( + DropTarget('item', itemTarget, (connect, monitor) => ({ + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver(), + })), + DragSource('item', itemSource, (connect, monitor) => ({ + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging(), + })), +)(Item) + +const SortableList = ({ items, moveItem, listItem }) => ( + <div> + {items.map((item, i) => ( + <DecoratedItem + index={i} + key={item.name} + listItem={listItem} + moveItem={moveItem} + {...item} + /> + ))} + </div> +) + +// helper function for sortable lists +SortableList.moveItem = (items, dragIndex, hoverIndex) => { + if (dragIndex <= hoverIndex) { + return [ + ...items.slice(0, dragIndex), + items[hoverIndex], + items[dragIndex], + ...items.slice(hoverIndex + 1), + ] + } + return [ + ...items.slice(0, hoverIndex), + items[dragIndex], + items[hoverIndex], + ...items.slice(dragIndex + 1), + ] +} + +export default SortableList diff --git a/packages/component-wizard/src/components/Steps.js b/packages/component-wizard/src/components/Steps.js new file mode 100644 index 0000000000000000000000000000000000000000..0bee01f14197e15d902fa32713a894b9938a44de --- /dev/null +++ b/packages/component-wizard/src/components/Steps.js @@ -0,0 +1,41 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { compose, withHandlers, withContext, getContext } from 'recompose' + +import classes from './Steps.local.scss' + +const Separator = () => <div className={classes.separator} /> + +const Step = ({ title, index, currentStep }) => ( + <div className={classes.step}> + {index === currentStep && <div className={classes.bullet} />} + {index < currentStep && <div className={classes.success}>✓</div>} + <span className={classes.stepTitle}>{`${index + 1}. ${title}`}</span> + </div> +) + +const Steps = ({ currentStep, children, renderSteps }) => ( + <div className={classes.container}>{renderSteps()}</div> +) + +const DecoratedSteps = compose( + withHandlers({ + renderSteps: ({ children }) => () => { + const c = [] + React.Children.forEach(children, (child, idx) => { + c.push(child) + if (idx !== React.Children.count(children) - 1) { + c.push(<Separator key={Math.random()} />) + } + }) + return c + }, + }), + withContext({ currentStep: PropTypes.number }, ({ currentStep }) => ({ + currentStep, + })), +)(Steps) + +DecoratedSteps.Step = getContext({ currentStep: PropTypes.number })(Step) + +export default DecoratedSteps diff --git a/packages/component-wizard/src/components/Steps.local.scss b/packages/component-wizard/src/components/Steps.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..b02d38008a85111d12e7d65c701c74de051fa72f --- /dev/null +++ b/packages/component-wizard/src/components/Steps.local.scss @@ -0,0 +1,53 @@ +.container { + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0 40px 40px 40px; + min-width: 400px; +} + +.separator { + background-color: #000; + flex: 1; + height: 2px; +} + +.step { + align-items: center; + border: 2px solid #000; + border-radius: 50%; + display: flex; + height: 16px; + justify-content: center; + position: relative; + width: 16px; +} + +.bullet { + background-color: #000; + border-radius: 50%; + height: 10px; + width: 10px; +} + +.stepTitle { + font-size: 14px; + left: -40px; + position: absolute; + top: 25px; + white-space: normal; + width: 120px; +} + +.success { + align-items: center; + background-color: #000; + border-radius: 50%; + color: #fff; + display: flex; + font-size: 12px; + height: 18px; + justify-content: center; + width: 18px; +} diff --git a/packages/component-wizard/src/components/Wizard.js b/packages/component-wizard/src/components/Wizard.js new file mode 100644 index 0000000000000000000000000000000000000000..d6cb3b09b0ea3e58f2eaa24a05423a541606f19e --- /dev/null +++ b/packages/component-wizard/src/components/Wizard.js @@ -0,0 +1,172 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' +import { connect } from 'react-redux' +import { pick, debounce } from 'lodash' +import { reduxForm } from 'redux-form' +import { withJournal } from 'xpub-journal' +import { ConnectPage } from 'xpub-connect' +import { ValidatedField, Button } from '@pubsweet/ui' +import { selectCollection, selectFragment } from 'xpub-selectors' +import { + compose, + withHandlers, + withState, + getContext, + withContext, + withProps, +} from 'recompose' +import { actions } from 'pubsweet-client' + +import classes from './Wizard.local.scss' +import { Steps } from './' + +const { Step } = Steps + +const validate = values => { + const errors = {} + return errors +} + +const onChange = (values, dispatch, { project, version }) => { + dispatch( + actions.updateFragment(project, { + id: version.id, + rev: version.rev, + ...values, + }), + ) +} + +const WizardStep = ({ + children: stepChildren, + title, + buttons, + nextStep, + prevStep, + handleSubmit, + isFinal, + isFirst, + goBack, +}) => ( + <div className={classnames(classes.step)}> + <form + className={classnames(classes.form)} + name="metadata" + onSubmit={handleSubmit} + > + <h3>{title}</h3> + {stepChildren && + stepChildren.map( + ({ fieldId, validate, renderComponent: Comp, ...rest }) => ( + <ValidatedField + component={input => <Comp {...rest} {...input} />} + key={fieldId} + name={fieldId} + validate={validate} + /> + ), + )} + <div className={classnames(classes.buttons)}> + <Button onClick={isFirst ? goBack : prevStep}> + {isFirst ? 'Cancel' : 'Back'} + </Button> + <Button primary type="submit"> + {isFinal ? 'Finish' : 'Next'} + </Button> + </div> + </form> + </div> +) + +const FormStep = compose( + getContext({ + goBack: PropTypes.func, + isFinal: PropTypes.bool, + isFirst: PropTypes.bool, + project: PropTypes.object, + version: PropTypes.object, + wizard: PropTypes.object, + }), + withProps(({ version, wizard }) => ({ + initialValues: pick(version, wizard.formSectionKeys), + readonly: !!version.submitted, + })), + reduxForm({ + form: 'wizard', + destroyOnUnmount: false, + forceUnregisterOnUnmount: true, + validate, + onSubmit: (values, dispatch, { nextStep, isFinal }) => { + if (!isFinal) { + nextStep() + } + }, + onChange: debounce(onChange, 1000, { maxWait: 5000 }), + }), +)(WizardStep) + +const Wizard = ({ + journal: { wizard: { showProgress, steps } }, + getSteps, + step, + nextStep, + prevStep, + ...rest +}) => ( + <div className={classnames(classes.container)}> + {showProgress && ( + <Steps currentStep={step}> + {getSteps().map((step, index) => ( + <Step index={index} key={step} title={step} /> + ))} + </Steps> + )} + <FormStep {...steps[step]} nextStep={nextStep} prevStep={prevStep} /> + </div> +) + +export default compose( + ConnectPage(({ match }) => [ + actions.getCollection({ id: match.params.project }), + actions.getFragment( + { id: match.params.project }, + { id: match.params.version }, + ), + ]), + connect((state, { match }) => { + const project = selectCollection(state, match.params.project) + const version = selectFragment(state, match.params.version) + + return { project, version } + }), + withJournal, + withState('step', 'changeStep', 0), + withHandlers({ + getSteps: ({ journal: { wizard: { steps } } }) => () => + steps.map(w => w.label), + nextStep: ({ changeStep, journal: { wizard: { steps } } }) => () => { + changeStep(step => (step === steps.length - 1 ? step : step + 1)) + }, + prevStep: ({ changeStep }) => () => + changeStep(step => (step <= 0 ? step : step - 1)), + }), + withContext( + { + goBack: PropTypes.func, + isFinal: PropTypes.bool, + isFirst: PropTypes.bool, + project: PropTypes.object, + version: PropTypes.object, + wizard: PropTypes.object, + }, + ({ history: { goBack }, step, project, version, journal: { wizard } }) => ({ + goBack, + isFinal: step === wizard.steps.length - 1, + isFirst: step === 0, + project, + version, + wizard, + }), + ), +)(Wizard) diff --git a/packages/component-wizard/src/components/Wizard.local.scss b/packages/component-wizard/src/components/Wizard.local.scss new file mode 100644 index 0000000000000000000000000000000000000000..f108dfb46bb426557ebc716d51e4b5e67abd6936 --- /dev/null +++ b/packages/component-wizard/src/components/Wizard.local.scss @@ -0,0 +1,31 @@ +.container { + align-items: center; + display: flex; + flex-direction: column; + margin: 0 auto; + width: 900px; +} + +.step { + align-items: stretch; + border: 1px solid #222; + display: flex; + flex-direction: column; + justify-content: flex-start; + padding: 0 20px; + width: 700px; +} + +.form { + display: flex; + flex-direction: column; +} + +.buttons { + align-items: center; + align-self: center; + display: flex; + justify-content: space-around; + margin: 15px 0; + width: 400px; +} diff --git a/packages/component-wizard/src/components/index.js b/packages/component-wizard/src/components/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e51f2b7f2ffa168113acd18b9894682b93cc8c69 --- /dev/null +++ b/packages/component-wizard/src/components/index.js @@ -0,0 +1,4 @@ +export { default as Steps } from './Steps' +export { default as Wizard } from './Wizard' +export { default as Dropdown } from './Dropdown' +export { default as SortableList } from './SortableList' diff --git a/packages/component-wizard/src/index.js b/packages/component-wizard/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..8d52f148e8c8927db1d86494ca404451da5a917d --- /dev/null +++ b/packages/component-wizard/src/index.js @@ -0,0 +1,5 @@ +module.exports = { + client: { + components: [() => require('./components')], + }, +} diff --git a/packages/component-wizard/src/redux/index.js b/packages/component-wizard/src/redux/index.js new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/packages/component-wizard/webpack.config.js b/packages/component-wizard/webpack.config.js new file mode 100644 index 0000000000000000000000000000000000000000..4aa149ad2cbf750326137fc79ccd7e7685271ad3 --- /dev/null +++ b/packages/component-wizard/webpack.config.js @@ -0,0 +1,3 @@ +const webpackConfig = require('xpub-styleguide/src/webpack-config') + +module.exports = webpackConfig(__dirname) diff --git a/packages/xpub-faraday/.gitignore b/packages/xpub-faraday/.gitignore index 3614a810088d89d9ccaa28d82401545634874a18..451370b4294d3ed649754d600992fc73291385d3 100644 --- a/packages/xpub-faraday/.gitignore +++ b/packages/xpub-faraday/.gitignore @@ -5,4 +5,4 @@ node_modules/ uploads/ .env.* .env -config/local*.* \ No newline at end of file +config/local*.* diff --git a/packages/xpub-faraday/README.md b/packages/xpub-faraday/README.md index 64c7aaf4b917b690da5d454a12b74fc37f5e6e2c..ccc58fff1e4e206a7bd10cf520ba2cdffedcba7f 100644 --- a/packages/xpub-faraday/README.md +++ b/packages/xpub-faraday/README.md @@ -1,3 +1,7 @@ ## xPub-faraday -An MVP implementation of the first design sessions which allows a user to go through the process of creating a submission, assigning editors and reviewers, submitting reviews and submitting a decision. \ No newline at end of file +An MVP implementation of the first design sessions which allows a user to go through the process of creating a submission, assigning editors and reviewers, submitting reviews and submitting a decision. + +## Faraday Project Roadmap + + \ No newline at end of file diff --git a/packages/xpub-faraday/app/config/journal/declarations.js b/packages/xpub-faraday/app/config/journal/declarations.js index 012e4d46c2bfe3b20da969a9277a391d61c18094..2f25fbee2e499dcc09f87585cc28e7b9b8af302b 100644 --- a/packages/xpub-faraday/app/config/journal/declarations.js +++ b/packages/xpub-faraday/app/config/journal/declarations.js @@ -25,4 +25,34 @@ export default { legend: 'Pre-registered?', }, ], + options: [ + { + value: 'step2-1', + label: `I have the email addresses of all the co-authors of the manuscript.`, + }, + { + value: 'step2-2', + label: + 'I have the manuscript file in Microsoft Word or Adobe PDF format with the tables and figures integrated in the manuscript body.', + }, + { + value: 'step2-3', + label: + 'I have the electronic files of any supplementary materials (e.g., datasets, images, audio, video) that I want to submit with the manuscript.', + }, + { + value: 'step2-4', + label: + 'I am aware that accepted manuscripts are subject to Article Processing Charges.', + }, + { + value: 'step2-5', + label: `I'm aware that an ORCID ID is required for the corresponding author before the article can be published (if accepted). The ORCID ID should added via your user account.`, + }, + { + value: 'step2-6', + label: + 'I am aware that if my submission is covered by an institutional membership, Hindawi will share details of the manuscript with the administrator of the membership.', + }, + ], } diff --git a/packages/xpub-faraday/app/config/journal/index.js b/packages/xpub-faraday/app/config/journal/index.js index 30bc3a405632a2e2ff3972ac0c490a26666ce330..6f766fe010681851881e41518ce6918f2a6d5382 100644 --- a/packages/xpub-faraday/app/config/journal/index.js +++ b/packages/xpub-faraday/app/config/journal/index.js @@ -7,3 +7,4 @@ export { default as articleSections } from './article-sections' export { default as articleTypes } from './article-types' export { default as editors } from './editors' export { default as roles } from './roles' +export { default as wizard } from './submit-wizard' diff --git a/packages/xpub-faraday/app/config/journal/metadata.js b/packages/xpub-faraday/app/config/journal/metadata.js index 7d662e97b2b267abc000756cf3b2056e80d1f562..422009e4150d333be600272d9bb0ba104c9d4f38 100644 --- a/packages/xpub-faraday/app/config/journal/metadata.js +++ b/packages/xpub-faraday/app/config/journal/metadata.js @@ -3,3 +3,8 @@ export default { name: 'Hindawi Faraday', logo: '/assets/hindawi-logo.png', } + +export const journal = { + label: 'Hindawi Faraday', + value: 'hindawi-faraday', +} diff --git a/packages/xpub-faraday/app/config/journal/submit-wizard.js b/packages/xpub-faraday/app/config/journal/submit-wizard.js new file mode 100644 index 0000000000000000000000000000000000000000..0c0f29a29829d5d339498e21e3d08908ed1f5a52 --- /dev/null +++ b/packages/xpub-faraday/app/config/journal/submit-wizard.js @@ -0,0 +1,151 @@ +import React from 'react' +import { AbstractEditor, TitleEditor } from 'xpub-edit' +import { + Menu, + CheckboxGroup, + YesOrNo, + TextField, + Supplementary, +} from '@pubsweet/ui' +import uploadFile from 'xpub-upload' +import { required, minChars } from 'xpub-validators' + +import { articleSections, declarations } from './' + +const min3Chars = minChars(3) +const yesNoWithLabel = ({ label, ...rest }) => ( + <div> + <label>{label}</label> + <YesOrNo {...rest} /> + </div> +) + +const journal = { + label: 'Hindawi Faraday', + value: 'hindawi-faraday', +} + +export default { + showProgress: true, + formSectionKeys: [ + 'metadata', + 'declarations', + 'suggestions', + 'notes', + 'files', + ], + steps: [ + { + label: 'Journal details', + title: 'Jounal & Field Selection', + children: [ + { + fieldId: 'metadata.journal', + renderComponent: Menu, + label: 'Journal', + options: [journal], + value: journal.value, + validate: [required], + }, + { + fieldId: 'subject', + renderComponent: Menu, + label: 'Subject area', + options: articleSections, + }, + { + fieldId: 'specialIssue', + renderComponent: Menu, + label: 'Special issue', + options: [ + { label: 'Special 2.1', value: 'dd21' }, + { label: 'Special 2.2', value: 'dd22' }, + ], + }, + ], + }, + { + label: 'Pre-submission checklist', + title: 'Pre-submission Checklist', + subtitle: + 'Before moving forward make sure you have all the needed details prepared by reviewing and checking off the items on this list.', + children: [ + { + fieldId: 'declarations', + renderComponent: CheckboxGroup, + options: declarations.options, + validate: [required], + }, + ], + }, + { + label: 'Manuscript & Authors Details', + title: 'Manuscript & Authors Details', + subtitle: + 'Please provide the details of all the authors of this manuscript....', + children: [ + { + fieldId: 'step3-1', + renderComponent: TitleEditor, + placeholder: 'Manuscript title', + title: 'Manuscript title', + }, + { + fieldId: 'step3-2', + renderComponent: Menu, + label: 'Manuscript type', + options: [ + { label: 'Type 1', value: 'type1' }, + { label: 'Type 2', value: 'type2' }, + ], + }, + { + fieldId: 'step3-3', + renderComponent: AbstractEditor, + title: 'Abstract', + placeholder: 'Write an abstract', + }, + // { + // fieldId: 'authors', + // renderComponent: 'sortable-list', + // label: 'Authors details', + // }, + { + fieldId: 'step3-4', + renderComponent: yesNoWithLabel, + label: 'Is there a potential conflict of interest?', + }, + { + fieldId: 'step3-5', + renderComponent: TextField, + label: 'Conflict of interest details', + validate: [required, min3Chars], + }, + ], + }, + { + label: 'Files upload', + title: 'Manuscript Files Upload', + children: [ + { + fieldId: 'mainManuscripts', + label: 'Main Manuscript', + renderComponent: Supplementary, + uploadFile, + }, + { + fieldId: 'supplementalFiles', + label: 'Supplemental Files', + renderComponent: Supplementary, + uploadFile, + }, + { + fieldId: 'coverLetter', + label: 'Cover Letter', + renderComponent: Supplementary, + uploadFile, + }, + ], + }, + ], +} diff --git a/packages/xpub-faraday/app/routes.js b/packages/xpub-faraday/app/routes.js index 568ad15477e9686e8a2f32f8c4b05074d681f137..dff1dde953e0d0b8c91dcdfe095bdc9c0681389f 100644 --- a/packages/xpub-faraday/app/routes.js +++ b/packages/xpub-faraday/app/routes.js @@ -1,5 +1,7 @@ import React from 'react' import { Route } from 'react-router-dom' +import { DragDropContext } from 'react-dnd' +import HTML5Backend from 'react-dnd-html5-backend' import App from 'pubsweet-component-xpub-app/src/components' @@ -11,12 +13,21 @@ import { import DashboardPage from 'pubsweet-component-xpub-dashboard/src/components/DashboardPage' +import { Wizard } from 'pubsweet-component-wizard/src/components' + +// import { Wizard } from './component-wizard' + const Routes = () => ( <App> <Route component={LoginPage} exact path="/login" /> <PrivateRoute component={DashboardPage} exact path="/" /> <PrivateRoute component={LogoutPage} exact path="/logout" /> + <PrivateRoute + component={Wizard} + exact + path="/projects/:project/versions/:version/submit" + /> </App> ) -export default Routes +export default DragDropContext(HTML5Backend)(Routes) diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json index 7ccdda755482909d995a7cd6c7dbc07e8ab9706f..d2f94630a129aac037beb019e7f000121cd6c0c1 100644 --- a/packages/xpub-faraday/config/components.json +++ b/packages/xpub-faraday/config/components.json @@ -3,5 +3,6 @@ "pubsweet-component-xpub-authentication", "pubsweet-component-xpub-dashboard", "xpub-faraday-server", - "pubsweet-component-ink-backend" + "pubsweet-component-ink-backend", + "pubsweet-component-wizard" ] diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index 36f785c3693194e7e9c1d5098f5f287f6f687de2..ac142cf190c5a2949aeb6142ab589fbb04fa27e3 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -24,7 +24,7 @@ module.exports = { 'pubsweet-client': { API_ENDPOINT: '/api', 'login-redirect': '/', - 'redux-log': false, + 'redux-log': true, theme: process.env.PUBSWEET_THEME, }, 'mail-transport': { diff --git a/packages/xpub-faraday/config/local-development.json b/packages/xpub-faraday/config/local-development.json index 8997f46ad0eed5a3eb86dc9ee2af2f3dc082166b..2b249d74f689ff658c0aa3b7aa908226cc03e18e 100644 --- a/packages/xpub-faraday/config/local-development.json +++ b/packages/xpub-faraday/config/local-development.json @@ -1,12 +1,2 @@ -{ - "pubsweet-server": { - "secret": - "033fb008ff867b50936c8278d3d3451e4334e29594fb3805afb4c7193b81f5d9b9bd6db7a6c40e5795247214bb5b6f85350a1a7d49fb3debe612183485dc7bef" - }, - "pubsweet-component-ink-backend": { - "inkEndpoint": "http://inkdemo-api.coko.foundation/", - "email": "editoria@coko.foundation", - "password": "editoria", - "recipes": { "editoria-typescript": "2" } - } -} +{"pubsweet-server":{"secret":"702e1d23496c143026b634af15af57f5a88df7b7da9e3c24746da152d7068c72b98c692a09e76d5a618f2c2e473a8b6153bc4524a604290d04591eab9e0811e2"}} + diff --git a/packages/xpub-faraday/config/validations.js b/packages/xpub-faraday/config/validations.js index 238686df675ccdbf4d0c3d84fea47860d2bb80ed..d30f26928056ba4df3e15b7977c2ad657249525c 100644 --- a/packages/xpub-faraday/config/validations.js +++ b/packages/xpub-faraday/config/validations.js @@ -17,6 +17,7 @@ module.exports = { submitted: Joi.date(), source: Joi.string(), // TODO: move to a file metadata: Joi.object({ + journal: Joi.string(), title: Joi.string(), abstract: Joi.string(), articleType: Joi.string(), @@ -24,7 +25,7 @@ module.exports = { // authors: Joi.array(), keywords: Joi.array(), }), - declarations: Joi.object().unknown(), + declarations: Joi.array(), suggestions: Joi.object({ reviewers: Joi.object({ suggested: Joi.array().items(Joi.string()), diff --git a/packages/xpub-faraday/package.json b/packages/xpub-faraday/package.json index bf6ff73a99c58ad166d1b3c04a17380f9b12a282..d026e0a0ecf49fa1acd90b55d34dd816e2aafb34 100644 --- a/packages/xpub-faraday/package.json +++ b/packages/xpub-faraday/package.json @@ -30,6 +30,8 @@ "pubsweet-component-xpub-submit": "^0.0.2", "pubsweet-server": "^1.0.1", "react": "^15.6.1", + "react-dnd": "^2.5.4", + "react-dnd-html5-backend": "^2.5.4", "react-dom": "^15.6.1", "react-router-dom": "^4.2.2", "recompose": "^0.26.0", diff --git a/packages/xpub-faraday/static/faraday-roadmap.png b/packages/xpub-faraday/static/faraday-roadmap.png new file mode 100644 index 0000000000000000000000000000000000000000..17a76c151e1dc4fb0d2d08e41aeccc7f233c1912 Binary files /dev/null and b/packages/xpub-faraday/static/faraday-roadmap.png differ diff --git a/yarn.lock b/yarn.lock index 9e397b3310606223e197f478b7cfb87caf71fdc9..026e42ed5bbd04af10413337e8fcaf8ad86eabf1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -380,7 +380,7 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@~2.0.3: +asap@^2.0.6, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -2673,10 +2673,23 @@ discontinuous-range@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" +disposables@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/disposables/-/disposables-1.0.2.tgz#36c6a674475f55a2d6913567a601444e487b4b6e" + dlv@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.0.tgz#fee1a7c43f63be75f3f679e85262da5f102764a7" +dnd-core@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-2.5.4.tgz#0c70a8dcbb609c0b222e275fcae9fa83e5897397" + dependencies: + asap "^2.0.6" + invariant "^2.0.0" + lodash "^4.2.0" + redux "^3.7.1" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -4274,7 +4287,7 @@ hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" -hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: +hoist-non-react-statics@^2.1.0, hoist-non-react-statics@^2.2.1, hoist-non-react-statics@^2.3.0, hoist-non-react-statics@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" @@ -8019,6 +8032,23 @@ react-dev-utils@^4.2.1: strip-ansi "3.0.1" text-table "0.2.0" +react-dnd-html5-backend@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-2.5.4.tgz#974ad083f67b12d56977a5b171f5ffeb29d78352" + dependencies: + lodash "^4.2.0" + +react-dnd@^2.5.4: + version "2.5.4" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-2.5.4.tgz#0b6dc5e9d0dfc2909f4f4fe736e5534f3afd1bd9" + dependencies: + disposables "^1.0.1" + dnd-core "^2.5.4" + hoist-non-react-statics "^2.1.0" + invariant "^2.1.0" + lodash "^4.2.0" + prop-types "^15.5.10" + react-docgen-annotation-resolver@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-docgen-annotation-resolver/-/react-docgen-annotation-resolver-1.0.0.tgz#abbb343698b3b319537142082b6bb7d835fe2f1f" @@ -8475,7 +8505,7 @@ redux-thunk@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.2.0.tgz#e615a16e16b47a19a515766133d1e3e99b7852e5" -redux@^3.6.0, redux@^3.7.2: +redux@^3.6.0, redux@^3.7.1, redux@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" dependencies: