diff --git a/packages/component-faraday-ui/src/AppBar.js b/packages/component-faraday-ui/src/AppBar.js index bca602b1cbb7b7e7df0730b95263d32dd29f81d9..c4899581113d8e68eeef1e47606d1f0e765d10a7 100644 --- a/packages/component-faraday-ui/src/AppBar.js +++ b/packages/component-faraday-ui/src/AppBar.js @@ -1,49 +1,58 @@ -import React from 'react' +import React, { Fragment } from 'react' import { get } from 'lodash' import styled from 'styled-components' import { H2, Button } from '@pubsweet/ui' import { th } from '@pubsweet/ui-toolkit' import { compose, setDisplayName, withProps } from 'recompose' -import { Item } from 'pubsweet-component-faraday-ui' +import { Item, Row, Text } from 'pubsweet-component-faraday-ui' const AppBar = ({ logo: Logo, menu: Menu, createDraft, - canCreateDraft, + canCreateDraft = true, currentUser = {}, fixed = true, isSubmit, autosave: Autosave, journal: { metadata: { backgroundImage, nameText } }, }) => ( - <Root fixed={fixed}> - {backgroundImage && <JournalBackground img={backgroundImage} />} - <LogoContainer> - <Logo /> - </LogoContainer> - <RightContainer> - <Item mr={2}> - <Autosave /> - </Item> - {createDraft && - !isSubmit && - currentUser.user && ( - <Button - data-test="new-manuscript" - disabled={!canCreateDraft} - ml={2} - mr={5} - onClick={createDraft} - primary - size="small" - > - SUBMIT - </Button> - )} - <Menu /> - </RightContainer> - </Root> + <Fragment> + <Root fixed={fixed}> + {backgroundImage && <JournalBackground img={backgroundImage} />} + <LogoContainer> + <Logo /> + </LogoContainer> + <RightContainer> + <Item mr={2}> + <Autosave /> + </Item> + {createDraft && + !isSubmit && + currentUser.user && ( + <Button + data-test="new-manuscript" + disabled={!canCreateDraft} + ml={2} + mr={5} + onClick={createDraft} + primary + size="small" + > + SUBMIT + </Button> + )} + <Menu /> + </RightContainer> + </Root> + {!canCreateDraft && ( + <RibbonRow bgColor={th('colorInfo')} fixed={fixed}> + <Text pb={1 / 2} pt={1}> + Your account is not confirmed. Please check your email. + </Text> + </RibbonRow> + )} + </Fragment> ) AppBar.defaultProps = { @@ -127,4 +136,9 @@ const Root = styled.div` z-index: 1; } ` + +const RibbonRow = styled(Row)` + position: ${props => (props.fixed ? 'fixed' : 'relative')}; + top: ${props => (props.fixed ? th('appBar.height') : '0')}; +` // #endregion diff --git a/packages/component-faraday-ui/src/AppBar.md b/packages/component-faraday-ui/src/AppBar.md index 1c1b61c6d055c07bd71995d536f977f0304ad0af..49d8caf19e0c34f49c73d564d41d0bee7c07a981 100644 --- a/packages/component-faraday-ui/src/AppBar.md +++ b/packages/component-faraday-ui/src/AppBar.md @@ -8,6 +8,7 @@ const currentUser = { firstName: 'Alex', lastName: 'Munteanu', }, + isAuthenticated: true, } const autosave = { @@ -33,11 +34,11 @@ const MenuComponent = () => ( const AutosaveComponent = () => ( <AutosaveIndicator isVisible autosave={autosave} /> ) - ;<AppBar autosave={AutosaveComponent} logo={HindawiLogo} menu={MenuComponent} + currentUser={currentUser} journal={{ metadata: { nameText: 'Chemistry Awesomeness', @@ -58,7 +59,9 @@ const currentUser = { username: 'cocojambo', firstName: 'Alex', lastName: 'Munteanu', + isConfirmed: true, }, + isAuthenticated: true, } const HindawiLogo = () => ( @@ -85,11 +88,11 @@ const MenuComponent = () => ( const AutosaveComponent = () => ( <AutosaveIndicator isVisible autosave={autosave} /> ) - ;<AppBar autosave={AutosaveComponent} logo={HindawiLogo} menu={MenuComponent} + currentUser={currentUser} journal={{ metadata: { nameText: 'Chemistry Awesomeness', @@ -104,11 +107,13 @@ With submit manuscript button ```js const currentUser = { user: { - admin: true, + admin: false, username: 'cocojambo', firstName: 'Alex', lastName: 'Munteanu', + isConfirmed: true, }, + isAuthenticated: true, } const HindawiLogo = () => ( @@ -135,11 +140,65 @@ const MenuComponent = () => ( const AutosaveComponent = () => ( <AutosaveIndicator isVisible autosave={autosave} /> ) +;<AppBar + autosave={AutosaveComponent} + logo={HindawiLogo} + menu={MenuComponent} + currentUser={currentUser} + createDraft={() => alert('Submit manuscript')} + journal={{ + metadata: { + nameText: 'Chemistry Awesomeness', + }, + }} + fixed={false} +/> +``` + +With account not confirmed + +```js +const currentUser = { + user: { + admin: false, + username: 'cocojambo', + firstName: 'Alex', + lastName: 'Munteanu', + isConfirmed: true, + }, + isAuthenticated: true, +} + +const HindawiLogo = () => ( + <Logo + onClick={() => console.log('Hindawi best publish!')} + title="Hindawi" + src="https://upload.wikimedia.org/wikipedia/en/thumb/c/ca/Hindawi.svg/1200px-Hindawi.svg.png" + /> +) +const autosave = { + isFetching: false, + lastUpdate: new Date(), +} + +const MenuComponent = () => ( + <AppBarMenu + currentUser={currentUser} + goTo={path => console.log(`navigating to ${path}`)} + logout={() => console.log('logging out')} + /> +) + +const AutosaveComponent = () => ( + <AutosaveIndicator isVisible autosave={autosave} /> +) ;<AppBar autosave={AutosaveComponent} logo={HindawiLogo} menu={MenuComponent} + currentUser={currentUser} + canCreateDraft={false} createDraft={() => alert('Submit manuscript')} journal={{ metadata: { diff --git a/packages/component-faraday-ui/src/ManuscriptCard.js b/packages/component-faraday-ui/src/ManuscriptCard.js index 0b8520fe6ee537b0b9c4e155db3b992332e1bd3e..c0834e3b7345ea14a6a3f80ed999d850c98f52a8 100644 --- a/packages/component-faraday-ui/src/ManuscriptCard.js +++ b/packages/component-faraday-ui/src/ManuscriptCard.js @@ -124,7 +124,7 @@ const MainContainer = styled.div` flex-direction: column; padding: calc(${th('gridUnit')} * 2); padding-bottom: ${th('gridUnit')}; - width: 100%; + width: calc(100% - (${th('gridUnit')} * 5 / 2)); ${Row} { [data-tooltipped] { diff --git a/packages/component-faraday-ui/src/gridItems/Item.js b/packages/component-faraday-ui/src/gridItems/Item.js index fa8ac7f6f2dd2becb7c2926aa18953b3c1f05604..222c607c9fa1de5e21b6b99c1bd6a8a6c5f109bd 100644 --- a/packages/component-faraday-ui/src/gridItems/Item.js +++ b/packages/component-faraday-ui/src/gridItems/Item.js @@ -10,7 +10,7 @@ export default styled.div.attrs({ display: flex; flex: ${({ flex }) => (isNumber(flex) ? flex : 1)}; flex-direction: ${({ vertical }) => (vertical ? 'column' : 'row')}; - flex-wrap: wrap; + flex-wrap: ${props => props.flexWrap || 'initial'}; justify-content: ${({ justify }) => justify || 'initial'}; align-items: ${({ alignItems }) => alignItems || 'initial'}; diff --git a/packages/component-faraday-ui/src/gridItems/Row.js b/packages/component-faraday-ui/src/gridItems/Row.js index 5030d4e48e428ad75b8c20d9b3f1a7c9cc801ee3..349abc8eee03c924aa219fa1ee4f4e1c41f9a8e4 100644 --- a/packages/component-faraday-ui/src/gridItems/Row.js +++ b/packages/component-faraday-ui/src/gridItems/Row.js @@ -10,7 +10,7 @@ export default styled.div.attrs({ align-items: ${props => get(props, 'alignItems', 'flex-start')}; background-color: ${props => props.bgColor || 'transparent'}; display: flex; - flex-wrap: wrap; + flex-wrap: ${props => props.flexWrap || 'initial'}; flex-direction: row; justify-content: ${({ justify }) => justify || 'space-evenly'}; height: ${props => get(props, 'height', 'auto')}; diff --git a/packages/component-publons/.gitignore b/packages/component-publons/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..59ff5cd1f687b2ad8339cb159a605672cbc76340 --- /dev/null +++ b/packages/component-publons/.gitignore @@ -0,0 +1,9 @@ +_build/ +api/ +logs/ +node_modules/ +uploads/ +.env.* +.env +config/local*.* +public/apidoc \ No newline at end of file diff --git a/packages/component-publons/README.md b/packages/component-publons/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a54c7ef61b096eae3e96f52c5228ab9c0e1adaa0 --- /dev/null +++ b/packages/component-publons/README.md @@ -0,0 +1,26 @@ +# Publons Component + +### Retrieve a list of suggested reviewers [GET] + +This endpoint allow to call the Publons API and get a list of suggested reviewers. + +#### Publons Reviewers Request + +`GET /api/fragments/:fragmentId/publons` + +| URI Parameter | Requiered | Requirements | Description | +| ------------- | --------- | ------------ | ------------------ | +| fragmentId | No | String | The ID of the fragment | + +#### Publons Reviewers Response + +```javascript +HTTP/1.1 200 OK +{ + "name": "Bradley Bundy", + "profileUrl": "https://publons.com/author/1249425/bradley-bundy", + "reviews": 16, + "institution": "Brigham Young University", + "email": "bundy@byu.edu" + } +``` \ No newline at end of file diff --git a/packages/component-publons/apidoc.json b/packages/component-publons/apidoc.json new file mode 100644 index 0000000000000000000000000000000000000000..6483df8bf42a56e7987a8d41c9fb643695a1a4fb --- /dev/null +++ b/packages/component-publons/apidoc.json @@ -0,0 +1,8 @@ +{ + "name": "Component Publons API documentation", + "version": "0.0.1", + "description": "A list of APIs for the Publons Component", + "template": { + "forceLanguage": "en" + } +} diff --git a/packages/component-publons/config/authsome-mode.js b/packages/component-publons/config/authsome-mode.js new file mode 100644 index 0000000000000000000000000000000000000000..9c663beae1962d9fc13141f8439dc0a84214ae08 --- /dev/null +++ b/packages/component-publons/config/authsome-mode.js @@ -0,0 +1,3 @@ +const authsomeMode = require('xpub-faraday/config/authsome-mode') + +module.exports = authsomeMode diff --git a/packages/component-publons/config/default.js b/packages/component-publons/config/default.js new file mode 100644 index 0000000000000000000000000000000000000000..9950c9b354fa8710a4f043a07ac2b86a3cf6bd2f --- /dev/null +++ b/packages/component-publons/config/default.js @@ -0,0 +1,3 @@ +const defaultConfig = require('xpub-faraday/config/default') + +module.exports = defaultConfig diff --git a/packages/component-publons/config/test.js b/packages/component-publons/config/test.js new file mode 100644 index 0000000000000000000000000000000000000000..9950c9b354fa8710a4f043a07ac2b86a3cf6bd2f --- /dev/null +++ b/packages/component-publons/config/test.js @@ -0,0 +1,3 @@ +const defaultConfig = require('xpub-faraday/config/default') + +module.exports = defaultConfig diff --git a/packages/component-publons/index.js b/packages/component-publons/index.js new file mode 100644 index 0000000000000000000000000000000000000000..42baa8ee9e58e3707967c281f66eaa34668476d8 --- /dev/null +++ b/packages/component-publons/index.js @@ -0,0 +1,5 @@ +module.exports = { + backend: () => app => { + require('./src/Publons')(app) + }, +} diff --git a/packages/component-publons/package.json b/packages/component-publons/package.json new file mode 100644 index 0000000000000000000000000000000000000000..c478a960ee0a1d9520f236fde80aceccf5a5e0e0 --- /dev/null +++ b/packages/component-publons/package.json @@ -0,0 +1,41 @@ +{ + "name": "pubsweet-component-publons", + "version": "0.0.1", + "description": "publons component for pubsweet", + "license": "MIT", + "author": "Collaborative Knowledge Foundation", + "files": [ + "src" + ], + "main": "index.js", + "scripts": { + "test": "jest", + "docs": "./node_modules/.bin/apidoc -e \"(node_modules|public)\" -o public/apidoc", + "open-docs": "open public/apidoc/index.html" + }, + "repository": { + "type": "git", + "url": "https://gitlab.coko.foundation/xpub/xpub-faraday", + "path": "component-publons" + }, + "dependencies": { + "axios": "^0.18.0", + "body-parser": "^1.17.2" + }, + "peerDependencies": { + "@pubsweet/logger": "^0.0.1", + "pubsweet-server": "^1.0.1" + }, + "devDependencies": { + "apidoc": "^0.17.6", + "jest": "^22.1.1", + "supertest": "^3.0.0" + }, + "jest": { + "verbose": true, + "testRegex": "/src/.*.test.js$" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/component-publons/src/Publons.js b/packages/component-publons/src/Publons.js new file mode 100644 index 0000000000000000000000000000000000000000..05ff6c1e02629c6b583a8b8aa2967e76ea46069d --- /dev/null +++ b/packages/component-publons/src/Publons.js @@ -0,0 +1,35 @@ +const bodyParser = require('body-parser') + +const Publons = app => { + app.use(bodyParser.json()) + const basePath = '/api/fragments/:fragmentId/publons' + const routePath = './routes/publons' + const authBearer = app.locals.passport.authenticate('bearer', { + session: false, + }) + /** + * @api {get} /api/fragments/:fragmentId/publons List publons reviewers + * @apiGroup Publons + * @apiParam {id} fragmentId Fragment id + * @apiSuccessExample {json} Success + * HTTP/1.1 200 OK + * [{ + * "name": "Bradley Bundy", + * "profileUrl": "https://publons.com/author/1249425/bradley-bundy", + * "reviews": 16, + * "institution": "Brigham Young University", + * "email": "bundy@byu.edu" + * }] + * @apiErrorExample {json} List errors + * HTTP/1.1 403 Forbidden + * HTTP/1.1 400 Bad Request + * HTTP/1.1 404 Not Found + */ + app.get( + `${basePath}`, + authBearer, + require(`${routePath}/get`)(app.locals.models), + ) +} + +module.exports = Publons diff --git a/packages/component-publons/src/routes/publons/get.js b/packages/component-publons/src/routes/publons/get.js new file mode 100644 index 0000000000000000000000000000000000000000..6bbfa752523b4a6ed330e5972c4cbbc590b5077d --- /dev/null +++ b/packages/component-publons/src/routes/publons/get.js @@ -0,0 +1,93 @@ +const config = require('config') + +const axios = require('axios') +const logger = require('@pubsweet/logger') + +const { + Team, + services, + Fragment, + authsome: authsomeHelper, +} = require('pubsweet-component-helper-service') +const helpers = require('./helpers') + +const publonsKey = config.get('publons.key') +const publonsUrl = config.get('publons.reviewersUrl') + +module.exports = models => async (req, res) => { + const { fragmentId } = req.params + + try { + const fragment = await models.Fragment.find(fragmentId) + + const authsome = authsomeHelper.getAuthsome(models) + const target = { + fragment, + path: req.route.path, + } + + const canGet = await authsome.can(req.user, 'GET', target) + + if (!canGet) { + return res.status(403).json({ + error: 'Unauthorized.', + }) + } + + const fragmentHelper = new Fragment({ fragment }) + const teamHelper = new Team({ + TeamModel: models.Team, + fragmentId: fragment.id, + }) + const parsedFragment = await fragmentHelper.getFragmentData() + const memberIds = await teamHelper.getTeamMembers({ + role: 'reviewer', + objectType: 'fragment', + }) + + const existingReviewers = await Promise.all( + memberIds.map(id => models.User.find(id)), + ) + + const authors = fragment.authors.map(fa => ({ + email: fa.email, + firstName: fa.firstName, + lastName: fa.lastName, + })) + + const postBody = { + searchArticle: { + title: parsedFragment.title, + abstract: parsedFragment.abstract, + journal: { + name: 'Research', + }, + authors, + }, + } + + try { + const { data: publonsReviewers } = await axios({ + method: 'post', + url: publonsUrl, + data: postBody, + headers: { 'x-apikey': publonsKey }, + }) + + const reviewers = helpers.parseReviewers({ + publonsReviewers, + existingReviewers, + }) + + res.status(200).json(reviewers) + } catch (e) { + logger.error(e.message) + res.status(e.response.status).json(e.response.statusText) + } + } catch (e) { + const notFoundError = await services.handleNotFoundError(e, 'Fragment') + return res.status(notFoundError.status).json({ + error: notFoundError.message, + }) + } +} diff --git a/packages/component-publons/src/routes/publons/helpers.js b/packages/component-publons/src/routes/publons/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..06c756695bf9424237275b162e220b7e3da940ea --- /dev/null +++ b/packages/component-publons/src/routes/publons/helpers.js @@ -0,0 +1,18 @@ +module.exports = { + parseReviewers: ({ publonsReviewers, existingReviewers }) => + publonsReviewers + .filter(rev => rev.profileUrl && rev.contact.emails.length > 0) + .filter( + rev => + !existingReviewers.find( + exRev => exRev.email === rev.contact.emails[0].email, + ), + ) + .map(reviewer => ({ + email: reviewer.contact.emails[0].email, + name: reviewer.publishingName, + profileUrl: reviewer.profileUrl, + reviews: reviewer.numVerifiedReviews, + affiliation: reviewer.recentOrganizations[0].name, + })), +} diff --git a/packages/component-publons/src/tests/publons/get.test.js b/packages/component-publons/src/tests/publons/get.test.js new file mode 100644 index 0000000000000000000000000000000000000000..f531a626f439ea9380734e91cccdb8c985fce5a7 --- /dev/null +++ b/packages/component-publons/src/tests/publons/get.test.js @@ -0,0 +1,59 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const cloneDeep = require('lodash/cloneDeep') +const fixturesService = require('pubsweet-component-fixture-service') +const requests = require('../requests') + +const { Model, fixtures } = fixturesService +// jest.mock('@pubsweet/component-send-email', () => ({ +// send: jest.fn(), +// })) + +const path = '../routes/publons/get' +const route = { + path: '/api/fragments/:fragmentId/publons', +} +describe('Publons route handler', () => { + let testFixtures = {} + let models + beforeEach(() => { + testFixtures = cloneDeep(fixtures) + models = Model.build(testFixtures) + }) + + it('should return an error when the fragment does not exist', async () => { + const { handlingEditor } = testFixtures.users + + const res = await requests.sendRequest({ + userId: handlingEditor.id, + route, + models, + path, + params: { + fragmentId: 'invalid-id', + }, + }) + expect(res.statusCode).toBe(404) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual(`Fragment not found`) + }) + + it('should return an error when a user does not have publons rights', async () => { + const { user } = testFixtures.users + const { fragment } = testFixtures.fragments + + const res = await requests.sendRequest({ + userId: user.id, + route, + models, + path, + params: { + fragmentId: fragment.id, + }, + }) + expect(res.statusCode).toBe(403) + const data = JSON.parse(res._getData()) + expect(data.error).toEqual('Unauthorized.') + }) +}) diff --git a/packages/component-publons/src/tests/publons/helpers.test.js b/packages/component-publons/src/tests/publons/helpers.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8d94f20cd6402f63385dea569582764b1724dc68 --- /dev/null +++ b/packages/component-publons/src/tests/publons/helpers.test.js @@ -0,0 +1,67 @@ +process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' +process.env.SUPPRESS_NO_CONFIG_WARNING = true + +const helpers = require('../../routes/publons/helpers') + +const publonsReviewers = [ + { + contact: { + emails: [{ email: 'email@email.com' }], + }, + publishingName: 'John Smith', + profileUrl: 'url', + numVerifiedReviews: 16, + recentOrganizations: [ + { + name: 'MIT', + }, + ], + }, + { + contact: { + emails: [{ email: 'email2@email.com' }], + }, + publishingName: 'Iures Marcel', + profileUrl: 'url', + numVerifiedReviews: 2, + recentOrganizations: [ + { + name: 'CalTech', + }, + ], + }, +] + +const existingReviewers = [ + { email: 'rev@email.com' }, + { email: 'rev2@email.com' }, +] + +describe('Publons helpers', () => { + it('should return a properly formatted list of publons reviewers', () => { + const reviewers = helpers.parseReviewers({ + publonsReviewers, + existingReviewers, + }) + + expect(reviewers).toHaveLength(2) + expect(reviewers[0]).toHaveProperty('affiliation') + expect(reviewers[0]).toHaveProperty('reviews') + expect(reviewers[0]).toHaveProperty('name') + expect(reviewers[0]).toHaveProperty('email') + expect(reviewers[0]).toHaveProperty('profileUrl') + }) + it('should return reviewers that do not already exist in the system', () => { + existingReviewers[0].email = publonsReviewers[0].contact.emails[0].email + const reviewers = helpers.parseReviewers({ + publonsReviewers, + existingReviewers, + }) + + expect(reviewers).toHaveLength(1) + const matchingReviewer = reviewers.find( + rev => rev.email === existingReviewers[0].email, + ) + expect(matchingReviewer).toBeUndefined() + }) +}) diff --git a/packages/component-publons/src/tests/requests.js b/packages/component-publons/src/tests/requests.js new file mode 100644 index 0000000000000000000000000000000000000000..6696fc7c91db5dff36caf192aae5ac5068fab506 --- /dev/null +++ b/packages/component-publons/src/tests/requests.js @@ -0,0 +1,24 @@ +const httpMocks = require('node-mocks-http') + +const sendRequest = async ({ + body = {}, + userId, + route, + models, + path, + params = {}, + query = {}, +}) => { + const req = httpMocks.createRequest({ + body, + }) + req.user = userId + req.route = route + req.params = params + req.query = query + const res = httpMocks.createResponse() + await require(path)(models)(req, res) + return res +} + +module.exports = { sendRequest } diff --git a/packages/component-user-manager/src/routes/fragmentsUsers/emails/emailCopy.js b/packages/component-user-manager/src/routes/fragmentsUsers/emails/emailCopy.js index 68132122b47e8b66bed920a01a9afcacc16ba8c6..32bb2f7ad628c799621a6159433471d61375f18f 100644 --- a/packages/component-user-manager/src/routes/fragmentsUsers/emails/emailCopy.js +++ b/packages/component-user-manager/src/routes/fragmentsUsers/emails/emailCopy.js @@ -1,11 +1,10 @@ const getEmailCopy = ({ emailType, titleText }) => { let paragraph switch (emailType) { - case 'author-added-to-manuscript': - paragraph = `You have been added as an author to ${titleText}. The manuscript will become visible on your dashboard once it's submitted. Please click on the link below to access your dashboard.` - break - case 'new-author-added-to-manuscript': - paragraph = `You have been added as an author to ${titleText}. In order to gain access to the manuscript, please confirm your account and set your account details by clicking on the link below.` + case 'submitting-author-added-by-admin': + paragraph = `${titleText}<br/> + Please verify your details by clicking the link below.<br/> + To confirm the submission and view the status of the manuscript, please verify your details by clicking the link below. <br/>` break default: throw new Error(`The ${emailType} email type is not defined.`) diff --git a/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js b/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js index f436f0f3d76697ef8d53338fbc53a3b38ef5ff86..e65d4175ed8b9c4135750f3a857a492adf46b9ff 100644 --- a/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js +++ b/packages/component-user-manager/src/routes/fragmentsUsers/emails/notifications.js @@ -14,113 +14,58 @@ const { getEmailCopy } = require('./emailCopy') module.exports = { async sendNotifications({ - user, baseUrl, fragment, - reqUserId, UserModel, collection, + submittingAuthor, }) { const fragmentHelper = new Fragment({ fragment }) const { title } = await fragmentHelper.getFragmentData({ handlingEditor: collection.handlingEditor, }) - const { submittingAuthor } = await fragmentHelper.getAuthorData({ - UserModel, - }) - - const titleText = `the manuscript titled "${title}" by ${ - submittingAuthor.firstName - } ${submittingAuthor.lastName}` const userHelper = new User({ UserModel }) - const subjectBaseText = `${collection.customId}: Manuscript` + const eicName = await userHelper.getEiCName() + + const titleText = `The manuscript titled "${title}" has been submitted to Hindawi by ${eicName}.` const email = new Email({ type: 'user', + toUser: { + email: submittingAuthor.email, + name: `${submittingAuthor.firstName} ${submittingAuthor.lastName}`, + }, content: { - ctaLink: baseUrl, - ctaText: 'VIEW DASHBOARD', - signatureName: await userHelper.getEiCName(), + ctaText: 'LOGIN', + signatureName: eicName, + subject: `Manuscript Submitted`, + ctaLink: services.createUrl(baseUrl, ''), + unsubscribeLink: services.createUrl(baseUrl, unsubscribeSlug, { + id: submittingAuthor.id, + }), }, }) - if (!user.isConfirmed) { - sendNewAuthorEmail({ - email, - user, - baseUrl, - titleText, - subjectBaseText, + if (!submittingAuthor.isConfirmed) { + email.content.ctaLink = services.createUrl(baseUrl, resetPath, { + email: submittingAuthor.email, + token: submittingAuthor.passwordResetToken, + firstName: submittingAuthor.firstName, + lastName: submittingAuthor.lastName, + affiliation: submittingAuthor.affiliation, + title: submittingAuthor.title, }) + email.content.ctaText = 'CONFIRM ACCOUNT' } - const requestUser = await UserModel.find(reqUserId) - if (requestUser.id !== user.id) { - sendAddedToManuscriptEmail({ - email, - baseUrl, - user, + const { html, text } = email.getBody({ + body: getEmailCopy({ + emailType: 'submitting-author-added-by-admin', titleText, - subjectBaseText, - }) - } - }, -} - -const sendAddedToManuscriptEmail = ({ - email, - baseUrl, - user, - titleText, - subjectBaseText, -}) => { - email.toUser = { - email: user.email, - name: `${user.firstName} ${user.lastName}`, - } - - email.content.subject = `${subjectBaseText} Created` - email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, { - id: user.id, - }) - - const { html, text } = email.getBody({ - body: getEmailCopy({ - emailType: 'author-added-to-manuscript', - titleText, - }), - }) - - email.sendEmail({ html, text }) -} - -const sendNewAuthorEmail = ({ email, baseUrl, user, titleText }) => { - email.toUser = { - email: user.email, - name: `${user.firstName} ${user.lastName}`, - } - - email.content.subject = `Confirm Your Account` - email.content.unsubscribeLink = services.createUrl(baseUrl, unsubscribeSlug, { - id: user.id, - }) - email.content.ctaLink = services.createUrl(baseUrl, resetPath, { - email: user.email, - token: user.passwordResetToken, - firstName: user.firstName, - lastName: user.lastName, - affiliation: user.affiliation, - title: user.title, - }) - email.content.ctaText = 'CONFIRM ACCOUNT' - - const { html, text } = email.getBody({ - body: getEmailCopy({ - emailType: 'new-author-added-to-manuscript', - titleText, - }), - }) + }), + }) - email.sendEmail({ html, text }) + email.sendEmail({ html, text }) + }, } diff --git a/packages/component-user-manager/src/routes/fragmentsUsers/post.js b/packages/component-user-manager/src/routes/fragmentsUsers/post.js index 7337aace0f394ee7c83561c91afef9f92472afc2..12744efedf5bcbd6b9f74192a44797260b57c0e7 100644 --- a/packages/component-user-manager/src/routes/fragmentsUsers/post.js +++ b/packages/component-user-manager/src/routes/fragmentsUsers/post.js @@ -8,6 +8,8 @@ const { authsome: authsomeHelper, } = require('pubsweet-component-helper-service') +const notifications = require('./emails/notifications') + const authorKeys = [ 'id', 'email', @@ -17,7 +19,6 @@ const authorKeys = [ 'affiliation', ] -// TODO: add authsome module.exports = models => async (req, res) => { const { email, role, isSubmitting, isCorresponding } = req.body @@ -50,6 +51,7 @@ module.exports = models => async (req, res) => { const UserModel = models.User const teamHelper = new Team({ TeamModel: models.Team, fragmentId }) const fragmentHelper = new Fragment({ fragment }) + const reqUser = await UserModel.find(req.user) try { let user = await UserModel.findByEmail(email) @@ -91,9 +93,16 @@ module.exports = models => async (req, res) => { collection.save() } - /* - TO DO: send email to SA when an Admin submits a manuscript on his behalf - */ + // send email to SA when an Admin submits a manuscript on his behalf + if ((reqUser.admin || reqUser.editorInChief) && isSubmitting) { + notifications.sendNotifications({ + fragment, + UserModel, + collection, + submittingAuthor: user, + baseUrl: services.getBaseUrl(req), + }) + } return res.status(200).json({ ...pick(user, authorKeys), @@ -125,9 +134,16 @@ module.exports = models => async (req, res) => { isCorresponding, }) - /* - TO DO: send email to SA when an Admin submits a manuscript on his behalf - */ + // send email to SA when an Admin submits a manuscript on his behalf + if ((reqUser.admin || reqUser.editorInChief) && isSubmitting) { + notifications.sendNotifications({ + fragment, + UserModel, + collection, + submittingAuthor: newUser, + baseUrl: services.getBaseUrl(req), + }) + } if (!collection.owners.includes(newUser.id)) { collection.owners.push(newUser.id) diff --git a/packages/components-faraday/src/components/Dashboard/DashboardItems.js b/packages/components-faraday/src/components/Dashboard/DashboardItems.js index 1239e6fa8bbb88c6420639e475caa3491a7830b5..2e6fe0eec9cd2fb9f3601c8e8ce656c05775af0f 100644 --- a/packages/components-faraday/src/components/Dashboard/DashboardItems.js +++ b/packages/components-faraday/src/components/Dashboard/DashboardItems.js @@ -56,6 +56,7 @@ export default compose( const Root = styled.div` height: calc(100vh - ${th('gridUnit')} * 21); overflow-y: auto; + padding-right: ${th('gridUnit')}; overflow-x: hidden; div[open] { width: auto; diff --git a/packages/hindawi-theme/src/index.js b/packages/hindawi-theme/src/index.js index ed50abf1ecd06215aa42d82277f4c616481e6752..10265c377adc7006e7da0b1052d4e4dd4b59b0e7 100644 --- a/packages/hindawi-theme/src/index.js +++ b/packages/hindawi-theme/src/index.js @@ -44,6 +44,7 @@ const hindawiTheme = { colorTextReverse: '#667080', colorTextPlaceholder: '#595959', colorWarning: '#007e92', // hack: we use this for action link icons + colorInfo: '#fcb74b', action: { color: '#007e92', diff --git a/packages/xpub-faraday/app/FaradayApp.js b/packages/xpub-faraday/app/FaradayApp.js index 68d625269c053530aded27bb8de7e6740c6c1464..bafd0256586ec9949c9b15ea0564704d321af443 100644 --- a/packages/xpub-faraday/app/FaradayApp.js +++ b/packages/xpub-faraday/app/FaradayApp.js @@ -1,7 +1,7 @@ import React from 'react' import { get } from 'lodash' import { connect } from 'react-redux' -import styled from 'styled-components' +import styled, { css } from 'styled-components' import { th } from '@pubsweet/ui-toolkit' import { actions } from 'pubsweet-client' import { withJournal } from 'xpub-journal' @@ -49,7 +49,12 @@ const App = ({ )} {...rest} /> - <MainContainer className="faraday-main">{children}</MainContainer> + <MainContainer + canCreateDraft={rest.canCreateDraft} + className="faraday-main" + > + {children} + </MainContainer> </Root> ) @@ -83,11 +88,21 @@ const Root = styled.div` } ` +const appBarPaddingHelper = props => + props.canCreateDraft + ? css` + padding: calc(${th('appBar.height')} + ${th('gridUnit')} * 2.5) + calc(${th('gridUnit')} * 12) calc(${th('gridUnit')} * 2); + ` + : css` + padding: calc(${th('appBar.height')} + ${th('gridUnit')} * 5) + calc(${th('gridUnit')} * 12) calc(${th('gridUnit')} * 2); + ` + const MainContainer = styled.div` display: flex; flex-direction: column; overflow-y: auto; - padding: calc(${th('appBar.height')} + ${th('gridUnit')} * 2.5) - calc(${th('gridUnit')} * 12) calc(${th('gridUnit')} * 2); + ${appBarPaddingHelper}; ` // #endregion diff --git a/packages/xpub-faraday/config/components.json b/packages/xpub-faraday/config/components.json index cfea7f64f6fcb4263f9aaeb690b8b28de9f3eb3b..bbb47dcc4bef0d9eb124126ee6368f5f21008374 100644 --- a/packages/xpub-faraday/config/components.json +++ b/packages/xpub-faraday/config/components.json @@ -10,5 +10,6 @@ "pubsweet-component-user-manager", "pubsweet-component-email", "pubsweet-component-manuscript", - "pubsweet-component-manuscript-manager" + "pubsweet-component-manuscript-manager", + "pubsweet-component-publons" ] diff --git a/packages/xpub-faraday/config/default.js b/packages/xpub-faraday/config/default.js index aebc901864fd5ce67fd587a4b69e4ee908129b3a..66a21abd7bac73846624ccc7f898e6a1500574ec 100644 --- a/packages/xpub-faraday/config/default.js +++ b/packages/xpub-faraday/config/default.js @@ -446,4 +446,8 @@ module.exports = { 'expression-of-concern': 'Expression of Concern', retraction: 'Retraction', }, + publons: { + key: process.env.PUBLONS_KEY || '', + reviewersUrl: 'https://api.clarivate.com/reviewer-connect/api/', + }, } diff --git a/yarn.lock b/yarn.lock index f6b5c5e8da4536cd6b919f7b7c3a6f0101cafaeb..246baf960a5f324070ad21e75f203c5701510145 100644 --- a/yarn.lock +++ b/yarn.lock @@ -893,6 +893,13 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +axios@^0.18.0: + version "0.18.0" + resolved "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz#32d53e4851efdc0a11993b6cd000789d70c05102" + dependencies: + follow-redirects "^1.3.0" + is-buffer "^1.1.5" + axobject-query@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" @@ -4440,9 +4447,9 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" -follow-redirects@^1.0.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.2.tgz#5a9d80e0165957e5ef0c1210678fc5c4acb9fb03" +follow-redirects@^1.0.0, follow-redirects@^1.3.0: + version "1.5.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.7.tgz#a39e4804dacb90202bca76a9e2ac10433ca6a69a" dependencies: debug "^3.1.0"