Skip to content
Snippets Groups Projects
Commit 93d86db3 authored by Bogdan Cochior's avatar Bogdan Cochior
Browse files

Merge branch 'faraday-master' of gitlab.coko.foundation:xpub/xpub into faraday-master

parents 0a35acc6 1591d6b6
No related branches found
No related tags found
No related merge requests found
Showing
with 589 additions and 332 deletions
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { connect } from 'react-redux'
import { get } from 'lodash'
import { reduxForm } from 'redux-form'
import { actions } from 'pubsweet-client'
import { required } from 'xpub-validators'
import { withRouter } from 'react-router-dom'
import { selectCurrentUser } from 'xpub-selectors'
import {
compose,
withHandlers,
withProps,
getContext,
lifecycle,
} from 'recompose'
import { TextField, Menu, Icon, ValidatedField, Button } from '@pubsweet/ui'
import {
addAuthor,
getFragmentAuthors,
setAuthors,
moveAuthors,
} from '../redux/authors'
import classes from './AuthorList.local.scss'
import SortableList from './SortableList'
const countries = [
{ label: 'Romania', value: 'ro' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
]
const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/)
const emailValidator = value =>
emailRegex.test(value) ? undefined : 'Invalid email'
const ValidatedTextField = ({ label, name, isRequired, validators = [] }) => {
const v = [isRequired && required, ...validators].filter(Boolean)
return (
<div className={classnames(classes['validated-text'])}>
<span className={classnames(classes.label)}>{label}</span>
<ValidatedField component={TextField} name={name} validate={v} />
</div>
)
}
const MenuItem = ({ label, name, options }) => (
<div className={classnames(classes['validated-text'])}>
<span className={classnames(classes.label)}>{label}</span>
<ValidatedField
component={input => <Menu {...input} options={options} />}
name={name}
validate={[required]}
/>
</div>
)
const Label = ({ label, value }) => (
<div className={classnames(classes['label-container'])}>
<span className={classnames(classes.label)}>{label}</span>
<span className={classnames(classes.value)}>{value}</span>
</div>
)
const DragHandle = () => (
<div className={classnames(classes['drag-handle'])}>
<Icon>chevron_up</Icon>
<Icon size={16}>menu</Icon>
<Icon>chevron_down</Icon>
</div>
)
const AuthorAdder = ({ authors, handleSubmit }) => (
<div className={classnames(classes.adder)}>
<Button onClick={handleSubmit} primary>
{authors.length === 0 ? '+ Add submitting author' : '+ Add author'}
</Button>
<span className={classnames(classes.title)}>
{authors.length === 0 ? 'Submitting author' : 'Author'}
</span>
<div className={classnames(classes.row)}>
<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="author.email"
validators={[emailValidator]}
/>
<ValidatedTextField
isRequired
label="Affiliation"
name="author.affiliation"
/>
<MenuItem label="Country" name="author.country" options={countries} />
</div>
</div>
)
const Adder = compose(
connect(state => ({
currentUser: selectCurrentUser(state),
})),
withProps(({ currentUser }) => {
const { admin, email, username } = currentUser
if (!admin) {
return {
initialValues: {
author: {
email,
firstName: username,
},
},
}
}
}),
reduxForm({
form: 'author',
onSubmit: (values, dispatch, { authors, addAuthor, reset, match }) => {
const collectionId = get(match, 'params.project')
const fragmentId = get(match, 'params.version')
const isFirstAuthor = authors.length === 0
addAuthor(
{
...values.author,
isSubmitting: isFirstAuthor,
isCorresponding: isFirstAuthor,
},
collectionId,
fragmentId,
).then(reset)
},
}),
)(AuthorAdder)
const Author = ({
firstName,
middleName,
lastName,
email,
affiliation,
country,
isDragging,
dragHandle,
isOver,
countryParser,
removeAuthor,
isSubmitting,
isCorresponding,
setAsCorresponding,
parseAuthorType,
}) => (
<div
className={classnames({
[classes.author]: true,
[classes.dashed]: isOver,
})}
>
{!isOver && dragHandle}
<div
className={classnames({
[classes.container]: true,
[classes.hide]: isOver,
})}
>
<span className={classnames(classes.title)}>
{parseAuthorType(isSubmitting, isCorresponding)}
</span>
<div className={classnames(classes.row)}>
<Label label="First name" value={firstName} />
<Label label="Middle name" value={middleName} />
<Label label="Last name" value={lastName} />
</div>
<div className={classnames(classes.row)}>
<Label label="Email" value={email} />
<Label label="Affiliation" value={affiliation} />
<Label label="Country" value={countryParser(country)} />
</div>
</div>
<div className={classnames(classes['button-container'])}>
{!isSubmitting && (
<div
className={classnames(classes['delete-button'])}
onClick={removeAuthor(email)}
>
<Icon>trash</Icon>
</div>
)}
{!isCorresponding && (
<div
className={classnames(classes.corresponding)}
onClick={setAsCorresponding(email)}
>
<Icon>mail</Icon>
</div>
)}
</div>
</div>
)
const Authors = ({
author,
authors,
moveAuthor,
addAuthor,
editAuthor,
match,
version,
dropItem,
...rest
}) => (
<div>
<Adder
addAuthor={addAuthor}
author={author}
authors={authors}
editAuthor={editAuthor}
match={match}
/>
<SortableList
dragHandle={DragHandle}
dropItem={dropItem}
items={authors}
listItem={Author}
moveItem={moveAuthor}
{...rest}
/>
</div>
)
export default compose(
withRouter,
getContext({ version: PropTypes.object, project: PropTypes.object }),
connect(
(state, { match: { params: { version } } }) => ({
authors: getFragmentAuthors(state, version),
}),
{
addAuthor,
setAuthors,
moveAuthors,
updateFragment: actions.updateFragment,
},
),
lifecycle({
componentDidMount() {
const { version, setAuthors } = this.props
setAuthors(version.authors, version.id)
},
}),
withHandlers({
dropItem: ({
updateFragment,
authors,
project,
version,
setAuthors,
}) => () => {
setAuthors(authors, version.id)
},
countryParser: () => countryCode =>
countries.find(c => c.value === countryCode).label,
parseAuthorType: () => (isSubmitting, isCorresponding) => {
if (isSubmitting) return 'Submitting author'
if (isCorresponding) return 'Corresponding author'
return 'Author'
},
moveAuthor: ({
authors,
moveAuthors,
project,
version,
updateFragment,
match: { params },
}) => (dragIndex, hoverIndex) => {
const newAuthors = SortableList.moveItem(authors, dragIndex, hoverIndex)
moveAuthors(newAuthors, params.version)
},
removeAuthor: ({
authors,
updateFragment,
project,
version,
setAuthors,
}) => authorEmail => () => {
const newAuthors = authors.filter(a => a.email !== authorEmail)
setAuthors(newAuthors, version.id)
},
setAsCorresponding: ({
authors,
updateFragment,
setAuthors,
version,
project,
}) => authorEmail => () => {
const newAuthors = authors.map(a => ({
...a,
isCorresponding: a.isSubmitting || a.email === authorEmail,
}))
setAuthors(newAuthors, version.id)
},
}),
)(Authors)
import React from 'react'
import classnames from 'classnames'
import { Icon } from '@pubsweet/ui'
import { Label } from './FormItems'
import classes from './AuthorList.local.scss'
export default ({
firstName,
middleName,
lastName,
email,
affiliation,
country,
dragHandle,
isOver,
countryParser,
removeAuthor,
isSubmitting,
isCorresponding,
setAsCorresponding,
parseAuthorType,
...rest
}) => (
<div
className={classnames({
[classes.author]: true,
[classes.dashed]: isOver,
})}
>
{!isOver && dragHandle}
<div
className={classnames({
[classes.container]: true,
[classes.hide]: isOver,
})}
>
<span className={classnames(classes.title)}>
{parseAuthorType(isSubmitting, isCorresponding)}
</span>
<div className={classnames(classes.row)}>
<Label label="First name" value={firstName} />
<Label label="Middle name" value={middleName} />
<Label label="Last name" value={lastName} />
</div>
<div className={classnames(classes.row)}>
<Label label="Email" value={email} />
<Label label="Affiliation" value={affiliation} />
<Label label="Country" value={countryParser(country)} />
</div>
</div>
<div className={classnames(classes['button-container'])}>
{!isSubmitting && (
<div
className={classnames(classes['delete-button'])}
onClick={removeAuthor(email)}
title="Delete author"
>
<Icon>trash</Icon>
</div>
)}
{!isCorresponding && (
<div
className={classnames(classes.corresponding)}
onClick={setAsCorresponding(email)}
title="Set as corresponding author"
>
<Icon>mail</Icon>
</div>
)}
{rest.editedAuthor < 0 && (
<div
className={classnames(classes.corresponding)}
onClick={rest.setAuthorEdit(rest.index)}
title="Edit author"
>
<Icon>edit-2</Icon>
</div>
)}
</div>
</div>
)
import React from 'react'
import { get } from 'lodash'
import classnames from 'classnames'
import { connect } from 'react-redux'
import { Button } from '@pubsweet/ui'
import { reduxForm } from 'redux-form'
import { compose, withProps } from 'recompose'
import { selectCurrentUser } from 'xpub-selectors'
import classes from './AuthorList.local.scss'
import { MenuItem, ValidatedTextField } from './FormItems'
const countries = [
{ label: 'Romania', value: 'ro' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
]
const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/)
const emailValidator = value =>
emailRegex.test(value) ? undefined : 'Invalid email'
const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => (
<div className={classnames(classes.adder)}>
<Button onClick={setEditMode(true)} primary>
{authors.length === 0 ? '+ Add submitting author' : '+ Add author'}
</Button>
{editMode && (
<div className={classnames(classes['form-body'])}>
<span className={classnames(classes.title)}>
{authors.length === 0 ? 'Submitting author' : 'Author'}
</span>
<div className={classnames(classes.row)}>
<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="author.email"
validators={[emailValidator]}
/>
<ValidatedTextField
isRequired
label="Affiliation"
name="author.affiliation"
/>
<MenuItem label="Country" name="author.country" options={countries} />
</div>
<div className={classnames(classes['form-buttons'])}>
<Button onClick={setEditMode(false)}>Cancel</Button>
<Button onClick={handleSubmit} primary>
Save
</Button>
</div>
</div>
)}
</div>
)
export default compose(
connect(state => ({
currentUser: selectCurrentUser(state),
})),
withProps(({ currentUser }) => {
const { admin, email, username } = currentUser
if (!admin) {
return {
initialValues: {
author: {
email,
firstName: username,
},
},
}
}
}),
reduxForm({
form: 'author',
onSubmit: (
values,
dispatch,
{ authors, addAuthor, setEditMode, reset, match },
) => {
const collectionId = get(match, 'params.project')
const fragmentId = get(match, 'params.version')
const isFirstAuthor = authors.length === 0
addAuthor(
{
...values.author,
isSubmitting: isFirstAuthor,
isCorresponding: isFirstAuthor,
},
collectionId,
fragmentId,
).then(() => {
reset()
setEditMode(false)()
})
},
}),
)(AuthorAdder)
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import { connect } from 'react-redux'
import { Button } from '@pubsweet/ui'
import { reduxForm } from 'redux-form'
import { withRouter } from 'react-router-dom'
import { compose, getContext } from 'recompose'
import { ValidatedTextField, MenuItem } from './FormItems'
import { getFragmentAuthors, setAuthors } from '../../redux/authors'
import classes from './AuthorList.local.scss'
const countries = [
{ label: 'Romania', value: 'ro' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
]
const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/)
const emailValidator = value =>
emailRegex.test(value) ? undefined : 'Invalid email'
const AuthorEdit = ({ setAuthorEdit, handleSubmit }) => (
<div className={classnames(classes['editor-body'])}>
<div className={classnames(classes.row)}>
<ValidatedTextField isRequired label="First name" name="edit.firstName" />
<ValidatedTextField label="Middle name" name="edit.middleName" />
<ValidatedTextField isRequired label="Last name" name="edit.lastName" />
</div>
<div className={classnames(classes.row)}>
<ValidatedTextField
isRequired
label="Email"
name="edit.email"
validators={[emailValidator]}
/>
<ValidatedTextField
isRequired
label="Affiliation"
name="edit.affiliation"
/>
<MenuItem label="Country" name="edit.country" options={countries} />
</div>
<div className={classnames(classes['form-buttons'])}>
<Button onClick={setAuthorEdit(-1)}>Cancel</Button>
<Button onClick={handleSubmit} primary>
Save
</Button>
</div>
</div>
)
export default compose(
withRouter,
getContext({ version: PropTypes.object, project: PropTypes.object }),
connect(
(state, { match: { params: { version } } }) => ({
authors: getFragmentAuthors(state, version),
}),
{
setAuthors,
},
),
reduxForm({
form: 'edit',
onSubmit: (
values,
dispatch,
{ setAuthorEdit, setAuthors, project, version, authors, index, ...rest },
) => {
const newAuthors = [
...authors.slice(0, index),
values.edit,
...authors.slice(index + 1),
]
setAuthors(newAuthors, version.id)
setTimeout(setAuthorEdit(-1), 100)
},
}),
)(AuthorEdit)
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
import {
compose,
withHandlers,
getContext,
lifecycle,
withState,
} from 'recompose'
import {
addAuthor,
getFragmentAuthors,
setAuthors,
moveAuthors,
} from '../../redux/authors'
import SortableList from '../SortableList'
import Author from './Author'
import StaticList from './StaticList'
import AuthorAdder from './AuthorAdder'
import AuthorEditor from './AuthorEditor'
import { DragHandle } from './FormItems'
const countries = [
{ label: 'Romania', value: 'ro' },
{ label: 'United Kingdom', value: 'uk' },
{ label: 'Germany', value: 'de' },
{ label: 'France', value: 'fr' },
]
const Authors = ({
authors,
moveAuthor,
addAuthor,
editAuthor,
match,
version,
dropItem,
editMode,
setEditMode,
editedAuthor,
...rest
}) => (
<div>
<AuthorAdder
addAuthor={addAuthor}
authors={authors}
editAuthor={editAuthor}
editMode={editMode}
match={match}
setEditMode={setEditMode}
/>
{editedAuthor > -1 ? (
<StaticList
authors={authors}
editComponent={AuthorEditor}
editIndex={editedAuthor}
{...rest}
/>
) : (
<SortableList
dragHandle={DragHandle}
dropItem={dropItem}
editedAuthor={editedAuthor}
items={authors}
listItem={Author}
moveItem={moveAuthor}
{...rest}
/>
)}
</div>
)
export default compose(
withRouter,
getContext({ version: PropTypes.object, project: PropTypes.object }),
connect(
(state, { match: { params: { version } } }) => ({
authors: getFragmentAuthors(state, version),
}),
{
addAuthor,
setAuthors,
moveAuthors,
},
),
lifecycle({
componentDidMount() {
const { version, setAuthors } = this.props
setAuthors(version.authors, version.id)
},
}),
withState('editMode', 'setEditMode', false),
withState('editedAuthor', 'setEditedAuthor', -1),
withHandlers({
setAuthorEdit: ({ setEditedAuthor }) => editedAuthor => e => {
e && e.preventDefault && e.preventDefault()
setEditedAuthor(prev => editedAuthor)
},
setEditMode: ({ setEditMode }) => mode => e => {
e && e.preventDefault()
setEditMode(v => mode)
},
dropItem: ({ authors, project, version, setAuthors }) => () => {
setAuthors(authors, version.id)
},
countryParser: () => countryCode =>
countries.find(c => c.value === countryCode).label,
parseAuthorType: () => (isSubmitting, isCorresponding) => {
if (isSubmitting) return 'Submitting author'
if (isCorresponding) return 'Corresponding author'
return 'Author'
},
moveAuthor: ({
authors,
moveAuthors,
project,
version,
match: { params },
}) => (dragIndex, hoverIndex) => {
const newAuthors = SortableList.moveItem(authors, dragIndex, hoverIndex)
moveAuthors(newAuthors, params.version)
},
removeAuthor: ({
authors,
project,
version,
setAuthors,
}) => authorEmail => () => {
const newAuthors = authors.filter(a => a.email !== authorEmail)
setAuthors(newAuthors, version.id)
},
setAsCorresponding: ({
authors,
setAuthors,
version,
project,
}) => authorEmail => () => {
const newAuthors = authors.map(a => ({
...a,
isCorresponding: a.isSubmitting || a.email === authorEmail,
}))
setAuthors(newAuthors, version.id)
},
}),
)(Authors)
...@@ -48,12 +48,34 @@ ...@@ -48,12 +48,34 @@
} }
} }
.adder { .editor-body {
border: 1px solid #444; border: 1px solid #444;
margin: 10px 0;
padding: 10px;
.form-buttons {
display: flex;
justify-content: space-around;
margin: 15px 0 0 0;
}
}
.adder {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin: 10px 0; margin: 10px 0;
padding: 10px;
.form-body {
border: 1px solid #444;
margin-top: 10px;
padding: 10px;
}
.form-buttons {
display: flex;
justify-content: space-around;
margin: 15px 0 0 0;
}
.button { .button {
background-color: #444; background-color: #444;
...@@ -83,15 +105,6 @@ ...@@ -83,15 +105,6 @@
} }
} }
.drag-handle {
align-items: center;
border-right: 1px solid #444;
cursor: move;
display: flex;
flex-direction: column;
justify-content: center;
}
.validated-text { .validated-text {
flex: 1; flex: 1;
margin-right: 20px; margin-right: 20px;
......
import React from 'react'
import classnames from 'classnames'
import { required } from 'xpub-validators'
import { TextField, Menu, ValidatedField, Icon } from '@pubsweet/ui'
import classes from './FormItems.local.scss'
export const ValidatedTextField = ({
label,
name,
isRequired,
validators = [],
}) => {
const v = [isRequired && required, ...validators].filter(Boolean)
return (
<div className={classnames(classes['validated-text'])}>
<span className={classnames(classes.label)}>{label}</span>
<ValidatedField component={TextField} name={name} validate={v} />
</div>
)
}
export const MenuItem = ({ label, name, options }) => (
<div className={classnames(classes['validated-text'])}>
<span className={classnames(classes.label)}>{label}</span>
<ValidatedField
component={input => <Menu {...input} options={options} />}
name={name}
validate={[required]}
/>
</div>
)
export const Label = ({ label, value }) => (
<div className={classnames(classes['label-container'])}>
<span className={classnames(classes.label)}>{label}</span>
<span className={classnames(classes.value)}>{value}</span>
</div>
)
export const DragHandle = () => (
<div className={classnames(classes['drag-handle'])}>
<Icon>chevron_up</Icon>
<Icon size={16}>menu</Icon>
<Icon>chevron_down</Icon>
</div>
)
.validated-text {
flex: 1;
margin-right: 20px;
}
.label {
font-size: 14px;
font-weight: 300;
text-transform: uppercase;
}
.label-container {
display: flex;
flex: 1;
flex-direction: column;
margin: 5px;
.value {
font-size: 16px;
font-weight: 600;
}
}
.drag-handle {
align-items: center;
border-right: 1px solid #444;
cursor: move;
display: flex;
flex-direction: column;
justify-content: center;
}
import React from 'react'
import Author from './Author'
export default ({
authors,
editIndex,
removeAuthor,
countryParser,
editComponent,
setAuthorEdit,
parseAuthorType,
}) => (
<div>
{authors.map(
(a, index) =>
index === editIndex ? (
React.createElement(editComponent, {
key: 'author-editor',
index,
initialValues: {
edit: a,
},
setAuthorEdit,
countryParser,
parseAuthorType,
})
) : (
<Author
key={a.firstName}
{...a}
countryParser={countryParser}
parseAuthorType={parseAuthorType}
removeAuthor={removeAuthor}
/>
),
)}
</div>
)
export { default as AuthorList } from './AuthorList'
...@@ -47,6 +47,7 @@ const Item = ({ ...@@ -47,6 +47,7 @@ const Item = ({
connectDropTarget, connectDropTarget,
listItem, listItem,
dragHandle, dragHandle,
isEditing,
...rest ...rest
}) => }) =>
dragHandle dragHandle
...@@ -82,12 +83,20 @@ const DecoratedItem = compose( ...@@ -82,12 +83,20 @@ const DecoratedItem = compose(
})), })),
)(Item) )(Item)
const SortableList = ({ items, moveItem, listItem, dragHandle, ...rest }) => ( const SortableList = ({
items,
moveItem,
listItem,
dragHandle,
editItem,
...rest
}) => (
<div> <div>
{items.map((item, i) => ( {items.map((item, i) => (
<DecoratedItem <DecoratedItem
dragHandle={dragHandle} dragHandle={dragHandle}
index={i} index={i}
isEditing={rest.editedAuthor !== -1}
key={item.name || Math.random()} key={item.name || Math.random()}
listItem={listItem} listItem={listItem}
moveItem={moveItem} moveItem={moveItem}
......
...@@ -4,6 +4,7 @@ import classnames from 'classnames' ...@@ -4,6 +4,7 @@ import classnames from 'classnames'
import { ValidatedField, Button } from '@pubsweet/ui' import { ValidatedField, Button } from '@pubsweet/ui'
import classes from './WizardStep.local.scss' import classes from './WizardStep.local.scss'
import AuthorList from './AuthorList/AuthorList'
export default ({ export default ({
children: stepChildren, children: stepChildren,
...@@ -60,6 +61,7 @@ export default ({ ...@@ -60,6 +61,7 @@ export default ({
) )
}, },
)} )}
<AuthorList />
<div className={classnames(classes.buttons)}> <div className={classnames(classes.buttons)}>
<Button onClick={isFirst ? () => history.push('/') : prevStep}> <Button onClick={isFirst ? () => history.push('/') : prevStep}>
{isFirst {isFirst
......
...@@ -5,5 +5,3 @@ export { default as WizardPage } from './WizardPage' ...@@ -5,5 +5,3 @@ export { default as WizardPage } from './WizardPage'
export { default as WizardStep } from './WizardStep' export { default as WizardStep } from './WizardStep'
export { default as SortableList } from './SortableList' export { default as SortableList } from './SortableList'
export { default as WizardFormStep } from './WizardFormStep' export { default as WizardFormStep } from './WizardFormStep'
export { default as AuthorList } from './AuthorList'
...@@ -14,8 +14,8 @@ const _setAuthors = (authors, fragmentId) => ({ ...@@ -14,8 +14,8 @@ const _setAuthors = (authors, fragmentId) => ({
// actions // actions
export const setAuthors = (authors, fragmentId) => dispatch => { export const setAuthors = (authors, fragmentId) => dispatch => {
dispatch(_setAuthors(authors, fragmentId))
dispatch(change('wizard', 'authors', authors)) dispatch(change('wizard', 'authors', authors))
dispatch(_setAuthors(authors, fragmentId))
} }
export const moveAuthors = (authors, fragmentId) => dispatch => { export const moveAuthors = (authors, fragmentId) => dispatch => {
......
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
} from '@pubsweet/ui' } from '@pubsweet/ui'
import uploadFileFn from 'xpub-upload' import uploadFileFn from 'xpub-upload'
import { required, minChars, minSize } from 'xpub-validators' import { required, minChars, minSize } from 'xpub-validators'
import { AuthorList } from 'pubsweet-component-wizard/src/components' import { AuthorList } from 'pubsweet-component-wizard/src/components/AuthorList'
import { declarations } from './' import { declarations } from './'
import issueTypes from './issues-types' import issueTypes from './issues-types'
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment