diff --git a/app/components/AdminPage.js b/app/components/AdminPage.js index 19c02ee7cc74b038f33781080431b0fa1b1c779a..cd3548780ecf395f8c82169cda14a8496f95a1bc 100644 --- a/app/components/AdminPage.js +++ b/app/components/AdminPage.js @@ -8,20 +8,21 @@ import { Switch, Redirect, } from 'react-router-dom' +import PropTypes from 'prop-types' import { JournalContext } from './xpub-journal/src' import { XpubContext } from './xpub-with-context/src' -import UsersManager from '../components/component-users-manager/src/UsersManager' -import Manuscripts from '../components/component-manuscripts/src/Manuscripts' -import Dashboard from '../components/component-dashboard/src/components/Dashboard' -import SubmitPage from '../components/component-submit/src/components/SubmitPage' -import ManuscriptPage from '../components/component-manuscript/src/components/ManuscriptPage' -import ReviewersPage from '../components/component-review/src/components/ReviewersPage' -import ReviewPage from '../components/component-review/src/components/ReviewPage' -import DecisionPage from '../components/component-review/src/components/DecisionPage' -import FormBuilderPage from '../components/component-formbuilder/src/components/FormBuilderPage' -import NewSubmissionPage from '../components/component-submit/src/components/NewSubmissionPage' -import { Profile } from '../components/component-profile/src' +import UsersManager from './component-users-manager/src/UsersManager' +import Manuscripts from './component-manuscripts/src/Manuscripts' +import Dashboard from './component-dashboard/src/components/Dashboard' +import SubmitPage from './component-submit/src/components/SubmitPage' +import ManuscriptPage from './component-manuscript/src/components/ManuscriptPage' +import ReviewersPage from './component-review/src/components/ReviewersPage' +import ReviewPage from './component-review/src/components/ReviewPage' +import DecisionPage from './component-review/src/components/DecisionPage' +import FormBuilderPage from './component-formbuilder/src/components/FormBuilderPage' +import NewSubmissionPage from './component-submit/src/components/NewSubmissionPage' +import { Profile } from './component-profile/src' import { GET_CURRENT_USER } from '../queries' @@ -31,18 +32,16 @@ import { Spinner } from './shared' import currentRolesVar from '../shared/currentRolesVar' import RolesUpdater from './RolesUpdater' -const getParams = routerPath => { - const path = '/journal/versions/:version' +const getParams = ({ routerPath, path }) => { return matchPath(routerPath, path).params } const Root = styled.div` display: grid; - grid-template-columns: 200px auto; grid-template-areas: 'menu main'; - max-height: 100vh; + grid-template-columns: 200px auto; height: 100vh; - overflow: hidden; + max-height: 100vh; ${({ converting }) => converting && ` @@ -51,26 +50,35 @@ const Root = styled.div` pointer-events: none; } `}; + overflow: hidden; ` // TODO: Redirect if token expires -const PrivateRoute = ({ component: Component, ...rest }) => ( +const PrivateRoute = ({ component: Component, redirectLink, ...rest }) => ( <Route {...rest} render={props => localStorage.getItem('token') ? ( <Component {...props} /> ) : ( - <Redirect to="/login?next=/journal/dashboard" /> + <Redirect to={redirectLink} /> ) } /> ) +PrivateRoute.propTypes = { + component: PropTypes.func.isRequired, + redirectLink: PropTypes.string.isRequired, +} + const updateStuff = data => { if (data?.currentUser) { + // eslint-disable-next-line no-underscore-dangle return currentRolesVar(data.currentUser._currentRoles) } + + return false } const AdminPage = () => { @@ -81,7 +89,7 @@ const AdminPage = () => { const { loading, error, data } = useQuery(GET_CURRENT_USER, { fetchPolicy: 'network-only', // TODO: useCallback used because of bug: https://github.com/apollographql/apollo-client/issues/6301 - onCompleted: useCallback(data => updateStuff(data), []), + onCompleted: useCallback(dataTemp => updateStuff(dataTemp), []), }) const previousDataRef = useRef(null) @@ -92,6 +100,7 @@ const AdminPage = () => { } let notice = '' + if (error) { if (error.networkError) { notice = 'You are offline.' @@ -104,16 +113,20 @@ const AdminPage = () => { previousDataRef.current = data + const urlFrag = journal.metadata.toplevel_urlfragment const { pathname } = history.location const showLinks = pathname.match(/^\/(submit|manuscript)/g) let links = [] - const formBuilderLink = `/journal/admin/form-builder` - const homeLink = '/journal/dashboard' - const profileLink = '/journal/profile' + const formBuilderLink = `${urlFrag}/admin/form-builder` + const homeLink = `${urlFrag}/dashboard` + const profileLink = `${urlFrag}/profile` + const loginLink = `/login?next=${homeLink}` + const path = `${urlFrag}/versions/:version` + const redirectLink = `/login?next=${homeLink}` if (showLinks) { - const params = getParams(pathname) - const baseLink = `/journal/versions/${params.version}` + const params = getParams(pathname, path) + const baseLink = `${urlFrag}/versions/${params.version}` const submitLink = `${baseLink}/submit` const manuscriptLink = `${baseLink}/manuscript` @@ -130,11 +143,10 @@ const AdminPage = () => { } if (currentUser && currentUser.admin) { - // links.push({ link: '/journal/admin/teams', name: 'Teams', icon: 'grid' }) links.push({ link: formBuilderLink, name: 'Forms', icon: 'check-square' }) - links.push({ link: '/journal/admin/users', name: 'Users', icon: 'users' }) + links.push({ link: `${urlFrag}/admin/users`, name: 'Users', icon: 'users' }) links.push({ - link: '/journal/admin/manuscripts', + link: `${urlFrag}/admin/manuscripts`, name: 'Manuscripts', icon: 'file-text', }) @@ -148,55 +160,79 @@ const AdminPage = () => { <Root converting={conversion.converting}> <Menu brand={journal.metadata.name} - brandLink="/journal/dashboard" - loginLink="/login?next=/journal/dashboard" + brandLink={homeLink} + className="" + loginLink={loginLink} navLinkComponents={links} notice={notice} + profileLink={profileLink} user={currentUser} /> <Switch> - <PrivateRoute component={Dashboard} exact path="/journal/dashboard" /> + <PrivateRoute + component={Dashboard} + exact + path={homeLink} + redirectLink={redirectLink} + /> <PrivateRoute component={NewSubmissionPage} exact - path="/journal/newSubmission" + path={`${urlFrag}/newSubmission`} + redirectLink={redirectLink} /> <PrivateRoute component={SubmitPage} exact - path="/journal/versions/:version/submit" + path={`${urlFrag}/versions/:version/submit`} + redirectLink={redirectLink} /> <PrivateRoute component={FormBuilderPage} exact - path="/journal/admin/form-builder" + path={`${urlFrag}/admin/form-builder`} + redirectLink={redirectLink} /> <PrivateRoute component={ManuscriptPage} exact - path="/journal/versions/:version/manuscript" + path={`${urlFrag}/versions/:version/manuscript`} + redirectLink={redirectLink} /> <PrivateRoute component={ReviewersPage} exact - path="/journal/versions/:version/reviewers" + path={`${urlFrag}/versions/:version/reviewers`} + redirectLink={redirectLink} /> <PrivateRoute component={ReviewPage} exact - path="/journal/versions/:version/review" + path={`${urlFrag}/versions/:version/review`} + redirectLink={redirectLink} /> <PrivateRoute component={DecisionPage} exact - path="/journal/versions/:version/decision" + path={`${urlFrag}/versions/:version/decision`} + redirectLink={redirectLink} + /> + <PrivateRoute + component={Profile} + exact + path={`${urlFrag}/profile`} + redirectLink={redirectLink} + /> + <PrivateRoute + component={UsersManager} + path={`${urlFrag}/admin/users`} + redirectLink={redirectLink} /> - <PrivateRoute component={Profile} exact path="/journal/profile" /> - <PrivateRoute component={UsersManager} path="/journal/admin/users" /> <PrivateRoute component={Manuscripts} - path="/journal/admin/manuscripts" + path={`${urlFrag}/admin/manuscripts`} + redirectLink={redirectLink} /> </Switch> <RolesUpdater /> diff --git a/app/components/Menu.js b/app/components/Menu.js index 8c39a1ede00ec449d3d2d46ddd631ad32a96fcc6..163b3be5b6867b15dc699659b42592374c408aea 100644 --- a/app/components/Menu.js +++ b/app/components/Menu.js @@ -1,31 +1,24 @@ import React from 'react' import styled, { css } from 'styled-components' -// import PropTypes from 'prop-types' +import PropTypes from 'prop-types' import { th, grid, lighten } from '@pubsweet/ui-toolkit' import { Link, useLocation } from 'react-router-dom' import { Icon } from '@pubsweet/ui' -import { UserAvatar } from '../components/component-avatar/src' +import { UserAvatar } from './component-avatar/src' const Root = styled.nav` - grid-area: menu; - padding: ${grid(2)}; - // display: flex; - // align-items: center; - // justify-content: space-between; + background: linear-gradient( + 134deg, + ${th('colorPrimary')}, + ${lighten('colorPrimary', 0.3)} + ); border-right: 1px solid ${th('colorFurniture')}; - // background: ${th('colorPrimary')}; - // background: linear-gradient(45deg, #191654, #43C6AC); - background: linear-gradient(134deg, ${th('colorPrimary')}, ${lighten( - 'colorPrimary', - 0.3, -)}); + grid-area: menu; max-height: 100vh; + padding: ${grid(2)}; ` -const Section = styled.div` - // display: flex; - // align-items: center; -` +const Section = styled.div`` // const Logo = styled.span` // // margin: ${grid(2)} 1rem ${grid(2)} 1rem; @@ -48,33 +41,21 @@ const NavItem = ({ className, link, name, icon }) => ( </Link> ) +NavItem.propTypes = { + className: PropTypes.string.isRequired, + link: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, +} + const Item = styled(NavItem)` + align-items: center; border-radius: 10px; - padding-left: ${grid(1)}; + color: ${th('colorTextReverse')}; + display: flex; height: ${grid(5)}; line-height: ${grid(3)}; - display: flex; - align-items: center; - color: ${th('colorTextReverse')}; - - &:hover { - color: ${th('colorText')}; - stroke: ${th('colorText')}; - background-color: ${lighten('colorPrimary', 0.5)}; - svg { - stroke: ${th('colorText')}; - - } - } - - svg { - &:hover { - } - width: 1em; - stroke: ${th('colorTextReverse')}; - } - ${props => props.active && css` @@ -88,26 +69,36 @@ const Item = styled(NavItem)` stroke: ${th('colorText')}; } `} - // align-items: center; - // display: inline-flex; - // margin: calc(${th('gridUnit')} * 3) 1rem calc(${th('gridUnit')} * 3) 0; + + padding-left: ${grid(1)}; + + svg { + stroke: ${th('colorTextReverse')}; + width: 1em; + } + + &:hover { + background-color: ${lighten('colorPrimary', 0.5)}; + color: ${th('colorText')}; + stroke: ${th('colorText')}; + + svg { + stroke: ${th('colorText')}; + } + } ` const UserItem = styled(Link)` - // height: ${grid(5)}; - // line-height: ${grid(2)}; color: ${th('colorTextReverse')}; display: flex; padding-bottom: ${grid(2)}; - // margin-bottom: ${grid(2)}; - // border-bottom: 1px solid ${th('colorFurniture')}; ` const UserInfo = styled.div` - margin-left: ${grid(1)}; display: flex; - justify-content: center; flex-direction: column; + justify-content: center; + margin-left: ${grid(1)}; ` const Menu = ({ @@ -116,6 +107,7 @@ const Menu = ({ navLinkComponents, user, notice, + profileLink, }) => { const location = useLocation() return ( @@ -123,7 +115,11 @@ const Menu = ({ <Section> {/* TODO: Place this notice (used for offline notification) better */} {notice} - <UserComponent loginLink={loginLink} user={user} /> + <UserComponent + loginLink={loginLink} + profileLink={profileLink} + user={user} + /> {navLinkComponents && navLinkComponents.map((navInfo, idx) => ( <Item @@ -137,10 +133,10 @@ const Menu = ({ ) } -const UserComponent = ({ user, loginLink }) => ( +const UserComponent = ({ user, loginLink, profileLink }) => ( <Section> {user && ( - <UserItem title="Go to your profile" to="/journal/profile"> + <UserItem title="Go to your profile" to={profileLink}> <UserAvatar isClickable={false} size={48} user={user} /> <UserInfo> <p>{user.defaultIdentity.name || user.username}</p> @@ -154,13 +150,27 @@ const UserComponent = ({ user, loginLink }) => ( </Section> ) -// Menu.propTypes = { -// brandLink: PropTypes.string, -// brand: PropTypes.node, -// loginLink: PropTypes.string, -// onLogoutClick: PropTypes.func, -// user: PropTypes.object, -// navLinkComponents: PropTypes.arrayOf(PropTypes.element), -// } +Menu.propTypes = { + className: PropTypes.string.isRequired, + loginLink: PropTypes.string.isRequired, + navLinkComponents: PropTypes.arrayOf(PropTypes.object).isRequired, + user: PropTypes.oneOfType([PropTypes.object]), + notice: PropTypes.node.isRequired, + profileLink: PropTypes.string.isRequired, +} + +Menu.defaultProps = { + user: undefined, +} + +UserComponent.propTypes = { + user: PropTypes.oneOfType([PropTypes.object]), + loginLink: PropTypes.string.isRequired, + profileLink: PropTypes.string.isRequired, +} + +UserComponent.defaultProps = { + user: undefined, +} export default Menu diff --git a/app/components/component-dashboard/src/components/Dashboard.js b/app/components/component-dashboard/src/components/Dashboard.js index 11a02f0a3cc40f895840092a1376fc3d3809b185..94c4b691b8f8b0247ab52bc71f799e85a7309f23 100644 --- a/app/components/component-dashboard/src/components/Dashboard.js +++ b/app/components/component-dashboard/src/components/Dashboard.js @@ -1,10 +1,10 @@ -/* eslint-disable react/prop-types */ - import React from 'react' import { useQuery, useMutation } from '@apollo/client' import { Button } from '@pubsweet/ui' // import Authorize from 'pubsweet-client/src/helpers/Authorize' +import config from 'config' +import ReactRouterPropTypes from 'react-router-prop-types' import queries from '../graphql/queries' import mutations from '../graphql/mutations' import { Container, Placeholder } from '../style' @@ -72,11 +72,16 @@ const Dashboard = ({ history, ...props }) => { ) .map(latestVersion) + const urlFrag = config.journal.metadata.toplevel_urlfragment + return ( <Container> <HeadingWithAction> <Heading>Dashboard</Heading> - <Button onClick={() => history.push('/journal/newSubmission')} primary> + <Button + onClick={() => history.push(`${urlFrag}/newSubmission`)} + primary + > + New submission </Button> </HeadingWithAction> @@ -145,4 +150,8 @@ const Dashboard = ({ history, ...props }) => { ) } +Dashboard.propTypes = { + history: ReactRouterPropTypes.history.isRequired, +} + export default Dashboard diff --git a/app/components/component-dashboard/src/components/sections/EditorItem.js b/app/components/component-dashboard/src/components/sections/EditorItem.js index 827eb9075af4cab1f89093e88cda71ebe2812b10..8dfbb1e03f3eb7d23c3d220c6b576cb26f7760eb 100644 --- a/app/components/component-dashboard/src/components/sections/EditorItem.js +++ b/app/components/component-dashboard/src/components/sections/EditorItem.js @@ -1,10 +1,9 @@ -/* eslint-disable react/prop-types */ /* eslint-disable no-underscore-dangle */ -/* eslint-disable no-param-reassign */ - import React from 'react' import styled from 'styled-components' import { Action, ActionGroup } from '@pubsweet/ui' +import config from 'config' +import PropTypes from 'prop-types' import { Item, StatusBadge } from '../../style' import Meta from '../metadata/Meta' import MetadataSubmittedDate from '../metadata/MetadataSubmittedDate' @@ -27,14 +26,16 @@ const getUserFromTeam = (version, role) => { return teams.length ? teams[0].members : [] } +const urlFrag = config.journal.metadata.toplevel_urlfragment + const EditorItemLinks = ({ version }) => ( <ActionGroup> - <Action to={`/journal/versions/${version.parentId || version.id}/submit`}> + <Action to={`${urlFrag}/versions/${version.parentId || version.id}/submit`}> Summary Info </Action> <Action data-testid="control-panel" - to={`/journal/versions/${version.parentId || version.id}/decision`} + to={`${urlFrag}/versions/${version.parentId || version.id}/decision`} > {version.decision && version.decision.status === 'submitted' ? `Decision: ${version.decision.recommendation}` @@ -43,7 +44,12 @@ const EditorItemLinks = ({ version }) => ( </ActionGroup> ) +EditorItemLinks.propTypes = { + version: PropTypes.element.isRequired, +} + const getDeclarationsObject = (version, value) => { + // eslint-disable-next-line no-param-reassign if (!version.meta) version.meta = {} const declarations = version.meta.declarations || {} @@ -95,4 +101,8 @@ const EditorItem = ({ version }) => ( // </Authorize> ) +EditorItem.propTypes = { + version: PropTypes.element.isRequired, +} + export default EditorItem diff --git a/app/components/component-dashboard/src/components/sections/OwnerItem.js b/app/components/component-dashboard/src/components/sections/OwnerItem.js index 18f36cc3dc06a0744e702efe99ff762b2176b33b..c0ba0332d1b99db9bc1d975ff6ae9e015c954d18 100644 --- a/app/components/component-dashboard/src/components/sections/OwnerItem.js +++ b/app/components/component-dashboard/src/components/sections/OwnerItem.js @@ -1,36 +1,42 @@ -/* eslint-disable react/prop-types */ - import React from 'react' import { Link } from 'react-router-dom' +import config from 'config' +import PropTypes from 'prop-types' import { Item, StatusBadge } from '../../style' import VersionTitle from './VersionTitle' import { Icon, ClickableSectionRow } from '../../../../shared' import theme from '../../../../../theme' -const OwnerItem = ({ version, journals, deleteManuscript }) => ( - // Links are based on the original/parent manuscript version - <Link - key={`version-${version.id}`} - to={`/journal/versions/${version.parentId || version.id}/submit`} - > - <ClickableSectionRow> - <Item> - <div> - {' '} - <StatusBadge - minimal - published={version.published} - status={version.status} - /> - <VersionTitle version={version} /> - </div> - <Icon color={theme.colorSecondary} noPadding size={2.5}> - chevron_right - </Icon> - {/* {actions} */} - </Item> - </ClickableSectionRow> - </Link> -) +const urlFrag = config.journal.metadata.toplevel_urlfragment + +const OwnerItem = ({ version }) => { + return ( + <Link + key={`version-${version.id}`} + to={`${urlFrag}/versions/${version.parentId || version.id}/submit`} + > + <ClickableSectionRow> + <Item> + <div> + {' '} + <StatusBadge + minimal + published={version.published} + status={version.status} + /> + <VersionTitle version={version} /> + </div> + <Icon color={theme.colorSecondary} noPadding size={2.5}> + chevron_right + </Icon> + </Item> + </ClickableSectionRow> + </Link> + ) +} + +OwnerItem.propTypes = { + version: PropTypes.oneOfType([PropTypes.object]).isRequired, +} export default OwnerItem diff --git a/app/components/component-dashboard/src/components/sections/ReviewerItem.js b/app/components/component-dashboard/src/components/sections/ReviewerItem.js index 9b8fd16934d355513f268051fb049ccc409012df..28b34d9ffa13aacc5b8f7c94494e558e195d6f0a 100644 --- a/app/components/component-dashboard/src/components/sections/ReviewerItem.js +++ b/app/components/component-dashboard/src/components/sections/ReviewerItem.js @@ -1,9 +1,8 @@ -/* eslint-disable react/prop-types */ -/* eslint-disable no-shadow */ - import React from 'react' import { Action, ActionGroup } from '@pubsweet/ui' // import Authorize from 'pubsweet-client/src/helpers/Authorize' +import PropTypes from 'prop-types' +import config from 'config' import { Item } from '../../style' import VersionTitle from './VersionTitle' @@ -12,9 +11,9 @@ import VersionTitle from './VersionTitle' // TODO: only return actions if not accepted or rejected // TODO: review id in link -const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => { +const ReviewerItem = ({ version, currentUser, reviewerRespond }) => { const team = - (version.teams || []).find(team => team.role === 'reviewer') || {} + (version.teams || []).find(team_ => team_.role === 'reviewer') || {} const currentMember = team.members && @@ -22,13 +21,15 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => { const status = currentMember && currentMember.status + const urlFrag = config.journal.metadata.toplevel_urlfragment + return ( <Item> <VersionTitle version={version} /> {(status === 'accepted' || status === 'completed') && ( <ActionGroup> - <Action to={`/journal/versions/${version.id}/review`}> + <Action to={`${urlFrag}/versions/${version.id}/review`}> {status === 'completed' ? 'Completed' : 'Do Review'} </Action> </ActionGroup> @@ -70,4 +71,10 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => { ) } +ReviewerItem.propTypes = { + version: PropTypes.string.isRequired, + currentUser: PropTypes.oneOfType([PropTypes.object]).isRequired, + reviewerRespond: PropTypes.func.isRequired, +} + export default ReviewerItem diff --git a/app/components/component-login/src/Login.jsx b/app/components/component-login/src/Login.jsx index d04d53a279a966cc029205f90a65debd58ebc2cd..b4988dc571cba8faf51f4b8199c7fcd2437a78ec 100644 --- a/app/components/component-login/src/Login.jsx +++ b/app/components/component-login/src/Login.jsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { Redirect } from 'react-router-dom' import config from 'config' import { th, grid, lighten } from '@pubsweet/ui-toolkit' -import { H1, Button } from '@pubsweet/ui' +import { Button } from '@pubsweet/ui' import styled from 'styled-components' const getNextUrl = () => { @@ -19,9 +19,11 @@ const getNextUrl = () => { const getToken = props => { const { location } = props + if (location && location.search && location.search.match(/^\?token=/)) { return location.search.replace(/^\?token=/, '') } + return null } @@ -55,6 +57,12 @@ const Content = styled.div` margin-bottom: ${grid(2)}; } margin-bottom: 1rem; + img { + max-width: 475px; + max-height: 307px; + width: auto; + height: auto; + } ` const Centered = styled.div` @@ -111,10 +119,9 @@ const Login = ({ logo = null, ...props }) => { {journalName === 'Aperture' && ( <img alt="Aperture" src="/public/logo-aperture.png" /> )} - <H1>Login to {journalName}</H1> - {journalName} uses ORCID <StyledORCIDIcon /> to identify authors and - staff. Login with your ORCID account below or{' '} - <a href="https://orcid.org/signin">register at the ORCID website.</a> + {journalName === 'Kotahi' && ( + <img alt="Kotahi" src="/public/logo-kotahi.png" /> + )} <LoginButton onClick={() => (window.location = '/auth/orcid')} primary @@ -122,7 +129,6 @@ const Login = ({ logo = null, ...props }) => { Login with ORCID </LoginButton> </Content> - <div>Powered by Kotahi</div> </Centered> </Container> ) diff --git a/app/components/component-manuscripts/src/Manuscript.jsx b/app/components/component-manuscripts/src/Manuscript.jsx index 00e2856a3f11da5ee774f918a666f29b0123a342..529e267e8ca0c5fc47a855c95e25e1f1a46a0b12 100644 --- a/app/components/component-manuscripts/src/Manuscript.jsx +++ b/app/components/component-manuscripts/src/Manuscript.jsx @@ -1,7 +1,10 @@ +/* eslint-disable react/jsx-filename-extension */ import React from 'react' import gql from 'graphql-tag' import { useMutation } from '@apollo/client' // import { Action } from '@pubsweet/ui' +import config from 'config' +import PropTypes from 'prop-types' import { UserAvatar } from '../../component-avatar/src' import { Row, @@ -26,14 +29,17 @@ const DELETE_MANUSCRIPT = gql` } ` +const urlFrag = config.journal.metadata.toplevel_urlfragment + // manuscriptId is always the parent manuscript's id const User = ({ manuscriptId, manuscript, submitter }) => { const [deleteManuscript] = useMutation(DELETE_MANUSCRIPT, { - update(cache, { data: { deleteManuscript } }) { + update(cache, { data: { deleteManuscriptId } }) { const id = cache.identify({ __typename: 'Manuscript', - id: deleteManuscript, + id: deleteManuscriptId, }) + cache.evict({ id }) }, }) @@ -60,10 +66,10 @@ const User = ({ manuscriptId, manuscript, submitter }) => { )} </Cell> <LastCell> - <Action to={`/journal/versions/${manuscriptId}/decision`}> + <Action to={`${urlFrag}/versions/${manuscriptId}/decision`}> Control </Action> - <Action to={`/journal/versions/${manuscriptId}/manuscript`}> + <Action to={`${urlFrag}/versions/${manuscriptId}/manuscript`}> View </Action> <Action @@ -76,4 +82,10 @@ const User = ({ manuscriptId, manuscript, submitter }) => { ) } +User.propTypes = { + manuscriptId: PropTypes.number.isRequired, + manuscript: PropTypes.element.isRequired, + submitter: PropTypes.element.isRequired, +} + export default User diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js index 9640ac9061164d67798b570ad815487aa3cbed16..8c23b05ef5cb8cfdf26c91a5c7dc92d17630b248 100644 --- a/app/components/component-review/src/components/ReviewPage.js +++ b/app/components/component-review/src/components/ReviewPage.js @@ -3,7 +3,9 @@ import { useMutation, useQuery } from '@apollo/client' import gql from 'graphql-tag' import { Formik } from 'formik' // import { cloneDeep } from 'lodash' -import ReviewLayout from '../components/review/ReviewLayout' +import config from 'config' +import ReactRouterPropTypes from 'react-router-prop-types' +import ReviewLayout from './review/ReviewLayout' import { Spinner } from '../../../shared' import useCurrentUser from '../../../../hooks/useCurrentUser' @@ -152,7 +154,9 @@ const updateReviewMutationQuery = gql` } ` -export default ({ match, ...props }) => { +const urlFrag = config.journal.metadata.toplevel__urlfragment + +const ReviewPage = ({ match, ...props }) => { const currentUser = useCurrentUser() const [updateReviewMutation] = useMutation(updateReviewMutationQuery) const [completeReview] = useMutation(completeReviewMutation) @@ -186,13 +190,11 @@ export default ({ match, ...props }) => { const { manuscript } = data const channelId = manuscript.channels.find(c => c.type === 'editorial').id - // eslint-disable-next-line - const status = ( + const { status } = ( (manuscript.teams.find(team => team.role === 'reviewer') || {}).status || [] - ).find(status => status.user === currentUser.id) || {} - ).status + ).find(statusTemp => statusTemp.user === currentUser.id) || {} const updateReview = (review, file) => { const reviewData = { @@ -215,13 +217,13 @@ export default ({ match, ...props }) => { id: existingReview.current.id || undefined, input: reviewData, }, - update: (cache, { data: { updateReview } }) => { + update: (cache, { data: { updateReviewTemp } }) => { cache.modify({ id: cache.identify(manuscript), fields: { reviews(existingReviewRefs = [], { readField }) { const newReviewRef = cache.writeFragment({ - data: updateReview, + data: updateReviewTemp, fragment: gql` fragment NewReview on Review { id @@ -252,7 +254,7 @@ export default ({ match, ...props }) => { }, }) - history.push('/journal/dashboard') + history.push(`${urlFrag}/dashboard`) } return ( @@ -296,3 +298,10 @@ export default ({ match, ...props }) => { </Formik> ) } + +ReviewPage.propTypes = { + match: ReactRouterPropTypes.match.isRequired, + history: ReactRouterPropTypes.history.isRequired, +} + +export default ReviewPage diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js index 7994f0be7e33062dfad397d62cb03e1ac400dd52..c4a741dc07f36dd81c8a3155bc009c2ee4a22ae7 100644 --- a/app/components/component-review/src/components/decision/DecisionReviews.js +++ b/app/components/component-review/src/components/decision/DecisionReviews.js @@ -1,8 +1,11 @@ import React from 'react' import { Action } from '@pubsweet/ui' +import PropTypes from 'prop-types' +import config from 'config' import DecisionReview from './DecisionReview' import { SectionHeader, SectionRow, Title } from '../style' import { SectionContent } from '../../../../shared' + // TODO: read reviewer ordinal and name from project reviewer // const { status } = // getUserFromTeam(manuscript, 'reviewer').filter( @@ -11,14 +14,18 @@ import { SectionContent } from '../../../../shared' // return status const getCompletedReviews = (manuscript, currentUser) => { - const team = manuscript.teams.find(team => team.role === 'reviewer') || {} + const team = manuscript.teams.find(team_ => team_.role === 'reviewer') || {} + if (!team.members) { return null } + const currentMember = team.members.find(m => m.user?.id === currentUser?.id) return currentMember && currentMember.status } +const urlFrag = config.journal.metadata.toplevel_urlfragment + const DecisionReviews = ({ manuscript }) => ( <SectionContent> <SectionHeader> @@ -47,11 +54,15 @@ const DecisionReviews = ({ manuscript }) => ( <SectionRow>No reviews completed yet.</SectionRow> )} <SectionRow> - <Action to={`/journal/versions/${manuscript.id}/reviewers`}> + <Action to={`${urlFrag}/versions/${manuscript.id}/reviewers`}> Manage Reviewers </Action> </SectionRow> </SectionContent> ) +DecisionReviews.propTypes = { + manuscript: PropTypes.element.isRequired, +} + export default DecisionReviews diff --git a/app/components/component-review/src/components/reviewers/Reviewers.js b/app/components/component-review/src/components/reviewers/Reviewers.js index 5ff9ad3024d1df2a45274171f193e96e9f320f56..95356d1e74f180119649c4c340155d65c28c5f7e 100644 --- a/app/components/component-review/src/components/reviewers/Reviewers.js +++ b/app/components/component-review/src/components/reviewers/Reviewers.js @@ -2,6 +2,8 @@ import React from 'react' import styled from 'styled-components' import { Action, Button } from '@pubsweet/ui' import { grid } from '@pubsweet/ui-toolkit' +import PropTypes from 'prop-types' +import config from 'config' import ReviewerForm from './ReviewerForm' import { Container, @@ -19,12 +21,14 @@ import { UserAvatar } from '../../../../component-avatar/src' const ReviewersList = styled.div` display: grid; - grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr)); grid-gap: ${grid(2)}; + grid-template-columns: repeat(auto-fill, minmax(${grid(15)}, 1fr)); ` const Reviewer = styled.div`` +const urlFrag = config.journal.metadata.toplevel_urlfragment + const Reviewers = ({ journal, isValid, @@ -43,7 +47,7 @@ const Reviewers = ({ <Heading>Reviewers</Heading> <Button onClick={() => - history.push(`/journal/versions/${manuscript.id}/decision`) + history.push(`${urlFrag}/versions/${manuscript.id}/decision`) } primary > @@ -73,7 +77,7 @@ const Reviewers = ({ {reviewers && reviewers.length ? ( <ReviewersList> {reviewers.map(reviewer => ( - <Reviewer> + <Reviewer key={reviewer.id}> <StatusBadge minimal status={reviewer.status} /> <UserAvatar key={reviewer.id} user={reviewer.user} /> {reviewer.user.defaultIdentity.name} @@ -102,4 +106,18 @@ const Reviewers = ({ </Container> ) +Reviewers.propTypes = { + journal: PropTypes.node.isRequired, + isValid: PropTypes.node.isRequired, + loadOptions: PropTypes.node.isRequired, + version: PropTypes.node.isRequired, + reviewers: PropTypes.node.isRequired, + reviewerUsers: PropTypes.node.isRequired, + manuscript: PropTypes.node.isRequired, + handleSubmit: PropTypes.node.isRequired, + removeReviewer: PropTypes.node.isRequired, + teams: PropTypes.node.isRequired, + history: PropTypes.node.isRequired, +} + export default Reviewers diff --git a/app/components/component-submit/src/components/FormTemplate.js b/app/components/component-submit/src/components/FormTemplate.js index a4e76b2a108058f70f05219c9c9ff4c6ba0f39f7..2ebd32a672b68d8c7bc747e65c9531cf5fc38ff4 100644 --- a/app/components/component-submit/src/components/FormTemplate.js +++ b/app/components/component-submit/src/components/FormTemplate.js @@ -11,6 +11,8 @@ import { } from '@pubsweet/ui' import * as validators from 'xpub-validators' import { AbstractEditor } from 'xpub-edit' +import PropTypes, { array } from 'prop-types' +import config from 'config' import { Section as Container, Select, FilesUpload } from '../../../shared' import { Heading1, Section, Legend, SubNote } from '../style' import AuthorsInput from './AuthorsInput' @@ -74,6 +76,14 @@ elements.AbstractEditor = ({ /> ) +elements.AbstractEditor.propTypes = { + validationStatus: PropTypes.node.isRequired, + setTouched: PropTypes.node.isRequired, + onChange: PropTypes.func.isRequired, + value: PropTypes.node.isRequired, + values: PropTypes.node.isRequired, +} + elements.AuthorsInput = AuthorsInput elements.Select = Select elements.LinksInput = LinksInput @@ -81,17 +91,17 @@ elements.LinksInput = LinksInput const rejectProps = (obj, keys) => Object.keys(obj) .filter(k => !keys.includes(k)) - .map(k => Object.assign({}, { [k]: obj[k] })) + .map(k => ({ [k]: obj[k] })) .reduce( (res, o) => - Object.values(o).includes('false') - ? Object.assign({}, res) - : Object.assign(res, o), + Object.values(o).includes('false') ? { ...res } : Object.assign(res, o), {}, ) +const urlFrag = config.journal.metadata.toplevel_urlfragment + const link = (journal, manuscript) => - String.raw`<a href=/journal/versions/${manuscript.id}/manuscript>view here</a>` + String.raw`<a href=${urlFrag}/versions/${manuscript.id}/manuscript>view here</a>` const createMarkup = encodedHtml => ({ __html: unescape(encodedHtml), @@ -109,16 +119,18 @@ const composeValidate = (vld = [], valueField = {}) => value => { validatorFn === 'required' ? validators[validatorFn](value) : validators[validatorFn](valueField[validatorFn])(value) + if (error) { errors.push(error) } + return validatorFn }) return errors.length > 0 ? errors[0] : undefined } -const groupElements = elements => { - const grouped = groupBy(elements, n => n.group || 'default') +const groupElements = els => { + const grouped = groupBy(els, n => n.group || 'default') Object.keys(grouped).forEach(element => { grouped[element].sort( @@ -135,7 +147,7 @@ const groupElements = elements => { startArr = startArr .slice(0, first) .concat([grouped[element]]) - .concat(startArr.slice(first)) // eslint-disable-line no-use-before-define + .concat(startArr.slice(first)) }) return startArr } @@ -149,6 +161,7 @@ const renderArray = (elementsComponentArray, onChange) => ({ const element = elementsComponentArray.find(elv => Object.values(elValues).includes(elv.type), ) + return ( <Section cssOverrides={JSON.parse(element.sectioncss || '{}')} @@ -180,6 +193,7 @@ const renderArray = (elementsComponentArray, onChange) => ({ notesType: element.type, content: value, } + replace(index, data, `${name}.[${index}]`, true) const notes = cloneDeep(values) set(notes, `${name}.[${index}]`, data) @@ -195,18 +209,19 @@ const renderArray = (elementsComponentArray, onChange) => ({ ) }) -const ElementComponentArray = ({ - elementsComponentArray, - onChange, - uploadFile, -}) => ( +const ElementComponentArray = ({ elementsComponentArray, onChange }) => ( <FieldArray name={elementsComponentArray[0].group} render={renderArray(elementsComponentArray, onChange)} /> ) -export default ({ +ElementComponentArray.propTypes = { + elementsComponentArray: PropTypes.oneOfType([array]).isRequired, + onChange: PropTypes.func.isRequired, +} + +const FormTemplate = ({ form, handleSubmit, journal, @@ -299,6 +314,7 @@ export default ({ onChange={value => { // TODO: Perhaps split components remove conditions here let val + if (value.target) { val = value.target.value } else if (value.value) { @@ -306,6 +322,7 @@ export default ({ } else { val = value } + setFieldValue(element.name, val, true) onChange(val, element.name) }} @@ -338,7 +355,7 @@ export default ({ <ElementComponentArray elementsComponentArray={element} // eslint-disable-next-line - key={i} + key={i} onChange={onChange} setFieldValue={setFieldValue} setTouched={setTouched} @@ -384,3 +401,23 @@ export default ({ </Container> ) } + +FormTemplate.propTypes = { + form: PropTypes.element.isRequired, + handleSubmit: PropTypes.element.isRequired, + journal: PropTypes.element.isRequired, + toggleConfirming: PropTypes.element.isRequired, + confirming: PropTypes.element.isRequired, + manuscript: PropTypes.element.isRequired, + setTouched: PropTypes.element.isRequired, + values: PropTypes.element.isRequired, + setFieldValue: PropTypes.element.isRequired, + createSupplementaryFile: PropTypes.element.isRequired, + onChange: PropTypes.element.isRequired, + onSubmit: PropTypes.element.isRequired, + submitSubmission: PropTypes.element.isRequired, + errors: PropTypes.element.isRequired, + validateForm: PropTypes.element.isRequired, +} + +export default FormTemplate diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js index 9aefa914562851bf8f188c2530c7095468bfc455..99a7b382acf34063a6362b55150f2ac58191846d 100644 --- a/app/components/component-submit/src/components/SubmitPage.js +++ b/app/components/component-submit/src/components/SubmitPage.js @@ -1,6 +1,8 @@ import React, { useState } from 'react' import { debounce, cloneDeep, set } from 'lodash' import { gql, useQuery, useMutation } from '@apollo/client' +import config from 'config' +import ReactRouterPropTypes from 'react-router-prop-types' import Submit from './Submit' import { Spinner } from '../../../shared' import gatherManuscriptVersions from '../../../../shared/manuscript_versions' @@ -170,11 +172,13 @@ const createNewVersionMutation = gql` } ` +const urlFrag = config.journal.metadata.toplevel_urlfragment + const SubmitPage = ({ match, history, ...props }) => { const [confirming, setConfirming] = useState(false) const toggleConfirming = () => { - setConfirming(confirming => !confirming) + setConfirming(confirm => !confirm) } const { data, loading, error } = useQuery(query, { @@ -192,11 +196,11 @@ const SubmitPage = ({ match, history, ...props }) => { const manuscript = data?.manuscript const form = data?.getFile - const updateManuscript = (versionId, manuscript) => + const updateManuscript = (versionId, manuscriptInput) => update({ variables: { id: versionId, - input: JSON.stringify(manuscript), + input: JSON.stringify(manuscriptInput), }, }) @@ -211,18 +215,18 @@ const SubmitPage = ({ match, history, ...props }) => { return debouncers[path](versionId, input) } - const onSubmit = async (versionId, manuscript) => { - const updateManuscript = { + const onSubmit = async versionId => { + const updateManuscriptInput = { status: 'submitted', } await submit({ variables: { id: versionId, - input: JSON.stringify(updateManuscript), + input: JSON.stringify(updateManuscriptInput), }, }) - history.push('/journal/dashboard') + history.push(`${urlFrag}/dashboard`) } const versions = gatherManuscriptVersions(manuscript) @@ -242,4 +246,9 @@ const SubmitPage = ({ match, history, ...props }) => { ) } +SubmitPage.propTypes = { + history: ReactRouterPropTypes.history.isRequired, + match: ReactRouterPropTypes.match.isRequired, +} + export default SubmitPage diff --git a/app/components/component-submit/src/upload.js b/app/components/component-submit/src/upload.js index 2a33bc2cddca6daa657d46249bbb2e2d3e43754a..8e4eb1d50af7f61e6d56d2c8bab1f0701b3e5883 100644 --- a/app/components/component-submit/src/upload.js +++ b/app/components/component-submit/src/upload.js @@ -3,6 +3,8 @@ import request from 'pubsweet-client/src/helpers/api' import gql from 'graphql-tag' import currentRolesVar from '../../../shared/currentRolesVar' +const urlFrag = config.journal.metadata.toplevel_urlfragment + const generateTitle = name => name .replace(/[_-]+/g, ' ') // convert hyphens/underscores to space @@ -86,6 +88,7 @@ const createManuscriptMutation = gql` const uploadPromise = (files, client) => { const [file] = files + if (files.length > 1) { throw new Error('Only one manuscript file can be uploaded') } @@ -127,6 +130,7 @@ const createManuscriptPromise = ( let source let title let files = [] + if (file) { source = typeof response === 'string' ? response : undefined title = extractTitle(response) || generateTitle(file.name) @@ -182,7 +186,7 @@ const createManuscriptPromise = ( const redirectPromise = (setConversionState, journals, history, data) => { setConversionState(() => ({ converting: false, completed: true })) - const route = `/journal/versions/${data.createManuscript.id}/submit` + const route = `${urlFrag}/versions/${data.createManuscript.id}/submit` // redirect after a short delay window.setTimeout(() => { history.push(route) @@ -205,10 +209,12 @@ export default ({ setConversion({ converting: true }) let manuscriptData let uploadResponse + try { if (files) { const [file] = files const { data } = await uploadPromise(files, client) + if (skipXSweet(file)) { uploadResponse = { fileURL: data.upload.url, @@ -217,6 +223,7 @@ export default ({ } else { uploadResponse = await DocxToHTMLPromise(file, data) } + manuscriptData = await createManuscriptPromise( file, client, @@ -234,6 +241,7 @@ export default ({ undefined, ) } + return redirectPromise( setConversion, journals, @@ -243,4 +251,6 @@ export default ({ } catch (error) { setConversion({ error }) } + + return false } diff --git a/app/routes.js b/app/routes.js index f427249776032b4e3f582529b04a75874a947118..e9a6019e4e806cc24332e41477c776da6da6f368 100644 --- a/app/routes.js +++ b/app/routes.js @@ -1,6 +1,7 @@ import React from 'react' import { Route, Switch } from 'react-router-dom' +import config from 'config' import Login from './components/component-login/src' import AdminPage from './components/AdminPage' @@ -13,7 +14,7 @@ import { export default ( <Switch> {/* AdminPage has nested routes within */} - <Route path="/journal"> + <Route path={config.journal.metadata.toplevel_urlfragment}> <AdminPage /> </Route> <Route component={Login} path="/login" /> diff --git a/config/default.js b/config/default.js index f6f93f7ed11e9e7d0d9bc2bc71381b355c94bcf5..478b96d0c4be5e9bf9eeb24b2b2836ffe5b2d56c 100644 --- a/config/default.js +++ b/config/default.js @@ -136,7 +136,7 @@ module.exports = { }, 'pubsweet-client': { API_ENDPOINT: '/api', - 'login-redirect': '/journal/dashboard', + 'login-redirect': `${journal.metadata.toplevel_urlfragment}/dashboard`, theme: process.env.PUBSWEET_THEME, baseUrl: deferConfig(cfg => { const { protocol, host, port } = cfg['pubsweet-client'] diff --git a/config/journal/metadata.js b/config/journal/metadata.js index 47074571b820d9123bb8fdec3442a8e024c0a669..27f8f71e190c7291482462f08f08b45523558de7 100644 --- a/config/journal/metadata.js +++ b/config/journal/metadata.js @@ -1,4 +1,5 @@ module.exports = { issn: '0000-0001', - name: 'Aperture', + name: 'Kotahi', + toplevel_urlfragment: '/kotahi', } diff --git a/package.json b/package.json index 7f2f19396249c3509d587b7e6dcb97662c206f41..a3d47bd9313ccb2f9b7f0413af697bd073bc6f2c 100644 --- a/package.json +++ b/package.json @@ -79,11 +79,13 @@ "config": "3.3.2", "faker": "4.1.0", "font-awesome": "4.7.0", + "formik": "^2.2.6", "fs-extra": "4.0.3", "got": "11.7.0", "graphql": "14.7.0", "graphql-middleware": "4.0.2", "graphql-shield": "^7.5.0", + "graphql-tag": "^2.11.0", "graphql-tools": "4.0.8", "history": "4.10.1", "jimp": "0.16.1", @@ -110,14 +112,17 @@ "react": "16.13.1", "react-dom": "16.13.1", "react-dropzone": "10.2.2", + "react-hot-loader": "^4.13.0", "react-html-parser": "2.0.2", "react-image": "4.0.3", "react-markdown": "4.3.1", "react-mentions": "3.3.2", "react-moment": "0.9.7", "react-router-dom": "5.2.0", + "react-router-prop-types": "^1.0.5", "react-select1": "npm:react-select@1.3.0", "react-visibility-sensor": "5.1.1", + "regenerator-runtime": "^0.13.7", "styled-components": "4.4.1", "supertest": "3.4.2", "wax-prosemirror-core": "0.0.10", @@ -168,7 +173,6 @@ "mini-css-extract-plugin": "0.11.2", "node-wait-for-it": "^0.2.0", "nodemon": "^2.0.6", - "react-hot-loader": "4.13.0", "react-router-redux": "5.0.0-alpha.9", "sass-loader": "6.0.7", "speed-measure-webpack-plugin": "1.3.3", diff --git a/public/logo-kotahi.png b/public/logo-kotahi.png new file mode 100644 index 0000000000000000000000000000000000000000..369d79aa7c859f414475c8d08bb9bd95f53316c3 Binary files /dev/null and b/public/logo-kotahi.png differ diff --git a/yarn.lock b/yarn.lock index 15dbf3c4c1309a76ff32a9c6658d54dbb8ac1088..ca6a6470166d42649b3b17b397b45799cd914f83 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4292,7 +4292,7 @@ resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.7.3": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== @@ -10757,6 +10757,19 @@ formik@^1.4.2, formik@^2.0.0: tiny-warning "^1.0.2" tslib "^1.10.0" +formik@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" + integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== + dependencies: + deepmerge "^2.1.1" + hoist-non-react-statics "^3.3.0" + lodash "^4.17.14" + lodash-es "^4.17.14" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.10.0" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -18246,7 +18259,7 @@ react-helmet-async@^1.0.2: react-fast-compare "^3.2.0" shallowequal "^1.1.0" -react-hot-loader@4.13.0: +react-hot-loader@^4.13.0: version "4.13.0" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.13.0.tgz#c27e9408581c2a678f5316e69c061b226dc6a202" integrity sha512-JrLlvUPqh6wIkrK2hZDfOyq/Uh/WeVEr8nc7hkn2/3Ul0sx1Kr5y4kOGNacNRoj7RhwLNcQ3Udf1KJXrqc0ZtA== @@ -18390,6 +18403,14 @@ react-router-dom@5.2.0, react-router-dom@^5.0.0, react-router-dom@^5.2.0: tiny-invariant "^1.0.2" tiny-warning "^1.0.0" +react-router-prop-types@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/react-router-prop-types/-/react-router-prop-types-1.0.5.tgz#2e671d8412a793106bf70dc15c9ecc83ea4bc15b" + integrity sha512-q1xlFU2ol2U5zeVbA5hyBuxD3scHenqgMgCTuJQUanA2SyG8A3Fb1S6DleOo1cnGJB5Q05hnLge64kRj+xsuPA== + dependencies: + "@types/prop-types" "^15.7.3" + prop-types "^15.7.2" + react-router-redux@5.0.0-alpha.9: version "5.0.0-alpha.9" resolved "https://registry.yarnpkg.com/react-router-redux/-/react-router-redux-5.0.0-alpha.9.tgz#825431516e0e6f1fd93b8807f6bd595e23ec3d10"