-
Alexandru Munteanu authored4c02f4c6
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
import React, { Fragment } from 'react'
import { connect } from 'react-redux'
import { isEmpty, merge } from 'lodash'
import { th } from '@pubsweet/ui-toolkit'
import { withJournal } from 'xpub-journal'
import { required } from 'xpub-validators'
import styled, { css } from 'styled-components'
import { compose, withHandlers, withProps } from 'recompose'
import { Menu, Icon, Button, ErrorText, ValidatedField } from '@pubsweet/ui'
import {
reduxForm,
isSubmitting,
getFormValues,
change as changeForm,
} from 'redux-form'
import {
uploadFile,
deleteFile,
getSignedUrl,
getRequestStatus,
} from 'pubsweet-components-faraday/src/redux/files'
import {
withModal,
ConfirmationModal,
} from 'pubsweet-component-modal/src/components'
import {
createRecommendation,
updateRecommendation,
} from 'pubsweet-components-faraday/src/redux/recommendations'
import {
onReviewSubmit,
onReviewChange,
parseReviewResponseToForm,
} from './utils'
const guidelinesLink =
'https://about.hindawi.com/authors/peer-review-at-hindawi/'
const TextAreaField = input => <Textarea {...input} height={70} rows={6} />
const ReviewerReportForm = ({
addFile,
fileError,
removeFile,
changeField,
isSubmitting,
handleSubmit,
fileFetching,
review = {},
formValues = {},
journal: { recommendations },
}) => (
<Root>
<Row>
<Label>Recommendation*</Label>
<ActionLink href={guidelinesLink} target="_blank">
Hindawi Reviewer Guidelines
</ActionLink>
</Row>
<Row>
<ValidatedField
component={input => (
<Menu
{...input}
inline
onChange={v => changeField('recommendation', v)}
options={recommendations}
placeholder="Select"
/>
)}
name="recommendation"
validate={[required]}
/>
</Row>
<Spacing />
<Row>
<FullWidth className="full-width">
<ValidatedField
component={TextAreaField}
name="public"
validate={isEmpty(formValues.files) ? [required] : []}
/>
</FullWidth>
</Row>
{formValues.hasConfidential ? (
<Fragment>
<Spacing />
<Row>
<Label>
Note for the editorial team <i>Not shared with the author</i>
</Label>
<ActionTextIcon onClick={() => changeField('hasConfidential', false)}>
<Icon primary size={3}>
x
</Icon>
Remove
</ActionTextIcon>
</Row>
<Row>
<FullWidth>
<ValidatedField
component={TextAreaField}
name="confidential"
validate={[required]}
/>
</FullWidth>
</Row>
</Fragment>
) : (
<Row>
<ActionText onClick={() => changeField('hasConfidential', true)}>
Add confidential note for the Editorial Team
</ActionText>
</Row>
)}
<Spacing />
{fileError && (
<Row>
<ErrorText>{fileError}</ErrorText>
</Row>
)}
<Row>
<ActionButton onClick={handleSubmit}>Submit report</ActionButton>
</Row>
</Root>
)
const ModalWrapper = compose(
connect(state => ({
fetching: false,
})),
)(({ fetching, ...rest }) => (
<ConfirmationModal {...rest} isFetching={fetching} />
))
export default compose(
withJournal,
connect(
state => ({
fileFetching: getRequestStatus(state),
formValues: getFormValues('reviewerReport')(state),
isSubmitting: isSubmitting('reviewerReport')(state),
}),
{
uploadFile,
deleteFile,
changeForm,
getSignedUrl,
getFormValues,
createRecommendation,
updateRecommendation,
},
),
withProps(({ review = {}, formValues = {} }) => ({
initialValues: merge(parseReviewResponseToForm(review), formValues),
})),
withModal(props => ({
modalComponent: ModalWrapper,
})),
withHandlers({
changeField: ({ changeForm }) => (field, value) => {
changeForm('reviewerReport', field, value)
},
addFile: ({ formValues = {}, uploadFile, changeForm, version }) => file => {
uploadFile(file, 'review', version)
.then(file => {
const files = formValues.files || []
const newFiles = [...files, file]
changeForm('reviewerReport', 'files', newFiles)
})
.catch(e => console.error(`Couldn't upload file.`, e))
},
removeFile: ({
formValues: { files = [] },
changeForm,
deleteFile,
}) => id => e => {
deleteFile(id)
.then(r => {
const newFiles = files.filter(f => f.id !== id)
changeForm('reviewerReport', 'files', newFiles)
})
.catch(e => console.error(`Couldn't delete the file.`, e))
const newFiles = files.filter(f => f.id !== id)
changeForm('reviewerReport', 'files', newFiles)
},
}),
reduxForm({
form: 'reviewerReport',
onChange: onReviewChange,
onSubmit: onReviewSubmit,
enableReinitialize: false,
keepDirtyOnReinitialize: true,
}),
)(ReviewerReportForm)
// #region styled-components
const defaultText = css`
color: ${th('colorPrimary')};
font-family: ${th('fontReading')};
font-size: ${th('fontSizeBaseSmall')};
`
const Root = styled.div`
display: flex;
flex-direction: column;
margin: auto;
[role='listbox'] {
min-width: 280px;
}
`
const Label = styled.div`
${defaultText};
text-transform: uppercase;
i {
text-transform: none;
margin-left: ${th('gridUnit')};
}
`
const ActionText = styled.span`
${defaultText};
cursor: pointer;
margin-left: ${({ left }) => left || 0}px;
text-decoration: underline;
`
const ActionTextIcon = styled(ActionText)`
align-items: center;
display: flex;
`
const ActionLink = styled.a`
${defaultText};
`
const Textarea = styled.textarea`
border-color: ${({ hasError }) =>
hasError ? th('colorError') : th('colorPrimary')};
font-size: ${th('fontSizeBaseSmall')};
font-family: ${th('fontWriting')};
padding: calc(${th('subGridUnit')} * 2);
transition: all 300ms linear;
width: 100%;
&:read-only {
background-color: ${th('colorBackgroundHue')};
}
`
const Spacing = styled.div`
flex: 1;
margin-top: calc(${th('gridUnit')} / 2);
`
const FullWidth = styled.div`
flex: 1;
> div {
flex: 1;
}
`
const Row = styled.div`
${defaultText};
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: row;
flex: 1;
flex-wrap: wrap;
justify-content: ${({ left }) => (left ? 'left' : 'space-between')};
div[role='alert'] {
margin-top: 0;
}
`
const ActionButton = styled(Button)`
${defaultText};
align-items: center;
background-color: ${th('colorPrimary')};
color: ${th('colorTextReverse')};
height: calc(${th('subGridUnit')} * 5);
display: flex;
padding: calc(${th('subGridUnit')} / 2) calc(${th('subGridUnit')});
text-align: center;
text-transform: uppercase;
`
// #endregion