diff --git a/packages/component-manuscript/src/components/Files.js b/packages/component-manuscript/src/components/Files.js index e48eab69bd603a2d03a5daab1761ed6bbc83116f..100f0563445db1c8838b207f0c91cf81f1893fa0 100644 --- a/packages/component-manuscript/src/components/Files.js +++ b/packages/component-manuscript/src/components/Files.js @@ -1,46 +1,8 @@ import React, { Fragment } from 'react' -import { last } from 'lodash' import styled, { css } from 'styled-components' -import { th, Icon } from '@pubsweet/ui' +import { th } from '@pubsweet/ui' -const parseFileSize = (size = 0) => { - const kbSize = Math.floor(size / 1000) - const mbSize = Math.floor(kbSize / 1000) - const gbSize = Math.floor(mbSize / 1000) - - if (gbSize) { - return `${gbSize} GB` - } else if (mbSize) { - return `${mbSize} MB` - } else if (kbSize) { - return `${kbSize} kB` - } - return `${size} bytes` -} - -const hasPreview = (name = '') => { - const extension = last(name.split('.')) - return ['pdf', 'png', 'jpg'].includes(extension) -} - -const File = ({ name = 'filename', size, id, previewFile, downloadFile }) => ( - <FileRoot> - {hasPreview(name) && ( - <IconButton onClick={previewFile(id)}> - <Icon primary size={3}> - eye - </Icon> - </IconButton> - )} - <IconButton onClick={downloadFile(id, name)}> - <Icon primary size={3}> - download - </Icon> - </IconButton> - <FileName>{name}</FileName> - <FileSize>{parseFileSize(size)}</FileSize> - </FileRoot> -) +import { FileItem } from 'pubsweet-components-faraday/src/components/Files' const Files = ({ previewFile, @@ -55,7 +17,9 @@ const Files = ({ <div /> </Header> {manuscripts.map(file => ( - <File + <FileItem + compact + id={file.id} key={file.id} {...file} downloadFile={downloadFile} @@ -71,7 +35,9 @@ const Files = ({ <div /> </Header> {supplementary.map(file => ( - <File + <FileItem + compact + id={file.id} key={file.id} {...file} downloadFile={downloadFile} @@ -87,7 +53,9 @@ const Files = ({ <div /> </Header> {coverLetter.map(file => ( - <File + <FileItem + compact + id={file.id} key={file.id} {...file} downloadFile={downloadFile} @@ -108,34 +76,6 @@ const defaultText = css` font-size: ${th('fontSizeBaseSmall')}; ` -const FileName = styled.span` - ${defaultText}; - margin: 0 ${th('subGridUnit')}; -` -const FileSize = FileName.extend` - margin-left: ${th('subGridUnit')}; -` - -const IconButton = styled.div` - align-items: center; - cursor: pointer; - display: flex; - justify-content: center; - margin: 0 ${th('subGridUnit')}; - &:hover { - opacity: 0.7; - } -` - -const FileRoot = styled.div` - align-items: center; - border: ${th('borderDefault')}; - display: flex; - flex-direction: row; - margin-bottom: ${th('subGridUnit')}; - padding: ${th('subGridUnit')}; -` - const Header = styled.div` align-self: stretch; align-items: center; diff --git a/packages/component-manuscript/src/components/ReviewerReportForm.js b/packages/component-manuscript/src/components/ReviewerReportForm.js index 2e91779f1489a064817700d2089ddc2ed3eaee99..3f8fe374cb496a6363c9657bc576a1a51f2ec3ad 100644 --- a/packages/component-manuscript/src/components/ReviewerReportForm.js +++ b/packages/component-manuscript/src/components/ReviewerReportForm.js @@ -2,8 +2,8 @@ import React, { Fragment } from 'react' import { connect } from 'react-redux' import { required } from 'xpub-validators' import styled, { css } from 'styled-components' +import { th, Menu, ValidatedField, Icon, Button, ErrorText } from '@pubsweet/ui' import { compose, withHandlers, withProps } from 'recompose' -import { th, Menu, ValidatedField, Icon, Button } from '@pubsweet/ui' import { reduxForm, isSubmitting, @@ -11,20 +11,33 @@ import { getFormValues, } from 'redux-form' import AutosaveIndicator from 'pubsweet-component-wizard/src/components/AutosaveIndicator' + +import { + uploadFile, + deleteFile, + getSignedUrl, +} from 'pubsweet-components-faraday/src/redux/files' import { - autosaveRequest, - autosaveSuccess, -} from 'pubsweet-component-wizard/src/redux/autosave' + FileItem, + FilePicker, +} from 'pubsweet-components-faraday/src/components/Files' + import { ConfirmationModal, withModal2, } from 'pubsweet-component-modal/src/components' import { - selectFetching, selectError, + selectFetching, + createRecommendation, + updateRecommendation, } from 'pubsweet-components-faraday/src/redux/recommendations' -import { parseReviewResponseToForm, parseReviewRequest } from './utils' +import { + parseReviewResponseToForm, + onReviewSubmit, + onReviewChange, +} from './utils' const guidelinesLink = 'https://about.hindawi.com/authors/peer-review-at-hindawi/' @@ -47,31 +60,16 @@ const options = [ }, ] -const review = { - id: 'revuewiuuuid', - userId: 'uuuuuuid', - recommendation: 'publish', - recommendationType: 'review', - comments: [ - { - content: 'Here is public text', - public: true, - files: [], - }, - { - content: 'Here is PRIVATE text', - public: false, - files: [], - }, - ], -} - const ReviewerReportForm = ({ + error, isSubmitting, changeField, handleSubmit, formValues = {}, - initialValues, + review = {}, + addFile, + removeFile, + previewFile, }) => ( <Root> <Row> @@ -96,10 +94,14 @@ const ReviewerReportForm = ({ /> </Row> <Spacing /> - <Row> - <Label> - Report <ActionText left={12}>Upload file</ActionText> - </Label> + <Row left> + <Label>Report</Label> + <FilePicker + allowedFileExtensions={['pdf', 'doc', 'docx']} + onUpload={addFile} + > + <ActionText left={12}>Upload file</ActionText> + </FilePicker> </Row> <Row> <FullWidth> @@ -117,6 +119,21 @@ const ReviewerReportForm = ({ /> </FullWidth> </Row> + {formValues.files && ( + <Row> + {formValues.files.map(file => ( + <FileItem + compact + id={file.id} + key={file.id} + {...file} + downloadFile={previewFile} + previewFile={previewFile} + removeFile={removeFile} + /> + ))} + </Row> + )} {formValues.hasConfidential ? ( <Fragment> <Row> @@ -156,16 +173,21 @@ const ReviewerReportForm = ({ )} <Spacing /> + {error && ( + <Row> + <ErrorText>{error}</ErrorText> + </Row> + )} <Row> <ActionButton onClick={handleSubmit}> Submit report </ActionButton> - <AutosaveIndicator formName="reviewerReport" /> + <AutosaveIndicator + formName="reviewerReport" + lastUpdated={review.updatedOn} + /> </Row> </Root> ) -// To be removed -const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) - const ModalWrapper = compose( connect(state => ({ fetching: selectFetching(state), @@ -181,10 +203,18 @@ export default compose( formValues: getFormValues('reviewerReport')(state), isSubmitting: isSubmitting('reviewerReport')(state), }), - { changeForm, getFormValues }, + { + changeForm, + getFormValues, + createRecommendation, + updateRecommendation, + uploadFile, + deleteFile, + getSignedUrl, + }, ), - withProps(() => ({ - initialValues: parseReviewResponseToForm({}), + withProps(({ review = {} }) => ({ + initialValues: parseReviewResponseToForm(review), })), withModal2(props => ({ modalComponent: ModalWrapper, @@ -193,34 +223,51 @@ export default compose( changeField: ({ changeForm }) => (field, value) => { changeForm('reviewerReport', field, value) }, + addFile: ({ + formValues: { files = [] }, + uploadFile, + changeForm, + version, + }) => file => { + uploadFile(file, 'review', version.id) + .then(file => { + const newFiles = [...files, file] + + setTimeout(() => { + changeForm('reviewerReport', 'files', newFiles) + }, 1000) + }) + .catch(e => console.error(`Couldn't upload file.`, e)) + }, + removeFile: ({ + formValues: { files = [] }, + changeForm, + deleteFile, + }) => id => e => { + deleteFile(id) + .then(r => { + const newFiles = files.filter(f => f.id !== id) + changeForm('reviewerReport', 'files', newFiles) + }) + .catch(e => console.error(`Couldn't delete the file.`, e)) + + const newFiles = files.filter(f => f.id !== id) + changeForm('reviewerReport', 'files', newFiles) + }, + previewFile: ({ getSignedUrl }) => fileId => e => { + e.preventDefault() + const windowReference = window.open() + getSignedUrl(fileId).then(({ signedUrl }) => { + windowReference.location = signedUrl + }) + }, }), reduxForm({ form: 'reviewerReport', enableReinitialize: true, - forceUnregisterOnUnmount: true, - onChange: (values, dispatch) => { - dispatch(autosaveRequest()) - sleep(1000).then(() => dispatch(autosaveSuccess(new Date()))) - }, - onSubmit: (values, dispatch, { isSubmitting, showModal, hideModal }) => { - showModal({ - title: 'Ready to Submit your Report?', - subtitle: 'Once submitted, the report can`t be modified', - confirmText: 'Submit report', - onConfirm: () => { - sleep(1000) - .then(hideModal) - .then(() => { - // TODO: link to backend - const review = parseReviewRequest(values) - window.alert( - `You submitted:\n\n${JSON.stringify(review, null, 2)}`, - ) - }) - }, - onCancel: hideModal, - }) - }, + keepDirtyOnReinitialize: true, + onChange: onReviewChange, + onSubmit: onReviewSubmit, }), )(ReviewerReportForm) @@ -292,7 +339,8 @@ const Row = styled.div` flex: 1; box-sizing: border-box; flex-wrap: wrap; - justify-content: space-between; + justify-content: ${({ left }) => (left ? 'left' : 'space-between')}; + ${defaultText}; ` const ActionButton = styled(Button)` diff --git a/packages/component-manuscript/src/components/ReviewsAndReports.js b/packages/component-manuscript/src/components/ReviewsAndReports.js index 15fa55f80fddbe236e0cb42015ff8e73145f2d3c..976793bc6823cbcbe9b4a1887d7ee75800c4d8b2 100644 --- a/packages/component-manuscript/src/components/ReviewsAndReports.js +++ b/packages/component-manuscript/src/components/ReviewsAndReports.js @@ -1,8 +1,9 @@ import React, { Fragment } from 'react' +import { head } from 'lodash' import { th } from '@pubsweet/ui' import { connect } from 'react-redux' import styled from 'styled-components' -import { compose, withHandlers, lifecycle } from 'recompose' +import { compose, withHandlers, lifecycle, withProps } from 'recompose' import { ReviewerBreakdown } from 'pubsweet-components-faraday/src/components/Invitations' import ReviewersDetailsList from 'pubsweet-components-faraday/src/components/Reviewers/ReviewersDetailsList' import ReviewerReportForm from 'pubsweet-component-manuscript/src/components/ReviewerReportForm' @@ -12,6 +13,7 @@ import { getCollectionReviewers, currentUserIsReviewer, } from 'pubsweet-components-faraday/src/redux/reviewers' +import { selectRecommendations } from 'pubsweet-components-faraday/src/redux/recommendations' import Tabs from '../molecules/Tabs' import Expandable from '../molecules/Expandable' @@ -33,9 +35,13 @@ const getTabSections = (collectionId, reviewers) => [ const ReviewsAndReports = ({ project, + version, reviewers = [], + recommendations = [], isReviewer, currentUserIs, + report, + review = {}, }) => ( <Fragment> {currentUserIs('staff') && ( @@ -57,7 +63,18 @@ const ReviewsAndReports = ({ {isReviewer && ( <Root id="review-report"> <Expandable label="Your Report" startExpanded> - <ReviewerReportForm /> + {report ? ( + <div> + You have a submitted report with recommendation{' '} + {report.recommendation} + </div> + ) : ( + <ReviewerReportForm + project={project} + review={review} + version={version} + /> + )} </Expandable> </Root> )} @@ -68,16 +85,21 @@ export default compose( connect( (state, { project }) => ({ reviewers: selectReviewers(state), + recommendations: selectRecommendations(state), fetchingReviewers: selectFetchingReviewers(state), isReviewer: currentUserIsReviewer(state, project.id), }), { getCollectionReviewers }, ), withHandlers({ - getReviewers: ({ project, setReviewers, getCollectionReviewers }) => () => { + getReviewers: ({ project, getCollectionReviewers }) => () => { getCollectionReviewers(project.id) }, }), + withProps(({ recommendations = [] }) => ({ + report: head(recommendations.filter(r => r.submittedOn)), + review: head(recommendations), + })), lifecycle({ componentDidMount() { const { getReviewers } = this.props diff --git a/packages/component-manuscript/src/components/utils.js b/packages/component-manuscript/src/components/utils.js index 17ad97ee832b37c0a6daa0b3a85c45b7be7e4d2b..d6411a9a3b7e81a4d4cf42123d6729c7426e533f 100644 --- a/packages/component-manuscript/src/components/utils.js +++ b/packages/component-manuscript/src/components/utils.js @@ -1,5 +1,10 @@ import moment from 'moment' -import { get, find, capitalize, omit } from 'lodash' +import { get, find, capitalize, omit, isEmpty, isEqual, debounce } from 'lodash' + +import { + autosaveRequest, + autosaveSuccess, +} from 'pubsweet-component-wizard/src/redux/autosave' export const parseTitle = version => { const title = get(version, 'metadata.title') @@ -82,6 +87,7 @@ export const redirectToError = redirectFn => err => { } export const parseReviewResponseToForm = (review = {}) => { + if (isEmpty(review)) return null const comments = review.comments || [] const publicComment = comments.find(c => c.public) const privateComment = comments.find(c => !c.public) @@ -95,10 +101,11 @@ export const parseReviewResponseToForm = (review = {}) => { } export const parseReviewRequest = (review = {}) => { + if (isEmpty(review)) return null const comments = [ { public: true, - content: review.public, + content: review.public || '', files: review.files || [], }, ] @@ -111,7 +118,67 @@ export const parseReviewRequest = (review = {}) => { }) } return { - ...omit(review, ['public', 'confidential', 'hasConfidential', 'files']), + ...omit(review, [ + 'public', + 'confidential', + 'hasConfidential', + 'files', + 'userId', + ]), + recommendationType: 'review', comments, } } + +const onChange = ( + values, + dispatch, + { project, version, createRecommendation, updateRecommendation }, + previousValues, +) => { + const newValues = parseReviewRequest(values) + const prevValues = parseReviewRequest(previousValues) + + if (!isEqual(newValues, prevValues) && !isEmpty(prevValues)) { + dispatch(autosaveRequest()) + if (newValues.id) { + updateRecommendation(project.id, version.id, newValues).then(r => + dispatch(autosaveSuccess(r.updatedOn)), + ) + } else { + createRecommendation(project.id, version.id, newValues).then(r => + dispatch(autosaveSuccess(r.updatedOn)), + ) + } + } +} + +export const onReviewChange = debounce(onChange, 1000, { maxWait: 5000 }) + +export const onReviewSubmit = ( + values, + dispatch, + { + isSubmitting, + showModal, + hideModal, + project, + version, + updateRecommendation, + }, +) => { + showModal({ + title: 'Ready to Submit your Report?', + subtitle: 'Once submitted, the report can`t be modified', + confirmText: 'Submit report', + onConfirm: () => { + const newValues = parseReviewRequest(values) + newValues.submittedOn = Date.now() + dispatch(autosaveRequest()) + updateRecommendation(project.id, version.id, newValues) + .then(r => dispatch(autosaveSuccess(r.updatedOn))) + .then(hideModal) + }, + onCancel: hideModal, + }) +} diff --git a/packages/component-wizard/src/components/AutosaveIndicator.js b/packages/component-wizard/src/components/AutosaveIndicator.js index 0ddbc2c5b898408ec81d467441bb43e6a461b5e6..67f658c843283ca7aaa0b29bf441aa3d2d8a24a2 100644 --- a/packages/component-wizard/src/components/AutosaveIndicator.js +++ b/packages/component-wizard/src/components/AutosaveIndicator.js @@ -24,6 +24,7 @@ const Indicator = ({ successText, autosave: { isFetching, error, lastUpdate }, theme, + lastUpdated, }) => isVisibile ? ( <Root> @@ -57,7 +58,9 @@ const Indicator = ({ </AutoSaveContainer> )} </Root> - ) : null + ) : ( + <div>{lastUpdated ? durationParser(lastUpdated) : ''}</div> + ) export default compose( connect((state, { formName }) => ({ diff --git a/packages/component-wizard/src/components/AutosaveIndicator.md b/packages/component-wizard/src/components/AutosaveIndicator.md index 86af657d2746b4e2a355c9c061675f13083eadd7..d83ba4fbda481d696db7e5c3ca2a0f6efe632f77 100644 --- a/packages/component-wizard/src/components/AutosaveIndicator.md +++ b/packages/component-wizard/src/components/AutosaveIndicator.md @@ -10,6 +10,7 @@ Display the status of the form (Saving in progress, Saved or Error while saving) | progressText | Text to show while API returns | false | 'Saving changes... ' | string | | errorText | Text to show on error | false | 'Changes not saved ' | string | | successText | Text to show on success | false | 'Progress saved ${duration.humanize()} ago.' | string | +| lastUpdated | Show last updated timestamp from the beginning | false | 'Progress saved ${duration.humanize()} ago.' | date | ## Examples ```js diff --git a/packages/components-faraday/src/components/Files/FileItem.js b/packages/components-faraday/src/components/Files/FileItem.js index 618292b0234f5e626ec62ff2b520da834572d764..960781cb067ec9d5228709508a714466067b1cc0 100644 --- a/packages/components-faraday/src/components/Files/FileItem.js +++ b/packages/components-faraday/src/components/Files/FileItem.js @@ -1,6 +1,7 @@ -import React from 'react' -import { Icon } from '@pubsweet/ui' -import styled, { withTheme } from 'styled-components' +import React, { Fragment } from 'react' +import { last } from 'lodash' +import { Icon, th } from '@pubsweet/ui' +import styled, { withTheme, css } from 'styled-components' const parseFileSize = size => { const kbSize = size / 1000 @@ -17,6 +18,11 @@ const parseFileSize = size => { return `${size} bytes` } +const hasPreview = (name = '') => { + const extension = last(name.split('.')) + return ['pdf', 'png', 'jpg'].includes(extension) +} + const FileItem = ({ dragHandle, name, @@ -24,42 +30,82 @@ const FileItem = ({ id, removeFile, previewFile, + downloadFile, + compact = false, theme, ...rest }) => ( - <Root data-test={`file-${id}`}> - {dragHandle} - <Info> - <span>{name}</span> - <span>{parseFileSize(size)}</span> - </Info> - <Buttons> - <button onClick={previewFile(id)}> - <Icon color={theme.colorPrimary} size={3}> - eye - </Icon> - </button> - <button onClick={removeFile(id)} title="Delete"> - <Icon color={theme.colorPrimary} size={3}> - trash-2 - </Icon> - </button> - </Buttons> - </Root> + <Fragment> + {compact ? ( + <FileRoot data-test={`file-${id}`}> + {hasPreview(name) && ( + <IconButton onClick={previewFile(id)}> + <Icon primary size={3}> + eye + </Icon> + </IconButton> + )} + {downloadFile && ( + <IconButton onClick={downloadFile(id, name)}> + <Icon primary size={3}> + download + </Icon> + </IconButton> + )} + <FileName>{name}</FileName> + <FileSize>{parseFileSize(size)}</FileSize> + {removeFile && ( + <IconButton onClick={removeFile(id)}> + <Icon primary size={3}> + trash-2 + </Icon> + </IconButton> + )} + </FileRoot> + ) : ( + <Root data-test={`file-${id}`}> + {dragHandle} + <Info> + <span>{name}</span> + <span>{parseFileSize(size)}</span> + </Info> + <Buttons> + <button onClick={previewFile(id)}> + <Icon color={theme.colorPrimary} size={3}> + eye + </Icon> + </button> + {removeFile && ( + <button onClick={removeFile(id)} title="Delete"> + <Icon color={theme.colorPrimary} size={3}> + trash-2 + </Icon> + </button> + )} + </Buttons> + </Root> + )} + </Fragment> ) export default withTheme(FileItem) // #region styles +const defaultText = css` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeBaseSmall')}; +` + const Root = styled.div` align-items: center; - border: ${({ theme }) => theme.borderDefault}; + border: ${th('borderDefault')}; display: flex; margin: 5px; ` const Info = styled.div` - border-right: ${({ theme }) => theme.borderDefault}; + border-right: ${th('borderDefault')}; display: flex; flex: 1; justify-content: space-between; @@ -88,4 +134,31 @@ const Buttons = styled.div` } } ` +const FileName = styled.span` + ${defaultText}; + margin: 0 ${th('subGridUnit')}; +` +const FileSize = FileName.extend` + margin-left: ${th('subGridUnit')}; +` + +const IconButton = styled.div` + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + margin: 0 ${th('subGridUnit')}; + &:hover { + opacity: 0.7; + } +` + +const FileRoot = styled.div` + align-items: center; + border: ${th('borderDefault')}; + display: flex; + flex-direction: row; + margin-bottom: ${th('subGridUnit')}; + padding: ${th('subGridUnit')}; +` // #endregion diff --git a/packages/components-faraday/src/components/Files/index.js b/packages/components-faraday/src/components/Files/index.js index 6df727395c45e4df9e5db1d031a4d43146ad8440..ab85f18a55158ad1fc24c64e32e0023158ec7ae2 100644 --- a/packages/components-faraday/src/components/Files/index.js +++ b/packages/components-faraday/src/components/Files/index.js @@ -1 +1,3 @@ export { default as Files } from './Files' +export { default as FileItem } from './FileItem' +export { default as FilePicker } from './FilePicker' diff --git a/packages/components-faraday/src/index.js b/packages/components-faraday/src/index.js index a73434f5efaa170268c552a69188a1a455e2491b..d21969e7b792fa03fadc8e92f38152c12935abb4 100644 --- a/packages/components-faraday/src/index.js +++ b/packages/components-faraday/src/index.js @@ -6,6 +6,7 @@ module.exports = { files: () => require('./redux/files').default, editors: () => require('./redux/editors').default, reviewers: () => require('./redux/reviewers').default, + recommendations: () => require('./redux/recommendations').default, }, }, } diff --git a/packages/components-faraday/src/redux/recommendations.js b/packages/components-faraday/src/redux/recommendations.js index b59d6abf4748ff2eb7e966677bfd9be7ae9ab7b2..38c278451bf32d80663a10ed55863b6341739b12 100644 --- a/packages/components-faraday/src/redux/recommendations.js +++ b/packages/components-faraday/src/redux/recommendations.js @@ -1,16 +1,13 @@ import { get } from 'lodash' -import { - get as apiGet, - create, - remove, - update, -} from 'pubsweet-client/src/helpers/api' +import { SubmissionError } from 'redux-form' +import { create, update } from 'pubsweet-client/src/helpers/api' const REQUEST = 'recommendations/REQUEST' const ERROR = 'recommendations/ERROR' +const GET_FRAGMENT_SUCCESS = 'GET_FRAGMENT_SUCCESS' const GET_RECOMMENDATIONS_SUCCESS = 'recommendations/GET_SUCCESS' -const GET_RECOMMENDATION_SUCCESS = 'recommendations/GET_ITEM_SUCCESS' +const CREATE_RECOMMENDATION_SUCCESS = 'recommendations/CREATE_SUCCESS' const UPDATE_RECOMMENDATION_SUCCESS = 'recommendations/UPDATE_SUCCESS' export const recommendationsRequest = () => ({ @@ -27,8 +24,8 @@ export const getRecommendationsSuccess = recommendations => ({ payload: { recommendations }, }) -export const getRecommendationSuccess = recommendation => ({ - type: GET_RECOMMENDATION_SUCCESS, +export const createRecommendationSuccess = recommendation => ({ + type: CREATE_RECOMMENDATION_SUCCESS, payload: { recommendation }, }) @@ -40,17 +37,72 @@ export const updateRecommendationSuccess = recommendation => ({ // Selectors export const selectFetching = state => get(state, 'recommendations.fetching') || false - export const selectError = state => get(state, 'recommendations.error') +export const selectRecommendations = state => + get(state, 'recommendations.recommendations') || [] // Actions +export const createRecommendation = ( + collId, + fragId, + recommendation, +) => dispatch => { + dispatch(recommendationsRequest()) + return create( + `/collections/${collId}/fragments/${fragId}/recommendations`, + recommendation, + ).then( + r => { + dispatch(getRecommendationsSuccess([r])) + return r + }, + err => { + const error = get(err, 'response') + if (error) { + const errorMessage = get(JSON.parse(error), 'error') + dispatch(recommendationsError(errorMessage)) + throw new SubmissionError({ + _error: errorMessage || 'Something went wrong', + }) + } + }, + ) +} + +export const updateRecommendation = ( + collId, + fragId, + recommendation, +) => dispatch => { + dispatch(recommendationsRequest()) + return update( + `/collections/${collId}/fragments/${fragId}/recommendations/${ + recommendation.id + }`, + recommendation, + ).then( + r => { + dispatch(getRecommendationsSuccess([r])) + return r + }, + err => { + const error = get(err, 'response') + if (error) { + const errorMessage = get(JSON.parse(error), 'error') + dispatch(recommendationsError(errorMessage)) + throw new SubmissionError({ + _error: errorMessage || 'Something went wrong', + }) + } + }, + ) +} // State const initialState = { fetching: false, error: null, recommendations: [], - recommendation: {}, } export default (state = initialState, action = {}) => { @@ -59,8 +111,6 @@ export default (state = initialState, action = {}) => { return { ...state, fetching: true, - recommendations: [], - recommendation: {}, } case ERROR: return { @@ -68,26 +118,27 @@ export default (state = initialState, action = {}) => { fetching: false, error: action.error, } - case GET_RECOMMENDATIONS_SUCCESS: + case GET_FRAGMENT_SUCCESS: return { ...state, fetching: false, error: null, - recommendations: action.payload.recommendations, + recommendations: get(action, 'fragment.recommendations'), } - case GET_RECOMMENDATION_SUCCESS: + case GET_RECOMMENDATIONS_SUCCESS: return { ...state, fetching: false, error: null, - recommendation: action.payload.recommendation, + recommendations: action.payload.recommendations, } case UPDATE_RECOMMENDATION_SUCCESS: + case CREATE_RECOMMENDATION_SUCCESS: return { ...state, fetching: false, error: null, - recommendation: action.payload.recommendation, + recommendations: [action.payload.recommendation], } default: return state diff --git a/packages/xpub-faraday/config/upload-validations.js b/packages/xpub-faraday/config/upload-validations.js index 23fa37d7d5a7b0403731429eb910f5f402799000..21b2412ef38c1f8aea0cf3f3e98faf486a3b7107 100644 --- a/packages/xpub-faraday/config/upload-validations.js +++ b/packages/xpub-faraday/config/upload-validations.js @@ -16,4 +16,5 @@ module.exports = { 'application/msword', ]) .error(new Error('Only Word documents and PDFs are allowed')), + review: Joi.any(), }