// #region imports import React from 'react' import PropTypes from 'prop-types' import { get, isEmpty } from 'lodash' import { connect } from 'react-redux' import { th } from '@pubsweet/ui-toolkit' import { required } from 'xpub-validators' import { DragDropContext } from 'react-dnd' import { withRouter } from 'react-router-dom' import styled, { css } from 'styled-components' import HTML5Backend from 'react-dnd-html5-backend' import { ValidatedField, Button } from '@pubsweet/ui' import { AbstractEditor, TitleEditor } from 'xpub-edit' import { reduxForm, getFormSyncErrors, getFormValues, change as changeForm, } from 'redux-form' import { withModal, ConfirmationModal, } from 'pubsweet-component-modal/src/components' import { AuthorList, Files } from 'pubsweet-components-faraday/src/components' import { submitRevision } from 'pubsweet-component-wizard/src/redux/conversion' import { selectReviewRecommendations } from 'pubsweet-components-faraday/src/redux/recommendations' import { toClass, compose, withProps, withContext, withHandlers, } from 'recompose' import { uploadFile, deleteFile, getRequestStatus, } from 'pubsweet-components-faraday/src/redux/files' import { requiredHTML, requiredFiles, onRevisionSubmit, onRevisionChange, } from './utils' import { Expandable } from '../molecules/' // #endregion const TextAreaField = input => <Textarea {...input} height={70} rows={6} /> const SubmitRevision = ({ addFile, project, version, formError, formValues, removeFile, handleSubmit, responseFiles, reviews = [], submitFailed, }) => ( <Root> <Expandable label="Submit Revision" startExpanded> <Expandable label="DETAILS & AUTHORS" startExpanded> <Title>MANUSCRIPT TITLE*</Title> <ValidatedField component={input => <TitleEditor {...input} />} name="metadata.title" validate={[requiredHTML]} /> <Title>ABSTRACT*</Title> <ValidatedField component={input => <AbstractEditor {...input} />} name="metadata.abstract" validate={[requiredHTML]} /> <Title>AUTHORS DETAILS*</Title> <CustomValidatedField> <ValidatedField component={() => ( <AuthorList authorPath="revision.authors" parentForm="revision" project={project} /> )} name="authors" validate={[required]} /> </CustomValidatedField> </Expandable> <Expandable label="FILES" startExpanded> <CustomValidatedField> <ValidatedField component={() => ( <Files filePath="revision.files" parentForm="revision" project={project} version={version} /> )} name="files" validate={[requiredFiles]} /> </CustomValidatedField> </Expandable> {!isEmpty(reviews) && ( <Expandable label="RESPONSE TO REVISION REQUEST" startExpanded> <Title>Reply text*</Title> <Row> <FullWidth className="full-width"> <ValidatedField component={TextAreaField} name="commentsToReviewers" validate={ isEmpty(get(formValues, 'files.responseToReviewers')) ? [required] : [] } /> </FullWidth> </Row> </Expandable> )} <SubmitContainer> {submitFailed && formError && <Error>There are some errors above.</Error>} <Button onClick={handleSubmit} primary> SUBMIT REVISION </Button> </SubmitContainer> </Expandable> </Root> ) export default compose( withContext( { version: PropTypes.object, project: PropTypes.object, }, ({ project, version }) => ({ project, version, }), ), withRouter, withModal(props => ({ modalComponent: ConfirmationModal, })), connect( (state, { version }) => ({ fileFetching: getRequestStatus(state), reviews: selectReviewRecommendations(state, version.id), formValues: getFormValues('revision')(state), formError: getFormSyncErrors('revision')(state), }), { changeForm, uploadFile, deleteFile, submitRevision, }, ), withHandlers({ addFile: ({ formValues = {}, uploadFile, changeForm, version }) => file => { uploadFile(file, 'responseToReviewers', version) .then(file => { const { files } = formValues const newFiles = files.responseToReviewers ? [...files.responseToReviewers, file] : [file] changeForm('revision', 'files', { ...files, responseToReviewers: newFiles, }) }) .catch(e => console.error(`Couldn't upload file.`, e)) }, removeFile: ({ formValues: { files }, changeForm, deleteFile, }) => id => e => { deleteFile(id) .then(r => { const newFiles = files.responseToReviewers.filter(f => f.id !== id) changeForm('revision', 'files', { ...files, responseToReviewers: newFiles, }) }) .catch(e => console.error(`Couldn't delete the file.`, e)) }, }), withProps(({ version, formValues }) => ({ initialValues: { metadata: { title: get(version, 'revision.metadata.title', ''), abstract: get(version, 'revision.metadata.abstract', ''), }, authors: get(version, 'revision.authors'), files: get(version, 'revision.files', []), commentsToReviewers: get(version, 'revision.commentsToReviewers'), }, responseFiles: get(formValues, 'files.responseToReviewers', []), })), reduxForm({ form: 'revision', onChange: onRevisionChange, onSubmit: onRevisionSubmit, }), DragDropContext(HTML5Backend), toClass, )(SubmitRevision) // #region styled-components const defaultText = css` color: ${th('colorPrimary')}; font-family: ${th('fontReading')}; font-size: ${th('fontSizeBaseSmall')}; ` const Error = styled.span` color: ${th('colorError')}; font-size: ${th('fontSizeBaseSmall')}; margin-right: ${th('subGridUnit')}; ` const CustomValidatedField = styled.div` div { div:last-child { margin-top: 0; } } ` 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 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 Root = styled.div` background-color: ${th('colorBackground')}; ` const Title = styled.span` align-self: center; font-family: ${th('fontHeading')}; font-size: ${th('fontSizeBaseSmall')}; text-transform: uppercase; ` const SubmitContainer = styled.div` align-items: center; display: flex; flex-direction: row; justify-content: space-between; margin-top: calc(${th('subGridUnit')} * 2); ` // #endregion