diff --git a/app/Root.jsx b/app/Root.jsx index a5d986e00184a2ea4f36252602fab6ef270e5914..a9129436c9989311c4458aa6cb64dbf087af9709 100644 --- a/app/Root.jsx +++ b/app/Root.jsx @@ -1,26 +1,43 @@ /* eslint-disable no-param-reassign */ import React from 'react' +// import { BrowserRouter } from 'react-router-dom' +// import PropTypes from 'prop-types' +// import { ThemeProvider } from 'styled-components' +// import { ApolloProvider } from '@apollo/react-components' +// import { ApolloClient } from 'apollo-client' +// import { WebSocketLink } from 'apollo-link-ws' +// import { split, ApolloLink } from 'apollo-link' +// import { getMainDefinition } from 'apollo-utilities' +// import { setContext } from 'apollo-link-context' +// import { +// InMemoryCache, +// IntrospectionFragmentMatcher, +// } from 'apollo-cache-inmemory' +// import { createUploadLink } from 'apollo-upload-client' +import GlobalStyle from './theme/elements/GlobalStyle' + +// import introspectionQueryResultData from './fragmentTypes.json' + import { BrowserRouter } from 'react-router-dom' import PropTypes from 'prop-types' import { ThemeProvider } from 'styled-components' -import { ApolloProvider } from '@apollo/react-components' -import { ApolloClient } from 'apollo-client' -import { WebSocketLink } from 'apollo-link-ws' -import { split, ApolloLink } from 'apollo-link' -import { getMainDefinition } from 'apollo-utilities' -import { setContext } from 'apollo-link-context' import { - InMemoryCache, - IntrospectionFragmentMatcher, -} from 'apollo-cache-inmemory' + ApolloProvider, + ApolloClient, + ApolloLink, + split, + gql, +} from '@apollo/client' +// import { ApolloClient } from 'apollo-client' +import { WebSocketLink } from '@apollo/client/link/ws' +// import { split, ApolloLink } from 'apollo-link' +import { getMainDefinition } from '@apollo/client/utilities' +import { setContext } from '@apollo/client/link/context' +import { InMemoryCache } from '@apollo/client/cache' import { createUploadLink } from 'apollo-upload-client' -import GlobalStyle from './theme/elements/GlobalStyle' -import introspectionQueryResultData from './fragmentTypes.json' - -const fragmentMatcher = new IntrospectionFragmentMatcher({ - introspectionQueryResultData, -}) +import { GET_CURRENT_USER } from './queries' +import currentRolesVar from './shared/currentRolesVar' // See https://github.com/apollographql/apollo-feature-requests/issues/6#issuecomment-465305186 export function stripTypenames(obj) { @@ -83,7 +100,25 @@ const makeApolloClient = (makeConfig, connectToWebSocket) => { } const config = { link, - cache: new InMemoryCache({ fragmentMatcher }), + cache: new InMemoryCache({ + possibleTypes: { + Identity: ['LocalIdentity', 'ExternalIdentity'], + }, + typePolicies: { + Manuscript: { + fields: { + _currentRoles: { + read(existing, { cache, args, readField }) { + const currentRoles = currentRolesVar() + const currentId = readField('id') + const r = currentRoles.find(r => r.id === currentId) + return (r && r.roles) || [] + }, + }, + }, + }, + }, + }), } return new ApolloClient(makeConfig ? makeConfig(config) : config) } diff --git a/app/app.js b/app/app.js index a2b58f454fc55c7c4f7b8ed7f3bd3eba4fb2dd6f..03597a268478f7cacbb2bf01c2cf25f89823327e 100644 --- a/app/app.js +++ b/app/app.js @@ -9,7 +9,7 @@ import theme from './theme' import { JournalProvider } from './components/xpub-journal' import { XpubProvider } from './components/xpub-with-context' -import * as journal from './config/journal' +import * as journal from '../config/journal' import routes from './routes' const history = createBrowserHistory() diff --git a/app/components/AdminPage.js b/app/components/AdminPage.js index fef8ac26882c663b4deba29a80dc9fa7dc2f4dae..8b71c88b2037cad8fe2480d5c27682d25c239d7e 100644 --- a/app/components/AdminPage.js +++ b/app/components/AdminPage.js @@ -1,7 +1,7 @@ -import React, { useContext } from 'react' +import React, { useContext, useCallback, useRef } from 'react' import styled from 'styled-components' import { compose } from 'recompose' -import { useQuery, useApolloClient } from '@apollo/react-hooks' +import { useQuery, useApolloClient, gql } from '@apollo/client' import { withRouter, matchPath, @@ -27,9 +27,12 @@ import FormBuilderPage from '../components/component-formbuilder/src/components/ import NewSubmissionPage from '../components/component-submit/src/components/NewSubmissionPage' import { Profile } from '../components/component-profile/src' -import queries from '../graphql' +import { GET_CURRENT_USER } from '../queries' import Menu from './Menu' +import { Spinner } from './shared' + +import currentRolesVar from '../shared/currentRolesVar' const getParams = routerPath => { const path = '/journal/versions/:version' @@ -67,15 +70,46 @@ const PrivateRoute = ({ component: Component, ...rest }) => ( /> ) +const updateStuff = data => { + currentRolesVar(data.currentUser._currentRoles) + console.log('updating stuff') +} + const AdminPage = ({ children, history, match }) => { const client = useApolloClient() const journal = useContext(JournalContext) const [conversion] = useContext(XpubContext) - const { data } = useQuery(queries.currentUser) + // Get the current user every 5 seconds (this includes authorization info) + const { loading, error, data } = useQuery(GET_CURRENT_USER, { + pollInterval: 5000, + notifyOnNetworkStatusChange: true, + fetchPolicy: 'network-only', + // TODO: useCallback used because of bug: https://github.com/apollographql/apollo-client/issues/6301 + onCompleted: useCallback(data => updateStuff(data), []), + }) + + const previousDataRef = useRef(null) + + // Do this to prevent polling-related flicker + if (loading && !previousDataRef.current) { + return <Spinner /> + } + + let notice = '' + if (error) { + if (error.networkError) { + notice = 'You are offline.' + } else { + return <Redirect to="/login" /> + } + } + const currentUser = data && data.currentUser + previousDataRef.current = data + const { pathname } = history.location const showLinks = pathname.match(/^\/(submit|manuscript)/g) let links = [] @@ -119,6 +153,7 @@ const AdminPage = ({ children, history, match }) => { brandLink="/journal/dashboard" loginLink="/login?next=/journal/dashboard" navLinkComponents={links} + notice={notice} user={currentUser} /> <Switch> diff --git a/app/components/Menu.js b/app/components/Menu.js index ffb5e2e8fca6f51c210e3b4fd44b3889f5038b92..ea09642f9aa5a4876005873d9fdfa2e110b66658 100644 --- a/app/components/Menu.js +++ b/app/components/Menu.js @@ -108,11 +108,18 @@ const UserInfo = styled.div` margin-left: ${grid(1)}; ` -const Menu = ({ className, loginLink = '/login', navLinkComponents, user }) => ( +const Menu = ({ + className, + loginLink = '/login', + navLinkComponents, + user, + notice, +}) => ( <Root className={className}> <Section> + {/* TODO: Place this notice (used for offline notification) better */} + {notice} <UserComponent loginLink={loginLink} user={user} /> - {navLinkComponents && navLinkComponents.map((navInfo, idx) => ( <Item @@ -129,7 +136,7 @@ const UserComponent = ({ user, loginLink }) => ( <Section> {user && ( <UserItem to="/journal/profile"> - <UserAvatar user={user} size={64} /> + <UserAvatar size={64} user={user} /> <UserInfo> {user.defaultIdentity.name || user.username} {/* ({user.username}) */} @@ -137,7 +144,7 @@ const UserComponent = ({ user, loginLink }) => ( </UserInfo> </UserItem> )} - {!user && <Item name="Login" link={loginLink} />} + {!user && <Item link={loginLink} name="Login" />} </Section> ) diff --git a/app/components/component-avatar/src/UserAvatar.js b/app/components/component-avatar/src/UserAvatar.js index 55f93f0e121c12507954bd131d6bd4a06cd6283c..1ea54d142ee4d74b6371e9db7cfa1bd055d74c18 100644 --- a/app/components/component-avatar/src/UserAvatar.js +++ b/app/components/component-avatar/src/UserAvatar.js @@ -1,6 +1,6 @@ // @flow import * as React from 'react' -import { useQuery } from '@apollo/react-hooks' +import { useQuery } from '@apollo/client' import styled from 'styled-components' // import { GET_USER } from '../../queries' // import { UserHoverProfile } from 'src/components/hoverProfile'; diff --git a/app/components/component-dashboard/src/components/Dashboard.js b/app/components/component-dashboard/src/components/Dashboard.js index 4edda6a4ee0b34c86cb195adb771939be5093c29..dc8adb343ef36e9d5c1e00d9525db6a41e39c6ee 100644 --- a/app/components/component-dashboard/src/components/Dashboard.js +++ b/app/components/component-dashboard/src/components/Dashboard.js @@ -1,5 +1,5 @@ import React from 'react' -import { useQuery, useMutation } from '@apollo/react-hooks' +import { useQuery, useMutation } from '@apollo/client' import { Action, Button, Icon } from '@pubsweet/ui' // import Authorize from 'pubsweet-client/src/helpers/Authorize' @@ -11,6 +11,7 @@ import { Heading, Content, HeadingWithAction, + Placeholder, } from '../style' import EditorItem from './sections/EditorItem' import OwnerItem from './sections/OwnerItem' @@ -23,6 +24,8 @@ import { SectionContent, } from '../../../shared' +import hasRole from '../../../../shared/hasRole' + const updateReviewer = (proxy, { data: { reviewerResponse } }) => { const id = reviewerResponse.object.objectId const data = proxy.readQuery({ @@ -45,7 +48,7 @@ const Dashboard = ({ history, ...props }) => { // const uploadManuscript = upload() // const [conversion] = useContext(XpubContext) - const { loading, data } = useQuery(queries.dashboard) + const { loading, data, error } = useQuery(queries.dashboard) const [reviewerRespond] = useMutation(mutations.reviewerResponseMutation, { // variables: { currentUserId, action, teamId }, update: updateReviewer, @@ -65,27 +68,37 @@ const Dashboard = ({ history, ...props }) => { }) if (loading) return <Spinner /> - + if (error) return error const dashboard = (data && data.manuscripts) || [] const currentUser = data && data.currentUser + const mySubmissions = dashboard.filter(submission => + hasRole(submission, 'author'), + ) + + const toReview = dashboard.filter(submission => + hasRole(submission, 'reviewer'), + ) + + const manuscriptsImEditorOf = dashboard.filter(submission => + hasRole(submission, ['seniorEditor', 'handlingEditor']), + ) + return ( <Container> <HeadingWithAction> <Heading>Dashboard</Heading> <Button onClick={() => history.push('/journal/newSubmission')} primary> - {/* <Icon>plus</Icon> */}+ New submission + + New submission </Button> </HeadingWithAction> - {!dashboard.length && <Section>Nothing to do at the moment.</Section>} - {/* <Authorize object={dashboard} operation="can view my submission section"> */} - {dashboard.length > 0 ? ( - <SectionContent> - <SectionHeader> - <Title>My Submissions</Title> - </SectionHeader> - {dashboard.map(submission => ( + <SectionContent> + <SectionHeader> + <Title>My Submissions</Title> + </SectionHeader> + {dashboard.length > 0 ? ( + mySubmissions.map(submission => ( <SectionRow key={`submission-${submission.id}`}> <OwnerItem deleteManuscript={() => @@ -97,18 +110,17 @@ const Dashboard = ({ history, ...props }) => { version={submission} /> </SectionRow> - ))} - </SectionContent> - ) : null} - {/* </Authorize> - <Authorize object={dashboard} operation="can view review section"> */} - {dashboard.length > 0 ? ( - <SectionContent> - <SectionHeader> - <Title>To Review</Title> - </SectionHeader> - - {dashboard.map(review => ( + )) + ) : ( + <Placeholder>You have not submitted any manuscripts yet</Placeholder> + )} + </SectionContent> + <SectionContent> + <SectionHeader> + <Title>To Review</Title> + </SectionHeader> + {toReview.length > 0 ? ( + toReview.map(review => ( <SectionRow key={review.id}> <ReviewerItem currentUser={currentUser} @@ -117,25 +129,30 @@ const Dashboard = ({ history, ...props }) => { version={review} /> </SectionRow> - ))} - </SectionContent> - ) : null} - {/* </Authorize> */} + )) + ) : ( + <Placeholder>You have not been assigned any reviews yet</Placeholder> + )} + </SectionContent> - {/* <Authorize object={dashboard} operation="can view my manuscripts section"> */} - {dashboard.length > 0 ? ( - <SectionContent> - <SectionHeader> - <Title>Manuscripts I'm editor of</Title> - </SectionHeader> - {dashboard.map(manuscript => ( + <SectionContent> + <SectionHeader> + <Title>Manuscripts I'm editor of</Title> + </SectionHeader> + {manuscriptsImEditorOf.length > 0 ? ( + manuscriptsImEditorOf.map(manuscript => ( <SectionRow key={`manuscript-${manuscript.id}`}> <EditorItem version={manuscript} /> </SectionRow> - ))} - </SectionContent> - ) : null} - {/* </Authorize> */} + )) + ) : ( + <SectionRow> + <Placeholder> + You are not an editor of any manuscript yet + </Placeholder> + </SectionRow> + )} + </SectionContent> </Container> ) } diff --git a/app/components/component-dashboard/src/components/Reviews.js b/app/components/component-dashboard/src/components/Reviews.js index 5a70702a70d667910401e01fc0fe27555c37d599..7d5356b147554d02569171398a50da6cf06dea34 100644 --- a/app/components/component-dashboard/src/components/Reviews.js +++ b/app/components/component-dashboard/src/components/Reviews.js @@ -29,7 +29,7 @@ const getUserFromTeam = (version, role) => { } const countStatus = (version, status) => { - const teamMember = getUserFromTeam(version, 'reviewerEditor') + const teamMember = getUserFromTeam(version, 'reviewer') if (status === 'rejected' || status === 'invited') { return sumBy(teamMember, member => (member.status === status ? 1 : 0)) diff --git a/app/components/component-dashboard/src/components/sections/OwnerItem.js b/app/components/component-dashboard/src/components/sections/OwnerItem.js index 0fb2012fcb0e85eda8b2a511e89bb6e317ff314b..a548bd1caf887aad14d44baa455531c7277aa182 100644 --- a/app/components/component-dashboard/src/components/sections/OwnerItem.js +++ b/app/components/component-dashboard/src/components/sections/OwnerItem.js @@ -47,16 +47,15 @@ const OwnerItem = ({ version, journals, deleteManuscript }) => { ) return ( - // <Authorize object={[version]} operation="can view my submission section"> <Item> <div> {' '} <StatusBadge minimal status={version.status} /> + {JSON.stringify(version._currentRoles)} <VersionTitle version={version} /> </div> {actions} </Item> - // </Authorize> ) } diff --git a/app/components/component-dashboard/src/components/sections/ReviewerItem.js b/app/components/component-dashboard/src/components/sections/ReviewerItem.js index 344c1bfaae713d10ff07c3c570930f7865096902..ff657d39a4080fcd8069779d46fc016fb788c528 100644 --- a/app/components/component-dashboard/src/components/sections/ReviewerItem.js +++ b/app/components/component-dashboard/src/components/sections/ReviewerItem.js @@ -20,7 +20,7 @@ import VersionTitle from './VersionTitle' const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => { const team = - (version.teams || []).find(team => team.role === 'reviewerEditor') || {} + (version.teams || []).find(team => team.role === 'reviewer') || {} const currentMember = team.members && @@ -29,7 +29,7 @@ const ReviewerItem = ({ version, journals, currentUser, reviewerRespond }) => { // Enable that when Team Models is updated // const { status } = - // getUserFromTeam(version, 'reviewerEditor').filter( + // getUserFromTeam(version, 'reviewer').filter( // member => member.id === currentUser.id, // )[0] || {} diff --git a/app/components/component-dashboard/src/graphql/queries/index.js b/app/components/component-dashboard/src/graphql/queries/index.js index 86cf636dfb4e1f90a57a84030dffee966be88164..1b45a6149ad022cf6d15961d793e6e9f506e7d68 100644 --- a/app/components/component-dashboard/src/graphql/queries/index.js +++ b/app/components/component-dashboard/src/graphql/queries/index.js @@ -59,6 +59,7 @@ export default { date } } + _currentRoles @client } } `, diff --git a/app/components/component-dashboard/src/style.js b/app/components/component-dashboard/src/style.js index da125ae71d89f1d274aba4fb617aefc0b5b5cb4e..a26994e622dc676cb09a5e561ae11289cfed7049 100644 --- a/app/components/component-dashboard/src/style.js +++ b/app/components/component-dashboard/src/style.js @@ -76,3 +76,11 @@ export const HeadingWithAction = styled.div` ` export { StatusBadge } from '../../shared' + +export const Placeholder = styled.div` + display: grid; + place-items: center; + color: ${th('colorTextPlaceholder')}; + height: 100%; + padding: 4em; +` diff --git a/app/components/component-formbuilder/src/components/FormBuilderPage.js b/app/components/component-formbuilder/src/components/FormBuilderPage.js index 57c20bb5a80f40658b4f3cb38418e76f26360ea3..9cfb45edbf88a31a577329ef35ea428ac5dfa572 100644 --- a/app/components/component-formbuilder/src/components/FormBuilderPage.js +++ b/app/components/component-formbuilder/src/components/FormBuilderPage.js @@ -1,5 +1,5 @@ import { compose, withState, withHandlers, withProps } from 'recompose' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import { withLoader } from 'pubsweet-client' diff --git a/app/components/component-login/src/Login.jsx b/app/components/component-login/src/Login.jsx index dab9a14201a4eb806691b39b532ff2d2b730251d..9fd99f091f431bfbd4b8bbba646a120f0c80cc52 100644 --- a/app/components/component-login/src/Login.jsx +++ b/app/components/component-login/src/Login.jsx @@ -7,6 +7,7 @@ import { th, grid, lighten } from '@pubsweet/ui-toolkit' import { CenteredColumn, H1, Button } from '@pubsweet/ui' import styled from 'styled-components' import { Section } from '../../shared' +import { Placeholder } from '../../component-chat/src/Messages/style' const getNextUrl = () => { const url = new URL(window.location.href) @@ -56,7 +57,11 @@ const LoginButton = styled(Button)` // TODO: Shared? const Container = styled.div` - background: linear-gradient(134deg, ${th('colorPrimary')}, ${lighten('colorPrimary', 0.3)}); + background: linear-gradient( + 134deg, + ${th('colorPrimary')}, + ${lighten('colorPrimary', 0.3)} + ); height: 100vh; display: grid; place-items: center; @@ -66,35 +71,39 @@ const Content = styled.div` border-radius: ${th('borderRadius')}; box-shadow: ${th('boxShadow')}; padding: ${grid(4)}; - max-width: 30em; + max-width: 40em; background: ${th('colorBackground')}; text-align: center; h1 { margin-bottom: ${grid(2)}; } + margin-bottom: 1rem; ` +const Centered = styled.div` + text-align: center; +` -const ORCIDIcon = ({className}) => ( +const ORCIDIcon = ({ className }) => ( <span className={className}> - <svg viewBox="0 0 256 256"> - <path - d="M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z" - fill="#A6CE39" - /> - <g> - <path d="M86.3,186.2H70.9V79.1h15.4v48.4V186.2z" fill="#FFFFFF" /> - <path - d="M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5 c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z" - fill="#FFFFFF" - /> + <svg viewBox="0 0 256 256"> <path - d="M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1 C84.2,46.7,88.7,51.3,88.7,56.8z" - fill="#FFFFFF" + d="M256,128c0,70.7-57.3,128-128,128C57.3,256,0,198.7,0,128C0,57.3,57.3,0,128,0C198.7,0,256,57.3,256,128z" + fill="#A6CE39" /> - </g> - </svg> + <g> + <path d="M86.3,186.2H70.9V79.1h15.4v48.4V186.2z" fill="#FFFFFF" /> + <path + d="M108.9,79.1h41.6c39.6,0,57,28.3,57,53.6c0,27.5-21.5,53.6-56.8,53.6h-41.8V79.1z M124.3,172.4h24.5 c34.9,0,42.9-26.5,42.9-39.7c0-21.5-13.7-39.7-43.7-39.7h-23.7V172.4z" + fill="#FFFFFF" + /> + <path + d="M88.7,56.8c0,5.5-4.5,10.1-10.1,10.1c-5.6,0-10.1-4.6-10.1-10.1c0-5.6,4.5-10.1,10.1-10.1 C84.2,46.7,88.7,51.3,88.7,56.8z" + fill="#FFFFFF" + /> + </g> + </svg> </span> ) @@ -123,23 +132,31 @@ const Login = ({ logo = null, ...props }) => { return <Redirect to={redirectLink} /> } + const journalName = config.journal.metadata.name return redirectLink ? ( <Redirect to={redirectLink} /> ) : ( <Container> - <Content> - <H1>Login to Kotahi</H1> - - Kotahi uses ORCID <StyledORCIDIcon /> to identify authors and staff. Login with your - ORCID account below or{' '} + <Centered> + <Content> + {journalName === 'Aperture' && ( + <img 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> - <LoginButton onClick={() => window.location = "/auth/orcid"} primary> - Login with ORCID - </LoginButton> - </Content> - + <LoginButton + onClick={() => (window.location = '/auth/orcid')} + primary + > + Login with ORCID + </LoginButton> + </Content> + <div>Powered by Kotahi</div> + </Centered> </Container> ) } -export default Login \ No newline at end of file +export default Login diff --git a/app/components/component-manuscript/src/components/ManuscriptPage.js b/app/components/component-manuscript/src/components/ManuscriptPage.js index 39387f32b092fbca0da81276b225aebd08a28eef..2d1847f503119b2b5ea93811d549324d4b1a34c1 100644 --- a/app/components/component-manuscript/src/components/ManuscriptPage.js +++ b/app/components/component-manuscript/src/components/ManuscriptPage.js @@ -1,5 +1,5 @@ import { compose, withProps } from 'recompose' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import { withLoader } from 'pubsweet-client' diff --git a/app/components/component-manuscripts/src/Manuscript.jsx b/app/components/component-manuscripts/src/Manuscript.jsx index 95493e7e2e6b66e6edc164e2ede0698991555261..53e0e7f1350fd3c40f52e63d332960310af2674a 100644 --- a/app/components/component-manuscripts/src/Manuscript.jsx +++ b/app/components/component-manuscripts/src/Manuscript.jsx @@ -1,6 +1,6 @@ import React from 'react' import gql from 'graphql-tag' -import { useMutation } from '@apollo/react-hooks' +import { useMutation } from '@apollo/client' // import { Action } from '@pubsweet/ui' import { UserAvatar } from '../../component-avatar/src' import { diff --git a/app/components/component-manuscripts/src/Manuscripts.jsx b/app/components/component-manuscripts/src/Manuscripts.jsx index f3d89a8d8efebf2d252851ec5c6c2c5be33054de..dd3dee928e6f6d45e9e95c9b37c98c6e438ca810 100644 --- a/app/components/component-manuscripts/src/Manuscripts.jsx +++ b/app/components/component-manuscripts/src/Manuscripts.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import gql from 'graphql-tag' -import { useQuery } from '@apollo/react-hooks' +import { useQuery } from '@apollo/client' import Manuscript from './Manuscript' import { diff --git a/app/components/component-profile/src/ChangeUsername.jsx b/app/components/component-profile/src/ChangeUsername.jsx index ea707cca30d53ca3a4b0f81e695661779c9a4397..6c981758d7d287104baa484b98804e245b2693e8 100644 --- a/app/components/component-profile/src/ChangeUsername.jsx +++ b/app/components/component-profile/src/ChangeUsername.jsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import PropTypes from 'prop-types' import gql from 'graphql-tag' -import { useMutation } from '@apollo/react-hooks' +import { useMutation } from '@apollo/client' import { TextField, Button } from '@pubsweet/ui' import { th } from '@pubsweet/ui-toolkit' import styled from 'styled-components' diff --git a/app/components/component-profile/src/Profile.jsx b/app/components/component-profile/src/Profile.jsx index 957788567f94c52a5a6d3681283dffa57616cadd..a137940b612b238a9e0cd3986bba83dadf09d2be 100644 --- a/app/components/component-profile/src/Profile.jsx +++ b/app/components/component-profile/src/Profile.jsx @@ -3,7 +3,7 @@ import { Button, Action } from '@pubsweet/ui' // import { th } from '@pubsweet/ui-toolkit' // import styled from 'styled-components' import gql from 'graphql-tag' -import { useQuery } from '@apollo/react-hooks' +import { useQuery } from '@apollo/client' import { useDropzone } from 'react-dropzone' import { Spinner } from '../../shared' diff --git a/app/components/component-review/src/components/DecisionPage.js b/app/components/component-review/src/components/DecisionPage.js index 9e532ee08eb41b0f5064d8d1aa4db54a33d4426e..f8768a6bf649c9564dbd13cfaa27b57168b140ed 100644 --- a/app/components/component-review/src/components/DecisionPage.js +++ b/app/components/component-review/src/components/DecisionPage.js @@ -3,8 +3,7 @@ import moment from 'moment' import { Tabs } from '@pubsweet/ui' import { Formik } from 'formik' -import gql from 'graphql-tag' -import { useMutation, useQuery } from '@apollo/react-hooks' +import { useMutation, useQuery, gql } from '@apollo/client' import DecisionForm from './decision/DecisionForm' import DecisionReviews from './decision/DecisionReviews' import AssignEditorsReviewers from './assignEditors/AssignEditorsReviewers' diff --git a/app/components/component-review/src/components/ReviewPage.js b/app/components/component-review/src/components/ReviewPage.js index 0acc2b86393b5c5387175031afeebbf782cb053c..cc8aef0165d5c4682248a6a55837c50dc9b436f1 100644 --- a/app/components/component-review/src/components/ReviewPage.js +++ b/app/components/component-review/src/components/ReviewPage.js @@ -1,9 +1,7 @@ import React from 'react' -// import { compose, withProps } from 'recompose' -import { useMutation, useQuery } from '@apollo/react-hooks' +import { useMutation, useQuery } from '@apollo/client' import gql from 'graphql-tag' import { Formik } from 'formik' -// import { withLoader } from 'pubsweet-client' import { cloneDeep } from 'lodash' import { getCommentContent } from './review/util' import ReviewLayout from '../components/review/ReviewLayout' @@ -247,8 +245,8 @@ export default ({ match, ...props }) => { const status = ( ( - (manuscript.teams.find(team => team.role === 'reviewerEditor') || {}) - .status || [] + (manuscript.teams.find(team => team.role === 'reviewer') || {}).status || + [] ).find(status => status.user === currentUser.id) || {} ).status @@ -311,7 +309,7 @@ export default ({ match, ...props }) => { const completeReview = history => { const team = cloneDeep(manuscript.teams).find( - team => team.role === 'reviewerEditor', + team => team.role === 'reviewer', ) team.members = team.members.map(m => { if (m.user.id === currentUser.id) { @@ -434,7 +432,7 @@ export default ({ match, ...props }) => { // ) || {}, // status: ( // ( -// (manuscript.teams.find(team => team.role === 'reviewerEditor') || {}) +// (manuscript.teams.find(team => team.role === 'reviewer') || {}) // .status || [] // ).find(status => status.user === currentUser.id) || {} // ).status, @@ -495,7 +493,7 @@ export default ({ match, ...props }) => { // }), // completeReview: history => { // const team = cloneDeep(manuscript.teams).find( -// team => team.role === 'reviewerEditor', +// team => team.role === 'reviewer', // ) // team.members = team.members.map(m => { // if (m.user.id === currentUser.id) { diff --git a/app/components/component-review/src/components/ReviewersPage.js b/app/components/component-review/src/components/ReviewersPage.js index f94c6319f0fffc6c2293c7ce7cc4eb36521b5eb0..6331ff1f7fb497afcc8ad5f89de5ad5bb67da522 100644 --- a/app/components/component-review/src/components/ReviewersPage.js +++ b/app/components/component-review/src/components/ReviewersPage.js @@ -1,6 +1,6 @@ import { compose, withProps } from 'recompose' import { withFormik } from 'formik' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import { withLoader } from 'pubsweet-client' import { omit } from 'lodash' @@ -142,15 +142,14 @@ const handleSubmit = ( { user }, { props: { manuscript, updateTeamMutation, createTeamMutation, match } }, ) => { - const team = - manuscript.teams.find(team => team.role === 'reviewerEditor') || {} + const team = manuscript.teams.find(team => team.role === 'reviewer') || {} const teamAdd = { objectId: manuscript.id, objectType: 'Manuscript', // status: [{ user: user.id, status: 'invited' }], - name: 'Reviewer Editor', - role: 'reviewerEditor', + name: 'Reviewers', + role: 'reviewer', members: [{ user: { id: user.id }, status: 'invited' }], } if (team.id) { @@ -207,7 +206,7 @@ export default compose( const reviewersTeam = teams.find( team => - team.role === 'reviewerEditor' && + team.role === 'reviewer' && team.object.objectId === manuscript.id && team.object.objectType === 'Manuscript', ) || {} diff --git a/app/components/component-review/src/components/assignEditors/AssignEditor.js b/app/components/component-review/src/components/assignEditors/AssignEditor.js index a7f634cd130b0888176aa0557558ff2af8e42b00..51cd4ab4d846021f406c697da7a401ad354a768d 100644 --- a/app/components/component-review/src/components/assignEditors/AssignEditor.js +++ b/app/components/component-review/src/components/assignEditors/AssignEditor.js @@ -3,7 +3,7 @@ import config from 'config' import { compose, withProps } from 'recompose' import { cloneDeep, get } from 'lodash' import { Menu } from '@pubsweet/ui' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import { withLoader } from 'pubsweet-client' diff --git a/app/components/component-review/src/components/assignEditors/AssignEditor.md b/app/components/component-review/src/components/assignEditors/AssignEditor.md deleted file mode 100644 index 49085706311761ea72d1fda712a31c5b85ccf935..0000000000000000000000000000000000000000 --- a/app/components/component-review/src/components/assignEditors/AssignEditor.md +++ /dev/null @@ -1,92 +0,0 @@ -A drop-down menu for assigning an editor to a project. - -```js -const { JournalProvider } = require('xpub-journal') -const journal = require('@pubsweet/styleguide/config/journal') - -const project = { - id: faker.random.uuid(), -} - -const team = { - members: [], -} - -const manuscriptTemplate = () => ({ - id: faker.random.uuid(), - teams: [ - { - id: faker.random.uuid(), - role: 'reviewerEditor', - name: 'reviewer', - object: { - id: faker.random.uuid(), - __typename: 'Manuscript', - }, - objectType: 'manuscript', - members: [ - { - user: { id: 1 }, - }, - ], - }, - ], - meta: { - title: faker.lorem.sentence(25), - abstract: faker.lorem.sentence(100), - articleType: 'original-research', - declarations: { - openData: 'yes', - openPeerReview: 'no', - preregistered: 'yes', - previouslySubmitted: 'yes', - researchNexus: 'no', - streamlinedReview: 'no', - }, - }, - decision: { - id: faker.random.uuid(), - comments: [{ type: 'note', content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - status: '<p>This is a decision</p>', - user: { id: 1 }, - }, - reviews: [ - { - comments: [{ content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - recommendation: 'revise', - user: { id: 1, username: 'test user' }, - }, - ], -}) - -const manuscript = Object.assign({}, manuscriptTemplate()) - -const options = [ - { - value: faker.random.uuid(), - label: faker.internet.userName(), - }, - { - value: faker.random.uuid(), - label: faker.internet.userName(), - }, - { - value: faker.random.uuid(), - label: faker.internet.userName(), - }, -] -;<JournalProvider journal={journal}> - <AssignEditor - manuscript={manuscript} - team={team} - teamName="Senior Editor" - teamTypeName="seniorEditor" - options={options} - addUserToTeam={value => console.log(value)} - /> -</JournalProvider> -``` diff --git a/app/components/component-review/src/components/decision/DecisionReviews.js b/app/components/component-review/src/components/decision/DecisionReviews.js index 0416fbc03ac441be6e0fa2baddf97b13b6ce6a74..38f957a6b0490d4c0a89ba14becc7f3212cbfa25 100644 --- a/app/components/component-review/src/components/decision/DecisionReviews.js +++ b/app/components/component-review/src/components/decision/DecisionReviews.js @@ -5,14 +5,13 @@ import { H1, Action } from '@pubsweet/ui' // TODO: read reviewer ordinal and name from project reviewer // const { status } = -// getUserFromTeam(manuscript, 'reviewerEditor').filter( +// getUserFromTeam(manuscript, 'reviewer').filter( // member => member.user.id === currentUser.id, // )[0] || {} // return status const getCompletedReviews = (manuscript, currentUser) => { - const team = - manuscript.teams.find(team => team.role === 'reviewerEditor') || {} + const team = manuscript.teams.find(team => team.role === 'reviewer') || {} if (!team.members) { return null } diff --git a/app/components/component-review/src/components/metadata/ReviewMetadata.md b/app/components/component-review/src/components/metadata/ReviewMetadata.md deleted file mode 100644 index 7666b612eec17aaec702fcc2e584472effbb85d2..0000000000000000000000000000000000000000 --- a/app/components/component-review/src/components/metadata/ReviewMetadata.md +++ /dev/null @@ -1,63 +0,0 @@ -Project metadata, displayed at the top of the review form. - -```js -const manuscriptTemplate = () => ({ - id: faker.random.uuid(), - teams: [ - { - id: faker.random.uuid(), - role: 'reviewerEditor', - name: 'Reviewer', - object: { - id: faker.random.uuid(), - __typename: 'Manuscript', - }, - objectType: 'manuscript', - members: [ - { - user: { - id: 1, - username: 'test user', - }, - status: 'accepted', - }, - ], - }, - ], - meta: { - title: faker.lorem.sentence(25), - abstract: faker.lorem.sentence(100), - articleType: 'original-research', - declarations: { - openData: 'yes', - openPeerReview: 'no', - preregistered: 'yes', - previouslySubmitted: 'yes', - researchNexus: 'no', - streamlinedReview: 'no', - }, - }, - decision: { - id: faker.random.uuid(), - comments: [{ type: 'note', content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - status: '<p>This is a decision</p>', - user: { id: 1 }, - }, - reviews: [ - { - comments: [{ content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - recommendation: 'revise', - user: { id: 1, username: 'test user' }, - }, - ], -}) - -const manuscript = Object.assign({}, manuscriptTemplate(), { - manuscriptVersions: [manuscriptTemplate()], -}) -;<ReviewMetadata manuscript={manuscript} /> -``` diff --git a/app/components/component-review/src/components/reviewers/ReviewerForm.md b/app/components/component-review/src/components/reviewers/ReviewerForm.md deleted file mode 100644 index 28ba3f44b2e30c6e50ac6e0bf0c41100539edef3..0000000000000000000000000000000000000000 --- a/app/components/component-review/src/components/reviewers/ReviewerForm.md +++ /dev/null @@ -1,40 +0,0 @@ -A form for inviting a reviewer to a version of a project. - -```js -const { withFormik } = require('formik') - -const reviewerUsers = [ - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, -] - -const loadOptions = input => { - // TODO: filter users - - return Promise.resolve({ options: reviewerUsers }) -} - -const ConnectedReviewerForm = withFormik({ - initialValues: {}, - mapPropsToValues: ({ manuscript }) => manuscript, - displayName: 'reviewers', - handleSubmit: () => {}, -})(ReviewerForm) -;<ConnectedReviewerForm - loadOptions={loadOptions} - form={{ values: { teams: [] } }} -/> -``` diff --git a/app/components/component-review/src/components/reviewers/Reviewers.md b/app/components/component-review/src/components/reviewers/Reviewers.md deleted file mode 100644 index af59ba8a78bd87cf531da1aca607646a3701194b..0000000000000000000000000000000000000000 --- a/app/components/component-review/src/components/reviewers/Reviewers.md +++ /dev/null @@ -1,146 +0,0 @@ -On the reviewers page, the handling editor can: - -- Search users by entering a username or email address. -- Add a user as a reviewer of this version (which also adds them as a reviewer of the project, if not already present). -- View a list of reviewers of this version and perform actions on each reviewer. - -```js -const { withFormik } = require('formik') -const { compose, withHandlers } = require('recompose') -const Reviewer = require('./Reviewer').default -const ReviewerForm = require('./ReviewerForm').default - -const journal = { - id: faker.random.uuid(), - reviewers: [ - { - id: faker.random.uuid(), - user: faker.random.uuid(), - }, - ], -} - -const manuscriptTemplate = () => ({ - id: faker.random.uuid(), - teams: [ - { - id: faker.random.uuid(), - role: 'reviewerEditor', - name: 'reviewer', - object: { - id: faker.random.uuid(), - __typename: 'Manuscript', - }, - objectType: 'manuscript', - members: [ - { - user: { id: 1 }, - }, - ], - }, - ], - meta: { - title: faker.lorem.sentence(25), - abstract: faker.lorem.sentence(100), - articleType: 'original-research', - declarations: { - openData: 'yes', - openPeerReview: 'no', - preregistered: 'yes', - previouslySubmitted: 'yes', - researchNexus: 'no', - streamlinedReview: 'no', - }, - }, - decision: { - id: faker.random.uuid(), - comments: [{ type: 'note', content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - status: '<p>This is a decision</p>', - user: { id: 1 }, - }, - reviews: [ - { - comments: [{ content: 'this needs review' }], - created: 'Thu Oct 11 2018', - open: false, - recommendation: 'revise', - user: { id: 1, username: 'test user' }, - }, - ], -}) - -const manuscript = Object.assign({}, manuscriptTemplate()) - -const reviewers = [ - { - status: 'invited', - user: { id: 1, username: 'test user' }, - }, -] - -const reviewerUsers = [ - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, - { - id: faker.random.uuid(), - email: faker.internet.email(), - username: faker.internet.userName(), - }, -] - -initialState = { - reviewers, -} - -const ReviewerFormContainer = compose( - withFormik({ - form: 'reviewers', - handleSubmit: ({ user }) => { - setState({ - reviewers: state.reviewers.concat({ - id: faker.random.uuid(), - reviewer: faker.random.uuid(), - events: { - invited: new Date().toISOString(), - }, - _user: user, - _reviewer: { - ordinal: null, - }, - }), - }) - }, - }), - withHandlers({ - loadOptions: props => input => - Promise.resolve({ options: props.reviewerUsers }), - }), -)(ReviewerForm) - -const ReviewerContainer = withHandlers({ - removeReviewer: props => () => - setState({ - reviewers: state.reviewers.filter( - reviewer => reviewer.id !== props.reviewer.id, - ), - }), -})(Reviewer) -;<Reviewers - ReviewerForm={ReviewerFormContainer} - Reviewer={ReviewerContainer} - journal={journal} - manuscript={manuscript} - reviewers={state.reviewers} - reviewerUsers={reviewerUsers} -/> -``` diff --git a/app/components/component-submit/src/components/NewSubmissionPage.jsx b/app/components/component-submit/src/components/NewSubmissionPage.jsx index 96a65839dcbfe74e13b2316494d86e2a1dd7e067..2ce2b53f61730cc79c8d318ae341cf6313c0cdce 100644 --- a/app/components/component-submit/src/components/NewSubmissionPage.jsx +++ b/app/components/component-submit/src/components/NewSubmissionPage.jsx @@ -1,5 +1,5 @@ import React from 'react' -import { useQuery, useMutation, ApolloConsumer } from '@apollo/react-hooks' +import { useQuery, useMutation, ApolloConsumer } from '@apollo/client' // import Authorize from 'pubsweet-client/src/helpers/Authorize' import config from 'config' diff --git a/app/components/component-submit/src/components/SubmitPage.js b/app/components/component-submit/src/components/SubmitPage.js index 53814f4ee0947c7b40f0e9d7168d8eb4d098a4b6..6bd3e8adab5575cc305e712363ed75d521aafcf0 100644 --- a/app/components/component-submit/src/components/SubmitPage.js +++ b/app/components/component-submit/src/components/SubmitPage.js @@ -1,6 +1,6 @@ import { debounce, cloneDeep, isEmpty, set } from 'lodash' import { compose, withProps, withState, withHandlers } from 'recompose' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import { withFormik } from 'formik' import { withLoader } from 'pubsweet-client' diff --git a/app/components/component-teams-manager/src/components/TeamsManagerPage.js b/app/components/component-teams-manager/src/components/TeamsManagerPage.js index 58a52ce95a69843da8618505e8b050107e7cb468..9a4c7cd09b5e6a982bbc196665cd1bc6813261ae 100644 --- a/app/components/component-teams-manager/src/components/TeamsManagerPage.js +++ b/app/components/component-teams-manager/src/components/TeamsManagerPage.js @@ -1,7 +1,7 @@ import { compose } from 'recompose' import { omit } from 'lodash' import config from 'config' -import { graphql } from '@apollo/react-hoc' +import { graphql } from '@apollo/client/react/hoc' import gql from 'graphql-tag' import queries from './graphql/queries' diff --git a/app/components/component-users-manager/src/User.jsx b/app/components/component-users-manager/src/User.jsx index 0d6341ba0017c25159be5b1c12285c708d1cbbae..31b144cbcfbed6efb2fd052f2f7974ea21a6e66b 100644 --- a/app/components/component-users-manager/src/User.jsx +++ b/app/components/component-users-manager/src/User.jsx @@ -1,6 +1,6 @@ import React from 'react' import gql from 'graphql-tag' -import { useMutation } from '@apollo/react-hooks' +import { useMutation } from '@apollo/client' import { Action } from '@pubsweet/ui' import { UserAvatar } from '../../component-avatar/src' import { Row, Cell, LastCell } from './style' diff --git a/app/components/component-users-manager/src/UsersManager.jsx b/app/components/component-users-manager/src/UsersManager.jsx index 83392a18c3d0eee04abc4bd0887dc74a60235678..5a09925c7978a4b2f0839064440df7e8c0540a9f 100644 --- a/app/components/component-users-manager/src/UsersManager.jsx +++ b/app/components/component-users-manager/src/UsersManager.jsx @@ -1,6 +1,6 @@ import React, { useState } from 'react' import gql from 'graphql-tag' -import { useQuery } from '@apollo/react-hooks' +import { useQuery } from '@apollo/client' // import { Heading } from '@pubsweet/ui' import User from './User' diff --git a/app/graphql/index.js b/app/graphql/index.js deleted file mode 100644 index b980b81b6a6225ecbaa97d7ccc6576e7243d1afa..0000000000000000000000000000000000000000 --- a/app/graphql/index.js +++ /dev/null @@ -1,20 +0,0 @@ -// TODO: Combine this with app/queries/index - -import gql from 'graphql-tag' - -export default { - currentUser: gql` - { - currentUser { - id - username - admin - profilePicture - defaultIdentity { - aff - name - } - } - } - `, -} diff --git a/app/hooks/useCurrentUser.js b/app/hooks/useCurrentUser.js index 5a5ec77ce07fd2d226772200140dc16e0c81074f..6477060da8aa262b9b05798792ebe0205ab33c9b 100644 --- a/app/hooks/useCurrentUser.js +++ b/app/hooks/useCurrentUser.js @@ -1,4 +1,4 @@ -import { useQuery } from '@apollo/react-hooks' +import { useQuery } from '@apollo/client' import { GET_CURRENT_USER } from '../queries' diff --git a/app/queries/index.js b/app/queries/index.js index 8aaf5dbb875500c0bcb0f6b06f7587f81ade91f2..17d6b3ba206f39b9addd2edd475969fc85f5c51a 100644 --- a/app/queries/index.js +++ b/app/queries/index.js @@ -6,6 +6,7 @@ export const GET_CURRENT_USER = gql` id profilePicture username + admin defaultIdentity { aff name @@ -17,6 +18,11 @@ export const GET_CURRENT_USER = gql` email } } + online + _currentRoles { + id + roles + } } } ` diff --git a/app/shared/currentRolesVar.js b/app/shared/currentRolesVar.js new file mode 100644 index 0000000000000000000000000000000000000000..7278c2abd93fc6a0f6a784c56b0682472910ca92 --- /dev/null +++ b/app/shared/currentRolesVar.js @@ -0,0 +1,5 @@ +import { makeVar } from '@apollo/client' + +const currenRolesVar = makeVar([]) + +export default currenRolesVar diff --git a/app/shared/hasRole.js b/app/shared/hasRole.js new file mode 100644 index 0000000000000000000000000000000000000000..23a73b87ff2ef9413884e3ed0e4ca726b12063e9 --- /dev/null +++ b/app/shared/hasRole.js @@ -0,0 +1,11 @@ +const hasRole = (obj, role) => { + const currentRoles = (obj && obj._currentRoles) || [] + + // When multiple roles are provided it acts as an 'OR' + if (Array.isArray(role)) { + return role.some(r => currentRoles.includes(r)) + } + return currentRoles.includes(role) +} + +export default hasRole diff --git a/app/theme/index.js b/app/theme/index.js index 18926d361975fae7334586fcdac531a0be98550b..9ecac132a259087b9e7994fcc02f54b879b89b1e 100644 --- a/app/theme/index.js +++ b/app/theme/index.js @@ -17,7 +17,7 @@ const cokoTheme = { /* Colors */ colorBackground: 'white', colorSecondaryBackground: '#f9fafb', // custom - colorPrimary: '#0D83DD', + colorPrimary: '#3AAE2A', colorSecondary: '#9e9e9e', colorFurniture: '#E8E8E8', colorBorder: '#AAA',