Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ManuscriptPage.js 12.34 KiB
import { connect } from 'react-redux'
import { actions } from 'pubsweet-client'
import { ConnectPage } from 'xpub-connect'
import { withJournal } from 'xpub-journal'
import { withRouter } from 'react-router-dom'
import { head, get, isUndefined } from 'lodash'
import { getFormValues, change as changeForm } from 'redux-form'
import {
  selectFragment,
  selectCollection,
  selectCurrentUser,
} from 'xpub-selectors'
import { get as apiGet } from 'pubsweet-client/src/helpers/api'
import {
  compose,
  withState,
  lifecycle,
  withProps,
  withHandlers,
  setDisplayName,
  fromRenderProps,
} from 'recompose'
import { getSignedUrl } from 'pubsweet-components-faraday/src/redux/files'
import { reviewerDecision } from 'pubsweet-components-faraday/src/redux/reviewers'
import {
  hasManuscriptFailure,
  clearCustomError,
} from 'pubsweet-components-faraday/src/redux/errors'
import {
  selectReviewRecommendations,
  selectEditorialRecommendations,
} from 'pubsweet-components-faraday/src/redux/recommendations'
import {
  canReview,
  getUserToken,
  currentUserIs,
  canViewReports,
  canMakeRevision,
  canMakeDecision,
  isHEToManuscript,
  canSubmitRevision,
  canEditManuscript,
  getVersionOptions,
  canInviteReviewers,
  pendingHEInvitation,
  currentUserIsReviewer,
  parseCollectionDetails,
  canMakeHERecommendation,
  canViewReviewersDetails,
  canViewEditorialComments,
  pendingReviewerInvitation,
  canViewResponseFromAuthor,
  getFragmentAuthorResponse,
  canOverrideTechnicalChecks,
  authorCanViewReportsDetails,
  getOwnPendingRecommendation,
  getOwnSubmittedRecommendation,
  canAuthorViewEditorialComments,
  getFragmentReviewerRecommendations,
  getInvitationsWithReviewersForFragment,
} from 'pubsweet-component-faraday-selectors'
import {
  handleError,
  withFetching,
  RemoteOpener,
} from 'pubsweet-component-faraday-ui'

import ManuscriptLayout from './ManuscriptLayout'
import withInviteReviewer from '../inviteReviewer/withInviteReviewer'
import withSubmitRevision from '../submitRevision/withSubmitRevision'
import withHandleRecommendation from '../handleRecommendation/withHandleRecommendation'
import withInviteHandlingEditor from '../inviteHandlingEditor/withInviteHandlingEditor'

import {
  redirectToError,
  parseSearchParams,
  getPublonsReviewers,
  isLatestVersion,
} from './utils'

import {
  canAssignHE,
  selectFetching,
  getHandlingEditors,
  selectHandlingEditors,
} from '../redux/editors'

export default compose(
  setDisplayName('ManuscriptPage'),
  withJournal,
  withRouter,
  withFetching,
  withState('publonReviewers', 'setPublonsReviewers', []),
  withState('editorInChief', 'setEiC', 'N/A'),
  ConnectPage(({ match }) => [
    actions.getCollection({ id: match.params.project }),
    actions.getFragments({ id: match.params.project }),
    actions.getUsers(),
    actions.getTeams(),
  ]),
  connect(
    (state, { match }) => ({
      currentUser: selectCurrentUser(state),
      handlingEditors: selectHandlingEditors(state),
      hasManuscriptFailure: hasManuscriptFailure(state),
      fragment: selectFragment(state, match.params.version),
      collection: parseCollectionDetails(
        state,
        selectCollection(state, match.params.project),
      ),
      reviewerReports: selectReviewRecommendations(state, match.params.version),
      pendingHEInvitation: pendingHEInvitation(state, match.params.project),
      pendingOwnRecommendation: getOwnPendingRecommendation(
        state,
        match.params.version,
      ),
      submittedOwnRecommendation: getOwnSubmittedRecommendation(
        state,
        match.params.version,
      ),
      pendingReviewerInvitation: pendingReviewerInvitation(
        state,
        match.params.version,
      ),
      editorialRecommendations: selectEditorialRecommendations(
        state,
        match.params.version,
      ),
      reviewerRecommendations: getFragmentReviewerRecommendations(
        state,
        match.params.version,
      ),
      authorResponseToRevisonRequest: getFragmentAuthorResponse(
        state,
        match.params.version,
      ),
    }),
    {
      changeForm,
      clearCustomError,
      getUsers: actions.getUsers,
      getFragment: actions.getFragment,
      getCollection: actions.getCollection,
      updateVersion: actions.updateFragment,
    },
  ),
  connect(
    (
      state,
      {
        match,
        journal,
        fragment,
        collection,
        isFetching,
        currentUser,
        pendingHEInvitation,
        pendingOwnRecommendation,
        pendingReviewerInvitation,
      },
    ) => ({
      currentUser: {
        ...currentUser,
        token: getUserToken(state),
        isHE: currentUserIs(state, 'isHE'),
        isEIC: currentUserIs(state, 'adminEiC'),
        isInvitedHE: !isUndefined(pendingHEInvitation),
        isInvitedToReview: !isUndefined(pendingReviewerInvitation),
        isReviewer: currentUserIsReviewer(state, get(fragment, 'id', '')),
        isHEToManuscript: isHEToManuscript(state, get(collection, 'id', '')),
        permissions: {
          canReview: canReview(state, collection, fragment),
          canSubmitRevision: canSubmitRevision(state, fragment),
          canMakeHERecommendation: canMakeHERecommendation(state, {
            collection,
            statuses: get(journal, 'statuses', {}),
          }),
          canAssignHE: canAssignHE(state, collection),
          canViewReports: canViewReports(state, match.params.project),
          canViewEditorialComments: canViewEditorialComments(
            state,
            collection,
            fragment,
          ),
          canInviteReviewers: canInviteReviewers(state, collection),
          canMakeRecommendation: !isUndefined(pendingOwnRecommendation),
          canMakeRevision: canMakeRevision(state, collection, fragment),
          canMakeDecision: canMakeDecision(state, collection),
          canEditManuscript: canEditManuscript(state, collection, fragment),
          canViewReviewersDetails: canViewReviewersDetails(state, collection),
          authorCanViewReportsDetails: authorCanViewReportsDetails(
            state,
            collection,
            get(fragment, 'id', ''),
          ),
          canOverrideTechChecks: canOverrideTechnicalChecks(state, collection),
          canAuthorViewEditorialComments: canAuthorViewEditorialComments(
            state,
            collection,
            match.params.version,
          ),
          canViewResponseFromAuthor: canViewResponseFromAuthor(
            state,
            collection,
            match.params.version,
          ),
        },
      },
      isFetchingData: {
        editorsFetching: selectFetching(state),
        publonsFetching: isFetching,
      },
      formValues: {
        revision: getFormValues('revision')(state),
        eicDecision: getFormValues('eic-decision')(state),
        reviewerReport: getFormValues('reviewerReport')(state),
        responseToInvitation: getFormValues('answer-invitation')(state),
        editorialRecommendation: getFormValues('HERecommendation')(state),
      },
      invitationsWithReviewers: getInvitationsWithReviewersForFragment(
        state,
        get(fragment, 'id', ''),
      ),
      versions: getVersionOptions(state, collection),
    }),
  ),
  ConnectPage(({ currentUser }) => {
    if (currentUser.isEIC) {
      return [getHandlingEditors()]
    }
    return []
  }),
  withHandlers({
    fetchUpdatedCollection: ({
      fragment,
      getUsers,
      collection,
      getFragment,
      getCollection,
    }) => () => {
      getCollection({ id: collection.id })
      getFragment(collection, fragment)
      getUsers()
    },
    getPublonsReviewers: ({ clearError, setFetching, setPublonsReviewers }) => (
      fragmentId,
      errorFn,
    ) => {
      clearError()
      setFetching(true)
      getPublonsReviewers(fragmentId)
        .then(res => {
          setFetching(false)
          setPublonsReviewers(res)
        })
        .catch(e => {
          setFetching(false)
          handleError(errorFn)(e)
        })
    },
  }),
  withHandlers({
    updateManuscript: ({ updateVersion, collection, fragment }) => data =>
      updateVersion(collection, {
        id: fragment.id,
        ...data,
      }),
    setEditorInChief: ({ setEiC }) => eic => {
      if (eic) {
        const { firstName = '', lastName = '' } = eic
        setEiC(`${firstName} ${lastName}`)
      }
    },
  }),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleAssignHE: toggle,
    heExpanded: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleHEResponse: toggle,
    heResponseExpanded: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleReviewerResponse: toggle,
    reviewerResponseExpanded: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleReviewerRecommendations: toggle,
    reviewerRecommendationExpanded: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleResponseToRevisionRequest: toggle,
    responseToRevisionRequest: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleReviewerDetails: toggle,
    reviewerDetailsExpanded: expanded,
  })),
  fromRenderProps(RemoteOpener, ({ toggle, expanded }) => ({
    toggleEditorialComments: toggle,
    editorialCommentsExpanded: expanded,
  })),
  withProps(({ collection, fragment }) => ({
    getSignedUrl,
    isLatestVersion: isLatestVersion(collection, fragment),
  })),
  withInviteHandlingEditor,
  withInviteReviewer,
  withSubmitRevision,
  withHandleRecommendation,
  lifecycle({
    componentDidMount() {
      const {
        match,
        history,
        setError,
        location,
        reviewerReports,
        setEditorInChief,
        clearCustomError,
        getPublonsReviewers,
        hasManuscriptFailure,
        fetchUpdatedCollection,
        editorialRecommendations,
        fragment,
        currentUser: {
          isEIC,
          isInvitedHE,
          isInvitedToReview,
          isHEToManuscript,
          permissions: { canInviteReviewers, canSubmitRevision, canReview },
        },
      } = this.props

      if (hasManuscriptFailure) {
        history.push('/not-found')
        clearCustomError()
      }

      const collectionId = match.params.project
      const fragmentId = match.params.version
      const { agree, invitationId } = parseSearchParams(location.search)
      if (agree === 'true') {
        history.replace(location.pathname)
        reviewerDecision({ invitationId, collectionId, fragmentId })
          .then(fetchUpdatedCollection)
          .catch(redirectToError(history.replace))
      }

      apiGet(`/users?editorInChief=true`).then(res =>
        setEditorInChief(head(res.users)),
      )

      if (canInviteReviewers) {
        getPublonsReviewers(fragmentId, setError)
      }

      if (isInvitedHE) {
        this.props.toggleHEResponse()
      }

      if (isInvitedToReview) {
        this.props.toggleReviewerResponse()
      }

      if (canReview && !get(fragment, 'responseToReviewers.content', false)) {
        this.props.toggleReviewerRecommendations()
      }
      if (
        isHEToManuscript &&
        !!reviewerReports.length &&
        editorialRecommendations.length === 0 &&
        get(fragment, 'responseToReviewers.content', false)
      ) {
        this.props.toggleReviewerDetails()
      }

      if ((isEIC || isHEToManuscript) && !!editorialRecommendations.length) {
        this.props.toggleEditorialComments()
      }

      if (canSubmitRevision) {
        this.props.toggleEditorialComments()
      }

      if (
        get(fragment, 'responseToReviewers.content', false) &&
        !editorialRecommendations.length
      ) {
        this.props.toggleResponseToRevisionRequest()
      }
    },
    componentDidUpdate(prevProps) {
      const {
        editorialRecommendations: prevEditorialRecommendations,
        editorInChief: prevEditorInChief,
      } = prevProps

      const {
        currentUser: { isEIC, isHEToManuscript },
        collection: { status },
        editorialRecommendations: currentEditorialRecommendations,
        editorInChief: currentEditorInChief,
      } = this.props

      if (
        prevEditorInChief === currentEditorInChief &&
        prevEditorialRecommendations !== currentEditorialRecommendations &&
        (isEIC || isHEToManuscript) &&
        status === 'rejected'
      ) {
        prevProps.toggleEditorialComments()
      }
    },
  }),
)(ManuscriptLayout)