diff --git a/packages/component-manuscript/src/atoms/index.js b/packages/component-manuscript/src/atoms/index.js index ca2cc52d61272c7ded2a6a651140939f5185e463..f28d7d8ad76093ec47f3f11af00428901375f2a5 100644 --- a/packages/component-manuscript/src/atoms/index.js +++ b/packages/component-manuscript/src/atoms/index.js @@ -1,4 +1,4 @@ -import { th } from '@pubsweet/ui' +import { th, Button } from '@pubsweet/ui' import styled, { css } from 'styled-components' const defaultText = css` @@ -129,6 +129,72 @@ const ManuscriptHeader = styled.div` padding-bottom: calc(${th('subGridUnit')}*2); ` +const ModalRoot = styled.form` + align-items: center; + background-color: ${th('backgroundColorReverse')}; + display: flex; + flex-direction: column; + padding: calc(${th('subGridUnit')} * 6); + position: relative; + width: 580px; +` + +const CloseIcon = styled.div` + cursor: pointer; + position: absolute; + top: ${th('subGridUnit')}; + right: ${th('subGridUnit')}; +` + +const ActionButton = styled(Button)` + ${defaultText}; + align-items: center; + background-color: ${th('colorPrimary')}; + color: ${th('colorTextReverse')}; + display: flex; + padding: 4px 8px; + text-align: center; + height: calc(${th('subGridUnit')}*5); + text-transform: uppercase; +` + +const ModalTitle = styled.span` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeHeading5')}; + margin-bottom: ${th('gridUnit')}; +` + +const CustomRadioGroup = styled.div` + width: 100%; + div { + flex-direction: column; + width: 100%; + label { + margin-bottom: ${th('subGridUnit')}!important; + span:last-child { + font-style: normal; + ${defaultText}; + } + } + } +` + +const SpinnerContainer = styled.div` + align-items: center; + display: flex; + justify-content: center; + height: calc(${th('gridUnit')} * 2); + min-width: calc(${th('gridUnit')} * 4); +` + +const ButtonsContainer = styled.div` + display: flex; + justify-content: space-evenly; + margin: ${th('gridUnit')} auto 0; + width: 100%; +` + export { Root, BreadCrumbs, @@ -144,4 +210,11 @@ export { DateField, Row, ManuscriptHeader, + ModalRoot, + ActionButton, + CloseIcon, + ModalTitle, + CustomRadioGroup, + SpinnerContainer, + ButtonsContainer, } diff --git a/packages/component-manuscript/src/components/MakeDecision.js b/packages/component-manuscript/src/components/MakeDecision.js new file mode 100644 index 0000000000000000000000000000000000000000..711afbc939d19fc7ea0c67a8705d01e53fb270f4 --- /dev/null +++ b/packages/component-manuscript/src/components/MakeDecision.js @@ -0,0 +1,148 @@ +import React from 'react' +import { get } from 'lodash' +import { connect } from 'react-redux' +import { AbstractEditor } from 'xpub-edit' +import { required } from 'xpub-validators' +import { withTheme } from 'styled-components' +import { compose, withHandlers } from 'recompose' +import { withModal } from 'pubsweet-component-modal/src/components' +import { + Icon, + RadioGroup, + ValidatedField, + Spinner, + Button, + ErrorText, +} from '@pubsweet/ui' +import { + reduxForm, + change as changeForm, + getFormValues, + isSubmitting, +} from 'redux-form' + +import { + ModalRoot, + ActionButton, + CloseIcon, + ModalTitle, + CustomRadioGroup, + ButtonsContainer, + SpinnerContainer, + Row, +} from '../atoms' + +const options = [ + { + value: 'publish', + label: 'Confirm recommendation to Publish', + }, + { + value: 'reject', + label: 'Confirm recommendation to Reject', + }, + { + value: 'return', + label: 'Return manuscript with comments', + }, +] + +const MakeDecision = ({ showDecisionModal }) => ( + <ActionButton onClick={showDecisionModal}> Make Decision </ActionButton> +) + +// To be removed +const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) + +const DecisionModal = compose( + connect( + state => ({ + decision: get(getFormValues('makeDecision')(state), 'decision'), + isSubmitting: isSubmitting('makeDecision')(state), + }), + { changeForm, getFormValues }, + ), + withTheme, + reduxForm({ + form: 'makeDecision', + onSubmit: (values, dispatch, { isSubmitting }) => + sleep(1000).then(() => { + // TODO: link to backend + window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`) + }), + }), + withHandlers({ + selectOption: ({ changeForm, formValues }) => value => { + changeForm('makeDecision', 'decision', value) + }, + }), +)( + ({ + theme, + error, + decision, + hideModal, + handleSubmit, + selectOption, + isSubmitting, + }) => ( + <ModalRoot> + <CloseIcon data-test="icon-modal-hide" onClick={hideModal}> + <Icon color={theme.colorPrimary}>x</Icon> + </CloseIcon> + + <ModalTitle>Manuscript Decision</ModalTitle> + <CustomRadioGroup> + <RadioGroup + isRequired + name="decision" + onChange={selectOption} + options={options} + /> + </CustomRadioGroup> + {decision === 'return' && ( + <Row> + <ValidatedField + component={AbstractEditor} + name="comment" + placeholder="Return with comment (required)" + title="Return with comment (required)" + validate={[required]} + /> + </Row> + )} + {error && <ErrorText>{error}</ErrorText>} + <ButtonsContainer> + <Button data-test="button-modal-hide" onClick={hideModal}> + Cancel + </Button> + {isSubmitting ? ( + <SpinnerContainer> + <Spinner size={4} /> + </SpinnerContainer> + ) : ( + <Button + data-test="button-modal-confirm" + onClick={handleSubmit} + primary + > + SEND REQUEST + </Button> + )} + </ButtonsContainer> + </ModalRoot> + ), +) + +export default compose( + connect(null, null), + withModal({ + modalKey: 'EiCMakeDecision', + modalComponent: DecisionModal, + }), + withHandlers({ + showDecisionModal: ({ showModal }) => () => { + showModal() + }, + }), +)(MakeDecision) diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index 9d7efeb259d33a125ac96b646d40307739924944..9ccbb1c0cd8c1560664d530eceb4ac3a8f970aa6 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -47,7 +47,11 @@ const ManuscriptLayout = ({ /> </Container> <SideBar flex={1}> - <SideBarActions project={project} version={version} /> + <SideBarActions + currentUser={currentUser} + project={project} + version={version} + /> <SideBarRoles currentUser={currentUser} editorInChief={editorInChief} diff --git a/packages/component-manuscript/src/components/SideBarActions.js b/packages/component-manuscript/src/components/SideBarActions.js index 82ba580f1fc89c75e7577db903c7077174df6fab..f461813582ecd6f88dfd32ff6335b7190c70d21e 100644 --- a/packages/component-manuscript/src/components/SideBarActions.js +++ b/packages/component-manuscript/src/components/SideBarActions.js @@ -1,46 +1,36 @@ import React from 'react' -import { th, Button, Icon } from '@pubsweet/ui' -import styled, { css } from 'styled-components' +import { th, Icon } from '@pubsweet/ui' +import styled from 'styled-components' +import { get } from 'lodash' import ZipFiles from 'pubsweet-components-faraday/src/components/Dashboard/ZipFiles' +import { MakeDecision } from './' -const SideBarActions = ({ project, version }) => ( - <Root> - <ActionButton> Make Decision </ActionButton> - <ZipFiles archiveName={`ID-${project.customId}`} fragmentId={version.id}> - <ClickableIcon> - <Icon>download</Icon> - </ClickableIcon> - </ZipFiles> - </Root> -) +const SideBarActions = ({ project, version, currentUser }) => { + const isAdmin = get(currentUser, 'admin') + const isEic = get(currentUser, 'editorInChief') + return ( + <Root> + {isEic || isAdmin ? <MakeDecision /> : <div />} + <ZipFiles archiveName={`ID-${project.customId}`} fragmentId={version.id}> + <ClickableIcon> + <Icon>download</Icon> + </ClickableIcon> + </ZipFiles> + </Root> + ) +} export default SideBarActions // #region styled-components -const defaultText = css` - color: ${th('colorText')}; - font-family: ${th('fontReading')}; - font-size: ${th('fontSizeBaseSmall')}; -` - const Root = styled.div` border-bottom: ${th('borderDefault')}; display: flex; justify-content: space-between; padding: ${th('subGridUnit')}; ` -const ActionButton = styled(Button)` - ${defaultText}; - align-items: center; - background-color: ${th('colorPrimary')}; - color: ${th('colorTextReverse')}; - display: flex; - padding: 4px 8px; - text-align: center; - height: calc(${th('subGridUnit')}*5); - text-transform: uppercase; -` + const ClickableIcon = styled.div` margin: 0 ${th('subGridUnit')}; &:hover { diff --git a/packages/component-manuscript/src/components/index.js b/packages/component-manuscript/src/components/index.js index ca25d8bba550a2c83a8a8c318b89c08c289e54f0..60e3d67328264819edd882da6cc370054a8c7549 100644 --- a/packages/component-manuscript/src/components/index.js +++ b/packages/component-manuscript/src/components/index.js @@ -5,3 +5,4 @@ export { default as ManuscriptPage } from './ManuscriptPage' export { default as ManuscriptDetails } from './ManuscriptDetails' export { default as SideBarActions } from './SideBarActions' export { default as SideBarRoles } from './SideBarRoles' +export { default as MakeDecision } from './MakeDecision'