From b42707e5b3fa8733c1f6488124b1333f4da3ee56 Mon Sep 17 00:00:00 2001
From: Jure Triglav <juretriglav@gmail.com>
Date: Mon, 17 Aug 2020 00:45:48 +0200
Subject: [PATCH] feat: very basic public-facing frontpage

---
 .../component-frontpage/src/Frontpage.js      | 91 ++++++++++++++++++
 .../src/ManuscriptDetails.js                  |  5 +
 .../component-frontpage/src/index.js          |  4 +
 .../component-frontpage/src/queries.js        | 51 ++++++++++
 .../component-frontpage/src/style.js          | 92 +++++++++++++++++++
 app/routes.js                                 |  7 ++
 cypress/integration/005-publish_spec.js       |  3 +
 server/model-manuscript/src/graphql.js        | 21 ++++-
 8 files changed, 273 insertions(+), 1 deletion(-)
 create mode 100644 app/components/component-frontpage/src/Frontpage.js
 create mode 100644 app/components/component-frontpage/src/ManuscriptDetails.js
 create mode 100644 app/components/component-frontpage/src/index.js
 create mode 100644 app/components/component-frontpage/src/queries.js
 create mode 100644 app/components/component-frontpage/src/style.js

diff --git a/app/components/component-frontpage/src/Frontpage.js b/app/components/component-frontpage/src/Frontpage.js
new file mode 100644
index 0000000000..b53e5c03c7
--- /dev/null
+++ b/app/components/component-frontpage/src/Frontpage.js
@@ -0,0 +1,91 @@
+import React, { useContext } from 'react'
+import { useQuery } from '@apollo/client'
+import { JournalContext } from '../../xpub-journal/src'
+import queries from './queries'
+import { Container, Placeholder } from './style'
+
+import {
+  Spinner,
+  SectionHeader,
+  Title,
+  SectionRow,
+  SectionContent,
+  Heading,
+  HeadingWithAction,
+} from '../../shared'
+
+const Frontpage = ({ history, ...props }) => {
+  const { loading, data, error } = useQuery(queries.frontpage)
+  const journal = useContext(JournalContext)
+  if (loading) return <Spinner />
+  if (error) return JSON.stringify(error)
+
+  const frontpage = (data.publishedManuscripts?.manuscripts || []).map(m => ({
+    ...m,
+    submission: JSON.parse(m.submission),
+  }))
+
+  return (
+    <Container>
+      <HeadingWithAction>
+        <Heading>Recent publications in {journal.metadata.name}</Heading>
+      </HeadingWithAction>
+      {frontpage.length > 0 ? (
+        frontpage.map(manuscript => (
+          <SectionContent>
+            <SectionHeader>
+              <Title>{manuscript.meta.title}</Title>
+            </SectionHeader>
+            <SectionRow key={`manuscript-${manuscript.id}`}>
+              <p>
+                {manuscript.submitter.defaultIdentity.name} (
+                {manuscript.submission.affiliation})
+              </p>
+              <div>
+                Submitted files:
+                {manuscript.files.map(file => (
+                  <p>
+                    <a
+                      href={file.url}
+                      rel="noopener noreferrer"
+                      target="_blank"
+                    >
+                      {file.filename}
+                    </a>
+                  </p>
+                ))}
+              </div>
+              <div>
+                Submitted research objects:
+                {manuscript.submission.links.map(link => (
+                  <p>
+                    <a
+                      href={link.url}
+                      rel="noopener noreferrer"
+                      target="_blank"
+                    >
+                      {link.url}
+                    </a>
+                  </p>
+                ))}
+              </div>
+              <div>
+                Reviews:
+                {manuscript.reviews.map(
+                  review =>
+                    review.reviewComment && (
+                      <p>&quot;{review.reviewComment?.content}&quot;</p>
+                    ),
+                )}
+              </div>
+              <div>Published: {manuscript.published}</div>
+            </SectionRow>
+          </SectionContent>
+        ))
+      ) : (
+        <Placeholder>No submissions have been published yet.</Placeholder>
+      )}
+    </Container>
+  )
+}
+export default Frontpage
diff --git a/app/components/component-frontpage/src/ManuscriptDetails.js b/app/components/component-frontpage/src/ManuscriptDetails.js
new file mode 100644
index 0000000000..6b5bfdab4e
--- /dev/null
+++ b/app/components/component-frontpage/src/ManuscriptDetails.js
@@ -0,0 +1,5 @@
+import React from 'react'
+
+const ManuscriptDetails = () => <div>Nothing here yet.</div>
+
+export default ManuscriptDetails
diff --git a/app/components/component-frontpage/src/index.js b/app/components/component-frontpage/src/index.js
new file mode 100644
index 0000000000..28cae899c5
--- /dev/null
+++ b/app/components/component-frontpage/src/index.js
@@ -0,0 +1,4 @@
+import Frontpage from './Frontpage'
+import ManuscriptDetails from './ManuscriptDetails'
+
+export { Frontpage, ManuscriptDetails }
diff --git a/app/components/component-frontpage/src/queries.js b/app/components/component-frontpage/src/queries.js
new file mode 100644
index 0000000000..a6ae1970d3
--- /dev/null
+++ b/app/components/component-frontpage/src/queries.js
@@ -0,0 +1,51 @@
+import gql from 'graphql-tag'
+
+export default {
+  frontpage: gql`
+    {
+      publishedManuscripts {
+        totalCount
+        manuscripts {
+          id
+          reviews {
+            id
+            open
+            recommendation
+            reviewComment {
+              content
+            }
+            created
+            isDecision
+            user {
+              id
+              username
+            }
+          }
+          status
+          files {
+            id
+            url
+            filename
+          }
+          meta {
+            manuscriptId
+            title
+            articleSections
+            articleType
+            history {
+              type
+              date
+            }
+          }
+          published
+          submission
+          submitter {
+            defaultIdentity {
+              name
+            }
+          }
+        }
+      }
+    }
+  `,
+}
diff --git a/app/components/component-frontpage/src/style.js b/app/components/component-frontpage/src/style.js
new file mode 100644
index 0000000000..d92c9154fe
--- /dev/null
+++ b/app/components/component-frontpage/src/style.js
@@ -0,0 +1,92 @@
+import styled from 'styled-components'
+import { th, grid } from '@pubsweet/ui-toolkit'
+
+export { Section, Content } from '../../shared'
+const Actions = styled.div``
+
+export const Container = styled.div`
+  background: ${th('colorBackgroundHue')};
+  padding: ${grid(2)};
+  min-height: 100vh;
+}`
+
+const ActionContainer = styled.div`
+  display: inline-block;
+`
+
+export { Actions, ActionContainer }
+
+const Item = styled.div`
+  display: grid;
+  grid-template-columns: 1fr auto;
+  margin-bottom: calc(${th('gridUnit') * 4});
+`
+
+const Header = styled.div`
+  align-items: baseline;
+  display: flex;
+  justify-content: space-between;
+  text-transform: uppercase;
+`
+
+const Body = styled.div`
+  align-items: space-between;
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: calc(${th('gridUnit')} * 4);
+  padding-left: 1.5em;
+  & > div:last-child {
+    flex-shrink: 0;
+  }
+`
+
+const Divider = styled.span.attrs(props => ({
+  children: ` ${props.separator} `,
+}))`
+  color: ${th('colorFurniture')};
+  white-space: pre;
+`
+
+export { Item, Header, Body, Divider }
+
+const Links = styled.div`
+  align-items: flex-end;
+  display: flex;
+  justify-content: bottom;
+`
+
+const LinkContainer = styled.div`
+  font-size: ${th('fontSizeBaseSmall')};
+  line-height: ${th('lineHeightBaseSmall')};
+`
+
+export { Links, LinkContainer }
+
+const Page = styled.div`
+  padding: ${grid(2)};
+`
+
+const Heading = styled.div`
+  color: ${th('colorPrimary')};
+  font-family: ${th('fontReading')};
+  font-size: ${th('fontSizeHeading3')};
+  line-height: ${th('lineHeightHeading3')};
+`
+
+export { Page, Heading }
+
+export const HeadingWithAction = styled.div`
+  display: grid;
+  grid-template-columns: 1fr auto;
+  align-items: center;
+`
+
+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/routes.js b/app/routes.js
index 0cc488d3d4..f427249776 100644
--- a/app/routes.js
+++ b/app/routes.js
@@ -5,6 +5,11 @@ import Login from './components/component-login/src'
 
 import AdminPage from './components/AdminPage'
 
+import {
+  Frontpage,
+  ManuscriptDetails,
+} from './components/component-frontpage/src'
+
 export default (
   <Switch>
     {/* AdminPage has nested routes within */}
@@ -12,5 +17,7 @@ export default (
       <AdminPage />
     </Route>
     <Route component={Login} path="/login" />
+    <Route component={Frontpage} exact path="/" />
+    <Route component={ManuscriptDetails} path="/:manuscriptId" />
   </Switch>
 )
diff --git a/cypress/integration/005-publish_spec.js b/cypress/integration/005-publish_spec.js
index 7ae8c4490e..97e56ca024 100644
--- a/cypress/integration/005-publish_spec.js
+++ b/cypress/integration/005-publish_spec.js
@@ -14,6 +14,9 @@ describe('Publishing a submission', () => {
     cy.contains('Dashboard').click()
     cy.contains('Accepted & Published')
 
+    cy.visit('/')
+    cy.contains('My URL submission')
+
     cy.task('dump', 'published_submission')
   })
 })
diff --git a/server/model-manuscript/src/graphql.js b/server/model-manuscript/src/graphql.js
index f95dbb4cc0..775e3f7ced 100644
--- a/server/model-manuscript/src/graphql.js
+++ b/server/model-manuscript/src/graphql.js
@@ -1,6 +1,6 @@
 // const merge = require('lodash/merge')
 const detailsForURLResolver = require('./detailsForURLResolver')
-const { ref } = require('objection')
+const { ref, raw } = require('objection')
 
 const resolvers = {
   Mutation: {
@@ -244,6 +244,24 @@ const resolvers = {
     async manuscripts(_, { where }, ctx) {
       return ctx.models.Manuscript.query().eager('[teams, reviews]')
     },
+    async publishedManuscripts(_, { offset, limit }, ctx) {
+      const query = ctx.models.Manuscript.query()
+        .where(raw('published IS NOT NULL'))
+        .eager('[reviews.[comments], files, submitter]')
+      const totalCount = await query.resultSize()
+      if (limit) {
+        query.limit(limit)
+      }
+
+      if (offset) {
+        query.offset(offset)
+      }
+      const manuscripts = await query
+      return {
+        totalCount,
+        manuscripts,
+      }
+    },
     async paginatedManuscripts(_, { sort, offset, limit, filter }, ctx) {
       const query = ctx.models.Manuscript.query().eager('submitter')
 
@@ -321,6 +339,7 @@ const typeDefs = `
     manuscripts: [Manuscript]!
     paginatedManuscripts(sort: String, offset: Int, limit: Int, filter: ManuscriptsFilter): PaginatedManuscripts
     detailsForURL(url: String!): URLMetadata
+    publishedManuscripts(offset: Int, limit: Int): PaginatedManuscripts
   }
 
   type URLMetadata {
-- 
GitLab