From 038d94b649f94cb8b7e62a5001990ad7a84713f0 Mon Sep 17 00:00:00 2001 From: Alexandru Munteanu <alexandru.munteanu@thinslices.com> Date: Tue, 6 Feb 2018 13:30:21 +0200 Subject: [PATCH] Refactor authors reducer --- .../src/components/FileSection.js | 9 +-- .../src/components/FileSection.local.scss | 45 -------------- .../src/components/WizardStep.js | 1 - .../src/components/AuthorList/AuthorAdder.js | 21 +++++-- .../src/components/AuthorList/AuthorEditor.js | 53 ++++++++++------- .../src/components/UIComponents/Spinner.js | 13 ++++ .../UIComponents/Spinner.local.scss | 44 ++++++++++++++ .../src/components/UIComponents/index.js | 1 + .../src/components/index.js | 2 +- .../components-faraday/src/redux/authors.js | 59 ++++++++++++++++--- 10 files changed, 166 insertions(+), 82 deletions(-) create mode 100644 packages/components-faraday/src/components/UIComponents/Spinner.js create mode 100644 packages/components-faraday/src/components/UIComponents/Spinner.local.scss diff --git a/packages/component-wizard/src/components/FileSection.js b/packages/component-wizard/src/components/FileSection.js index 5f3726de3..f495196cc 100644 --- a/packages/component-wizard/src/components/FileSection.js +++ b/packages/component-wizard/src/components/FileSection.js @@ -5,7 +5,10 @@ import { Icon } from '@pubsweet/ui' import { DropTarget } from 'react-dnd' import { NativeTypes } from 'react-dnd-html5-backend' import { compose, getContext, withHandlers, withState } from 'recompose' -import { SortableList } from 'pubsweet-components-faraday/src/components' +import { + SortableList, + Spinner, +} from 'pubsweet-components-faraday/src/components' import FileItem from './FileItem' import FilePicker from './FilePicker' @@ -73,9 +76,7 @@ const FileSection = ({ </div> </FilePicker> ) : ( - <div className={classnames(classes.rotate, classes.icon)}> - <Icon size={16}>loader</Icon> - </div> + <Spinner /> )} </div> <span className={classnames(classes.error)}>{error}</span> diff --git a/packages/component-wizard/src/components/FileSection.local.scss b/packages/component-wizard/src/components/FileSection.local.scss index 6f1429c74..709f425a0 100644 --- a/packages/component-wizard/src/components/FileSection.local.scss +++ b/packages/component-wizard/src/components/FileSection.local.scss @@ -60,48 +60,3 @@ .is-over { background-color: #ddd; } - -@keyframes rotating { - from { - -o-transform: rotate(0deg); - -ms-transform: rotate(0deg); - -moz-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - to { - -o-transform: rotate(360deg); - -ms-transform: rotate(360deg); - -moz-transform: rotate(360deg); - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -@-webkit-keyframes rotating { - from { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.rotate { - -webkit-animation: rotating 1.5s linear infinite; - -moz-animation: rotating 1.5s linear infinite; - -ms-animation: rotating 1.5s linear infinite; - -o-animation: rotating 1.5s linear infinite; - animation: rotating 1.5s linear infinite; -} - -.icon { - align-items: center; - display: flex; - justify-content: center; - margin: 0 5px 0 0; -} diff --git a/packages/component-wizard/src/components/WizardStep.js b/packages/component-wizard/src/components/WizardStep.js index 4c369eeed..ff5bdc3b9 100644 --- a/packages/component-wizard/src/components/WizardStep.js +++ b/packages/component-wizard/src/components/WizardStep.js @@ -65,7 +65,6 @@ export default ({ ) }, )} - <div className={classnames(classes.buttons)}> <Button onClick={isFirst ? () => history.push('/') : prevStep}> {isFirst diff --git a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js index 30a532678..a3380004f 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorAdder.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorAdder.js @@ -7,7 +7,9 @@ import { reduxForm } from 'redux-form' import { compose, withProps } from 'recompose' import { selectCurrentUser } from 'xpub-selectors' +import { Spinner } from '../UIComponents/' import classes from './AuthorList.local.scss' +import { getAuthorFetching } from '../../redux/authors' import { MenuItem, ValidatedTextField } from './FormItems' const countries = [ @@ -22,7 +24,13 @@ const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/) const emailValidator = value => emailRegex.test(value) ? undefined : 'Invalid email' -const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( +const AuthorAdder = ({ + authors, + editMode, + setEditMode, + handleSubmit, + isFetching, +}) => ( <div className={classnames(classes.adder)}> <Button onClick={setEditMode(true)} primary> {authors.length === 0 ? '+ Add submitting author' : '+ Add author'} @@ -54,9 +62,13 @@ const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( </div> <div className={classnames(classes['form-buttons'])}> <Button onClick={setEditMode(false)}>Cancel</Button> - <Button onClick={handleSubmit} primary> - Save - </Button> + {!isFetching ? ( + <Button onClick={handleSubmit} primary> + Save + </Button> + ) : ( + <Spinner /> + )} </div> </div> )} @@ -66,6 +78,7 @@ const AuthorAdder = ({ authors, editMode, setEditMode, handleSubmit }) => ( export default compose( connect(state => ({ currentUser: selectCurrentUser(state), + isFetching: getAuthorFetching(state), })), withProps(({ currentUser: { admin, username, email }, authors }) => { if (!admin && authors.length === 0) { diff --git a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js index 41533434e..10f4f5a81 100644 --- a/packages/components-faraday/src/components/AuthorList/AuthorEditor.js +++ b/packages/components-faraday/src/components/AuthorList/AuthorEditor.js @@ -1,8 +1,12 @@ import React from 'react' import classnames from 'classnames' +import { compose } from 'recompose' import { Button } from '@pubsweet/ui' +import { connect } from 'react-redux' import { reduxForm } from 'redux-form' +import { Spinner } from '../UIComponents' +import { getAuthorFetching } from '../../redux/authors' import { ValidatedTextField, MenuItem } from './FormItems' import classes from './AuthorList.local.scss' @@ -19,7 +23,7 @@ const emailRegex = new RegExp(/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/) const emailValidator = value => emailRegex.test(value) ? undefined : 'Invalid email' -const AuthorEdit = ({ setAuthorEdit, handleSubmit }) => ( +const AuthorEdit = ({ isFetching, setAuthorEdit, handleSubmit }) => ( <div className={classnames(classes['editor-body'])}> <div className={classnames(classes.row)}> <ValidatedTextField isRequired label="First name" name="edit.firstName" /> @@ -44,26 +48,35 @@ const AuthorEdit = ({ setAuthorEdit, handleSubmit }) => ( <div className={classnames(classes['form-buttons'])}> <Button onClick={setAuthorEdit(-1)}>Cancel</Button> - <Button onClick={handleSubmit} primary> - Save - </Button> + {!isFetching ? ( + <Button onClick={handleSubmit} primary> + Save + </Button> + ) : ( + <Spinner /> + )} </div> </div> ) -export default reduxForm({ - form: 'edit', - onSubmit: ( - values, - dispatch, - { setAuthorEdit, setAuthors, authors, index, changeForm }, - ) => { - const newAuthors = [ - ...authors.slice(0, index), - values.edit, - ...authors.slice(index + 1), - ] - setAuthorEdit(-1)() - setAuthors(newAuthors) - }, -})(AuthorEdit) +export default compose( + connect(state => ({ + isFetching: getAuthorFetching(state), + })), + reduxForm({ + form: 'edit', + onSubmit: ( + values, + dispatch, + { setAuthorEdit, setAuthors, authors, index, changeForm }, + ) => { + const newAuthors = [ + ...authors.slice(0, index), + values.edit, + ...authors.slice(index + 1), + ] + setAuthorEdit(-1)() + setAuthors(newAuthors) + }, + }), +)(AuthorEdit) diff --git a/packages/components-faraday/src/components/UIComponents/Spinner.js b/packages/components-faraday/src/components/UIComponents/Spinner.js new file mode 100644 index 000000000..42c828c4b --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/Spinner.js @@ -0,0 +1,13 @@ +import React from 'react' +import classnames from 'classnames' +import { Icon } from '@pubsweet/ui' + +import classes from './Spinner.local.scss' + +const Spinner = () => ( + <div className={classnames(classes.rotate, classes.icon)}> + <Icon size={16}>loader</Icon> + </div> +) + +export default Spinner diff --git a/packages/components-faraday/src/components/UIComponents/Spinner.local.scss b/packages/components-faraday/src/components/UIComponents/Spinner.local.scss new file mode 100644 index 000000000..b82f01d0e --- /dev/null +++ b/packages/components-faraday/src/components/UIComponents/Spinner.local.scss @@ -0,0 +1,44 @@ +@keyframes rotating { + from { + -o-transform: rotate(0deg); + -ms-transform: rotate(0deg); + -moz-transform: rotate(0deg); + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -o-transform: rotate(360deg); + -ms-transform: rotate(360deg); + -moz-transform: rotate(360deg); + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@-webkit-keyframes rotating { + from { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.rotate { + -webkit-animation: rotating 1.8s linear infinite; + -moz-animation: rotating 1.8s linear infinite; + -ms-animation: rotating 1.8s linear infinite; + -o-animation: rotating 1.8s linear infinite; + animation: rotating 1.8s linear infinite; +} + +.icon { + align-items: center; + display: flex; + justify-content: center; + margin: 0 5px 0 0; +} diff --git a/packages/components-faraday/src/components/UIComponents/index.js b/packages/components-faraday/src/components/UIComponents/index.js index 8c2c826a2..0f79f62b6 100644 --- a/packages/components-faraday/src/components/UIComponents/index.js +++ b/packages/components-faraday/src/components/UIComponents/index.js @@ -1,2 +1,3 @@ export { default as Logo } from './Logo' +export { default as Spinner } from './Spinner' export { default as Dropdown } from './Dropdown' diff --git a/packages/components-faraday/src/components/index.js b/packages/components-faraday/src/components/index.js index da51b2a17..0415e239b 100644 --- a/packages/components-faraday/src/components/index.js +++ b/packages/components-faraday/src/components/index.js @@ -1,5 +1,5 @@ export { default as SortableList } from './SortableList/SortableList' export { default as AuthorList } from './AuthorList/AuthorList' -export { Dropdown, Logo } from './UIComponents' +export { Dropdown, Logo, Spinner } from './UIComponents' export { DragHandle } from './AuthorList/FormItems' diff --git a/packages/components-faraday/src/redux/authors.js b/packages/components-faraday/src/redux/authors.js index 5d78978e7..8a9e3a9c7 100644 --- a/packages/components-faraday/src/redux/authors.js +++ b/packages/components-faraday/src/redux/authors.js @@ -1,19 +1,64 @@ +import { get } from 'lodash' import * as api from 'pubsweet-client/src/helpers/api' // constants -export const SET_AUTHORS = 'authors/SET_AUTHORS' +const REQUEST = 'authors/REQUEST' +const FAILURE = 'authors/FAILURE' +const SUCCESS = 'authors/SUCCESS' // actions -export const addAuthor = (author, collectionId, fragmentId) => dispatch => - api.create( - `/collections/${collectionId}/fragments/${fragmentId}/authors`, - author, - ) +export const authorRequest = () => ({ + type: REQUEST, +}) + +export const authorFaiure = error => ({ + type: FAILURE, + error, +}) + +export const authorSuccess = () => ({ + type: SUCCESS, +}) + +export const addAuthor = (author, collectionId, fragmentId) => dispatch => { + dispatch(authorRequest()) + return api + .create( + `/collections/${collectionId}/fragments/${fragmentId}/authors`, + author, + ) + .then(author => { + dispatch(authorSuccess()) + return author + }) + .catch(err => dispatch(authorFaiure(err))) +} // selectors +export const getFragmentAuthors = (state, fragmentId) => + get(state, `authors.${fragmentId}`) || [] + +export const getAuthorFetching = state => state.authors.isFetching +export const getAuthorError = state => state.authors.error + +const initialState = { isFetching: false, error: null } -export default (state = {}, action) => { +export default (state = initialState, action) => { switch (action.type) { + case 'UPDATE_FRAGMENT_REQUEST': + case REQUEST: + return { + ...initialState, + isFetching: true, + } + case FAILURE: + return { + ...initialState, + error: action.error, + } + case 'UPDATE_FRAGMENT_SUCCESS': + case SUCCESS: + return initialState default: return state } -- GitLab