diff --git a/packages/component-invite/src/helpers/Invitation.js b/packages/component-invite/src/helpers/Invitation.js index d073162a17d19eab5ae88f1d47f39185f781bb55..c72fe3b0703802b7364b27cef7991d028089327e 100644 --- a/packages/component-invite/src/helpers/Invitation.js +++ b/packages/component-invite/src/helpers/Invitation.js @@ -45,7 +45,7 @@ const setupReviewerInvitation = async (req, models, collection, user) => { const authors = await Promise.all(authorsPromises) let { abstract, title } = fragment.metadata title = title.replace(/<(.|\n)*?>/g, '') - abstract = abstract.replace(/<(.|\n)*?>/g, '') + abstract = abstract ? abstract.replace(/<(.|\n)*?>/g, '') : '' await mailService.setupReviewerInvitationEmail( user, baseUrl, diff --git a/packages/components-faraday/src/components/Reviewers/InviteReviewers.js b/packages/components-faraday/src/components/Reviewers/InviteReviewers.js index 6e898b29f88d819cb49ce982b71f56af01afa8aa..6100c920a7ee86bc4ea13ff5c6ae3d2f7c9ae1c3 100644 --- a/packages/components-faraday/src/components/Reviewers/InviteReviewers.js +++ b/packages/components-faraday/src/components/Reviewers/InviteReviewers.js @@ -13,6 +13,7 @@ import { getCollectionReviewers, selectReviewers, selectFetchingReviewers, + selectFechingInvite, } from '../../redux/reviewers' const InviteReviewers = ({ showInviteModal }) => ( @@ -25,6 +26,7 @@ const InviteReviewersModal = compose( state => ({ reviewers: selectReviewers(state), fetchingReviewers: selectFetchingReviewers(state), + fetchingInvite: selectFechingInvite(state), }), { getCollectionReviewers }, ), @@ -53,6 +55,7 @@ const InviteReviewersModal = compose( reviewers, getReviewers, fetchingReviewers, + fetchingInvite, }) => ( <Root> <CloseIcon data-test="icon-modal-hide" onClick={hideModal}> @@ -62,7 +65,11 @@ const InviteReviewersModal = compose( <Title>Invite Reviewers</Title> <Subtitle>Invite reviewer</Subtitle> - <ReviewerForm collectionId={collectionId} getReviewers={getReviewers} /> + <ReviewerForm + collectionId={collectionId} + getReviewers={getReviewers} + isFetching={fetchingInvite} + /> <Row> <Subtitle>Reviewers Info</Subtitle> diff --git a/packages/components-faraday/src/components/Reviewers/ReviewerForm.js b/packages/components-faraday/src/components/Reviewers/ReviewerForm.js index fb92dbad249f6c063ceee38516cdac8e24ada128..923c669e1b543d86fa691aae0b0f6ba529d9f624 100644 --- a/packages/components-faraday/src/components/Reviewers/ReviewerForm.js +++ b/packages/components-faraday/src/components/Reviewers/ReviewerForm.js @@ -2,15 +2,21 @@ import React from 'react' import { get, pick } from 'lodash' import { connect } from 'react-redux' import styled from 'styled-components' -import { th, Button } from '@pubsweet/ui' import { compose, withHandlers } from 'recompose' +import { th, Button, Spinner } from '@pubsweet/ui' import { reduxForm, change as changeForm, initialize } from 'redux-form' import { ReviewersSelect } from './' import { inviteReviewer } from '../../redux/reviewers' import { ValidatedTextField } from '../AuthorList/FormItems' -const ReviewerForm = ({ clearForm, selectReviewer, handleSubmit, users }) => ( +const ReviewerForm = ({ + clearForm, + selectReviewer, + handleSubmit, + users, + isFetching, +}) => ( <Root> <Row> <ReviewersSelect onSelect={selectReviewer} values={users} /> @@ -22,9 +28,15 @@ const ReviewerForm = ({ clearForm, selectReviewer, handleSubmit, users }) => ( </Row> <ButtonsContainer> <FormButton onClick={clearForm}>Clear</FormButton> - <FormButton onClick={handleSubmit} primary> - Send - </FormButton> + {isFetching ? ( + <SpinnerContainer> + <Spinner size={4} /> + </SpinnerContainer> + ) : ( + <FormButton onClick={handleSubmit} primary> + Send + </FormButton> + )} </ButtonsContainer> </Root> ) @@ -80,6 +92,12 @@ const ButtonsContainer = styled.div` width: 100%; ` +const SpinnerContainer = styled.div` + height: calc(${th('subGridUnit')} * 5); + margin: ${th('subGridUnit')}; + min-width: calc(${th('gridUnit')} * 4); +` + const Row = styled.div` align-items: flex-start; display: flex; diff --git a/packages/components-faraday/src/redux/reviewers.js b/packages/components-faraday/src/redux/reviewers.js index d3a141fff0f038eab5264fa2e137fc027d2fddb9..26733696179b0d243fb45af5de48b32ff5628b1c 100644 --- a/packages/components-faraday/src/redux/reviewers.js +++ b/packages/components-faraday/src/redux/reviewers.js @@ -19,8 +19,28 @@ export const getReviewersSuccess = reviewers => ({ payload: { reviewers }, }) +const INVITE_REVIEWER_REQUEST = 'INVITE_REVIEWER_REQUEST' +const INVITE_REVIEWER_SUCCESS = 'INVITE_REVIEWER_SUCCESS' +const INVITE_REVIEWER_ERROR = 'INVITE_REVIEWER_ERROR' + +const inviteRequest = () => ({ + type: INVITE_REVIEWER_REQUEST, +}) + +const inviteSuccess = () => ({ + type: INVITE_REVIEWER_SUCCESS, +}) + +const inviteError = error => ({ + type: INVITE_REVIEWER_ERROR, + error, +}) + const initialState = { - fetching: false, + fetching: { + invite: false, + reviewers: false, + }, error: null, reviewers: [], } @@ -28,20 +48,25 @@ const initialState = { // selectors export const selectReviewers = state => get(state, 'reviewers.reviewers') || [] export const selectFetchingReviewers = state => - get(state, 'reviewers.fetching') || false + get(state, 'reviewers.fetching.reviewers') || false +export const selectFechingInvite = state => + get(state, 'reviewers.fetching.invite') || false export const getCollectionReviewers = collectionId => dispatch => { dispatch(getReviewersRequest()) return apiGet(`/collections/${collectionId}/invitations?role=reviewer`).then( r => dispatch(getReviewersSuccess(r)), + err => dispatch(getReviewersError(err)), ) } -export const inviteReviewer = (reviewerData, collectionId) => dispatch => - create(`/collections/${collectionId}/invitations`, { +export const inviteReviewer = (reviewerData, collectionId) => dispatch => { + dispatch(inviteRequest()) + return create(`/collections/${collectionId}/invitations`, { ...reviewerData, role: 'reviewer', - }) + }).then(() => dispatch(inviteSuccess()), err => dispatch(inviteError(err))) +} export const revokeReviewer = (invitationId, collectionId) => dispatch => remove(`/collections/${collectionId}/invitations/${invitationId}`) @@ -51,20 +76,54 @@ export default (state = initialState, action = {}) => { case GET_REVIEWERS_REQUEST: return { ...state, - fetching: true, + fetching: { + ...state.fetching, + reviewers: true, + }, } case GET_REVIEWERS_ERROR: return { ...state, - fetching: false, + fetching: { + ...state.fetching, + reviewers: false, + }, error: action.error, } case GET_REVIEWERS_SUCCESS: return { ...state, - fetching: false, + fetching: { + ...state.fetching, + reviewers: false, + }, reviewers: action.payload.reviewers, } + case INVITE_REVIEWER_REQUEST: + return { + ...state, + fetching: { + ...state.fetching, + invite: true, + }, + } + case INVITE_REVIEWER_SUCCESS: + return { + ...state, + fetching: { + ...state.fetching, + invite: false, + }, + } + case INVITE_REVIEWER_ERROR: + return { + ...state, + fetching: { + ...state.fetching, + invite: false, + }, + error: action.error, + } default: return state }