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 { inviteReviewer, revokeReviewer, reviewerDecision, } from 'pubsweet-components-faraday/src/redux/reviewers' import { hasManuscriptFailure, clearCustomError, } from 'pubsweet-components-faraday/src/redux/errors' import { createRecommendation, selectReviewRecommendations, selectEditorialRecommendations, } from 'pubsweet-components-faraday/src/redux/recommendations' import { getUserToken, currentUserIs, canViewReports, canMakeRevision, canMakeDecision, isHEToManuscript, canEditManuscript, canInviteReviewers, pendingHEInvitation, currentUserIsReviewer, parseCollectionDetails, canMakeHERecommendation, canViewReviewersDetails, pendingReviewerInvitation, canOverrideTechnicalChecks, getOwnPendingRecommendation, getOwnSubmittedRecommendation, getFragmentReviewerRecommendations, getInvitationsWithReviewersForFragment, } from 'pubsweet-component-faraday-selectors' import { RemoteOpener, handleError, withFetching, } from 'pubsweet-component-faraday-ui' import ManuscriptLayout from './ManuscriptLayout' import withSubmitRevision from '../submitRevision/withSubmitRevision' import { parseEicDecision, parseSearchParams, redirectToError, getPublonsReviewers, } from './utils' import { canAssignHE, selectFetching, getHandlingEditors, assignHandlingEditor, revokeHandlingEditor, selectHandlingEditors, handlingEditorDecision, } from '../redux/editors' export default compose( setDisplayName('ManuscriptPage'), withJournal, withRouter, withFetching, withState('publonReviewers', 'setPub', []), withState('editorInChief', 'setEiC', 'N/A'), ConnectPage(({ match }) => [ actions.getCollection({ id: match.params.project }), actions.getFragments({ id: match.params.project }), actions.getUsers(), ]), 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, ), }), { changeForm, clearCustomError, assignHandlingEditor, revokeHandlingEditor, getUsers: actions.getUsers, getFragment: actions.getFragment, getCollection: actions.getCollection, updateVersion: actions.updateFragment, }, ), connect( ( state, { match, journal, fragment, collection, currentUser, pendingHEInvitation, pendingOwnRecommendation, pendingReviewerInvitation, isFetching, }, ) => ({ 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: { canMakeHERecommendation: canMakeHERecommendation(state, { collection, statuses: get(journal, 'statuses', {}), }), canAssignHE: canAssignHE(state, match.params.project), canViewReports: canViewReports(state, match.params.project), canInviteReviewers: canInviteReviewers(state, collection), canMakeRecommendation: !isUndefined(pendingOwnRecommendation), canMakeRevision: canMakeRevision(state, collection, fragment), canMakeDecision: canMakeDecision(state, collection, fragment), canEditManuscript: canEditManuscript(state, collection, fragment), canViewReviewersDetails: canViewReviewersDetails(state, collection), canOverrideTechChecks: canOverrideTechnicalChecks(state, collection), }, }, isFetching: { 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', ''), ), }), ), ConnectPage(({ currentUser }) => { if (currentUser.isEIC) { return [getHandlingEditors()] } return [] }), withHandlers({ fetchUpdatedCollection: ({ fragment, getUsers, collection, getFragment, getCollection, }) => () => { getCollection({ id: collection.id }) getFragment(collection, fragment) getUsers() }, }), withHandlers({ updateManuscript: ({ updateVersion, collection, fragment }) => data => updateVersion(collection, { id: fragment.id, ...data, }), setEditorInChief: ({ setEiC }) => eic => { if (eic) { const { firstName = '', lastName = '' } = eic setEiC(`${firstName} ${lastName}`) } }, setPublons: ({ setPub }) => (publonReviewers = []) => setPub(publonReviewers), assignHE: ({ assignHandlingEditor, fetchUpdatedCollection, collection: { id: collectionId }, }) => (email, modalProps) => assignHandlingEditor({ email, collectionId, }) .then(() => { fetchUpdatedCollection() modalProps.hideModal() }) .catch(handleError(modalProps.setModalError)), revokeHE: ({ getCollection, revokeHandlingEditor, collection: { id: collectionId }, }) => (invitationId, modalProps) => revokeHandlingEditor({ invitationId, collectionId, }) .then(() => { getCollection({ id: collectionId }) modalProps.hideModal() }) .catch(handleError(modalProps.setModalError)), createRecommendation: ({ fragment, collection, fetchUpdatedCollection, }) => (values, modalProps) => { const recommendation = parseEicDecision(values) modalProps.setFetching(true) createRecommendation({ recommendation, fragmentId: fragment.id, collectionId: collection.id, }) .then(() => { modalProps.setFetching(false) modalProps.hideModal() fetchUpdatedCollection() }) .catch(err => { modalProps.setFetching(false) handleError(modalProps.setModalError)(err) }) }, onHEResponse: ({ history, collection, pendingHEInvitation, fetchUpdatedCollection, }) => (values, { hideModal, setModalError, setFetching }) => { const isAccepted = get(values, 'decision', 'decline') === 'accept' setFetching(true) return handlingEditorDecision({ isAccepted, collectionId: collection.id, reason: get(values, 'reason', ''), invitationId: pendingHEInvitation.id, }) .then(() => { setFetching(false) hideModal() if (isAccepted) { fetchUpdatedCollection() } else { history.replace('/') } }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, onReviewerResponse: ({ history, fragment, collection, fetchUpdatedCollection, pendingReviewerInvitation, }) => (values, { hideModal, setModalError, setFetching }) => { const isAccepted = get(values, 'decision', 'decline') === 'accept' setFetching(true) reviewerDecision({ agree: isAccepted, fragmentId: fragment.id, collectionId: collection.id, invitationId: pendingReviewerInvitation.id, }) .then(() => { setFetching(false) hideModal() if (isAccepted) { fetchUpdatedCollection() } else { history.replace('/') } }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, onInviteReviewer: ({ collection, fragment, fetchUpdatedCollection }) => ( values, { hideModal, setModalError, setFetching }, ) => { setFetching(true) inviteReviewer({ reviewerData: values, fragmentId: fragment.id, collectionId: collection.id, }) .then(() => { setFetching(false) hideModal() fetchUpdatedCollection() }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, onResendReviewerInvite: ({ fragment, collection, fetchUpdatedCollection, }) => (email, { hideModal, setFetching, setModalError }) => { setFetching(true) inviteReviewer({ reviewerData: { email, role: 'reviewer', }, fragmentId: fragment.id, collectionId: collection.id, }) .then(() => { setFetching(false) hideModal() fetchUpdatedCollection() }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, onRevokeReviewerInvite: ({ fragment, collection, fetchUpdatedCollection, }) => (invitationId, { hideModal, setFetching, setModalError }) => { setFetching(true) revokeReviewer({ invitationId, fragmentId: fragment.id, collectionId: collection.id, }) .then(() => { setFetching(false) hideModal() fetchUpdatedCollection() }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, onEditorialRecommendation: ({ fragment, collection, fetchUpdatedCollection, }) => (recommendation, { hideModal, setFetching, setModalError }) => { setFetching(true) createRecommendation({ recommendation, fragmentId: fragment.id, collectionId: collection.id, }) .then(r => { setFetching(false) hideModal() fetchUpdatedCollection() }) .catch(e => { setFetching(false) handleError(setModalError)(e) }) }, }), withHandlers({ onInvitePublonReviewer: ({ collection, fragment, fetchUpdatedCollection, setPublons, setFetching: setListFetching, setError, clearError, }) => (values, { hideModal, setModalError, setFetching }) => { setFetching(true) inviteReviewer({ reviewerData: values, fragmentId: fragment.id, collectionId: collection.id, isPublon: true, }) .then(() => { setFetching(false) hideModal() fetchUpdatedCollection() getPublonsReviewers({ fragmentId: fragment.id, setPublons, setFetching: setListFetching, setError, clearError, }) }) .catch(err => { setFetching(false) handleError(setModalError)(err) }) }, }), 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 }) => ({ toggleReviewerDetails: toggle, reviewerDetailsExpanded: expanded, })), withProps(({ currentUser, collection, submittedOwnRecommendation }) => ({ getSignedUrl, shouldReview: get(currentUser, 'isReviewer', false) && isUndefined(submittedOwnRecommendation), })), withSubmitRevision, lifecycle({ componentDidMount() { const { match, history, location, setPublons, shouldReview, reviewerReports, setEditorInChief, clearCustomError, hasManuscriptFailure, fetchUpdatedCollection, currentUser: { isInvitedHE, isInvitedToReview, isHEToManuscript, permissions: { canInviteReviewers }, }, setFetching, setError, clearError, } = 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, setPublons, setFetching, setError, clearError, }) } if (isInvitedHE) { this.props.toggleHEResponse() } if (isInvitedToReview) { this.props.toggleReviewerResponse() } if (shouldReview) { this.props.toggleReviewerRecommendations() } if (isHEToManuscript && !!reviewerReports.length) { this.props.toggleReviewerDetails() } }, }), )(ManuscriptLayout)