diff --git a/packages/client/.storybook/main.ts b/packages/client/.storybook/main.ts
index 10fde5f32..a1f2d6bec 100644
--- a/packages/client/.storybook/main.ts
+++ b/packages/client/.storybook/main.ts
@@ -5,4 +5,9 @@ import { defineMain } from '@storybook/react-vite/node'
export default defineMain({
framework: '@storybook/react-vite',
stories: ['../stories/**/*.stories.tsx'],
+ addons: ['@storybook/addon-docs'],
+ viteFinal: async config => {
+ config.define = { ...config.define, 'process.env': {} }
+ return config
+ },
})
diff --git a/packages/client/.storybook/preview-head.html b/packages/client/.storybook/preview-head.html
new file mode 100644
index 000000000..e1aa23c44
--- /dev/null
+++ b/packages/client/.storybook/preview-head.html
@@ -0,0 +1,20 @@
+
diff --git a/packages/client/.storybook/preview.ts b/packages/client/.storybook/preview.ts
deleted file mode 100644
index 6fc8b03fb..000000000
--- a/packages/client/.storybook/preview.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-/* eslint-disable import/no-extraneous-dependencies */
-
-import { definePreview } from '@storybook/react-vite'
-import * as addonActions from '@storybook/addon-actions/preview'
-import * as addonBackgrounds from '@storybook/addon-backgrounds/preview'
-import * as addonViewport from '@storybook/addon-viewport/preview'
-
-export default definePreview({
- addons: [addonActions, addonBackgrounds, addonViewport],
- initialGlobals: {
- viewport: { value: '', isRotated: false },
- },
-})
diff --git a/packages/client/.storybook/preview.tsx b/packages/client/.storybook/preview.tsx
new file mode 100644
index 000000000..416361cc8
--- /dev/null
+++ b/packages/client/.storybook/preview.tsx
@@ -0,0 +1,69 @@
+/* eslint-disable import/no-extraneous-dependencies */
+
+import { definePreview, type Decorator } from '@storybook/react-vite'
+import addonDocs from '@storybook/addon-docs'
+
+import { MemoryRouter, Route, Routes } from 'react-router-dom'
+import { ThemeProvider } from 'styled-components'
+import { I18nextProvider } from 'react-i18next'
+import { AntConfigProvider } from '@coko/client'
+
+import { makeTheme } from '../app/theme'
+import GlobalStyle from '../app/theme/elements/GlobalStyle'
+import i18next from '../app/i18n'
+
+const theme = makeTheme()
+
+const withProviders: Decorator = (Story, context) => {
+ const initialEntries = context.parameters?.router?.initialEntries ?? ['/']
+ const routePath = context.parameters?.router?.path
+
+ const content = (
+
+
+
+
+ {
+ const anchor = (e.target as HTMLElement).closest('a')
+ if (anchor) {
+ /* eslint-disable-next-line no-console */
+ console.log('navigate to:', anchor.getAttribute('href'))
+ }
+ }}
+ onKeyDown={e => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ const anchor = (e.target as HTMLElement).closest('a')
+ if (anchor) {
+ /* eslint-disable-next-line no-console */
+ console.log('navigate to:', anchor.getAttribute('href'))
+ }
+ }
+ }}
+ role="presentation"
+ >
+
+
+
+
+
+ )
+
+ return (
+
+ {routePath ? (
+
+
+
+ ) : (
+ content
+ )}
+
+ )
+}
+
+export default definePreview({
+ addons: [addonDocs()],
+ tags: ['autodocs'],
+ decorators: [withProviders],
+})
diff --git a/packages/client/app/Router.tsx b/packages/client/app/Router.tsx
index ac748276e..928dda1e9 100644
--- a/packages/client/app/Router.tsx
+++ b/packages/client/app/Router.tsx
@@ -39,7 +39,13 @@ import UsersPage from './components/component-users-manager/src/UsersPage'
import { ConfigContext } from './components/config/src'
import { XpubContext } from './components/xpub-with-context/src'
-import { AuthenticatedPage, GroupPage, MenuPage } from './pages'
+import {
+ AuthenticatedPage,
+ CMSPage,
+ FormsPage,
+ GroupPage,
+ MenuPage,
+} from './pages'
import { CURRENT_USER } from './queries'
@@ -107,16 +113,19 @@ const RoleGate = (props: RoleGateProps): ReactNode => {
*/
const DashboardRoutes = (): ReactNode => {
const config = useContext(ConfigContext)
+ // @ts-ignore
+ const dashboard = config?.dashboard
+
return (
} path="" />
- {config?.dashboard?.showSections?.includes('submission') && (
+ {dashboard?.showSections?.includes('submission') && (
} path="submissions" />
)}
- {config?.dashboard?.showSections?.includes('review') && (
+ {dashboard?.showSections?.includes('review') && (
} path="reviews" />
)}
- {config?.dashboard?.showSections?.includes('editor') && (
+ {dashboard?.showSections?.includes('editor') && (
} path="edits" />
)}
@@ -216,35 +225,39 @@ const Router = (): ReactNode => {
>
} path="reports" />
+ } path="forms" />
+
}
- path="submission-form-builder"
+ path="forms/submission-form-builder"
/>
}
- path="review-form-builder"
+ path="forms/review-form-builder"
/>
}
- path="decision-form-builder"
+ path="forms/decision-form-builder"
/>
} path="users" />
} path="tasks" />
-
- } path="pages/:pageId?" />
- } path="layout" />
- } path="article" />
- } path="filebrowser" />
- } path="metadata" />
- }
- path="collections"
- />
-
+ } path="cms" />
+ } path="cms/pages/:pageId?" />
+ } path="cms/layout" />
+ } path="cms/article" />
+ }
+ path="cms/filebrowser"
+ />
+ } path="cms/metadata" />
+ }
+ path="cms/collections"
+ />
} path="configuration" />
diff --git a/packages/client/app/components/Menu.jsx b/packages/client/app/components/Menu.jsx
deleted file mode 100644
index 7b35ef473..000000000
--- a/packages/client/app/components/Menu.jsx
+++ /dev/null
@@ -1,119 +0,0 @@
-import { useState, useMemo } from 'react'
-import PropTypes from 'prop-types'
-import { useTranslation } from 'react-i18next'
-import { useMutation } from '@apollo/client/react'
-import {
- NavItem,
- NavLinks,
- MainNavWrapper,
- SectionNavLayoutSettings,
- Root,
- ScrollWrapper,
- SubMenu,
- UserComponent,
- StyledPinButton,
-} from './component-menu'
-
-import { UPDATE_MENU } from '../queries'
-
-const Menu = ({
- // loginLink = '/login',
- navLinkComponents,
- user,
- notice,
- profileLink,
-}) => {
- const { t } = useTranslation()
- const [menuIsMinimal, setMenuIsMinimal] = useState(!user.menuPinned)
- const [menuPinned, setMenuPinned] = useState(!!user.menuPinned)
- const [updateMenuState] = useMutation(UPDATE_MENU)
-
- const renderLinks = useMemo(() => {
- return (
- navLinkComponents &&
- navLinkComponents.map(navInfo =>
- navInfo.menu ? (
-
- ) : (
-
- ),
- )
- )
- }, [menuIsMinimal, navLinkComponents])
-
- const handlers = {
- pinNavbar: () => {
- updateMenuState({ variables: { expanded: !menuPinned } })
- setMenuPinned(!menuPinned)
- },
- mouseoverNav: () => {
- if (menuIsMinimal) {
- setMenuIsMinimal(false)
- }
- },
- mouseleaveNav: () => {
- if (!menuPinned) {
- setMenuIsMinimal(true)
- }
- },
- expandCollapse: () => {
- setMenuIsMinimal(!menuIsMinimal)
- },
- }
-
- return (
-
-
-
-
- {/* TODO: Place this notice (used for offline notification) better */}
- {notice}
-
-
-
- {renderLinks}
-
-
-
- )
-}
-
-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,
-}
-
-export default Menu
diff --git a/packages/client/app/components/component-ai-assistant/ChatBubble.jsx b/packages/client/app/components/component-ai-assistant/ChatBubble.jsx
index 10a12f369..a4bf3704e 100644
--- a/packages/client/app/components/component-ai-assistant/ChatBubble.jsx
+++ b/packages/client/app/components/component-ai-assistant/ChatBubble.jsx
@@ -176,8 +176,8 @@ const ChatBubble = ({ forceHide, icons, hasApiKey }) => {
width: '35px',
height: '35px',
borderRadius: '50%',
- backgroundColor: theme.color.brand1.base(),
- border: theme.color.brand1.shade50(),
+ backgroundColor: theme.color.brand1.base,
+ border: theme.color.brand1.shade50,
textAlign: 'center',
color: '#fff',
}}
diff --git a/packages/client/app/components/component-cms-manager/src/CMSFileBrowserPage.jsx b/packages/client/app/components/component-cms-manager/src/CMSFileBrowserPage.jsx
index cdb06bf01..25909b526 100644
--- a/packages/client/app/components/component-cms-manager/src/CMSFileBrowserPage.jsx
+++ b/packages/client/app/components/component-cms-manager/src/CMSFileBrowserPage.jsx
@@ -12,13 +12,14 @@ import { css } from '@codemirror/lang-css'
import SplitPane, { Pane } from 'split-pane-react'
import 'split-pane-react/esm/themes/default.css'
-import { serverUrl } from '@coko/client'
+import { serverUrl, th } from '@coko/client'
import Browser from './browser/FileBrowser'
-import { CommsErrorBanner, Spinner } from '../../shared'
+import { CommsErrorBanner, SectionContent, Spinner } from '../../shared'
import UploadComponent from '../../component-production/src/components/uploadManager/UploadComponent'
import { EditPageContainer } from './style'
import { ConfigContext } from '../../config/src'
+import Page from '../../../ui/shared/Page'
import {
GET_CMS_FILE_CONTENT,
@@ -45,8 +46,13 @@ const SpanActive = styled.span`
font-weight: bold;
`
+const FullHeightContainer = styled(EditPageContainer)`
+ background: ${th('color.backgroundC')};
+ height: calc(100vh - 160px);
+`
+
const MainFileContent = styled.div`
- background-color: #f4f5f7;
+ border-left: 2px solid ${th('colorPrimary')};
display: flex;
height: 100%;
overflow: auto;
@@ -283,47 +289,56 @@ const CMSFileBrowserPage = () => {
}
return (
-
-
-
-
-
-
- {!activeContent.isFolder &&
- activeContent.id &&
- !activeContent.isImage && (
-
- )}
- {activeContent.isFolder && activeContent.id && (
-
- Upload To Folder {activeContent.name}:
-
+
+
+
+
+
-
- )}
- {activeContent.isImage &&
}
-
-
-
+
+
+ {!activeContent.isFolder &&
+ activeContent.id &&
+ !activeContent.isImage && (
+
+ )}
+ {activeContent.isFolder && activeContent.id && (
+
+ Upload To Folder {activeContent.name}
+ :
+
+
+ )}
+ {activeContent.isImage &&
}
+
+
+
+
+
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/CMSLayoutPage.jsx b/packages/client/app/components/component-cms-manager/src/CMSLayoutPage.jsx
index 95c4e58f7..7a76c8300 100644
--- a/packages/client/app/components/component-cms-manager/src/CMSLayoutPage.jsx
+++ b/packages/client/app/components/component-cms-manager/src/CMSLayoutPage.jsx
@@ -4,8 +4,8 @@ import { useMutation, useQuery } from '@apollo/client/react'
import { useTranslation } from 'react-i18next'
import { ConfigContext } from '../../config/src'
import LayoutForm from './layout/LayoutForm'
-import { Container, Spinner, CommsErrorBanner } from '../../shared'
-import PageHeader from './components/PageHeader'
+import { Spinner, CommsErrorBanner } from '../../shared'
+import Page from '../../../ui/shared/Page'
import {
GET_CMS_LAYOUT,
@@ -125,9 +125,7 @@ const CMSLayoutPage = () => {
const { cmsLayout } = data
return (
-
-
-
+
publish(values)}
@@ -148,7 +146,7 @@ const CMSLayoutPage = () => {
)
}}
-
+
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/CMSMetadataPage.jsx b/packages/client/app/components/component-cms-manager/src/CMSMetadataPage.jsx
index 9e7eac285..7a3655154 100644
--- a/packages/client/app/components/component-cms-manager/src/CMSMetadataPage.jsx
+++ b/packages/client/app/components/component-cms-manager/src/CMSMetadataPage.jsx
@@ -9,15 +9,14 @@ import { useTranslation } from 'react-i18next'
import { ConfigContext } from '../../config/src'
import journalSchema from './collection/ui/journalSchema' // Import the function that generates the schema and uiSchema
-import {
- ActionButton,
- Container,
- HeadingWithAction,
- Heading,
- PaddedContent,
- SectionContent,
- WidthLimiter,
-} from '../../shared'
+import { ActionButton, PaddedContent, SectionContent } from '../../shared'
+import Page from '../../../ui/shared/Page'
+
+const StyledContent = styled.div`
+ font-family: ${th('fontInterface')};
+ font-size: ${th('fontSizeBase')};
+ line-height: ${th('lineHeightBase')};
+`
const FieldWrapper = styled.div`
display: flex;
@@ -73,11 +72,10 @@ const CmsMetadataPage = () => {
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"
rel="stylesheet"
/>
-
-
- {t('leftMenu.Metadata')}
-
-
+ {/* Reset Bootstrap 3's html { font-size: 10px } which bleeds into the rest of the app */}
+
+
+
-
-
+
+
>
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/CMSPagesPage.jsx b/packages/client/app/components/component-cms-manager/src/CMSPagesPage.jsx
index ec1694386..1643a94b6 100644
--- a/packages/client/app/components/component-cms-manager/src/CMSPagesPage.jsx
+++ b/packages/client/app/components/component-cms-manager/src/CMSPagesPage.jsx
@@ -2,7 +2,9 @@ import { useContext, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useMutation, useQuery } from '@apollo/client/react'
import { useTranslation } from 'react-i18next'
-import { Spinner, CommsErrorBanner } from '../../shared'
+import styled from 'styled-components'
+import { th, grid } from '@coko/client'
+import { Spinner, CommsErrorBanner, SectionContent } from '../../shared'
import CMSPageEditForm from './pages/CMSPageEdit'
@@ -10,9 +12,13 @@ import CMSPageEditSidebar from './pages/CMSPageEditSidebar'
import { ConfigContext } from '../../config/src'
-import { EditPageContainer, EditPageLeft, EditPageRight } from './style'
+import {
+ EditPageContainer,
+ EditPageLeft as BaseEditPageLeft,
+ EditPageRight as BaseEditPageRight,
+} from './style'
-import PageHeader from './components/PageHeader'
+import Page from '../../../ui/shared/Page'
import {
GET_CMS_PAGES,
@@ -22,6 +28,26 @@ import {
DELETE_CMS_PAGE,
} from '../../../queries'
+const EditPageLeft = styled(BaseEditPageLeft)`
+ overflow-y: auto;
+`
+
+const EditPageRight = styled(BaseEditPageRight)`
+ background-color: transparent;
+ border-left: 2px solid ${th('colorPrimary')};
+ overflow-y: auto;
+
+ /* stylelint-disable-next-line selector-class-pattern */
+ .ProseMirror {
+ padding: ${grid(2)};
+ }
+`
+
+const FullHeightContainer = styled(EditPageContainer)`
+ background: ${th('color.backgroundC')};
+ height: calc(100vh - 160px);
+`
+
const CMSPagesPage = () => {
const navigate = useNavigate()
const params = useParams()
@@ -86,38 +112,40 @@ const CMSPagesPage = () => {
}
return (
-
-
- showPage(selectedCmsPage)}
- onNewItemButtonClick={() => addNewPage()}
- />
-
-
-
- {cmsPage && (
-
- )}
-
-
+
+
+
+
+ showPage(selectedCmsPage)}
+ onNewItemButtonClick={() => addNewPage()}
+ />
+
+
+ {cmsPage && (
+
+ )}
+
+
+
+
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/article/article.jsx b/packages/client/app/components/component-cms-manager/src/article/article.jsx
index e53ccee99..b68a83816 100644
--- a/packages/client/app/components/component-cms-manager/src/article/article.jsx
+++ b/packages/client/app/components/component-cms-manager/src/article/article.jsx
@@ -11,40 +11,48 @@ import { html } from '@codemirror/lang-html'
import { css } from '@codemirror/lang-css'
import {
- Heading,
- HeadingWithAction,
- HiddenTabs,
- Manuscript,
+ HiddenTabsContainer,
+ Tab,
+ TabContainer,
ErrorBoundary,
} from '../../../shared'
import { FormActionButton } from '../style'
+import Page from '../../../../ui/shared/Page'
+import { FlexRow } from '../../../../globals'
import UploadAsset from '../../../component-production/src/components/uploadManager/UploadAsset'
import ManuscriptMetadata from './ManuscriptMetadata'
-const FlexRow = styled.div`
+const Tabs = styled.div`
display: flex;
- gap: ${grid(1)};
- justify-content: space-between;
+ margin-top: ${grid(1)};
`
-const StyledManuscript = styled(Manuscript)`
- display: flex;
- flex: 1 1 100%;
- flex-direction: column;
- height: 100vh;
- overflow-y: auto;
+const TabRow = styled(FlexRow)`
width: 100%;
`
+const PublishButton = styled(FormActionButton)`
+ margin-bottom: ${grid(1)};
+ margin-left: auto;
+ margin-right: 0;
+`
+
const ScrollableTabContent = styled.section`
background-color: ${th('color.backgroundA')};
border-radius: ${th('borderRadius')};
- box-shadow: ${({ theme }) => theme.boxShadow.shades[200]};
- height: calc(100vh - 108px);
+ height: calc(100vh - 180px);
+
overflow: auto;
- width: calc(100% - 50px);
+
+ .cm-theme-light {
+ height: 100%;
+ }
+
+ .cm-editor {
+ height: 100%;
+ }
`
const Article = ({
@@ -59,6 +67,8 @@ const Article = ({
const [htmlValue, setHtmlValue] = useState(articleTemplate.article)
+ const [activeTab, setActiveTab] = useState('article-template')
+
const onChangeCss = useCallback(
debounce(cssContent => {
setCssValue(cssContent)
@@ -107,84 +117,90 @@ const Article = ({
),
}
- const cssArticle = {
- content: (
-
-
-
- ),
- key: 'article-css',
- label: 'Article Css',
- }
-
- const htmlTemplate = {
- content: (
-
-
-
- ),
- key: 'article-template',
- label: 'Article Template',
- }
-
- const uploadAssets = {
- content: (
-
-
-
- ),
- key: 'template-assets',
- label: 'Template Assets',
- }
-
- const manuscriptMetadata = {
- content: (
-
-
-
- ),
- key: 'manuscript-metadata',
- label: 'Article Metadata',
- }
+ const sections = [
+ {
+ key: 'article-css',
+ label: 'Article Css',
+ content: (
+
+
+
+ ),
+ },
+ {
+ key: 'article-template',
+ label: 'Article Template',
+ content: (
+
+
+
+ ),
+ },
+ {
+ key: 'template-assets',
+ label: 'Template Assets',
+ content: (
+
+
+
+ ),
+ },
+ {
+ key: 'manuscript-metadata',
+ label: 'Article Metadata',
+ content: (
+
+
+
+ ),
+ },
+ ]
return (
-
-
-
- {t('cmsPage.article.title')}
-
- {submitButtonText}
-
-
-
+
-
+
+
+
+ {sections.map(({ key, label }) => (
+ setActiveTab(key)}>
+
+ {label}
+
+
+ ))}
+
+
+ {submitButtonText}
+
+
+
+ {sections.map(section => (
+
+ {section.content}
+
+ ))}
-
+
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/collection/CollectionList.jsx b/packages/client/app/components/component-cms-manager/src/collection/CollectionList.jsx
index b5b839ef3..5a8c4fe0a 100644
--- a/packages/client/app/components/component-cms-manager/src/collection/CollectionList.jsx
+++ b/packages/client/app/components/component-cms-manager/src/collection/CollectionList.jsx
@@ -12,16 +12,19 @@ import { HeadingCell } from '../../../component-production/src/components/styles
import {
Action,
ActionButton,
- Container,
- HeadingWithAction,
- Heading,
PaddedContent,
SectionContent,
- WidthLimiter,
LabelBadge,
} from '../../../shared'
+import Page from '../../../../ui/shared/Page'
import CollectionModalForm from './CollectionModalForm'
+const StyledContent = styled.div`
+ font-family: ${th('fontInterface')};
+ font-size: ${th('fontSizeBase')};
+ line-height: ${th('lineHeightBase')};
+`
+
const CmsHeadStyled = styled.div`
button {
font-size: ${th('fontSizeBase')};
@@ -216,11 +219,10 @@ const CollectionList = ({
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"
rel="stylesheet"
/>
-
-
- {t('leftMenu.Collections')}
-
-
+ {/* Reset Bootstrap 3's html { font-size: 10px } which bleeds into the rest of the app */}
+
+
+
@@ -263,8 +265,8 @@ const CollectionList = ({
)}
-
-
+
+
>
)
}
diff --git a/packages/client/app/components/component-cms-manager/src/collection/ui/journalSchema.js b/packages/client/app/components/component-cms-manager/src/collection/ui/journalSchema.js
index 003e813a8..f507c64f9 100644
--- a/packages/client/app/components/component-cms-manager/src/collection/ui/journalSchema.js
+++ b/packages/client/app/components/component-cms-manager/src/collection/ui/journalSchema.js
@@ -1,7 +1,7 @@
const generateSchema = t => {
const schema = {
type: 'object',
- title: t('leftMenu.Metadata'),
+ title: t('cmsIndexPage.metadataTitle'),
properties: {
title: {
type: 'string',
diff --git a/packages/client/app/components/component-cms-manager/src/editor/EditorStyles.jsx b/packages/client/app/components/component-cms-manager/src/editor/EditorStyles.jsx
index e28c8bab6..27fb6a77d 100644
--- a/packages/client/app/components/component-cms-manager/src/editor/EditorStyles.jsx
+++ b/packages/client/app/components/component-cms-manager/src/editor/EditorStyles.jsx
@@ -1,7 +1,7 @@
/* stylelint-disable string-quotes, declaration-block-no-redundant-longhand-properties */
import styled from 'styled-components'
-import { th } from '@coko/client'
+import { th, grid } from '@coko/client'
import waxDefaultStyles from '../../../wax-collab/src/layout/waxDefaultStyles'
import EditorElements from '../../../wax-collab/src/layout/EditorElements'
@@ -59,7 +59,7 @@ export const EditorDiv = styled.div`
height: 100%;
${props => !props.$hideComments && 'min-width: 800px'};
overflow: auto;
- padding: 5px;
+ padding: ${grid(2)};
position: relative;
.error & {
diff --git a/packages/client/app/components/component-cms-manager/src/layout/LayoutForm.jsx b/packages/client/app/components/component-cms-manager/src/layout/LayoutForm.jsx
index ad00b1cb3..6af9a7041 100644
--- a/packages/client/app/components/component-cms-manager/src/layout/LayoutForm.jsx
+++ b/packages/client/app/components/component-cms-manager/src/layout/LayoutForm.jsx
@@ -1,5 +1,7 @@
/* eslint-disable react/prop-types */
+import styled from 'styled-components'
+
import { SectionContent, PaddedContent } from '../../../shared'
import { ActionButtonContainer, FormActionButton } from '../style'
import PublishStatus from '../components/PublishStatus'
@@ -9,6 +11,12 @@ import Branding from './Branding'
import Footer from './Footer'
import SiteStatus from './SiteStatus'
+const Wrapper = styled.div`
+ > section:first-child > div {
+ margin-top: 0;
+ }
+`
+
const LayoutForm = ({
formikProps,
flaxSiteUrlForGroup,
@@ -81,7 +89,7 @@ const LayoutForm = ({
}
return (
-
+
{renderBranding()}
{renderHeader()}
{renderFooter()}
@@ -98,7 +106,7 @@ const LayoutForm = ({
-
+
)
}
diff --git a/packages/client/app/components/component-coar/CoarNotifyInbox.jsx b/packages/client/app/components/component-coar/CoarNotifyInbox.jsx
index c2bd027c6..a54d3f593 100644
--- a/packages/client/app/components/component-coar/CoarNotifyInbox.jsx
+++ b/packages/client/app/components/component-coar/CoarNotifyInbox.jsx
@@ -1,16 +1,16 @@
/* eslint-disable react/prop-types */
import { useTranslation } from 'react-i18next'
+import styled from 'styled-components'
import CoarMessages from '../component-review/src/components/coar/CoarMessages'
-import {
- Container,
- FlexRow,
- Heading,
- Pagination,
- PaginationContainerShadowed,
-} from '../shared'
+import { FlexRow, Pagination, PaginationContainerShadowed } from '../shared'
+import Page from '../../ui/shared/Page'
import SearchControl from '../component-manuscripts/src/SearchControl'
import { URI_PAGENUM_PARAM, URI_SEARCH_PARAM } from '../../shared/urlParamUtils'
+const SearchRow = styled(FlexRow)`
+ justify-content: flex-end;
+`
+
// type CoarNotifyMessageProps = {
// id: string
// manuscriptId: string | null
@@ -43,40 +43,41 @@ const CoarNotifyInbox = ({
const { t } = useTranslation()
return (
-
-
- {t('coarNotifyInboxPage.title')}
-
- applyQueryParams({
- [URI_PAGENUM_PARAM]: 1,
- [URI_SEARCH_PARAM]: newQuery,
- })
- }
- currentSearchQuery={currentSearchQuery}
- />
-
+
-
- {!loading && (
-
- applyQueryParams({ [URI_PAGENUM_PARAM]: newPage })
+
+
+ applyQueryParams({
+ [URI_PAGENUM_PARAM]: 1,
+ [URI_SEARCH_PARAM]: newQuery,
+ })
}
- totalCount={totalMessageCount}
+ currentSearchQuery={currentSearchQuery}
+ />
+
+
+
- )}
+ {!loading && (
+
+ applyQueryParams({ [URI_PAGENUM_PARAM]: newPage })
+ }
+ totalCount={totalMessageCount}
+ />
+ )}
+
-
+
)
}
diff --git a/packages/client/app/components/component-config-manager/src/ConfigManagerForm.jsx b/packages/client/app/components/component-config-manager/src/ConfigManagerForm.jsx
index 99925d1ee..32c7e287a 100644
--- a/packages/client/app/components/component-config-manager/src/ConfigManagerForm.jsx
+++ b/packages/client/app/components/component-config-manager/src/ConfigManagerForm.jsx
@@ -17,15 +17,8 @@ import {
configTabLabels,
} from './ui/schema' // Import the function that generates the schema and uiSchema
-import {
- ActionButton,
- Container,
- HeadingWithAction,
- Heading,
- SectionContent,
- HiddenTabs,
- Alert,
-} from '../../shared'
+import { ActionButton, SectionContent, HiddenTabs, Alert } from '../../shared'
+import Page from '../../../ui/shared/Page'
import EmailTemplatesPage from '../../component-email-templates/src/EmailTemplatesPage'
import emailTemplatesToSchema from './helpers'
import { EmailTemplatesProvider } from '../../component-email-templates/hooks/EmailTemplatesContext'
@@ -34,24 +27,29 @@ import { T } from '../../component-notification-event/misc/constants'
import { getFormBadgeBg } from '../../component-email-templates/src/handlebarsAutocomplete/helpers'
import DescriptionField from './ui/DescriptionField'
-const StyledContainer = styled(Container)`
+const StyledContainer = styled.div`
--tabs-border: 1px solid #ddd;
display: flex;
flex-direction: column;
+ height: 100%;
+ min-height: 0;
overflow: hidden;
+
+ /* stylelint-disable-next-line declaration-no-important */
+ font-family: ${th('fontInterface')} !important;
+ /* stylelint-disable-next-line declaration-no-important */
+ font-size: ${th('fontSizeBase')} !important;
+ /* stylelint-disable-next-line declaration-no-important */
+ line-height: ${th('lineHeightBase')} !important;
`
const StyledSectionContent = styled(SectionContent)`
margin: 0;
overflow-y: auto;
- padding: ${th('spacing.g')} ${th('spacing.g')} 0 ${th('spacing.g')};
+ padding: ${grid(3.75)} ${grid(3.75)} 0 ${grid(3.75)};
width: 100%;
`
-const StyledHeading = styled(Heading)`
- padding: 0.5rem 0 1.5rem;
-`
-
const InstanceTypeLegend = styled.legend`
border: 0;
border-bottom: 1px solid #e5e5e5;
@@ -396,31 +394,32 @@ const ConfigManagerForm = ({
integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu"
rel="stylesheet"
/>
-
-
- {t('configPage.Configuration')}
-
-
- {!['emails', 'events'].includes(activeTab) && (
-
- )}
-
+ {/* Reset Bootstrap 3's html { font-size: 10px } which bleeds into the rest of the app */}
+
+
+
+
+ {!['emails', 'events'].includes(activeTab) && (
+
+ )}
+
+
>
)
}
diff --git a/packages/client/app/components/component-dashboard/src/components/DashboardLayout.jsx b/packages/client/app/components/component-dashboard/src/components/DashboardLayout.jsx
index ee63cecb3..a4fa72a9e 100644
--- a/packages/client/app/components/component-dashboard/src/components/DashboardLayout.jsx
+++ b/packages/client/app/components/component-dashboard/src/components/DashboardLayout.jsx
@@ -2,19 +2,13 @@
import { useContext } from 'react'
import { th, grid } from '@coko/client'
-import { Link, useLocation, useParams, useNavigate } from 'react-router-dom'
+import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import { Button } from '../../../pubsweet'
-import {
- Heading,
- HeadingWithAction,
- HiddenTabsContainer,
- Tab,
- TabContainer,
-} from '../../../shared'
-import { Container } from '../style'
+import { HiddenTabsContainer, Tab, TabContainer } from '../../../shared'
+import Page from '../../../../ui/shared/Page'
import SearchControl from '../../../component-manuscripts/src/SearchControl'
import { ControlsContainer } from '../../../component-manuscripts/src/style'
import {
@@ -35,13 +29,21 @@ const Tabs = styled.div`
margin-top: ${grid(1)};
`
+const TabRow = styled(FlexRow)`
+ width: 100%;
+`
+
+const RightControls = styled(ControlsContainer)`
+ margin-left: auto;
+ margin-bottom: ${grid(1)};
+`
+
const DashboardLayout = ({ children }) => {
const config = useContext(ConfigContext)
const navigate = useNavigate()
const location = useLocation()
const { groupName } = useParams()
const applyQueryParams = useQueryParams()
-
const uriQueryParams = new URLSearchParams(location.search)
const currentSearchQuery = uriQueryParams.get(URI_SEARCH_PARAM)
const dashboardPages = []
@@ -67,11 +69,22 @@ const DashboardLayout = ({ children }) => {
})
return (
-
-
-
- {t('dashboardPage.Dashboard')}
-
+
+ {/* TODO The following block should be replaced with a new LinkTabs component */}
+
+
+
+ {dashboardPages.map(({ href, label }) => (
+
+
+
+ {label}
+
+
+
+ ))}
+
+
applyQueryParams({
@@ -87,26 +100,11 @@ const DashboardLayout = ({ children }) => {
>
{t('dashboardPage.New submission')}
-
-
-
-
- {/* TODO The following block should be replaced with a new LinkTabs component */}
-
-
- {dashboardPages.map(({ href, label }) => (
-
-
-
- {label}
-
-
-
- ))}
-
+
+
{children}
-
+
)
}
diff --git a/packages/client/app/components/component-dashboard/src/style.jsx b/packages/client/app/components/component-dashboard/src/style.jsx
index ffa78d6a5..19d560041 100644
--- a/packages/client/app/components/component-dashboard/src/style.jsx
+++ b/packages/client/app/components/component-dashboard/src/style.jsx
@@ -90,7 +90,7 @@ export const Centered = styled.div`
export const InvitationContent = styled.div`
background: ${th('color.backgroundA')};
border-radius: ${th('borderRadius')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
margin-bottom: 1rem;
max-height: calc(100vh - 32px);
max-width: 50em;
diff --git a/packages/client/app/components/component-formbuilder/src/components/FormBuilderLayout.jsx b/packages/client/app/components/component-formbuilder/src/components/FormBuilderLayout.jsx
index 2aa0b2538..b3ec38144 100644
--- a/packages/client/app/components/component-formbuilder/src/components/FormBuilderLayout.jsx
+++ b/packages/client/app/components/component-formbuilder/src/components/FormBuilderLayout.jsx
@@ -11,8 +11,6 @@ import FormSettingsModal from './FormSettingsModal'
import FieldSettingsModal from './FieldSettingsModal'
import FormBuilder from './FormBuilder'
import {
- Container,
- Heading,
HiddenTabs,
SectionContent,
SectionRow,
@@ -20,6 +18,7 @@ import {
ActionButton,
RoundIconButton,
} from '../../../shared'
+import Page from '../../../../ui/shared/Page'
import { ConfirmationModal } from '../../../component-modal/src/ConfirmationModal'
import FormSummary from './FormSummary'
import { ConfigContext } from '../../../config/src'
@@ -53,7 +52,6 @@ const WidthLimiter = styled.div`
display: flex;
flex: 1 1 0;
flex-direction: column;
- max-width: 1200px;
min-height: 0;
`
@@ -228,48 +226,49 @@ const FormBuilderLayout = ({
return (
<>
-
-
- {t(`formBuilder.${category}.title`)}
-
-
{
- setSelectedFormId(null)
- setIsEditingFormSettings(true)
+
+
+
+
- {t('formBuilder.Add new form')}
-
- {
- setSelectedFormId(tab)
- setSelectedFieldId(null)
- }}
- sections={sections}
- shouldFillFlex
- />
-
-
-
+
{
+ setSelectedFormId(null)
+ setIsEditingFormSettings(true)
+ }}
+ >
+ {t('formBuilder.Add new form')}
+
+
{
+ setSelectedFormId(tab)
+ setSelectedFieldId(null)
+ }}
+ sections={sections}
+ shouldFillFlex
+ />
+
+
+
+
@@ -81,12 +81,8 @@ const TitleWithAbstractAsTooltip = ({ manuscript }) => {
)}
- {isManuscriptFromSemanticScholar && (
-
- )}
- {isManuscriptFromCoarNotify && (
-
- )}
+ {isManuscriptFromSemanticScholar && }
+ {isManuscriptFromCoarNotify && }
{url ? (
diff --git a/packages/client/app/components/component-manuscripts/src/Manuscripts.jsx b/packages/client/app/components/component-manuscripts/src/Manuscripts.jsx
index d65daf352..cd60f9d13 100644
--- a/packages/client/app/components/component-manuscripts/src/Manuscripts.jsx
+++ b/packages/client/app/components/component-manuscripts/src/Manuscripts.jsx
@@ -9,6 +9,7 @@ import { ToastContainer } from 'react-toastify'
import 'react-toastify/dist/ReactToastify.css'
import { grid } from '@coko/client'
+import Page from '../../../ui/shared/Page'
import { Checkbox, Dropdown } from '../../pubsweet'
@@ -26,7 +27,6 @@ import {
Columns,
CommsErrorBanner,
Container,
- Heading,
Pagination,
PaginationContainerShadowed,
RoundIconButton,
@@ -47,19 +47,24 @@ const OuterContainer = styled(Container)`
padding: 0;
`
+const ManuscriptsColumns = styled(Columns)`
+ height: 100%;
+`
+
const ManuscriptsPane = styled.div`
- overflow-y: scroll;
- padding: 16px 16px 0;
+ overflow-y: auto;
`
const FlexRow = styled.div`
display: flex;
gap: ${grid(1)};
- justify-content: space-between;
+ justify-content: flex-end;
`
const FlexRowWithSmallGapAbove = styled(FlexRow)`
- margin-top: 10px;
+ align-items: end;
+ justify-content: flex-start;
+ margin-bottom: ${grid(1)};
`
const RoundIconButtonWrapper = styled(RoundIconButton).attrs({
@@ -349,7 +354,11 @@ const Manuscripts = props => {
: t('manuscriptsPage.Refresh')}
)}
+
+ )
+ const searchAndChatControls = (
+
applyQueryParams({
@@ -399,115 +408,115 @@ const Manuscripts = props => {
]
return (
-
-
-
-
-
-
- {t(
- archived
- ? 'manuscriptsPage.archivedManuscripts'
- : 'manuscriptsPage.Manuscripts',
- )}
-
- {topRightControls}
-
-
-
-
-
- selectedNewManuscripts.includes(manuscript.id),
+
+
+
+
+
+ {topRightControls}
+
+
+
+
+ selectedNewManuscripts.includes(manuscript.id),
+ )}
+ label={t('manuscriptsPage.Select All')}
+ onChange={toggleAllNewManuscriptsCheck}
+ />
+
+
+
+
+
+ {t('manuscriptsPage.takeAction')}
+
+
+
+
+ {t(
+ archived
+ ? 'manuscriptsPage.viewUnarchived'
+ : 'manuscriptsPage.viewArchived',
)}
- label={t('manuscriptsPage.Select All')}
- onChange={toggleAllNewManuscriptsCheck}
- />
-
-
+ {searchAndChatControls}
+
+
+
+
+
-
-
-
- {t('manuscriptsPage.takeAction')}
-
-
-
-
- {t(
- archived
- ? 'manuscriptsPage.viewUnarchived'
- : 'manuscriptsPage.viewArchived',
- )}
-
-
-
-
-
-
+
+ applyQueryParams({ [URI_PAGENUM_PARAM]: newPage })
+ }
+ totalCount={totalCount}
/>
-
-
- applyQueryParams({ [URI_PAGENUM_PARAM]: newPage })
- }
- totalCount={totalCount}
+
+
+
+ {/* Group Manager Discussion, Video Chat, Hide Chat, Chat component */}
+ {isAdminChatOpen && !hideManuscriptsChat && (
+
-
-
-
- {/* Group Manager Discussion, Video Chat, Hide Chat, Chat component */}
- {isAdminChatOpen && !hideManuscriptsChat && (
-
- )}
-
- setIsOpenBulkArchiveModal(false)}
- confirmationAction={doBulkArchive}
- isOpen={isOpenBulkArchiveModal}
- message={t('manuscriptsPage.confirmArchive')}
- />
- setIsOpenBulkUnarchiveModal(false)}
- confirmationAction={doBulkUnarchive}
- isOpen={isOpenBulkUnarchiveModal}
- message={t('manuscriptsPage.confirmUnarchive')}
- />
-
+ )}
+
+ setIsOpenBulkArchiveModal(false)}
+ confirmationAction={doBulkArchive}
+ isOpen={isOpenBulkArchiveModal}
+ message={t('manuscriptsPage.confirmArchive')}
+ />
+ setIsOpenBulkUnarchiveModal(false)}
+ confirmationAction={doBulkUnarchive}
+ isOpen={isOpenBulkUnarchiveModal}
+ message={t('manuscriptsPage.confirmUnarchive')}
+ />
+
+
)
}
diff --git a/packages/client/app/components/component-manuscripts/src/SearchControl.jsx b/packages/client/app/components/component-manuscripts/src/SearchControl.jsx
index 08721f233..717b737d3 100644
--- a/packages/client/app/components/component-manuscripts/src/SearchControl.jsx
+++ b/packages/client/app/components/component-manuscripts/src/SearchControl.jsx
@@ -36,7 +36,7 @@ const InlineTextField = styled.input`
&:focus {
border-color: ${th('color.brand1.base')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
}
`
diff --git a/packages/client/app/components/component-menu/components/NavItem.jsx b/packages/client/app/components/component-menu/components/NavItem.jsx
deleted file mode 100644
index 3f12812a0..000000000
--- a/packages/client/app/components/component-menu/components/NavItem.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/* stylelint-disable declaration-no-important */
-
-/* eslint-disable react/prop-types */
-
-import { useLocation } from 'react-router-dom'
-import PropTypes from 'prop-types'
-import { Icon } from '../../pubsweet'
-import { AlertIndicator, LinkLabel, StyledIcon, StyledLink } from '../styles'
-
-const MenuItem = ({ alert, icon, menu, name, open }) => (
-
- {icon && {icon}}
- {name}
- {alert && }
- {menu && chevron-{open ? 'up' : 'down'}}
-
-)
-
-const NavItem = props => {
- const {
- className = '',
- link,
- onClick,
- alert,
- icon,
- menu,
- menuIsMinimal,
- name,
- open,
- depth,
- } = props
-
- const itemProps = { alert, icon, menu, name, open }
- const location = useLocation()
-
- return link ? (
-
-
-
- ) : (
-
-
-
- )
-}
-
-NavItem.propTypes = {
- className: PropTypes.string,
- link: PropTypes.string,
- name: PropTypes.string.isRequired,
- icon: PropTypes.string,
-}
-
-export default NavItem
diff --git a/packages/client/app/components/component-menu/components/SubMenu.jsx b/packages/client/app/components/component-menu/components/SubMenu.jsx
deleted file mode 100644
index b836cc039..000000000
--- a/packages/client/app/components/component-menu/components/SubMenu.jsx
+++ /dev/null
@@ -1,64 +0,0 @@
-/* eslint-disable react/prop-types */
-
-import { useState, memo } from 'react'
-import { useLocation } from 'react-router-dom'
-import {
- SubMenuContainer,
- SubMenuFirstLevelContainer,
- menuItemContainsCurrentPage,
-} from '../styles'
-import NavItem from './NavItem'
-
-const SubMenu = ({
- depth = 0,
- menuIsMinimal,
- setMenuIsMinimal,
- ...navInfo
-}) => {
- const location = useLocation()
-
- const [open, setOpen] = useState(
- menuItemContainsCurrentPage(navInfo, location),
- )
-
- const handleOpen = () => {
- setOpen(!open || menuIsMinimal)
- setMenuIsMinimal(false)
- }
-
- return (
-
-
-
- {navInfo.links.map(subNavInfo =>
- subNavInfo.menu ? (
-
- ) : (
-
- ),
- )}
-
-
- )
-}
-
-export default memo(SubMenu)
diff --git a/packages/client/app/components/component-menu/components/UserComponent.jsx b/packages/client/app/components/component-menu/components/UserComponent.jsx
deleted file mode 100644
index d41e45500..000000000
--- a/packages/client/app/components/component-menu/components/UserComponent.jsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/* eslint-disable react/prop-types */
-
-/* eslint-disable new-cap */
-
-import PropTypes from 'prop-types'
-import { UserAvatar } from '../../component-avatar/src'
-import {
- FormattedGlobalAndGroupRoles,
- RolesLabel,
- UserInfo,
- UserItem,
- UserMenuContainer,
- UserName,
-} from '../styles'
-// import NavItem from './NavItem'
-
-const UserComponent = ({
- user,
- // loginLink,
- profileLink,
- t,
- expanded,
-}) => {
- return (
-
- {user && (
-
-
-
- {user.username}
- {!user.isOnline && Offline}
- ({FormattedGlobalAndGroupRoles(user, t)})
-
-
- )}
- {/* {!user && } */}
-
- )
-}
-
-UserComponent.propTypes = {
- user: PropTypes.oneOfType([PropTypes.object]),
- loginLink: PropTypes.string.isRequired,
- profileLink: PropTypes.string.isRequired,
-}
-
-export default UserComponent
diff --git a/packages/client/app/components/component-menu/components/index.js b/packages/client/app/components/component-menu/components/index.js
deleted file mode 100644
index 86db63700..000000000
--- a/packages/client/app/components/component-menu/components/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { default as SubMenu } from './SubMenu'
-export { default as UserComponent } from './UserComponent'
-export { default as NavItem } from './NavItem'
diff --git a/packages/client/app/components/component-menu/index.js b/packages/client/app/components/component-menu/index.js
deleted file mode 100644
index 22424051e..000000000
--- a/packages/client/app/components/component-menu/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from './components'
-export * from './styles'
diff --git a/packages/client/app/components/component-menu/styles/PinButton.jsx b/packages/client/app/components/component-menu/styles/PinButton.jsx
deleted file mode 100644
index 22f66f078..000000000
--- a/packages/client/app/components/component-menu/styles/PinButton.jsx
+++ /dev/null
@@ -1,14 +0,0 @@
-/* eslint-disable react/prop-types */
-
-import { Sidebar } from 'react-feather'
-
-const PinButton = props => {
- return (
-
- )
-}
-
-export default PinButton
diff --git a/packages/client/app/components/component-menu/styles/helpers.js b/packages/client/app/components/component-menu/styles/helpers.js
deleted file mode 100644
index cf15f5d6a..000000000
--- a/packages/client/app/components/component-menu/styles/helpers.js
+++ /dev/null
@@ -1,27 +0,0 @@
-/* eslint-disable react/prop-types */
-
-import { convertCamelCaseToTitleCase } from '../../../shared/textUtils'
-
-export const menuItemContainsCurrentPage = (item, location) =>
- item.link === location.pathname ||
- item.links?.some(subItem => menuItemContainsCurrentPage(subItem, location))
-
-export const FormattedGlobalAndGroupRoles = (
- { globalRoles, groupRoles },
- t,
-) => {
- const allRoles = globalRoles.concat(groupRoles)
-
- let unCamelCasedRoles =
- allRoles.includes('groupManager') || allRoles.includes('admin')
- ? allRoles
- .filter(role => role !== 'user')
- .map(role => convertCamelCaseToTitleCase(role))
- : allRoles.map(role => convertCamelCaseToTitleCase(role))
-
- if (!unCamelCasedRoles.length) return null
- unCamelCasedRoles = unCamelCasedRoles.map(elem => {
- return t(`common.roles.${elem}`)
- })
- return unCamelCasedRoles.join(', ')
-}
diff --git a/packages/client/app/components/component-menu/styles/index.js b/packages/client/app/components/component-menu/styles/index.js
deleted file mode 100644
index f529ef3d0..000000000
--- a/packages/client/app/components/component-menu/styles/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './menuStyleds'
-export * from './styleGlobals'
-export * from './helpers'
diff --git a/packages/client/app/components/component-menu/styles/menuStyleds.jsx b/packages/client/app/components/component-menu/styles/menuStyleds.jsx
deleted file mode 100644
index ffaca5561..000000000
--- a/packages/client/app/components/component-menu/styles/menuStyleds.jsx
+++ /dev/null
@@ -1,355 +0,0 @@
-/* stylelint-disable declaration-no-important, string-quotes */
-import styled from 'styled-components'
-import { Link } from 'react-router-dom'
-import { th, grid } from '@coko/client'
-
-import { Icon } from '../../pubsweet'
-import { menuStyles } from './styleGlobals'
-import PinButton from './PinButton'
-
-// #region Menu
-
-export const MainNavWrapper = styled.div.attrs({
- 'data-testid': 'main-nav-wrapper',
-})`
- /* stylelint-disable declaration-no-important */
-
- align-items: center;
- background: linear-gradient(
- 134deg,
- ${th('color.brand1.base')},
- ${th('color.brand1.tint25')}
- );
- display: flex;
- flex-direction: column;
- height: 100%;
- justify-content: center;
- overflow: hidden;
- padding: 3.2px 4.8px;
- transition: all var(--transition-time);
- width: 100%;
-`
-
-export const ScrollWrapper = styled.div`
- align-items: center;
- display: flex;
- flex-direction: column;
- height: 100%;
- overflow: hidden auto;
- position: relative;
- scrollbar-color: ${th('color.brand1.tint50')} #fff0;
- scrollbar-width: thin;
- transition: all var(transition-time);
- width: 100%;
-
- ::-webkit-scrollbar {
- height: 10px;
- width: 5px;
- }
-
- ::-webkit-scrollbar-thumb {
- background: ${th('color.brand1.tint50')};
- border-radius: 0.3rem;
- }
-
- ::-webkit-scrollbar-track {
- background: #0000;
- }
-`
-
-export const NavLinks = styled.div`
- align-items: flex-start;
- display: flex;
- flex-direction: column;
- padding: 0;
- transition: padding var(--transition-time);
- width: 100%;
-`
-// #endregion Menu
-
-// #region PinNav
-export const SectionNavLayoutSettings = styled.div`
- background: ${th('color.brand1.shade10')};
- border: none;
- border-bottom: 1px solid #fff2;
- height: ${p => (p.$pinned ? '0' : '22px')};
- margin-bottom: ${p => (p.$pinned ? '0' : '-14px')};
- position: relative;
- transition:
- margin ease 0.5s,
- height ease 0.5s,
- background-color ease 0.2s;
- width: 100%;
-
- &:hover {
- background-color: ${th('color.brand1.shade15')};
-
- * {
- color: ${th('color.brand1.tint70')};
- }
- }
-`
-
-export const StyledPinButton = styled(PinButton)`
- background: ${p => (p.$pinned ? '#fff4' : 'transparent')};
- border-radius: 4px;
- color: ${p =>
- p.$pinned ? p.theme.color.brand1.tint90 : p.theme.color.brand1.tint25};
- max-width: ${p => (p.$menuIsMinimal ? '0' : '100%')};
- opacity: ${p => (p.$menuIsMinimal ? '0' : '1')};
- overflow: hidden;
- padding: 1px 2px 2px;
- position: absolute;
- right: 4px;
- top: 1px;
- transition: opacity ease 0.2s;
-
- svg {
- aspect-ratio: 1 / 1;
- fill: #fff0;
- height: 15px;
- vertical-align: middle;
- width: 15px;
- }
-`
-// #endregion PinNav
-
-// #region Link
-export const StyledLink = styled(Link)`
- background-color: ${p => (p.$active ? p.theme.color.brand1.tint70 : 'unset')};
- border-radius: 10px;
- color: ${p =>
- p.$active ? p.theme.color.text : p.theme.color.textReverse} !important;
- cursor: pointer;
- display: flex;
- font-size: ${th('fontSizeBase')} !important;
- height: ${grid(5)};
- justify-content: space-between;
- line-height: ${grid(3)};
- margin-bottom: 4.8px;
- padding: 0 ${grid(1)};
- position: relative;
- text-decoration: none !important;
- transition:
- max-width var(--transition-time),
- background-color var(--transition-link-colors),
- color var(--transition-link-colors);
- user-select: none;
- width: calc(100% - ${p => (p.$depth || 0) * 1.6 * 10}px);
-
- & > span {
- align-items: center;
- display: flex;
-
- line-height: 1;
- overflow: ${p => (p.$submenu ? 'hidden' : 'unset')};
- width: 100%;
- }
-
- svg {
- stroke: ${p =>
- p.$active ? p.theme.color.text : p.theme.color.textReverse};
- transition: stroke var(--transition-link-colors);
- width: 1em;
- }
-
- svg#coar {
- color: ${p => (p.$active ? p.theme.color.text : p.theme.color.textReverse)};
- transition: color var(--transition-time);
- }
-
- &:hover {
- background-color: ${th('color.brand1.tint70')};
- color: ${th('color.text')} !important;
- stroke: ${th('color.text')};
-
- svg {
- stroke: ${th('color.text')};
- }
-
- svg#coar {
- color: ${th('color.text')};
- }
- }
-`
-
-export const NonLink = styled.div`
- & > span {
- justify-content: center;
- overflow: hidden;
- }
-`
-
-export const LinkLabel = styled.span`
- margin-left: 0;
- opacity: 0;
- overflow: hidden;
- padding-top: 2px;
- transition:
- margin var(--transition-time),
- opacity 0.2s;
- white-space: nowrap;
- width: 100%;
-`
-
-export const StyledIcon = styled(Icon)``
-
-export const AlertIndicator = styled.div`
- background: ${th('color.error.base')};
- border-radius: 50%;
- display: flex;
- height: 10px;
- margin: 0;
- position: absolute;
- right: 4.8px;
- transition: top var(--transition-time);
- width: 10px;
-`
-// #endregion Link
-
-// #region SubMenu
-export const SubMenuFirstLevelContainer = styled.span`
- display: flex;
- flex-direction: column;
- justify-items: ${p => (p.$depth > 0 ? 'end' : '')};
- overflow: hidden;
- width: 100%;
-`
-
-export const SubMenuContainer = styled(NavLinks)`
- align-items: end;
- height: fit-content;
- max-height: 0;
- opacity: 0;
- overflow: hidden;
- padding: 0;
- position: relative;
- transition: all var(--transition-time);
- width: 100%;
-`
-// #endregion SubMenu
-
-// #region UserNav
-export const RolesLabel = styled.div`
- color: ${th('color.brand1.tint50')};
- font-size: ${th('fontSizeBaseSmaller')};
- font-weight: normal;
- line-height: 1;
- white-space: nowrap;
-`
-
-export const UserItem = styled(Link).attrs({
- 'data-testid': 'user-item',
-})`
- align-items: end;
- border-bottom: 1px solid #fff2;
- color: ${th('color.textReverse')};
- display: flex;
- gap: ${p => (p.$expanded ? '16px' : '0')};
- overflow: hidden;
- padding: ${p => (p.$expanded ? '11.2px 16px 16px 8px' : '8px 0 8px 2.4px')};
- text-decoration: none !important;
- transition: all var(--transition-time);
- width: 100%;
-
- &:hover {
- color: ${th('color.textReverse')} !important;
- }
-`
-
-export const UserInfo = styled.div`
- display: flex;
- flex-direction: column;
- font-size: ${th('fontSizeBase')} !important;
- justify-content: center;
- opacity: ${p => (p.$expanded ? '1' : '0')};
- overflow: hidden;
- transition: all var(--transition-time);
- width: ${p => (p.$expanded ? '100%' : '0')};
-`
-
-export const UserName = styled.div`
- max-width: 30ch;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-`
-
-export const UserMenuContainer = styled.div`
- display: flex;
- gap: 24px;
- justify-content: flex-start;
- margin-bottom: 4.8px;
- margin-top: -5px;
- overflow: visible;
- width: 100%;
-
- & img {
- outline: 4px solid ${th('color.brand1.base')};
- }
-`
-// #endregion UserNav
-
-// Here all related to expand/collapse
-// Handling it based on aria-expanded instead of state to avoid re-renders
-export const Root = styled.nav`
- --transition-time: ${menuStyles.transition.time} ease;
- --transition-link-colors: 0.3s ease;
-
- align-items: flex-start;
- background: ${th('color.brand1.base')};
- display: flex;
- flex-direction: column;
- font-family: ${th('fontInterface')}, sans-serif !important;
- max-height: 100vh;
- max-width: 100%;
- min-width: 250px;
- transition: all var(--transition-time);
- width: fit-content;
-
- &[aria-expanded='false'] {
- max-width: 50px;
- min-width: 0;
-
- ${LinkLabel} {
- margin-left: 0;
- opacity: 0;
- }
-
- ${AlertIndicator} {
- top: 3.2px;
- }
- }
-
- &[aria-expanded='true'] {
- ${LinkLabel} {
- margin-left: 6px;
- opacity: 1;
- }
-
- ${NavLinks} {
- padding: 4.8px;
- }
-
- ${SubMenuFirstLevelContainer} {
- display: grid;
- }
-
- ${SubMenuContainer} {
- &[aria-expanded='true'] {
- max-height: 100%;
- opacity: 1;
- padding: 0;
- }
-
- &[aria-expanded='false'] {
- max-height: 0;
- padding: 0;
- }
- }
-
- ${AlertIndicator} {
- top: 4.8px;
- }
- }
-`
diff --git a/packages/client/app/components/component-menu/styles/styleGlobals.js b/packages/client/app/components/component-menu/styles/styleGlobals.js
deleted file mode 100644
index 70cb35b8a..000000000
--- a/packages/client/app/components/component-menu/styles/styleGlobals.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const menuStyles = {
- transition: {
- time: '0.2s',
- },
-}
diff --git a/packages/client/app/components/component-notification-event/components/CustomInputs.jsx b/packages/client/app/components/component-notification-event/components/CustomInputs.jsx
index 5951de83b..95a26eda3 100644
--- a/packages/client/app/components/component-notification-event/components/CustomInputs.jsx
+++ b/packages/client/app/components/component-notification-event/components/CustomInputs.jsx
@@ -39,7 +39,7 @@ const styles = theme => ({
: 'inset 0px 0px 4px rgb(0 0 0 / 7%)'
: state.isFocused
? `0 0 0 1px ${theme.color.brand1.base}`
- : theme.boxShadow,
+ : theme.boxShadow200,
borderRadius: theme.borderRadius,
'&:hover': {
diff --git a/packages/client/app/components/component-production/src/components/Production.jsx b/packages/client/app/components/component-production/src/components/Production.jsx
index 396b3cc0b..68b052142 100644
--- a/packages/client/app/components/component-production/src/components/Production.jsx
+++ b/packages/client/app/components/component-production/src/components/Production.jsx
@@ -10,17 +10,17 @@ import CodeMirror from '@uiw/react-codemirror'
import { html } from '@codemirror/lang-html'
import { css } from '@codemirror/lang-css'
+import Page from '../../../../ui/shared/Page'
+import { FlexRow } from '../../../../globals'
import { ConfigContext } from '../../../config/src'
import ProductionWaxEditor from '../../../wax-collab/src/ProductionWaxEditor'
import { DownloadDropdown } from './DownloadDropdown'
import {
- Heading,
- HeadingWithAction,
- Tabs,
- Manuscript,
ErrorBoundary,
- SectionContent,
+ HiddenTabsContainer,
Spinner,
+ Tab,
+ TabContainer,
VersionSwitcher,
} from '../../../shared'
import { Info } from './styles'
@@ -41,31 +41,38 @@ const PreviousFeedBackSection = styled.div`
margin-bottom: calc(${th('gridUnit')} * 3);
`
-const FlexRow = styled.div`
+const TabsRow = styled.div`
display: flex;
- gap: ${grid(1)};
- justify-content: space-between;
+ margin-top: ${grid(1)};
`
-const FormTemplateStyled = styled.div`
- max-height: calc(100vh - 150px);
+const TabRow = styled(FlexRow)`
+ width: 100%;
`
-const StyledManuscript = styled(Manuscript)`
- display: flex;
- flex-direction: column;
- height: 100vh;
- overflow-y: auto;
- width: 100%;
+const RightControls = styled(ControlsContainer)`
+ margin-left: auto;
+ margin-bottom: ${grid(1)};
+`
+
+const FormTemplateStyled = styled.div`
+ max-height: calc(100vh - 150px);
`
const ScrollableTabContent = styled.section`
background-color: ${th('color.backgroundA')};
border-radius: ${th('borderRadius')};
- box-shadow: ${({ theme }) => theme.boxShadow.shades[200]};
- height: calc(100vh - 108px);
+ box-shadow: ${({ theme }) => theme.boxShadow200};
+ height: calc(100vh - 180px);
overflow: auto;
- width: calc(100vw - 232px);
+
+ .cm-theme-light {
+ height: 100%;
+ }
+
+ .cm-editor {
+ height: 100%;
+ }
`
const LabeledTab = styled.div`
@@ -109,6 +116,8 @@ const Production = ({
authorList,
addNewVersion,
}) => {
+ const [activeKey, setActiveKey] = useState('editor')
+
const versions = gatherManuscriptVersions(unparsedManuscript)
const showFeedbackTab = versions.some(
@@ -209,9 +218,9 @@ const Production = ({
const editorSection = {
content: (
- <>
+
{showContent ? (
-
+ <>
{manuscript ? (
)}
-
+ >
) : (
-
- {t('productionPage.No supported view of the file')}
-
+ {t('productionPage.No supported view of the file')}
)}
- >
+
),
key: 'editor',
label: `${t('productionPage.Editor')} ${
@@ -249,16 +256,14 @@ const Production = ({
const feedbackSection = {
content: (
- <>
+
{isAuthorProofingVersion &&
['assigned', 'inProgress'].includes(manuscript.status) && (
-
-
-
+
)}
@@ -270,7 +275,7 @@ const Production = ({
))}
- >
+
),
key: 'feedback',
label: t('productionPage.Feedback'),
@@ -340,13 +345,15 @@ const Production = ({
const cssAiAssistant = {
content: (
-
- JSON.parse(manuscript.meta.comments) || []}
- />
-
+
+
+ JSON.parse(manuscript.meta.comments) || []}
+ />
+
+
),
hideOnly: true,
key: 'css-ai-assistant',
@@ -359,15 +366,17 @@ const Production = ({
key: 'versioning',
label: 'History',
content: (
- JSON.parse(manuscript.meta.comments) || []}
- />
+
+ JSON.parse(manuscript.meta.comments) || []}
+ />
+
),
}
@@ -395,15 +404,25 @@ const Production = ({
}
return (
-
-
-
-
- {isAuthorProofingVersion
- ? t('productionPage.AuthorProofing')
- : t('productionPage.Production')}
-
-
+
+
+
+
+ {tabSections.map(({ key, label }) => (
+ setActiveKey(key)}>
+
+ {label}
+
+
+ ))}
+
+
-
-
-
+
+
+
-
+ {tabSections.map(section =>
+ section.key === activeKey ? (
+ {section.content}
+ ) : null,
+ )}
-
+
)
}
diff --git a/packages/client/app/components/component-production/src/components/ProductionPage.jsx b/packages/client/app/components/component-production/src/components/ProductionPage.jsx
index 67650c751..cf090a543 100644
--- a/packages/client/app/components/component-production/src/components/ProductionPage.jsx
+++ b/packages/client/app/components/component-production/src/components/ProductionPage.jsx
@@ -279,7 +279,7 @@ const ProductionPage = () => {
isOpen={modalKey !== undefined}
/>
)}
-
+
{makingPdf ? (
+
{shownVersion + 1 < versionList.length ? (
@@ -365,7 +359,7 @@ const Versioning = ({
-
+
)
}
diff --git a/packages/client/app/components/component-production/src/components/uploadManager/UploadAsset.jsx b/packages/client/app/components/component-production/src/components/uploadManager/UploadAsset.jsx
index b81d4cf27..97ce0936d 100644
--- a/packages/client/app/components/component-production/src/components/uploadManager/UploadAsset.jsx
+++ b/packages/client/app/components/component-production/src/components/uploadManager/UploadAsset.jsx
@@ -12,24 +12,23 @@ import { convertTimestampToRelativeDateString } from '../../../../../shared/date
import { Placeholder } from '../../../../component-dashboard/src/style'
import { ConfirmationModal } from '../../../../component-modal/src/ConfirmationModal'
import FileRow from '../FileRow'
-import {
- Container,
- SectionContent,
- SectionRow,
- ActionButton,
-} from '../../../../shared'
+import { SectionContent, SectionRow, ActionButton } from '../../../../shared'
import { HeadingCell } from '../styles'
import UploadComponent from './UploadComponent'
import { TagDropdown } from './TagDropDown'
import { UPDATE_FILE_TAGS } from '../../../../../queries'
-const UploadAssetContainer = styled(Container)`
+const NoShadowSectionContent = styled(SectionContent)`
+ box-shadow: none;
+`
+
+const UploadAssetContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-between;
overflow: auto;
- padding: calc(8px * 2) calc(8px * 3);
+ width: 100%;
`
export const FileTableStyled = styled.div`
@@ -294,7 +293,7 @@ const UploadAsset = ({
return (
-
+
Css Upload:
@@ -340,7 +339,7 @@ const UploadAsset = ({
)}
-
+
setFileBeingDeletedId(null)}
confirmationAction={onDelete(fileBeingDeletedId)}
diff --git a/packages/client/app/components/component-profile/src/Profile.jsx b/packages/client/app/components/component-profile/src/Profile.jsx
index 54ef14e22..48059fad1 100644
--- a/packages/client/app/components/component-profile/src/Profile.jsx
+++ b/packages/client/app/components/component-profile/src/Profile.jsx
@@ -15,13 +15,8 @@ import { Button, Checkbox } from '../../pubsweet'
import Modal from '../../component-modal/src/ConfirmationModal'
import { convertCamelCaseToTitleCase } from '../../../shared/textUtils'
-import {
- Container,
- SectionContent,
- HeadingWithAction,
- SectionRow,
- Heading,
-} from '../../shared'
+import { SectionContent, SectionRow } from '../../shared'
+import Page from '../../../ui/shared/Page'
import ChangeUsername from './ChangeUsername'
import { BigProfileImage } from './ProfileImage'
import ChangeEmail from './ChangeEmail'
@@ -31,14 +26,15 @@ import { getLanguages } from '../../../i18n'
const VersionText = styled.div`
color: #757575;
- display: flex;
- justify-content: flex-end;
+ position: fixed;
+ bottom: ${grid(2)};
+ right: ${grid(3)};
`
-const ProfileContainer = styled(Container)`
+const RolesRow = styled.div`
display: flex;
- flex-direction: column;
- justify-content: space-between;
+ align-items: end;
+ margin-bottom: ${grid(1)};
`
const StyledCheckbox = styled(Checkbox)`
@@ -194,104 +190,102 @@ const Profile = ({
user.profilePicture || `${serverUrl}/profiles/default_avatar.svg`
return (
-
+
-
-
-
- {isCurrentUsersOwnProfile
- ? t('profilePage.Your profile')
- : t('profilePage.Profile: ') + user.username}
-
-
- {isCurrentUsersOwnProfile && (
-
- )}
-
-
+
-
-
-
-
- {isCurrentUsersOwnProfile ? (
-
- ) : (
-
- )}
-
-
-
-
- {' '}
- {user.defaultIdentity.identifier}
-
-
-
-
-
- {canEditProfile ? (
-
- ) : (
-
{user.username}
- )}
-
-
-
-
-
-
- {canEditProfile ? (
-
- ) : (
-
{user.email}
- )}
-
-
-
-
-
-
- {canEditProfile ? (
-
- ) : (
-
- {
- languages.find(elem => elem.value === i18next.language)
- .label
- }
-
- )}
-
-
-
-
-
-
-
-
+ {isCurrentUsersOwnProfile && (
+
+ )}
+
+
+
+
+
+ {isCurrentUsersOwnProfile ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {' '}
+ {user.defaultIdentity.identifier}
+
+
+
+
+
+ {canEditProfile ? (
+
+ ) : (
+
{user.username}
+ )}
+
+
+
+
+
+
+ {canEditProfile ? (
+
+ ) : (
+
{user.email}
+ )}
+
+
+
+
+
+
+ {canEditProfile ? (
+
+ ) : (
+
+ {languages.find(elem => elem.value === i18next.language).label}
+
+ )}
+
+
+
+
+
+
+
{kotahiVersion}
-
+
)
}
diff --git a/packages/client/app/components/component-profile/src/ProfilePage.jsx b/packages/client/app/components/component-profile/src/ProfilePage.jsx
index 52e43ca0e..2348847e6 100644
--- a/packages/client/app/components/component-profile/src/ProfilePage.jsx
+++ b/packages/client/app/components/component-profile/src/ProfilePage.jsx
@@ -1,8 +1,7 @@
/* eslint-disable promise/catch-or-return */
/* eslint-disable promise/always-return */
-import { useContext, useState } from 'react'
-import { Navigate, useParams } from 'react-router-dom'
+import { useNavigate, useParams } from 'react-router-dom'
import { useQuery, useMutation } from '@apollo/client/react'
import { serverUrl } from '@coko/client'
@@ -11,8 +10,8 @@ import Profile from './Profile'
import { Spinner, CommsErrorBanner } from '../../shared'
import packageJson from '../../../../package.json'
-import { ConfigContext } from '../../config/src'
import { useCurrentUser } from '../../../pages/hooks/useCurrentUser'
+import { useLogout } from '../../../pages/hooks/useLogout'
import {
GET_USER,
@@ -26,16 +25,15 @@ import {
const { version: kotahiVersion } = packageJson
const ProfilePage = () => {
- const { id } = useParams()
+ const { id, groupName } = useParams()
const currentUser = useCurrentUser()
- const { urlFrag } = useContext(ConfigContext)
- const [didLogout, setDidLogout] = useState(false)
+ const navigate = useNavigate()
+ const logout = useLogout()
const {
loading,
error,
data,
- client,
refetch: refetchUser,
} = useQuery(GET_USER, {
variables: { id: id || currentUser?.id },
@@ -73,16 +71,9 @@ const ProfilePage = () => {
if (loading) return
if (error) return
- if (didLogout) {
- return
- }
-
- const localStorage = window.localStorage || undefined
-
const logoutUser = () => {
- localStorage.removeItem('token')
- client.clearStore()
- setDidLogout(true)
+ logout()
+ navigate(`/${groupName}/login`, { replace: true })
}
// This is a bridge between the fetch results and the Apollo cache/state
diff --git a/packages/client/app/components/component-published-artifact/components/ArticleArtifactPage.jsx b/packages/client/app/components/component-published-artifact/components/ArticleArtifactPage.jsx
index bb9a6a2b3..40827f6be 100644
--- a/packages/client/app/components/component-published-artifact/components/ArticleArtifactPage.jsx
+++ b/packages/client/app/components/component-published-artifact/components/ArticleArtifactPage.jsx
@@ -3,7 +3,7 @@ import { useParams } from 'react-router-dom'
import PropTypes from 'prop-types'
import { sanitize } from 'isomorphic-dompurify'
import styled from 'styled-components'
-import { th } from '@coko/client'
+import { grid, th } from '@coko/client'
import { Spinner, CommsErrorBanner, PlainOrRichText } from '../../shared'
import { PUBLISHED_MANUSCRIPT_AND_FORMS } from '../../../queries'
@@ -18,16 +18,15 @@ const Container = styled.div`
background: ${th('color.gray97')};
border: 1px solid ${th('color.brand1.shade25')};
border-radius: ${th('borderRadius')};
- margin: ${th('spacing.g')} auto;
+ margin: ${grid(3.75)} auto;
max-width: 1000px;
- padding: ${th('spacing.h')} ${th('spacing.i')} ${th('spacing.i')}
- ${th('spacing.i')};
+ padding: ${grid(5.625)} ${grid(7.5)} ${grid(7.5)} ${grid(7.5)};
width: 90%;
& > h1 {
color: ${th('color.brand1.shade25')};
font-size: 180%;
- margin: ${th('spacing.e')} 0 ${th('spacing.f')} 0;
+ margin: ${grid(0.9375)} 0 ${grid(1.875)} 0;
}
`
diff --git a/packages/client/app/components/component-reporting/src/Report.jsx b/packages/client/app/components/component-reporting/src/Report.jsx
index 5027a07df..4c4b07759 100644
--- a/packages/client/app/components/component-reporting/src/Report.jsx
+++ b/packages/client/app/components/component-reporting/src/Report.jsx
@@ -4,37 +4,23 @@
import PropTypes from 'prop-types'
import styled, { useTheme } from 'styled-components'
import { forEach, map } from 'lodash'
-import { th, grid } from '@coko/client'
+import { th } from '@coko/client'
import { Trans, useTranslation } from 'react-i18next'
import i18next from 'i18next'
import { Icon } from '../../pubsweet'
+import Page from '../../../ui/shared/Page'
import DateRangePicker from './DateRangePicker'
import SummaryReport from './SummaryReport'
import Table from './Table'
import SparkBar from './SparkBar'
import Tooltip from './Tooltip'
-const Page = styled.div`
- flex: 1 1 100%;
- height: 100vh;
- overflow-y: auto;
- padding: 0 ${grid(2)} ${grid(3)} ${grid(2)};
-`
-
const Select = styled.select`
border: 1px solid ${th('colorBorder')};
padding: 5px;
`
-const Heading = styled.div`
- color: ${th('color.brand1.base')};
- font-family: ${th('fontReading')};
- font-size: ${th('fontSizeHeading3')};
- line-height: ${th('lineHeightHeading3')};
- margin: ${th('gridUnit')} 0;
-`
-
const MinorNote = styled.span`
color: ${th('color.gray50')};
font-size: 80%;
@@ -43,7 +29,7 @@ const MinorNote = styled.span`
export const Content = styled.div`
background-color: ${th('color.backgroundC')};
border-radius: ${th('borderRadius')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
`
const SelectionLine = styled.div`
@@ -413,8 +399,7 @@ const Report = ({
}) => {
const { t } = useTranslation()
return (
-
- {t('reportsPage.Reports')}
+
{t('reportsPage.Show')}{' '}
)
}
diff --git a/packages/client/app/components/component-review/src/components/decision/EditorSection.jsx b/packages/client/app/components/component-review/src/components/decision/EditorSection.jsx
index ce05d56d0..5777299f4 100644
--- a/packages/client/app/components/component-review/src/components/decision/EditorSection.jsx
+++ b/packages/client/app/components/component-review/src/components/decision/EditorSection.jsx
@@ -4,13 +4,26 @@
import { useContext } from 'react'
import PropTypes from 'prop-types'
+import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
+import { th } from '@coko/client'
import FullWaxEditor from '../../../../wax-collab/src/FullWaxEditor'
import { ConfigContext } from '../../../../config/src'
import { Info } from '../style'
import { useGetSpecificFiles } from '../../../../asset-manager/src/queries'
import ModalContext from '../../../../asset-manager/src/ui/Modal/ModalContext'
+const Wrapper = styled.div`
+ background: ${th('color.backgroundA')};
+ border-radius: ${th('borderRadius')};
+ box-shadow: ${th('boxShadow200')};
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-height: 0;
+ overflow: hidden;
+`
+
const EditorSection = ({
manuscript,
saveSource,
@@ -130,7 +143,7 @@ const EditorSection = ({
isOpen={modalKey !== undefined}
/>
)}
-
+
-
+
>
)
}
diff --git a/packages/client/app/components/component-submit/src/components/FormTemplate.jsx b/packages/client/app/components/component-submit/src/components/FormTemplate.jsx
index 356b8fc91..066ba1243 100644
--- a/packages/client/app/components/component-submit/src/components/FormTemplate.jsx
+++ b/packages/client/app/components/component-submit/src/components/FormTemplate.jsx
@@ -10,7 +10,7 @@ import styled from 'styled-components'
import { Formik, ErrorMessage } from 'formik'
import { unescape, get, set, debounce } from 'lodash'
import { sanitize } from 'isomorphic-dompurify'
-import { th } from '@coko/client'
+import { grid, th } from '@coko/client'
import { useTranslation } from 'react-i18next'
import {
@@ -115,7 +115,7 @@ const SafeRadioGroup = styled(RadioGroup).attrs({
const NoteRight = styled.div`
font-size: ${th('fontSizeBaseSmall')};
line-height: ${th('lineHeightBaseSmall')};
- padding: ${({ theme }) => `${theme.spacing.e} ${theme.spacing.f}`};
+ padding: ${grid(0.9375)} ${grid(1.875)};
text-align: right;
`
diff --git a/packages/client/app/components/component-submit/src/components/MultipleDoi.jsx b/packages/client/app/components/component-submit/src/components/MultipleDoi.jsx
index 28756d391..2f167a50b 100644
--- a/packages/client/app/components/component-submit/src/components/MultipleDoi.jsx
+++ b/packages/client/app/components/component-submit/src/components/MultipleDoi.jsx
@@ -2,7 +2,7 @@
import styled from 'styled-components'
import PropTypes from 'prop-types'
-import { th, uuid } from '@coko/client'
+import { grid, th, uuid } from '@coko/client'
import { useTranslation } from 'react-i18next'
import { DeleteControl, TextInput } from '../../../shared'
@@ -14,8 +14,8 @@ const Doi = styled.div`
display: grid;
gap: 36px;
grid-template-columns: 1fr 1fr;
- margin-bottom: ${th('spacing.f')};
- margin-top: ${th('spacing.e')};
+ margin-bottom: ${grid(1.875)};
+ margin-top: ${grid(0.9375)};
position: relative;
width: 600px;
diff --git a/packages/client/app/components/component-submit/src/components/NewSubmissionPage.jsx b/packages/client/app/components/component-submit/src/components/NewSubmissionPage.jsx
index 5a5aee045..5854a1dd1 100644
--- a/packages/client/app/components/component-submit/src/components/NewSubmissionPage.jsx
+++ b/packages/client/app/components/component-submit/src/components/NewSubmissionPage.jsx
@@ -2,7 +2,8 @@ import { useContext } from 'react'
import PropTypes from 'prop-types'
import { useApolloClient } from '@apollo/client/react'
import { useTranslation } from 'react-i18next'
-import { Container, Content, UploadContainer, Heading } from '../style'
+import { Content, UploadContainer } from '../style'
+import Page from '../../../../ui/shared/Page'
import UploadManuscript from './UploadManuscript'
import { ConfigContext } from '../../../config/src'
import { useCurrentUser } from '../../../../pages/hooks/useCurrentUser'
@@ -52,12 +53,13 @@ const Dashboard = () => {
}
return (
-
-
- {submissionPage?.title
+
+ : t('newSubmission.New submission')
+ }
+ >
{
/>
-
+
)
}
diff --git a/packages/client/app/components/component-submit/src/components/Submit.jsx b/packages/client/app/components/component-submit/src/components/Submit.jsx
index 900e03968..ed526e35e 100644
--- a/packages/client/app/components/component-submit/src/components/Submit.jsx
+++ b/packages/client/app/components/component-submit/src/components/Submit.jsx
@@ -3,8 +3,10 @@
import React, { useCallback, useContext, useEffect } from 'react'
import PropTypes from 'prop-types'
+import styled from 'styled-components'
import { set, debounce } from 'lodash'
import { useTranslation } from 'react-i18next'
+import { th } from '@coko/client'
import { ConfigContext } from '../../../config/src'
import DecisionAndReviews from './DecisionAndReviews'
import CreateANewVersion from './CreateANewVersion'
@@ -18,6 +20,7 @@ import {
Chat,
Manuscript,
ErrorBoundary,
+ SectionContent,
} from '../../../shared'
// TODO: Improve the import, perhaps a shared component?
@@ -30,6 +33,16 @@ import {
CollapseButton,
} from '../../../component-review/src/components/style'
+const TabPanel = styled.div`
+ background: ${th('color.backgroundA')};
+ border-radius: ${th('borderRadius')};
+ box-shadow: ${th('boxShadow200')};
+
+ ${SectionContent} {
+ box-shadow: none;
+ }
+`
+
export const createBlankSubmissionBasedOnForm = form => {
const allBlankedFields = {}
const fieldNames = form.children.map(field => field.name)
@@ -160,14 +173,18 @@ const Submit = ({
}
decisionSection = {
- content: ,
+ content: (
+
+
+
+ ),
key: version.id,
label: t('manuscriptSubmit.Edit submission info'),
}
} else {
decisionSection = {
content: (
- <>
+
- >
+
),
key: version.id,
label: t('manuscriptSubmit.Submitted info'),
@@ -215,7 +232,11 @@ const Submit = ({
manuscript={version}
/>
)}
-
+
>
),
key: version.id,
diff --git a/packages/client/app/components/component-submit/src/style.jsx b/packages/client/app/components/component-submit/src/style.jsx
index 6b8e871d5..bb4057a74 100644
--- a/packages/client/app/components/component-submit/src/style.jsx
+++ b/packages/client/app/components/component-submit/src/style.jsx
@@ -26,7 +26,7 @@ export const Section = styled.section.attrs(props => ({
export const Legend = styled.div`
font-size: ${th('fontSizeBase')};
font-weight: 500;
- margin-bottom: ${th('spacing.e')};
+ margin-bottom: ${grid(0.9375)};
`
export const SubNote = styled.span`
@@ -125,7 +125,6 @@ export const SubNote = styled.span`
`
export const UploadContainer = styled.div`
- margin-top: ${grid(2)};
padding: ${grid(3)};
text-align: center;
`
diff --git a/packages/client/app/components/component-task-manager/src/TasksTemplate.jsx b/packages/client/app/components/component-task-manager/src/TasksTemplate.jsx
index 13bb3d208..2eab0d316 100644
--- a/packages/client/app/components/component-task-manager/src/TasksTemplate.jsx
+++ b/packages/client/app/components/component-task-manager/src/TasksTemplate.jsx
@@ -3,7 +3,8 @@
import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import TaskList from './TaskList'
-import { Container, Heading, SectionContent, WidthLimiter } from '../../shared'
+import { SectionContent } from '../../shared'
+import Page from '../../../ui/shared/Page'
const TaskListContainer = styled.div`
padding: 18px 8px;
@@ -21,27 +22,24 @@ const TasksTemplate = ({
}) => {
const { t } = useTranslation()
return (
-
- {t('tasksPage.Task Template Builder')}
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
}
diff --git a/packages/client/app/components/component-users-manager/src/UsersTable.jsx b/packages/client/app/components/component-users-manager/src/UsersTable.jsx
index 4e869ff84..35cc61d2f 100644
--- a/packages/client/app/components/component-users-manager/src/UsersTable.jsx
+++ b/packages/client/app/components/component-users-manager/src/UsersTable.jsx
@@ -6,10 +6,8 @@ import styled from 'styled-components'
import { useTranslation } from 'react-i18next'
import User from './User'
import {
- Container,
Table,
Header,
- Heading,
Content,
Pagination,
PaginationContainer,
@@ -17,10 +15,12 @@ import {
SortDown,
TightRow,
} from '../../shared'
+import Page from '../../../ui/shared/Page'
const StyledTable = styled(Table)`
border-radius: 0;
border-width: 0;
+ margin-top: 0;
`
const HeaderCell = styled.th`
@@ -63,8 +63,7 @@ const UsersTable = ({
const { t } = useTranslation()
return (
-
- {t('usersTable.Users')}
+
@@ -105,7 +104,7 @@ const UsersTable = ({
totalCount={totalCount}
/>
-
+
)
}
diff --git a/packages/client/app/components/pubsweet/Button.jsx b/packages/client/app/components/pubsweet/Button.jsx
index c29899506..bed401fa6 100644
--- a/packages/client/app/components/pubsweet/Button.jsx
+++ b/packages/client/app/components/pubsweet/Button.jsx
@@ -19,6 +19,7 @@ const StyledButton = styled.button.attrs(props => ({
&:focus,
&:hover {
background-color: ${darken('colorSecondary', 0.3)};
+ color: ${th('colorText')};
transition: ${th('transitionDuration')} ${th('transitionTimingFunction')};
}
@@ -46,6 +47,7 @@ const StyledButton = styled.button.attrs(props => ({
&:focus,
&:hover {
background-color: ${darken('colorPrimary', 0.3)};
+ color: ${th('colorTextReverse')};
}
&:active {
diff --git a/packages/client/app/components/pubsweet/Icon.jsx b/packages/client/app/components/pubsweet/Icon.jsx
index c144d10ff..dd5c372ce 100644
--- a/packages/client/app/components/pubsweet/Icon.jsx
+++ b/packages/client/app/components/pubsweet/Icon.jsx
@@ -9,9 +9,6 @@ import PropTypes from 'prop-types'
import { th, override } from '@coko/client'
import Colorize from './Colorize'
-import { CoarIcon } from '../shared/Icons'
-
-const StyledCoarIcon = styled(CoarIcon)``
const Container = styled.span`
display: inline-flex;
@@ -30,13 +27,12 @@ const Container = styled.span`
const Icon = ({ children, color, size = 3, theme, ...props }) => {
// convert `arrow_left` to `ArrowLeft`
const name = upperFirst(camelCase(children))
- const isCoarIcon = name === 'CoarNotify'
// select the icon, checking for override in theme, otherwise defaulting
// to the react feather icon set
const icon = get(theme.icons, name, icons[name])
- if (!icon && !isCoarIcon) {
+ if (!icon) {
console.warn("Icon '%s' not found", name)
}
@@ -44,8 +40,7 @@ const Icon = ({ children, color, size = 3, theme, ...props }) => {
return (
- {IconComponent && !isCoarIcon ? : ''}
- {isCoarIcon ? : ''}
+ {IconComponent ? : ''}
)
}
diff --git a/packages/client/app/components/shared/Action.jsx b/packages/client/app/components/shared/Action.jsx
index 951e6ae21..71fd40acb 100644
--- a/packages/client/app/components/shared/Action.jsx
+++ b/packages/client/app/components/shared/Action.jsx
@@ -15,7 +15,7 @@ const ActionLink = styled.button`
display: inline-flex;
flex-direction: row;
font-size: inherit;
- gap: ${th('spacing.d')};
+ gap: ${grid(0.625)};
line-height: inherit;
opacity: ${({ disabled }) => (disabled ? '0.5' : '1')};
width: fit-content;
diff --git a/packages/client/app/components/shared/Checkbox.jsx b/packages/client/app/components/shared/Checkbox.jsx
index 36d1ad487..547f69f3f 100644
--- a/packages/client/app/components/shared/Checkbox.jsx
+++ b/packages/client/app/components/shared/Checkbox.jsx
@@ -1,7 +1,7 @@
/* eslint-disable react/prop-types */
import styled from 'styled-components'
-import { th } from '@coko/client'
+import { grid, th } from '@coko/client'
const CheckboxContainer = styled.div`
align-content: center;
@@ -29,7 +29,7 @@ const CheckboxContainer = styled.div`
}
label {
- margin-left: ${th('spacing.e')};
+ margin-left: ${grid(0.9375)};
}
`
diff --git a/packages/client/app/components/shared/DateRangeCalendar.jsx b/packages/client/app/components/shared/DateRangeCalendar.jsx
index 63c9e68c3..09ba4bdc3 100644
--- a/packages/client/app/components/shared/DateRangeCalendar.jsx
+++ b/packages/client/app/components/shared/DateRangeCalendar.jsx
@@ -13,7 +13,7 @@ const MainContainer = styled.div`
align-items: stretch;
background: ${th('colorBackground')};
border: 2px solid ${th('colorBorder')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
display: flex;
gap: ${grid(1)};
padding: ${grid(2)};
diff --git a/packages/client/app/components/shared/General.jsx b/packages/client/app/components/shared/General.jsx
index 4ae175fd1..aa0319a5c 100644
--- a/packages/client/app/components/shared/General.jsx
+++ b/packages/client/app/components/shared/General.jsx
@@ -11,7 +11,7 @@ export const Section = styled.section.attrs({
export const Content = styled.div`
background-color: ${th('color.backgroundA')};
border-radius: ${th('borderRadius')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
clear: both;
`
@@ -25,9 +25,7 @@ export const ScrollableContent = styled(Content)`
export const SectionContent = styled(Section)`
background-color: ${th('color.backgroundA')};
border-radius: ${th('borderRadius')};
- box-shadow: ${({ theme }) => theme.boxShadow.shades[200]};
- margin-bottom: ${grid(2)};
- margin-top: ${grid(2)};
+ box-shadow: ${({ theme }) => theme.boxShadow200};
padding: 0;
/* stylelint-disable-next-line */
diff --git a/packages/client/app/components/shared/HiddenTabs.jsx b/packages/client/app/components/shared/HiddenTabs.jsx
index 48af72931..c9dcfb035 100644
--- a/packages/client/app/components/shared/HiddenTabs.jsx
+++ b/packages/client/app/components/shared/HiddenTabs.jsx
@@ -5,7 +5,7 @@
import { useContext, useState, useEffect } from 'react'
import styled from 'styled-components'
-import { th, override } from '@coko/client'
+import { grid, th, override } from '@coko/client'
import { useTranslation } from 'react-i18next'
import { TabsContainer } from './Tabs'
@@ -43,11 +43,11 @@ export const Tab = styled.div.attrs(props => ({
cursor: pointer;
font-size: ${th('fontSizeBaseSmall')};
font-weight: 500;
- margin-right: ${props => props.theme.spacing.e};
+ margin-right: ${grid(0.9375)};
padding: calc(${th('gridUnit')} - 1px) 1em;
padding-bottom: 0;
position: relative;
- z-index: 1;
+ z-index: 6;
& > div {
border-bottom: 3px solid
@@ -154,6 +154,7 @@ const HiddenTabs = ({
flex: shouldFillFlex ? '1' : undefined,
flexDirection: 'column',
minHeight: shouldFillFlex ? '0' : undefined,
+ overflowY: shouldFillFlex ? 'auto' : undefined,
...(section?.tabStyles ?? {}),
...hideMethod(section, activeKey),
}}
diff --git a/packages/client/app/components/shared/Icons.jsx b/packages/client/app/components/shared/Icons.jsx
index 3ff3945e5..d2ce7a3ee 100644
--- a/packages/client/app/components/shared/Icons.jsx
+++ b/packages/client/app/components/shared/Icons.jsx
@@ -27,44 +27,6 @@ export const DragVerticalIcon = ({
)
}
-/**
- * Keep original colors commented out because it will be a pain to find them if
- * we ever decice to revert.
- */
-export const SemanticScholarIcon = ({ className, height, width }) => {
- return (
-
- )
-}
-
export const SendIcon = ({ className, height, width, stroke }) => {
return (
)
}
-
-export const CoarIcon = ({ className, height, width }) => {
- return (
-
- )
-}
diff --git a/packages/client/app/components/shared/MinimalDatePicker.jsx b/packages/client/app/components/shared/MinimalDatePicker.jsx
index fcb19e446..ca407c2e8 100644
--- a/packages/client/app/components/shared/MinimalDatePicker.jsx
+++ b/packages/client/app/components/shared/MinimalDatePicker.jsx
@@ -13,7 +13,7 @@ const MainContainer = styled.div`
align-items: stretch;
background: ${th('colorBackground')};
border: 2px solid ${th('colorBorder')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
display: flex;
gap: ${grid(1)};
padding: ${grid(2)};
diff --git a/packages/client/app/components/shared/Pagination.jsx b/packages/client/app/components/shared/Pagination.jsx
index 726da4844..35c5bdd43 100644
--- a/packages/client/app/components/shared/Pagination.jsx
+++ b/packages/client/app/components/shared/Pagination.jsx
@@ -28,7 +28,7 @@ export const PaginationContainerShadowed = styled(PaginationContainer).attrs(
)`
background-color: ${th('colorBackground')};
border-radius: ${th('borderRadius')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
`
const Styles = styled.div`
@@ -36,7 +36,7 @@ const Styles = styled.div`
align-items: center;
background-color: ${th('colorBackground')};
border-radius: ${th('borderRadius')};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
display: flex;
flex-wrap: wrap;
justify-content: start;
diff --git a/packages/client/app/components/shared/Select.jsx b/packages/client/app/components/shared/Select.jsx
index 14b19b562..854bd03d9 100644
--- a/packages/client/app/components/shared/Select.jsx
+++ b/packages/client/app/components/shared/Select.jsx
@@ -32,7 +32,7 @@ const styles = theme => ({
: 'inset 0px 0px 4px rgb(0 0 0 / 7%)'
: state.isFocused
? `0 0 0 1px ${theme.color.brand1.base}`
- : theme.boxShadow,
+ : theme.boxShadow200,
borderRadius: theme.borderRadius,
'&:hover': {
diff --git a/packages/client/app/components/shared/Tabs.jsx b/packages/client/app/components/shared/Tabs.jsx
index 7bda9405b..318ab6032 100644
--- a/packages/client/app/components/shared/Tabs.jsx
+++ b/packages/client/app/components/shared/Tabs.jsx
@@ -3,7 +3,7 @@
import { useContext, useState, useEffect } from 'react'
import styled, { css } from 'styled-components'
-import { th, override, uuid } from '@coko/client'
+import { grid, th, override, uuid } from '@coko/client'
import { ConfigContext } from '../config/src'
const Tab = styled.div`
@@ -27,7 +27,7 @@ const Tab = styled.div`
cursor: pointer;
font-size: ${th('fontSizeBaseSmall')};
font-weight: 500;
- margin-right: ${props => props.theme.spacing.e};
+ margin-right: ${grid(0.9375)};
padding: calc(${th('gridUnit')} - 1px) 1em;
padding-bottom: 2px;
diff --git a/packages/client/app/components/shared/VersionSwitcher.jsx b/packages/client/app/components/shared/VersionSwitcher.jsx
index 0aa404cf7..2e52e6634 100644
--- a/packages/client/app/components/shared/VersionSwitcher.jsx
+++ b/packages/client/app/components/shared/VersionSwitcher.jsx
@@ -3,15 +3,29 @@
import { useState, useEffect } from 'react'
import styled from 'styled-components'
-import { grid } from '@coko/client'
import { useTranslation } from 'react-i18next'
+
+import { grid } from '@coko/client'
+
import { Select } from './Select'
import PlainOrRichText from './PlainOrRichText'
import { VersionIndicator, VersionLabelWrapper, VersionTitle } from './General'
-const Container = styled.div`
+const Wrapper = styled.div`
+ display: flex;
+ flex-direction: column;
height: 100%;
+ min-height: 0;
+ overflow: hidden;
+`
+
+const Container = styled.div`
+ display: flex;
+ flex: 1;
+ flex-direction: column;
margin-top: ${grid(2)};
+ min-height: 0;
+ overflow: hidden;
`
const StyledSelect = styled(Select)`
@@ -20,7 +34,7 @@ const StyledSelect = styled(Select)`
}
border: 1px solid ${({ theme }) => theme.color.gray95};
- box-shadow: ${props => props.theme.boxShadow.shades[100]};
+ box-shadow: ${props => props.theme.boxShadow200};
width: ${props => (props.fullWidth ? 100 : 94)}%;
`
@@ -77,7 +91,7 @@ export const VersionSwitcher = ({
)
return (
- <>
+
selectVersionKey(option.value)}
@@ -101,6 +115,6 @@ export const VersionSwitcher = ({
{mode === 'props' ? selectedVersion.content : selectedVersion}
- >
+
)
}
diff --git a/packages/client/app/components/wax-collab/src/ProductionWaxEditor.jsx b/packages/client/app/components/wax-collab/src/ProductionWaxEditor.jsx
index 30fc1f055..61439a0f8 100644
--- a/packages/client/app/components/wax-collab/src/ProductionWaxEditor.jsx
+++ b/packages/client/app/components/wax-collab/src/ProductionWaxEditor.jsx
@@ -7,7 +7,7 @@
import { useRef, useEffect, useContext } from 'react'
import PropTypes from 'prop-types'
import { Wax } from 'wax-prosemirror-core'
-import { ThemeProvider, useTheme } from 'styled-components'
+import styled, { ThemeProvider, useTheme } from 'styled-components'
import waxTheme from './layout/waxTheme'
import { JournalContext } from '../../xpub-journal'
@@ -28,6 +28,11 @@ import {
GET_CALLOUT_TEXT,
} from '../../../queries'
+const Wrapper = styled.div`
+ height: 100%;
+ overflow: hidden;
+`
+
// TODO Save this image via the server
const renderImage = file => {
const reader = new FileReader()
@@ -288,7 +293,7 @@ const ProductionWaxEditor = ({
-
+
-
+
)
}
diff --git a/packages/client/app/components/wax-collab/src/layout/EditorStyles.jsx b/packages/client/app/components/wax-collab/src/layout/EditorStyles.jsx
index 247cba6e4..a0c41b878 100644
--- a/packages/client/app/components/wax-collab/src/layout/EditorStyles.jsx
+++ b/packages/client/app/components/wax-collab/src/layout/EditorStyles.jsx
@@ -33,6 +33,7 @@ export const Menu = styled.div`
height: fit-content;
max-width: 100%;
min-height: 40px;
+ padding-top: ${grid(2)};
position: sticky;
top: -20px;
user-select: none;
@@ -164,9 +165,9 @@ export const ReadOnlyEditorDiv = styled.div`
`
export const InfoContainer = styled.div`
- bottom: -27px;
+ bottom: 0;
position: absolute;
- right: 1px;
+ right: ${grid(2)};
z-index: 999;
`
@@ -305,7 +306,6 @@ export const SimpleInfoContainer = styled.div`
export const ProductionEditorDiv = styled.div`
background-color: ${th('color.backgroundA')};
border-bottom-left-radius: 6px;
- box-shadow: ${({ theme }) => theme.boxShadow.shades[200]};
display: flex;
flex-grow: 1;
diff --git a/packages/client/app/declarations.d.ts b/packages/client/app/declarations.d.ts
new file mode 100644
index 000000000..ea430dbd9
--- /dev/null
+++ b/packages/client/app/declarations.d.ts
@@ -0,0 +1,9 @@
+declare module '*.svg' {
+ const src: string
+ export default src
+}
+
+declare module '*.png' {
+ const src: string
+ export default src
+}
diff --git a/packages/client/app/i18n/en/translation.js b/packages/client/app/i18n/en/translation.js
index b3d527fe7..d49ba053e 100644
--- a/packages/client/app/i18n/en/translation.js
+++ b/packages/client/app/i18n/en/translation.js
@@ -103,34 +103,55 @@ const en = {
error: 'Error',
},
},
- leftMenu: {
- 'Summary Info': 'Summary Info',
- Manuscript: 'Manuscript',
+ menu: {
+ CMS: 'CMS',
+ CoarNotifyInbox: 'COAR Notify Inbox',
+ CollapseMenu: 'Collapse menu',
+ Configuration: 'Configuration',
Dashboard: 'Dashboard',
+ ExpandMenu: 'Expand menu',
+ Forms: 'Forms',
+ MainNavigation: 'Main navigation',
Manuscripts: 'Manuscripts',
Reports: 'Reports',
- Settings: 'Settings',
- Forms: 'Forms',
- Submission: 'Submission',
- Review: 'Review',
- Decision: 'Decision',
Tasks: 'Tasks',
Users: 'Users',
- Configuration: 'Configuration',
- Emails: 'Emails',
- CMS: 'CMS',
- Pages: 'Pages',
- Layout: 'Layout',
- Article: 'Article template',
- 'Go to your profile': 'Go to your profile',
- FileBrowser: 'File browser',
- Metadata: 'Publication Metadata',
- Collections: 'Collections',
- CoarNotifyInbox: 'COAR Notify Inbox',
},
- menuSettings: {
- KeepMenuVisible: 'Keep menu visible',
- MinimalSidebar: 'Minimal sidebar',
+ formsPage: {
+ title: 'Forms',
+ description:
+ 'These are the forms authors, reviewers, and editors fill in at each stage of the manuscript workflow. Editing a form here changes what information gets collected from that point on, including which fields end up available as metadata elsewhere in Kotahi — it does not change data already submitted on existing manuscripts.',
+ submissionFormTitle: 'Submission Form',
+ submissionFormDescription:
+ 'Edit the form that authors will see when submitting a manuscript. Also affects the metadata fields available to editors and the data captured that will later be available for publishing.',
+ reviewFormTitle: 'Review Form',
+ reviewFormDescription:
+ 'Edit the form that reviewers will see when submitting a review on a manuscript.',
+ decisionFormTitle: 'Decision Form',
+ decisionFormDescription:
+ 'Edit the form that editors will see when making a decision on a manuscript.',
+ },
+ cmsIndexPage: {
+ title: 'CMS',
+ description:
+ 'This is where you control your published website, separate from the internal editorial workflow. Manage static pages, site-wide layout and branding, the article template, publication metadata, and the files backing your site. Changes here go live on your published site.',
+ pagesTitle: 'Pages',
+ pagesDescription:
+ 'Manage what pages are available on your published website.',
+ layoutTitle: 'Layout',
+ layoutDescription:
+ "Control your website's colors, footer, logo and more.",
+ articleTitle: 'Article Template',
+ articleDescription:
+ 'Control the structure, styles and assets of the published article page.',
+ metadataTitle: 'Publication Metadata',
+ metadataDescription:
+ 'Manage your journal name, ISSNs and contact information.',
+ fileBrowserTitle: 'File Browser',
+ fileBrowserDescription:
+ 'View and edit all the files that will be included in your published website.',
+ collectionsTitle: 'Collections',
+ collectionsDescription: 'Manage collections of articles.',
},
profilePage: {
'Your profile': 'Your profile',
diff --git a/packages/client/app/i18n/es-la/translation.js b/packages/client/app/i18n/es-la/translation.js
index 2868576d0..e025fdc6f 100644
--- a/packages/client/app/i18n/es-la/translation.js
+++ b/packages/client/app/i18n/es-la/translation.js
@@ -103,34 +103,55 @@ const esLa = {
error: 'Error',
},
},
- leftMenu: {
- 'Summary Info': 'Información Resumen',
- Manuscript: 'Manuscrito',
+ menu: {
+ CMS: 'Gestor de Contenidos',
+ CoarNotifyInbox: 'Bandeja de entrada de COAR Notify',
+ CollapseMenu: 'Contraer menú',
+ Configuration: 'Configuración',
Dashboard: 'Tablero',
+ ExpandMenu: 'Expandir menú',
+ Forms: 'Formularios',
+ MainNavigation: 'Navegación principal',
Manuscripts: 'Manuscritos',
Reports: 'Informes',
- Settings: 'Configuración',
- Forms: 'Formularios',
- Submission: 'Envío',
- Review: 'Revisión',
- Decision: 'Decisión',
Tasks: 'Tareas',
Users: 'Usuarios',
- Configuration: 'Configuración',
- Emails: 'Correos Electrónicos',
- CMS: 'Gestor de Contenidos',
- Pages: 'Páginas',
- Layout: 'Diseño',
- 'Go to your profile': 'Ir a tu perfil',
- FileBrowser: 'Explorador de archivos',
- Article: 'Plantilla de Publicación',
- Metadata: 'Metadatos del Diario',
- Collections: 'Colecciones',
- CoarNotifyInbox: 'Bandeja de entrada de COAR Notify',
},
- menuSettings: {
- KeepMenuVisible: 'Mantener menú visible',
- MinimalSidebar: 'Menu lateral minimo',
+ formsPage: {
+ title: 'Formularios',
+ description:
+ 'Estos son los formularios que completan los autores, revisores y editores en cada etapa del flujo de trabajo del manuscrito. Editar un formulario aquí cambia la información que se recopila a partir de ese momento, incluyendo qué campos quedan disponibles como metadatos en otras partes de Kotahi; no cambia los datos ya enviados en manuscritos existentes.',
+ submissionFormTitle: 'Formulario de envío',
+ submissionFormDescription:
+ 'Edita el formulario que verán los autores al enviar un manuscrito. También afecta los campos de metadatos disponibles para los editores y los datos capturados que estarán disponibles para la publicación.',
+ reviewFormTitle: 'Formulario de revisión',
+ reviewFormDescription:
+ 'Edita el formulario que verán los revisores al enviar una revisión de un manuscrito.',
+ decisionFormTitle: 'Formulario de decisión',
+ decisionFormDescription:
+ 'Edita el formulario que verán los editores al tomar una decisión sobre un manuscrito.',
+ },
+ cmsIndexPage: {
+ title: 'Gestor de Contenidos',
+ description:
+ 'Aquí es donde controlas tu sitio web publicado, independientemente del flujo de trabajo editorial interno. Administra páginas estáticas, el diseño y la marca de todo el sitio, la plantilla de artículo, los metadatos de la publicación y los archivos que respaldan tu sitio. Los cambios aquí se publican de inmediato en tu sitio.',
+ pagesTitle: 'Páginas',
+ pagesDescription:
+ 'Administra qué páginas están disponibles en tu sitio web publicado.',
+ layoutTitle: 'Diseño',
+ layoutDescription:
+ 'Controla los colores, el pie de página, el logo y más de tu sitio web.',
+ articleTitle: 'Plantilla de artículo',
+ articleDescription:
+ 'Controla la estructura, los estilos y los recursos de la página de artículo publicada.',
+ metadataTitle: 'Metadatos de publicación',
+ metadataDescription:
+ 'Administra el nombre de tu revista, ISSNs e información de contacto.',
+ fileBrowserTitle: 'Explorador de archivos',
+ fileBrowserDescription:
+ 'Visualiza y edita todos los archivos que se incluirán en tu sitio web publicado.',
+ collectionsTitle: 'Colecciones',
+ collectionsDescription: 'Administra colecciones de artículos.',
},
profilePage: {
'Your profile': 'Tu perfil',
diff --git a/packages/client/app/i18n/fr/translation.js b/packages/client/app/i18n/fr/translation.js
index 091705821..7acb44aaa 100644
--- a/packages/client/app/i18n/fr/translation.js
+++ b/packages/client/app/i18n/fr/translation.js
@@ -103,34 +103,55 @@ const fr = {
error: 'Erreur',
},
},
- leftMenu: {
- 'Summary Info': 'Résumé des infos',
- Manuscript: 'Manuscrit',
+ menu: {
+ CMS: 'CMS',
+ CoarNotifyInbox: 'Boîte de réception COAR Notify',
+ CollapseMenu: 'Réduire le menu',
+ Configuration: 'Configuration',
Dashboard: 'Tableau de bord',
+ ExpandMenu: 'Développer le menu',
+ Forms: 'Formulaires',
+ MainNavigation: 'Navigation principale',
Manuscripts: 'Manuscrits',
Reports: 'Rapports',
- Settings: 'Paramètres',
- Forms: 'Formulaires',
- Submission: 'Soumission',
- Review: 'Révision',
- Decision: 'Décision',
Tasks: 'Tâches',
Users: 'Utilisateurs',
- Configuration: 'Configuration',
- Emails: 'E-mails',
- CMS: 'CMS',
- Pages: 'Pages',
- Layout: 'Mise en page',
- Article: "Modèle d' article",
- 'Go to your profile': 'Accéder à votre profil',
- FileBrowser: 'Explorateur de fichiers',
- Metadata: 'Métadonnées de Publication',
- Collections: 'Colecciones',
- CoarNotifyInbox: 'Boîte de réception COAR Notify',
},
- menuSettings: {
- KeepMenuVisible: 'Garder le menu visible',
- MinimalSidebar: 'Barre latérale minimale',
+ formsPage: {
+ title: 'Formulaires',
+ description:
+ 'Voici les formulaires que les auteurs, les évaluateurs et les rédacteurs remplissent à chaque étape du traitement du manuscrit. Modifier un formulaire ici change les informations collectées à partir de ce moment, y compris les champs disponibles en tant que métadonnées ailleurs dans Kotahi ; cela ne modifie pas les données déjà soumises sur les manuscrits existants.',
+ submissionFormTitle: 'Formulaire de soumission',
+ submissionFormDescription:
+ 'Modifiez le formulaire que les auteurs verront lors de la soumission d’un manuscrit. Cela affecte également les champs de métadonnées disponibles pour les rédacteurs et les données capturées qui seront disponibles pour la publication.',
+ reviewFormTitle: 'Formulaire d’évaluation',
+ reviewFormDescription:
+ 'Modifiez le formulaire que les évaluateurs verront lors de la soumission d’une évaluation d’un manuscrit.',
+ decisionFormTitle: 'Formulaire de décision',
+ decisionFormDescription:
+ 'Modifiez le formulaire que les rédacteurs verront lors de la prise d’une décision sur un manuscrit.',
+ },
+ cmsIndexPage: {
+ title: 'CMS',
+ description:
+ "C'est ici que vous gérez votre site web publié, indépendamment du flux de travail éditorial interne. Gérez les pages statiques, la mise en page et l'image de marque de l'ensemble du site, le modèle d'article, les métadonnées de publication et les fichiers qui composent votre site. Les modifications apportées ici sont publiées immédiatement sur votre site.",
+ pagesTitle: 'Pages',
+ pagesDescription:
+ 'Gérez les pages disponibles sur votre site web publié.',
+ layoutTitle: 'Mise en page',
+ layoutDescription:
+ 'Contrôlez les couleurs, le pied de page, le logo et plus encore de votre site web.',
+ articleTitle: "Modèle d'article",
+ articleDescription:
+ "Contrôlez la structure, les styles et les ressources de la page d'article publiée.",
+ metadataTitle: 'Métadonnées de publication',
+ metadataDescription:
+ 'Gérez le nom de votre revue, les ISSN et les informations de contact.',
+ fileBrowserTitle: 'Explorateur de fichiers',
+ fileBrowserDescription:
+ 'Consultez et modifiez tous les fichiers inclus dans votre site web publié.',
+ collectionsTitle: 'Collections',
+ collectionsDescription: "Gérez les collections d'articles.",
},
profilePage: {
'Your profile': 'Votre profil',
diff --git a/packages/client/app/i18n/ru/translation.js b/packages/client/app/i18n/ru/translation.js
index 07f09e874..a87a8a108 100644
--- a/packages/client/app/i18n/ru/translation.js
+++ b/packages/client/app/i18n/ru/translation.js
@@ -105,34 +105,54 @@ const ru = {
error: 'Ошибка',
},
},
- leftMenu: {
- 'Summary Info': 'Сводная информация',
- Manuscript: 'Рукопись',
+ menu: {
+ CMS: 'Система управления контентом',
+ CoarNotifyInbox: 'Входящие COAR Notify',
+ CollapseMenu: 'Свернуть меню',
+ Configuration: 'Настройки',
Dashboard: 'Панель управления',
+ ExpandMenu: 'Развернуть меню',
+ Forms: 'Формы',
+ MainNavigation: 'Основная навигация',
Manuscripts: 'Статьи',
Reports: 'Аналитика',
- Settings: 'Параметры',
- Forms: 'Формы',
- Submission: 'Подача',
- Review: 'Рецензирование',
- Decision: 'Решение',
Tasks: 'Задачи',
Users: 'Пользователи',
- Configuration: 'Настройки',
- Emails: 'Почта',
- CMS: 'Система управления контентом',
- Pages: 'Страницы',
- Layout: 'Макет',
- Article: 'Шаблон cтатьи',
- 'Go to your profile': 'Перейти в ваш профиль',
- FileBrowser: 'Файловый браузер',
- Metadata: 'Metadannye publikatsii',
- Collections: 'Коллекции',
- CoarNotifyInbox: 'Входящие COAR Notify',
},
- menuSettings: {
- KeepMenuVisible: 'оставить меню видимым',
- MinimalSidebar: 'Минимальная боковая панель',
+ formsPage: {
+ title: 'Формы',
+ description:
+ 'Это формы, которые авторы, рецензенты и редакторы заполняют на каждом этапе работы с рукописью. Изменение формы здесь меняет то, какая информация собирается начиная с этого момента, включая то, какие поля становятся доступны как метаданные в других разделах Kotahi — уже отправленные данные по существующим рукописям не изменяются.',
+ submissionFormTitle: 'Форма подачи рукописи',
+ submissionFormDescription:
+ 'Измените форму, которую авторы увидят при подаче рукописи. Это также влияет на поля метаданных, доступные редакторам, и на данные, которые будут доступны при публикации.',
+ reviewFormTitle: 'Форма рецензии',
+ reviewFormDescription:
+ 'Измените форму, которую рецензенты увидят при отправке рецензии на рукопись.',
+ decisionFormTitle: 'Форма решения',
+ decisionFormDescription:
+ 'Измените форму, которую редакторы увидят при принятии решения по рукописи.',
+ },
+ cmsIndexPage: {
+ title: 'Система управления контентом',
+ description:
+ 'Здесь вы управляете вашим опубликованным сайтом, отдельно от внутреннего редакционного процесса. Управляйте статичными страницами, оформлением и брендингом всего сайта, шаблоном статьи, метаданными публикации и файлами, на которых держится сайт. Изменения здесь сразу же публикуются на вашем сайте.',
+ pagesTitle: 'Страницы',
+ pagesDescription: 'Управляйте страницами, доступными на вашем сайте.',
+ layoutTitle: 'Оформление',
+ layoutDescription:
+ 'Управляйте цветами, подвалом, логотипом и другими элементами вашего сайта.',
+ articleTitle: 'Шаблон статьи',
+ articleDescription:
+ 'Управляйте структурой, стилями и ресурсами опубликованной страницы статьи.',
+ metadataTitle: 'Метаданные публикации',
+ metadataDescription:
+ 'Управляйте названием журнала, ISSN и контактной информацией.',
+ fileBrowserTitle: 'Проводник файлов',
+ fileBrowserDescription:
+ 'Просматривайте и редактируйте все файлы, включённые в ваш опубликованный сайт.',
+ collectionsTitle: 'Коллекции',
+ collectionsDescription: 'Управляйте коллекциями статей.',
},
profilePage: {
'Your profile': 'Ваш профиль',
diff --git a/packages/client/app/pages/Authenticated.page.tsx b/packages/client/app/pages/Authenticated.page.tsx
index dd1fb3231..2aaefa06f 100644
--- a/packages/client/app/pages/Authenticated.page.tsx
+++ b/packages/client/app/pages/Authenticated.page.tsx
@@ -1,8 +1,10 @@
/* eslint-disable react-hooks/immutability */
-import { type ReactNode, useContext, useEffect } from 'react'
+import { type ReactNode, useContext, useEffect, useRef } from 'react'
import { Outlet, Navigate, useParams, useLocation } from 'react-router-dom'
+import { LinkError } from '@apollo/client/errors'
import { useMutation, useQuery } from '@apollo/client/react'
+import { useNotification } from '@coko/client'
import i18next from 'i18next'
import { CURRENT_USER, UPDATE_LANGUAGE } from '../queries'
@@ -11,14 +13,21 @@ import { JournalContext } from '../components/xpub-journal'
import { ConfigContext } from '../components/config/src'
import { getLanguages } from '../i18n'
import { CurrentUserContext } from './hooks/useCurrentUser'
+import { useLogout } from './hooks/useLogout'
const AuthenticatedPage = (): ReactNode => {
const { pathname } = useLocation()
const { groupName } = useParams()
const config = useContext(ConfigContext)
const journal = useContext(JournalContext)
+ const notification = useNotification()
+ const wasDisconnected = useRef(false)
+ const logout = useLogout()
- const { loading, data } = useQuery(CURRENT_USER, {
+ // @ts-ignore
+ const { instanceName } = config
+
+ const { loading, error, data } = useQuery(CURRENT_USER, {
fetchPolicy: 'network-only',
pollInterval: 60000,
notifyOnNetworkStatusChange: false, // stops screen flickering
@@ -51,13 +60,27 @@ const AuthenticatedPage = (): ReactNode => {
}
}, [currentUser, updateLanguage])
+ useEffect(() => {
+ if (LinkError.is(error)) {
+ wasDisconnected.current = true
+ notification.warning({ message: 'Connection to the server lost' })
+ } else if (wasDisconnected.current && currentUser) {
+ wasDisconnected.current = false
+ notification.success({ message: 'Connection to the server restored' })
+ }
+ }, [error, currentUser, notification])
+
+ useEffect(() => {
+ if (!loading && !currentUser) {
+ logout()
+ }
+ }, [loading, currentUser, logout])
+
if (loading) {
return
}
if (!currentUser) {
- // TO DO - reuse a logout function here that clearn cache etc.
- localStorage.removeItem('token')
return
}
@@ -67,7 +90,7 @@ const AuthenticatedPage = (): ReactNode => {
journal.textStyles = data?.builtCss?.css
if (
- ['journal', 'prc'].includes(config.instanceName) &&
+ ['journal', 'prc'].includes(instanceName) &&
currentUser &&
!currentUser.email &&
pathname !== `/${groupName}/profile` // TODO configure this url via config manager
diff --git a/packages/client/app/pages/CMS.page.tsx b/packages/client/app/pages/CMS.page.tsx
new file mode 100644
index 000000000..f33069a3d
--- /dev/null
+++ b/packages/client/app/pages/CMS.page.tsx
@@ -0,0 +1,9 @@
+import { type ReactNode } from 'react'
+
+import CMS from '../ui/pages/cms/CMS'
+
+const CMSPage = (): ReactNode => {
+ return
+}
+
+export default CMSPage
diff --git a/packages/client/app/pages/Forms.page.tsx b/packages/client/app/pages/Forms.page.tsx
new file mode 100644
index 000000000..8cb79b921
--- /dev/null
+++ b/packages/client/app/pages/Forms.page.tsx
@@ -0,0 +1,9 @@
+import { type ReactNode } from 'react'
+
+import FormBuilder from '../ui/pages/formBuilder/FormBuilder'
+
+const FormsPage = (): ReactNode => {
+ return
+}
+
+export default FormsPage
diff --git a/packages/client/app/pages/Menu.page.tsx b/packages/client/app/pages/Menu.page.tsx
index 55307f713..5527505d7 100644
--- a/packages/client/app/pages/Menu.page.tsx
+++ b/packages/client/app/pages/Menu.page.tsx
@@ -1,229 +1,47 @@
import { type ReactNode, useContext } from 'react'
-import { useTranslation } from 'react-i18next'
-import { useLocation, useParams, Navigate } from 'react-router-dom'
-import { useApolloClient, useQuery } from '@apollo/client/react'
import { ConfigContext } from '../components/config/src'
-import Menu from '../components/Menu'
-import { CURRENT_USER } from '../queries'
+import { useCurrentUser } from './hooks/useCurrentUser'
-// const loginLink = `${urlFrag}/login?next=${homeLink}`
-
-type Link = {
- name: string
-
- link?: string
- icon?: string
- hasAlert?: boolean
- menu?: string
- links?: Link[]
-}
+import Menu from '../ui/base/Menu'
const MenuPage = (): ReactNode => {
- const location = useLocation()
- const params = useParams()
- const { t } = useTranslation()
- const client = useApolloClient()
+ const menuCollapsed = localStorage.getItem('menuCollapsed') === 'true'
- const config = useContext(ConfigContext)
- const { urlFrag, instanceName } = config
-
- // TO DO - this should be in a more generic place than the menu
- const { loading, error, data } = useQuery(CURRENT_USER)
-
- if (loading) return null
+ const updateMenuCollapsed = (collapsed: boolean): void => {
+ localStorage.setItem('menuCollapsed', String(collapsed))
+ }
- // @ts-ignore
- const currentUser = data?.currentUser
+ const config = useContext(ConfigContext)
+ const user = useCurrentUser()
// @ts-ignore
- const hasAlert = data?.userHasTaskAlerts
+ const { groupRoles, globalRoles, username, profilePicture } = user
- let notice = ''
+ const isUserGroupAdmin = groupRoles.includes('groupAdmin')
+ const isUserGroupManager = groupRoles.includes('groupManager')
+ const isUserAdmin = globalRoles.includes('admin')
// @ts-ignore
- if (error || !data?.currentUser) {
- // @ts-ignore
- if (error?.networkError) {
- notice = 'You are offline.'
- } else {
- if (localStorage.getItem('token') !== null) {
- localStorage.removeItem('token')
- }
-
- client.cache.reset()
-
- localStorage.setItem('intendedPage', location.pathname + location.search)
- const redirectlocation = `${urlFrag}/login`
- return
- }
- }
-
- const submissionFormBuilderLink = `${urlFrag}/admin/submission-form-builder`
- const reviewFormBuilderLink = `${urlFrag}/admin/review-form-builder`
- const decisionFormBuilderLink = `${urlFrag}/admin/decision-form-builder`
- const configurationLink = `${urlFrag}/admin/configuration`
- const manuscriptsLink = `${urlFrag}/admin/manuscripts`
- const userAdminLink = `${urlFrag}/admin/users`
- const tasksTemplateLink = `${urlFrag}/admin/tasks`
- const CMSPagesPageLink = `${urlFrag}/admin/cms/pages`
- const CMSLayoutPageLink = `${urlFrag}/admin/cms/layout`
- const CMSArticlePageLink = `${urlFrag}/admin/cms/article`
- const CMSFileBrowserLink = `${urlFrag}/admin/cms/filebrowser`
- const CMSMetadataPageLink = `${urlFrag}/admin/cms/metadata`
- const CMSPublishingCollectionPageLink = `${urlFrag}/admin/cms/collections`
- const profileLink = `${urlFrag}/profile`
- const reportsLink = `${urlFrag}/admin/reports`
- const homeLink = `${urlFrag}/dashboard`
- const coarNotifyLink = `${urlFrag}/admin/coar-inbox`
-
- const showLinks = location.pathname.match(/^\/(submit|manuscript)/g)
+ const { instanceName, groupIdentity, report, controlPanel } = config
- const isUser = currentUser?.groupRoles?.includes('user')
- const isGroupManager = currentUser?.groupRoles?.includes('groupManager')
- const isGroupAdmin = currentUser?.groupRoles?.includes('groupAdmin')
- const isAdmin = currentUser?.globalRoles?.includes('admin')
-
- let links: Link[] = []
-
- if (showLinks) {
- const baseLink = `${urlFrag}/versions/${params.version}`
- const submitLink = `${baseLink}/submit`
- const manuscriptLink = `${baseLink}/manuscript`
-
- links = [
- { link: submitLink, name: t('leftMenu.Summary Info') },
- {
- link: manuscriptLink,
- name: t('leftMenu.Manuscript'),
- },
- ]
- }
-
- if (
- currentUser &&
- (isUser || isGroupManager || isGroupAdmin || isAdmin) &&
- ['journal', 'prc', 'preprint2'].includes(instanceName) // TODO: remove instance based logic and refactor it to be enabled and disabled from config manager
- ) {
- links.push({
- link: homeLink,
- name: t('leftMenu.Dashboard'),
- icon: 'home',
- hasAlert,
- })
- }
-
- if (isGroupManager || isGroupAdmin) {
- links.push({
- link: manuscriptsLink,
- name: t('leftMenu.Manuscripts'),
- icon: 'file-text',
- })
-
- if (config?.report?.showInMenu && isGroupAdmin)
- links.push({
- link: reportsLink,
- name: t('leftMenu.Reports'),
- icon: 'activity',
- })
-
- if (config?.controlPanel?.showTabs.includes('COAR Notify Metadata')) {
- links.push({
- link: coarNotifyLink,
- name: t('leftMenu.CoarNotifyInbox'),
- icon: '_coar-notify',
- })
- }
- }
-
- if (isGroupAdmin || isAdmin) {
- links.push({
- menu: 'Settings',
- name: t('leftMenu.Settings'),
- icon: 'settings',
- links: [
- {
- menu: 'Forms',
- name: t('leftMenu.Forms'),
- icon: 'check-square',
- links: [
- {
- link: submissionFormBuilderLink,
- name: t('leftMenu.Submission'),
- },
- {
- link: reviewFormBuilderLink,
- name: t('leftMenu.Review'),
- },
- {
- link: decisionFormBuilderLink,
- name: t('leftMenu.Decision'),
- },
- ],
- },
- {
- link: tasksTemplateLink,
- name: t('leftMenu.Tasks'),
- icon: 'list',
- },
- {
- link: userAdminLink,
- name: t('leftMenu.Users'),
- icon: 'users',
- },
- {
- link: configurationLink,
- name: t('leftMenu.Configuration'),
- icon: 'sliders',
- },
- {
- menu: 'CMS',
- name: t('leftMenu.CMS'),
- icon: 'layout',
- links: [
- {
- link: CMSPagesPageLink,
- name: t('leftMenu.Pages'),
- icon: '',
- },
- {
- link: CMSLayoutPageLink,
- name: t('leftMenu.Layout'),
- icon: '',
- },
- {
- link: CMSArticlePageLink,
- name: t('leftMenu.Article'),
- icon: '',
- },
- {
- link: CMSMetadataPageLink,
- name: t('leftMenu.Metadata'),
- icon: '',
- },
- {
- link: CMSFileBrowserLink,
- name: t('leftMenu.FileBrowser'),
-
- icon: '',
- },
- {
- link: CMSPublishingCollectionPageLink,
- name: t('leftMenu.Collections'),
- icon: '',
- },
- ],
- },
- ],
- })
- }
+ const showDashboard = ['journal', 'prc', 'preprint2'].includes(instanceName)
+ const showCoar = controlPanel?.showTabs.includes('COAR Notify Metadata')
return (
)
}
diff --git a/packages/client/app/pages/hooks/useLogout.ts b/packages/client/app/pages/hooks/useLogout.ts
new file mode 100644
index 000000000..392280806
--- /dev/null
+++ b/packages/client/app/pages/hooks/useLogout.ts
@@ -0,0 +1,11 @@
+import { useCallback } from 'react'
+import { useApolloClient } from '@apollo/client/react'
+
+export const useLogout = (): (() => void) => {
+ const client = useApolloClient()
+
+ return useCallback(() => {
+ localStorage.removeItem('token')
+ client.clearStore()
+ }, [client])
+}
diff --git a/packages/client/app/pages/index.ts b/packages/client/app/pages/index.ts
index c3a9752d7..e0a1de64a 100644
--- a/packages/client/app/pages/index.ts
+++ b/packages/client/app/pages/index.ts
@@ -1,3 +1,5 @@
export { default as AuthenticatedPage } from './Authenticated.page'
+export { default as CMSPage } from './CMS.page'
+export { default as FormsPage } from './Forms.page'
export { default as GroupPage } from './Group.page'
export { default as MenuPage } from './Menu.page'
diff --git a/packages/client/app/queries/group.queries.ts b/packages/client/app/queries/group.queries.ts
index 00e686e4a..aaa370cea 100644
--- a/packages/client/app/queries/group.queries.ts
+++ b/packages/client/app/queries/group.queries.ts
@@ -55,7 +55,6 @@ export const GET_CONFIG_AND_EMAIL_TEMPLATES = gql`
logo {
${fileFields}
}
-
icon {
${fileFields}
}
diff --git a/packages/client/app/queries/user.queries.ts b/packages/client/app/queries/user.queries.ts
index b9664ba1c..0f36b4207 100644
--- a/packages/client/app/queries/user.queries.ts
+++ b/packages/client/app/queries/user.queries.ts
@@ -21,7 +21,6 @@ export const CURRENT_USER = gql`
groupRoles
email
recentTab
- menuPinned
preferredLanguage
chatExpanded
defaultIdentity {
diff --git a/packages/client/app/queries/util.queries.ts b/packages/client/app/queries/util.queries.ts
index 226dd16fe..e48c99d18 100644
--- a/packages/client/app/queries/util.queries.ts
+++ b/packages/client/app/queries/util.queries.ts
@@ -35,12 +35,3 @@ export const UPDATE_TAB = gql`
}
}
`
-
-export const UPDATE_MENU = gql`
- mutation UpdateMenu($expanded: Boolean!) {
- updateMenuUI(expanded: $expanded) {
- id
- menuPinned
- }
- }
-`
diff --git a/packages/client/app/styled.d.ts b/packages/client/app/styled.d.ts
new file mode 100644
index 000000000..46d5f6ad3
--- /dev/null
+++ b/packages/client/app/styled.d.ts
@@ -0,0 +1,58 @@
+import 'styled-components'
+
+declare module 'styled-components' {
+ export interface DefaultTheme {
+ colorBackground: string
+ colorSecondaryBackground: string
+ colorPrimary: string
+ colorSecondary: string
+ colorFurniture: string
+ colorBorder: string
+ colorBackgroundHue: string
+ colorSuccess: string
+ colorError: string
+ colorText: string
+ colorTextReverse: string
+ colorTextPlaceholder: string
+ colorWarning: string
+ colorWarningLight: string
+ colorWarningDark: string
+ colorSuccessLight: string
+ colorSuccessDark: string
+ colorIconPrimary: string
+ colorContainerBorder: string
+ colorDisabled: string
+
+ fontInterface: string
+ fontHeading: string
+ fontReading: string
+ fontWriting: string
+
+ fontSizeBase: string
+ fontSizeBaseSmall: string
+ fontSizeBaseSmaller: string
+ fontSizeHeading1: string
+ fontSizeHeading2: string
+ fontSizeHeading3: string
+ fontSizeHeading4: string
+ fontSizeHeading5: string
+ fontSizeHeading6: string
+
+ lineHeightBase: string
+ lineHeightBaseSmall: string
+ lineHeightBaseSmaller: string
+ lineHeightHeading1: string
+ lineHeightHeading2: string
+ lineHeightHeading3: string
+ lineHeightHeading4: string
+ lineHeightHeading5: string
+ lineHeightHeading6: string
+
+ gridUnit: string
+
+ borderRadius: string
+ borderRadiusLarge: string
+ borderWidth: string
+ borderStyle: string
+ }
+}
diff --git a/packages/client/app/theme/elements/GlobalStyle.jsx b/packages/client/app/theme/elements/GlobalStyle.jsx
index f46dfd6cb..66c5a0940 100644
--- a/packages/client/app/theme/elements/GlobalStyle.jsx
+++ b/packages/client/app/theme/elements/GlobalStyle.jsx
@@ -1,7 +1,9 @@
-import { createGlobalStyle } from 'styled-components'
-import { th } from '@coko/client'
+import { css, createGlobalStyle } from 'styled-components'
+import { th, grid } from '@coko/client'
+
+const globalStyles = css`
+ /* stylelint-disable declaration-no-important */
-export default createGlobalStyle`
html {
box-sizing: border-box;
display: flex;
@@ -35,7 +37,6 @@ export default createGlobalStyle`
* {
border: 0;
- /* -webkit-font-smoothing: auto; */
font-weight: inherit;
margin: 0;
outline: 0;
@@ -46,10 +47,52 @@ export default createGlobalStyle`
a {
color: ${th('color.brand1.base')};
+ text-decoration: none !important;
}
strong,
b {
font-weight: bold;
}
+
+ /* Ant notifications */
+
+ .ant-notification-notice {
+ padding: ${grid(3)} !important;
+
+ > button {
+ top: calc(${grid(3)} + 3px) !important;
+ right: ${grid(3)} !important;
+ }
+ }
+
+ .ant-notification-notice-progress::-webkit-progress-value {
+ background: ${th('colorBorder')} !important;
+ }
+
+ .ant-notification-notice-progress::-moz-progress-bar {
+ background: ${th('colorBorder')} !important;
+ }
+
+ .ant-notification-notice-with-icon > div.ant-notification-notice-title {
+ margin-bottom: 0 !important;
+ }
+
+ .ant-notification-notice-success {
+ border-left: 5px solid ${th('colorSuccess')};
+ }
+
+ .ant-notification-notice-error {
+ border-left: 5px solid ${th('colorError')};
+ }
+
+ .ant-notification-notice-warning {
+ border-left: 5px solid ${th('colorWarning')};
+ }
+
+ .ant-notification-notice-info {
+ border-left: 5px solid ${th('colorInfo')};
+ }
`
+
+export default createGlobalStyle`${globalStyles}`
diff --git a/packages/client/app/theme/elements/TextField.jsx b/packages/client/app/theme/elements/TextField.jsx
index d5c064a7e..60554d724 100644
--- a/packages/client/app/theme/elements/TextField.jsx
+++ b/packages/client/app/theme/elements/TextField.jsx
@@ -43,7 +43,7 @@ export default {
return props.theme.color.brand1.base
}
}};
- box-shadow: ${th('boxShadow')};
+ box-shadow: ${th('boxShadow200')};
color: inherit;
}
diff --git a/packages/client/app/theme/index.jsx b/packages/client/app/theme/index.jsx
index 202a9a81e..b4c75c1d6 100644
--- a/packages/client/app/theme/index.jsx
+++ b/packages/client/app/theme/index.jsx
@@ -24,6 +24,8 @@ import '@fontsource/roboto/700.css'
import '@fontsource/roboto/900.css'
import '@fontsource/roboto/400-italic.css'
+import '@fontsource/spectral-sc'
+
export const validateColor = colorCode => {
try {
Color(colorCode)
@@ -34,18 +36,31 @@ export const validateColor = colorCode => {
}
}
-const defaultBrandColor1 = '#3aae2a'
-const defaultBrandColor2 = '#9e9e9e'
+// Look into a warmer papery theme
+// background
+// 1. #F9F8F6 • Cotton Rag Paper (The Safe Bet)
+// 2. #F7F5F0 • Heavy Cardstock (More Alabaster)
+// 3. #FAF9F6 • Clean Washi Paper (Whisper Light)
+
+// font
+// warm, dark gray like #2C2A29
+// or #333230
+
+// const defaultBrandColor1 = '#3aae2a'
+// const defaultBrandColor2 = '#9e9e9e'
+const defaultBrandColor1 = '#4a7c59'
+const defaultBrandColor2 = '#6b7280'
export const makeTheme = (
colorBrand1 = defaultBrandColor1,
colorBrand2 = defaultBrandColor2,
) => ({
+ // DEPRECATED - unused values are commented out
color: {
black: '#000000',
- gray0: '#000000',
+ // gray0: '#000000',
gray5: '#111111', // 7%: colorText
- gray10: '#191919', // 10%
+ // gray10: '#191919', // 10%
gray20: '#323232', // 20%
gray30: '#4F4F4F', // 30%
// gray35: '#595959', // 35%: colorTextPlaceholder
@@ -58,7 +73,7 @@ export const makeTheme = (
gray95: '#EEEEEE', // 93%
gray97: '#F8F8F9', // 97%
gray99: '#FCFCFD', // 99%
- gray100: '#FFFFFF',
+ // gray100: '#FFFFFF',
white: '#FFFFFF',
text: '#111111',
@@ -67,13 +82,14 @@ export const makeTheme = (
backgroundA: '#FFFFFF',
backgroundB: '#f9fafb',
- backgroundC: '#f4f5f7',
+ // backgroundC: '#f4f5f7',
+ backgroundC: '#f2f2f2',
brand1: {
shade50: Color(colorBrand1).darken(0.52),
shade25: Color(colorBrand1).darken(0.27),
- shade15: Color(colorBrand1).darken(0.17),
- shade10: Color(colorBrand1).darken(0.11),
+ // shade15: Color(colorBrand1).darken(0.17),
+ // shade10: Color(colorBrand1).darken(0.11),
base: colorBrand1,
tint10: lightenBy(colorBrand1, 0.1),
tint25: lightenBy(colorBrand1, 0.26),
@@ -82,186 +98,130 @@ export const makeTheme = (
tint90: lightenBy(colorBrand1, 0.93),
},
brand2: {
- shade50: Color(colorBrand2).darken(0.52),
- shade25: Color(colorBrand2).darken(0.27),
- shade15: Color(colorBrand2).darken(0.17),
- shade10: Color(colorBrand2).darken(0.11),
+ // shade50: Color(colorBrand2).darken(0.52),
+ // shade25: Color(colorBrand2).darken(0.27),
+ // shade15: Color(colorBrand2).darken(0.17),
+ // shade10: Color(colorBrand2).darken(0.11),
base: colorBrand2,
- tint10: lightenBy(colorBrand2, 0.1),
- tint25: lightenBy(colorBrand2, 0.26),
- tint50: lightenBy(colorBrand2, 0.53),
+ // tint10: lightenBy(colorBrand2, 0.1),
+ // tint25: lightenBy(colorBrand2, 0.26),
+ // tint50: lightenBy(colorBrand2, 0.53),
tint70: lightenBy(colorBrand2, 0.73),
- tint90: lightenBy(colorBrand2, 0.93),
+ // tint90: lightenBy(colorBrand2, 0.93),
},
success: {
- shade50: '#133a0e',
- shade25: '#1b5414',
- shade15: '#25721c',
- shade10: '#2c8a21',
+ // shade50: '#133a0e',
+ // shade25: '#1b5414',
+ // shade15: '#25721c',
+ // shade10: '#2c8a21',
base: '#329a25',
- tint10: '#3bb32b',
- tint25: '#4fcb3e',
- tint50: '#8ddf83',
- tint70: '#bcedb6',
- tint90: '#e3f8e0',
+ // tint10: '#3bb32b',
+ // tint25: '#4fcb3e',
+ // tint50: '#8ddf83',
+ // tint70: '#bcedb6',
+ // tint90: '#e3f8e0',
},
warning: {
shade50: '#6f3f00',
- shade25: '#8e5000',
- shade15: '#ae6200',
+ // shade25: '#8e5000',
+ // shade15: '#ae6200',
shade10: '#c56f00',
base: '#e48100',
- tint10: '#f69414',
- tint25: '#f8ae4c',
+ // tint10: '#f69414',
+ // tint25: '#f8ae4c',
tint50: '#f6c88d',
tint70: '#fadfbe',
tint90: '#fdf1df',
},
error: {
- shade50: '#6f1919',
- shade25: '#8a1e1e',
- shade15: '#a52424',
- shade10: '#bf2828',
+ // shade50: '#6f1919',
+ // shade25: '#8a1e1e',
+ // shade15: '#a52424',
+ // shade10: '#bf2828',
base: '#d22b2b',
- tint10: '#d94747',
- tint25: '#e06969',
- tint50: '#e88e8e',
- tint70: '#f0b4b4',
- tint90: '#f8dcdc',
+ // tint10: '#d94747',
+ // tint25: '#e06969',
+ // tint50: '#e88e8e',
+ // tint70: '#f0b4b4',
+ // tint90: '#f8dcdc',
},
additional: {
- blue: '#71AED2',
- purple: '#615CCF',
- aqua: '#7ED3A6',
+ // blue: '#71AED2',
+ // purple: '#615CCF',
+ // aqua: '#7ED3A6',
green: '#89D46C',
- mustard: '#CCD66E',
- },
- },
- spacing: {
- /** 1px */
- a: '1px',
- /** 2px */
- b: '2px',
- /** 3px */
- c: '3px',
- /** 5px */
- d: '5px',
- /** 7.5px */
- e: '7.5px',
- /** 15px */
- f: '15px',
- /** 30px */
- g: '30px',
- /** 45px */
- h: '45px',
- /** 60px */
- i: '60px',
- /** 90px */
- j: '90px',
- /** 135px */
- k: '135px',
- },
- typography: {
- fonts: {
- size: {
- small: '12px',
- small2: '14px',
- regular: '16px',
- large: '20px',
- large2: '30px',
- },
+ // mustard: '#CCD66E',
},
},
- /* Colors */
- // TODO Deprecate these in favour of definitions in color.js
- /** @deprecated in favor of color.backgroundA */
colorBackground: '#FFFFFF',
- /** @deprecated in favor of color.backgroundB */
- colorSecondaryBackground: '#f9fafb',
- /** @deprecated in favor of color.brand1.base */
+ colorBackgroundHue: '#f4f5f7',
+ colorBorder: '#A5A5A5',
+ colorContainerBorder: '#DEDEDE',
+ colorDisabled: '#bfbfbf',
+ colorError: '#B53930',
+ colorFurniture: '#E8E8E8',
+ colorIconPrimary: '#666666',
+ colorInfo: '#666666',
colorPrimary: colorBrand1,
- /** @deprecated in favor of color.brand2.base */
colorSecondary: colorBrand2,
- /** @deprecated in favor of color.gray90 */
- colorFurniture: '#E8E8E8',
- /** @deprecated in favor of color.gray60 */
- colorBorder: '#A5A5A5',
- /** @deprecated in favor of color.backgroundC */
- colorBackgroundHue: '#f4f5f7',
+ colorSecondaryBackground: '#f9fafb',
colorSuccess: '#008800',
- colorError: '#FF2D1A',
- /** @deprecated in favor of color.text */
+ colorSuccessDark: '#17510F',
+ colorSuccessLight: '#d2ffcc',
colorText: '#111111',
- /** @deprecated in favor of color.textReverse */
- colorTextReverse: '#FFFFFF',
- /** @deprecated in favor of color.textPlaceholder */
+ colorTextHeading: '#111111',
colorTextPlaceholder: '#666666',
- colorWarning: '#ffc107',
- colorWarningLight: '#fff9ed',
+ colorTextReverse: '#FFFFFF',
+ colorWallpaper: '#f2f2f2',
+ colorWarning: '#C18D33',
colorWarningDark: '#503303',
- colorSuccessLight: '#d2ffcc',
- colorSuccessDark: '#17510F',
- /** @deprecated in favor of color.gray40 */
- colorIconPrimary: '#666666',
- /** @deprecated in favor of color.gray80 */
- colorContainerBorder: '#DEDEDE',
-
- /* Text variables */
+ colorWarningLight: '#fff9ed',
// fonts
fontInterface:
"Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
- fontHeading:
- "Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
+ fontHeading: "'Spectral SC', Georgia, 'Times New Roman', serif",
fontReading:
"Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
fontWriting:
"Roboto, -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'",
- // font sizes
- fontSizeBase: '16px',
- fontSizeBaseSmall: '14px',
- fontSizeBaseSmaller: '12px',
- fontSizeHeading1: '40px',
- fontSizeHeading2: '36px',
- fontSizeHeading3: '28px',
- fontSizeHeading4: '24px',
- fontSizeHeading5: '20px',
- fontSizeHeading6: '16px',
+ // https://typescale.com (major second)
+ fontSizeBase: '1rem',
+ fontSizeBaseSmall: '0.889rem',
+ fontSizeBaseSmaller: '0.79rem',
+ fontSizeHeading1: '2.027rem',
+ fontSizeHeading2: '1.802rem',
+ fontSizeHeading3: '1.602rem',
+ fontSizeHeading4: '1.424rem',
+ fontSizeHeading5: '1.266rem',
+ fontSizeHeading6: '1.125rem',
- // line heights
- lineHeightBase: '24px',
- lineHeightBaseSmall: '16px',
- lineHeightBaseSmaller: '14px',
- lineHeightHeading1: '48px',
- lineHeightHeading2: '40px',
- lineHeightHeading3: '32px',
- lineHeightHeading4: '32px',
- lineHeightHeading5: '24px',
- lineHeightHeading6: '24px',
+ lineHeightBase: '1.5',
+ lineHeightBaseSmall: '1.5',
+ lineHeightBaseSmaller: '1.4',
+ lineHeightHeading6: '1.35',
+ lineHeightHeading5: '1.3',
+ lineHeightHeading4: '1.25',
+ lineHeightHeading3: '1.2',
+ lineHeightHeading2: '1.15',
+ lineHeightHeading1: '1.1',
/* Spacing */
gridUnit: '8px',
/* Border */
- borderRadius: '6px',
+ borderRadius: '3px',
+ borderRadiusLarge: '15%',
borderWidth: '1px', // julien: not 0
borderStyle: 'solid',
// Does not exist
// $borderColor: var($colorFurniture);
- /* Shadow (for tooltip) */
- boxShadow: {
- inset: 'inset 0px 0px 4px rgb(0 0 0 / 7%)',
- shades: {
- 100: '0 2px 6px 0 rgb(0 0 0 / 5%)',
- 200: '0 2px 6px 0 rgb(0 0 0 / 10%)',
- 300: '0 2px 6px 0 rgb(0 0 0 / 20%)',
- },
- },
+ boxShadow200: '0 2px 6px 0 rgb(0 0 0 / 10%)',
/* Transition */
// transitionDuration: '0.2s', // TODO -- julien: not 0.05s
diff --git a/packages/client/app/ui/assets/avatar_fallback.svg b/packages/client/app/ui/assets/avatar_fallback.svg
new file mode 100644
index 000000000..bc59fe59c
--- /dev/null
+++ b/packages/client/app/ui/assets/avatar_fallback.svg
@@ -0,0 +1,13 @@
+
diff --git a/packages/client/app/ui/base/Icons.tsx b/packages/client/app/ui/base/Icons.tsx
new file mode 100644
index 000000000..40911e4e4
--- /dev/null
+++ b/packages/client/app/ui/base/Icons.tsx
@@ -0,0 +1,80 @@
+import { type AriaAttributes, type ReactNode } from 'react'
+
+export {
+ ArrowRightOutlined as ArrowRight,
+ BarChartOutlined as Report,
+ ControlOutlined as Settings,
+ FileTextOutlined as File,
+ FormOutlined as Form,
+ HomeOutlined as Home,
+ ReadOutlined as Book,
+ UnorderedListOutlined as Tasks,
+ UserOutlined as User,
+ VerticalAlignTopOutlined as ExpandMenu,
+} from '@ant-design/icons'
+
+type IconProps = {
+ className?: string
+ 'aria-hidden'?: AriaAttributes['aria-hidden']
+}
+
+export const Coar = ({
+ className,
+ 'aria-hidden': ariaHidden,
+}: IconProps): ReactNode => {
+ return (
+
+
+
+ )
+}
+
+export const SemanticScholar = ({
+ className,
+ 'aria-hidden': ariaHidden,
+}: IconProps): ReactNode => {
+ return (
+
+
+
+ )
+}
diff --git a/packages/client/app/ui/base/Layout.tsx b/packages/client/app/ui/base/Layout.tsx
index c5aebccd7..9f4a0147a 100644
--- a/packages/client/app/ui/base/Layout.tsx
+++ b/packages/client/app/ui/base/Layout.tsx
@@ -20,6 +20,7 @@ const StyledLayout = styled.div<{ $converting: boolean }>`
> *:last-child {
flex-grow: 1;
+ min-width: 0;
}
`
diff --git a/packages/client/app/ui/base/Menu.tsx b/packages/client/app/ui/base/Menu.tsx
new file mode 100644
index 000000000..d1fdd3e83
--- /dev/null
+++ b/packages/client/app/ui/base/Menu.tsx
@@ -0,0 +1,554 @@
+import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
+import { useLocation, useParams } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+import styled, { css, RuleSet } from 'styled-components'
+import { grid, th, Link as UILink } from '@coko/client'
+
+import Avatar from '../shared/Avatar'
+import {
+ Home,
+ File,
+ Report,
+ Form,
+ Tasks,
+ User,
+ Settings,
+ ExpandMenu,
+ Book,
+ Coar,
+} from './Icons'
+
+// #region styled
+const fullWidth = '272px'
+const collapsedWidth = '64px'
+const collapseTime = '0.3s'
+const collapseTransition = `${collapseTime} ease`
+
+/**
+ * If you notice redundant font-family, font-size and line-height values all
+ * over this file, it's because bootstrap's (!) css is interfering when it's
+ * loaded. Once bootstrap has been cleared out from the client, these can be
+ * removed.
+ */
+
+const Wrapper = styled.nav<{ $menuCollapsed: boolean }>`
+ background-color: ${th('colorPrimary')};
+ color: ${th('colorTextReverse')};
+
+ height: 100%;
+ width: ${(props): string =>
+ props.$menuCollapsed ? collapsedWidth : fullWidth};
+ flex-shrink: 0;
+ transition: width ${collapseTransition};
+ will-change: width;
+
+ padding: ${grid(1)} 0;
+
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+
+ && a:focus {
+ outline: 1px solid ${th('colorTextReverse')};
+ }
+`
+
+const GroupSection = styled.div`
+ margin: ${grid(1)} ${grid(1)} 0 ${grid(1)};
+ display: flex;
+ align-items: center;
+ font-family: ${th('fontInterface')};
+ line-height: ${th('lineHeightBase')};
+`
+
+const GroupLetter = styled.div<{ $menuCollapsed: boolean }>`
+ height: ${grid(6)};
+ width: ${grid(6)};
+ border-radius: ${th('borderRadiusLarge')};
+
+ background-color: ${th('colorBackground')};
+ color: ${th('colorPrimary')};
+ font-size: 1.5rem;
+
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+
+ margin-left: ${(props): string =>
+ props.$menuCollapsed ? '0' : grid(1)(props)};
+ margin-right: ${(props): string =>
+ props.$menuCollapsed ? '0' : grid(1.5)(props)};
+ transition: margin ${collapseTransition};
+`
+
+const GroupRight = styled.div<{ $menuCollapsed: boolean }>`
+ display: flex;
+ flex-direction: column;
+ padding-top: ${grid(0.75)};
+
+ visibility: ${(props): string =>
+ props.$menuCollapsed ? 'hidden' : 'visible'};
+ opacity: ${(props): string => (props.$menuCollapsed ? '0' : '1')};
+ height: ${(props): string => (props.$menuCollapsed ? '0' : 'auto')};
+`
+
+const GroupName = styled.div<{ $labelsWrap: boolean }>`
+ color: ${th('colorTextReverse')};
+ font-size: ${th('fontSizeHeading5')};
+ font-weight: 500;
+ white-space: ${(props): string => (props.$labelsWrap ? 'wrap' : 'nowrap')};
+`
+
+const GroupType = styled.div`
+ font-size: ${th('fontSizeBaseSmall')};
+ text-transform: capitalize;
+`
+
+const Separator = styled.div`
+ height: 1px;
+ /* background-color: ${th('colorBackground')}; */
+ margin: ${grid(2)} ${grid(0.5)};
+ border-top: 1.5px solid ${th('colorBackground')};
+`
+
+const LinkSection = styled.div`
+ padding: 0 ${grid(1)};
+ user-select: none;
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ font-family: ${th('fontInterface')};
+ line-height: ${th('lineHeightBase')};
+`
+
+const LinkItems = styled.ul`
+ flex-grow: 1;
+ list-style: none;
+ padding: 0;
+`
+
+const hoverFade = '0.3s ease'
+
+const active = css`
+ background-color: ${th('colorTextReverse')};
+ color: ${th('colorText')};
+`
+
+const LinkItem = styled.div<{ $active: boolean }>`
+ color: ${th('colorTextReverse')};
+ cursor: pointer;
+ font-size: ${th('fontSizeBase')};
+ margin-bottom: ${grid(0.5)};
+ padding: ${grid(1)} ${grid(2)};
+ display: flex;
+ white-space: nowrap;
+
+ transition:
+ background-color ${hoverFade},
+ color ${hoverFade};
+
+ > div:first-child {
+ flex-shrink: 0;
+ margin-right: ${grid(1)};
+ }
+
+ &:hover {
+ ${active}
+ }
+
+ ${(props): RuleSet | false => props.$active && active};
+`
+
+const Link = styled(UILink)`
+ display: block;
+`
+
+const UserSection = styled.div`
+ display: flex;
+ flex-direction: column;
+ padding: 0 ${grid(1)} ${grid(1)} ${grid(1)};
+ font-family: ${th('fontInterface')};
+ line-height: ${th('lineHeightBase')};
+`
+
+const UserTop = styled.div<{ $menuCollapsed: boolean }>`
+ display: flex;
+ align-items: center;
+
+ margin-left: ${(props): string =>
+ props.$menuCollapsed ? '0' : grid(1)(props)};
+ margin-right: ${(props): string =>
+ props.$menuCollapsed ? '0' : grid(1.5)(props)};
+ margin-bottom: ${grid(1)};
+ transition: margin ${collapseTransition};
+
+ > a {
+ flex-grow: 1;
+ display: flex;
+ align-items: center;
+
+ > div:first-child {
+ flex-shrink: 0;
+ margin-right: ${grid(1)};
+ }
+
+ > div:last-child {
+ visibility: ${(props): string =>
+ props.$menuCollapsed ? 'hidden' : 'visible'};
+ opacity: ${(props): string => (props.$menuCollapsed ? '0' : '1')};
+ }
+ }
+`
+
+const UserBottom = styled.div<{ $menuCollapsed: boolean }>`
+ margin-left: ${grid(1)};
+
+ visibility: ${(props): string =>
+ props.$menuCollapsed ? 'hidden' : 'visible'};
+ opacity: ${(props): string => (props.$menuCollapsed ? '0' : '1')};
+ height: ${(props): string => (props.$menuCollapsed ? '0' : 'auto')};
+`
+
+const UserName = styled.div<{ $labelsWrap: boolean }>`
+ color: ${th('colorTextReverse')};
+ font-size: ${th('fontSizeBase')};
+ font-weight: 500;
+ flex-grow: 1;
+ white-space: ${(props): string => (props.$labelsWrap ? 'wrap' : 'nowrap')};
+`
+
+const UserRoles = styled.div<{ $labelsWrap: boolean }>`
+ display: flex;
+ gap: ${grid(0.5)};
+ flex-wrap: ${(props): string => (props.$labelsWrap ? 'wrap' : 'nowrap')};
+ overflow: hidden;
+`
+
+const UserLabel = styled.span`
+ background-color: ${th('colorTextReverse')};
+ color: ${th('colorPrimary')};
+ font-size: ${th('fontSizeBaseSmaller')};
+ padding: 0 ${grid(1)};
+ border-radius: ${th('borderRadius')};
+ white-space: nowrap;
+`
+
+const CollapseIconWrapper = css`
+ align-self: center;
+ background: none;
+ border: none;
+ color: ${th('colorTextReverse')};
+ cursor: pointer;
+ padding: 0;
+
+ opacity: 1;
+ visibility: visible;
+ transition:
+ opacity ${collapseTransition},
+ visibility ${collapseTransition};
+
+ &:focus-visible {
+ outline: 1px solid ${th('colorTextReverse')};
+ }
+
+ > span[role='img'] {
+ font-size: 1.4rem;
+ transition: font-size ${collapseTransition};
+ }
+
+ &:hover > span[role='img'] {
+ font-size: 1.7rem;
+ }
+`
+
+const UserCollapseIconWrapper = styled.button<{ $menuCollapsed: boolean }>`
+ ${CollapseIconWrapper};
+
+ ${(props): RuleSet =>
+ props.$menuCollapsed &&
+ css`
+ opacity: 0;
+ visibility: hidden;
+ `}
+
+ > span[role='img'] {
+ transform: rotate(270deg);
+ }
+`
+
+const LinksCollapseIconWrapper = styled.button<{ $menuCollapsed: boolean }>`
+ ${CollapseIconWrapper};
+
+ ${(props): RuleSet =>
+ !props.$menuCollapsed &&
+ css`
+ opacity: 0;
+ visibility: hidden;
+ `}
+
+ > span[role='img'] {
+ transform: rotate(90deg);
+ }
+`
+// #endregion styled
+
+// #region types
+type LinkRow = {
+ label: string
+ url: string
+ icon?: ReactNode
+ key: string
+}
+
+type LinkArray = LinkRow[]
+
+type MenuProps = {
+ /** The group display name as defined in the configuration settings. */
+ groupDisplayName: string
+ /** The type of the group (eg. journal, prc etc) */
+ groupType: string
+ isUserAdmin: boolean
+ isUserGroupAdmin: boolean
+ isUserGroupManager: boolean
+ userDisplayName: string
+ userProfileImage?: string
+ /** Whether to start the menu from collapsed state (eg. stored value in localstorage) */
+ initialMenuCollapsed: boolean
+ /** Store latest menu collapse state (eg. store value in localstorage) */
+ onMenuCollapseChange: (isCollapsed: boolean) => void
+
+ showCoar: boolean
+ showDashboard: boolean
+ showReports: boolean
+}
+// #endregion types
+
+/**
+ * roles
+ * collapse
+ */
+
+const Menu = (props: MenuProps): ReactNode => {
+ const {
+ groupDisplayName,
+ groupType,
+ isUserAdmin,
+ isUserGroupAdmin,
+ isUserGroupManager,
+ userDisplayName,
+ userProfileImage,
+ initialMenuCollapsed,
+ onMenuCollapseChange,
+ showCoar,
+ showDashboard,
+ showReports,
+ } = props
+ const { t } = useTranslation()
+ const { pathname } = useLocation()
+ const { groupName } = useParams()
+ const wrapperRef = useRef(null)
+ const [menuCollapsed, setMenuCollapsed] = useState(initialMenuCollapsed)
+ const [labelsWrap, setLabelsWrap] = useState(!initialMenuCollapsed)
+
+ const toggleMenuCollapsed = (): void => {
+ const newCollapsed = !menuCollapsed
+ setMenuCollapsed(newCollapsed)
+ onMenuCollapseChange(newCollapsed)
+ if (newCollapsed) setLabelsWrap(false)
+ }
+
+ /**
+ * This (and the ref) exist as a compromise. When the user has enough user
+ * labels, we need to wrap the labels into a second line. But keeping wrap
+ * on the labels caused the menu animation to glitch (making the user avatar
+ * go up then down while the wrap was being automatically adjusted). What we
+ * do here is remove wrap from the labels when collapsing the menu and then
+ * reinstating it only when the menu expansion transition is complete.
+ *
+ * Try the many roles story to see it adjust after the menu completely expands.
+ *
+ * This is a bit of an edge case, as the user is unlikely to have that many
+ * roles, but still worth taking care of for future-proofing (eg. if we add
+ * more roles).
+ *
+ * Same thing is applied to names if they are long enough. See the long names
+ * story.
+ */
+ useEffect(() => {
+ const wrapper = wrapperRef.current
+ const onTransitionEnd = (e: TransitionEvent): void => {
+ if (e.propertyName === 'width' && !menuCollapsed) setLabelsWrap(true)
+ }
+ wrapper.addEventListener('transitionend', onTransitionEnd)
+ return (): void => {
+ wrapper.removeEventListener('transitionend', onTransitionEnd)
+ }
+ }, [menuCollapsed])
+
+ const links: LinkArray = useMemo(() => {
+ return [
+ {
+ label: t('menu.Dashboard'),
+ url: `/${groupName}/dashboard`,
+ icon: ,
+ key: 'dashboard',
+ show: showDashboard,
+ },
+ {
+ label: t('menu.Manuscripts'),
+ url: `/${groupName}/admin/manuscripts`,
+ icon: ,
+ key: 'manuscripts',
+ show: isUserGroupAdmin || isUserGroupManager || isUserAdmin,
+ },
+ {
+ label: t('menu.Reports'),
+ url: `/${groupName}/admin/reports`,
+ icon: ,
+ key: 'reports',
+ show: showReports && (isUserGroupAdmin || isUserAdmin),
+ },
+ {
+ label: t('menu.CoarNotifyInbox'),
+ url: `/${groupName}/admin/coar-inbox`,
+ icon: ,
+ key: 'coar',
+ show:
+ showCoar && (isUserGroupAdmin || isUserGroupManager || isUserAdmin),
+ },
+ {
+ label: t('menu.Forms'),
+ url: `/${groupName}/admin/forms`,
+ icon: ,
+ key: 'forms',
+ show: isUserGroupAdmin || isUserAdmin,
+ },
+ {
+ label: t('menu.Tasks'),
+ url: `/${groupName}/admin/tasks`,
+ icon: ,
+ key: 'tasks',
+ show: isUserGroupAdmin || isUserAdmin,
+ },
+ {
+ label: t('menu.Users'),
+ url: `/${groupName}/admin/users`,
+ icon: ,
+ key: 'users',
+ show: isUserGroupAdmin || isUserAdmin,
+ },
+ {
+ label: t('menu.Configuration'),
+ url: `/${groupName}/admin/configuration`,
+ icon: ,
+ key: 'configuration',
+ show: isUserGroupAdmin || isUserAdmin,
+ },
+ {
+ label: t('menu.CMS'),
+ url: `/${groupName}/admin/cms`,
+ icon: ,
+ key: 'cms',
+ show: isUserGroupAdmin || isUserAdmin,
+ },
+ ].filter(item => item.show)
+ }, [
+ t,
+ groupName,
+ showCoar,
+ showDashboard,
+ showReports,
+ isUserAdmin,
+ isUserGroupAdmin,
+ isUserGroupManager,
+ ])
+
+ return (
+
+
+
+ {groupDisplayName.slice(0, 1)}
+
+
+ {groupDisplayName}
+ {groupType}
+
+
+
+
+
+
+
+ {links.map(item => {
+ const isActive = !!pathname.match(item.url)
+
+ return (
+
+
+
+ {item.icon}
+ {!menuCollapsed && {item.label}
}
+
+
+
+ )
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {userDisplayName}
+
+
+
+
+
+
+
+
+ {isUserAdmin && {t('common.roles.Admin')}}
+ {isUserGroupAdmin && (
+ {t('common.roles.Group Admin')}
+ )}
+ {isUserGroupManager && (
+ {t('common.roles.Group Manager')}
+ )}
+
+
+
+
+ )
+}
+
+export default Menu
diff --git a/packages/client/app/ui/pages/cms/CMS.tsx b/packages/client/app/ui/pages/cms/CMS.tsx
new file mode 100644
index 000000000..4d1fb4c24
--- /dev/null
+++ b/packages/client/app/ui/pages/cms/CMS.tsx
@@ -0,0 +1,60 @@
+import { type ReactNode } from 'react'
+import { useParams } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+
+import CardGrid from '../../shared/CardGrid'
+import Page from '../../shared/Page'
+import PageDescription from '../../shared/PageDescription'
+
+const CMS = (): ReactNode => {
+ const { groupName } = useParams()
+ const { t } = useTranslation()
+
+ return (
+
+ {t('cmsIndexPage.description')}
+
+
+ )
+}
+
+export default CMS
diff --git a/packages/client/app/ui/pages/formBuilder/FormBuilder.tsx b/packages/client/app/ui/pages/formBuilder/FormBuilder.tsx
new file mode 100644
index 000000000..660f813dc
--- /dev/null
+++ b/packages/client/app/ui/pages/formBuilder/FormBuilder.tsx
@@ -0,0 +1,42 @@
+import { type ReactNode } from 'react'
+import { useParams } from 'react-router-dom'
+import { useTranslation } from 'react-i18next'
+
+import CardGrid from '../../shared/CardGrid'
+import Page from '../../shared/Page'
+import PageDescription from '../../shared/PageDescription'
+
+const FormBuilder = (): ReactNode => {
+ const { groupName } = useParams()
+ const { t } = useTranslation()
+
+ return (
+
+ {t('formsPage.description')}
+
+
+ )
+}
+
+export default FormBuilder
diff --git a/packages/client/app/ui/shared/Avatar.tsx b/packages/client/app/ui/shared/Avatar.tsx
new file mode 100644
index 000000000..f99b328bd
--- /dev/null
+++ b/packages/client/app/ui/shared/Avatar.tsx
@@ -0,0 +1,57 @@
+import { type ReactNode } from 'react'
+import styled from 'styled-components'
+
+import { grid, th } from '@coko/client'
+
+import fallback from '../assets/avatar_fallback.svg'
+
+const Wrapper = styled.div<{ $size: number }>`
+ height: ${(props): string => grid(props.$size)(props)};
+ width: ${(props): string => grid(props.$size)(props)};
+ position: relative;
+`
+
+const Img = styled.img`
+ border-radius: ${th('borderRadius')};
+ height: 100%;
+ object-fit: cover;
+ width: 100%;
+`
+
+const OnlineIndicator = styled.div<{ $online: boolean }>`
+ border: 3px solid ${th('colorBackground')};
+ border-radius: 50%;
+ height: ${grid(2)};
+ width: ${grid(2)};
+ background-color: ${(props): string =>
+ props.$online ? props.theme.colorPrimary : props.theme.colorDisabled};
+
+ position: absolute;
+ bottom: -6px;
+ right: -6px;
+`
+
+type AvatarProps = {
+ /** User's profile image. */
+ src?: string
+ /** How many multiples of grid unit from the theme. */
+ size?: number
+ isUserOnline?: boolean
+ showOnlineIndicator?: boolean
+}
+
+const Avatar = ({
+ src,
+ size = 6,
+ isUserOnline,
+ showOnlineIndicator = false,
+}: AvatarProps): ReactNode => {
+ return (
+
+
+ {showOnlineIndicator && }
+
+ )
+}
+
+export default Avatar
diff --git a/packages/client/app/ui/shared/Breadcrumb.tsx b/packages/client/app/ui/shared/Breadcrumb.tsx
new file mode 100644
index 000000000..436a105a0
--- /dev/null
+++ b/packages/client/app/ui/shared/Breadcrumb.tsx
@@ -0,0 +1,97 @@
+import { type ReactNode } from 'react'
+import styled, { css } from 'styled-components'
+import {
+ Breadcrumb as AntBreadcrumb,
+ type BreadcrumbProps as AntBreadcrumbProps,
+} from 'antd'
+
+import { grid, th, lighten } from '@coko/client'
+
+const flatEdges = css`
+ .ant-breadcrumb-item:first-child .ant-breadcrumb-link {
+ clip-path: polygon(
+ 0 0,
+ calc(100% - var(--depth)) 0,
+ 100% 50%,
+ calc(100% - var(--depth)) 100%,
+ 0 100%
+ );
+
+ padding-left: ${grid(3)};
+
+ border-top-left-radius: ${th('borderRadius')};
+ border-bottom-left-radius: ${th('borderRadius')};
+ }
+
+ .ant-breadcrumb-item:last-child .ant-breadcrumb-link {
+ clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%, var(--depth) 50%);
+
+ padding-right: ${grid(3)};
+
+ border-top-right-radius: ${th('borderRadius')};
+ border-bottom-right-radius: ${th('borderRadius')};
+ }
+`
+
+const StyledBreadcrumb = styled(AntBreadcrumb)`
+ li.ant-breadcrumb-separator {
+ display: none;
+ }
+
+ .ant-breadcrumb-link {
+ background-color: ${th('colorPrimary')};
+ color: ${th('colorTextReverse')};
+ padding: ${grid(1.5)} ${grid(4)};
+ font-weight: 500;
+ cursor: pointer;
+
+ margin-left: ${grid(-2)};
+
+ --depth: ${grid(2.5)};
+
+ clip-path: polygon(
+ 0 0,
+ calc(100% - var(--depth)) 0,
+ 100% 50%,
+ calc(100% - var(--depth)) 100%,
+ 0 100%,
+ var(--depth) 50%
+ );
+
+ transition:
+ background-color 0.2s ease,
+ color 0.2s ease;
+
+ a {
+ color: ${th('colorTextReverse')};
+ transition: background-color 0.2s ease;
+
+ &:hover {
+ color: ${th('colorTextReverse')};
+ background-color: ${th('colorPrimary')};
+ }
+ }
+ }
+
+ .ant-breadcrumb-item:first-child .ant-breadcrumb-link {
+ margin-left: 0;
+ }
+
+ .ant-breadcrumb-item:not(:last-child) .ant-breadcrumb-link {
+ &:hover {
+ background: ${lighten('colorPrimary', 30)};
+
+ a:hover {
+ background: ${lighten('colorPrimary', 30)};
+ }
+ }
+ }
+
+ ${flatEdges};
+`
+
+const Breadcrumb = ({ items }: AntBreadcrumbProps): ReactNode => {
+ return
+}
+
+export default Breadcrumb
diff --git a/packages/client/app/ui/shared/CardGrid.tsx b/packages/client/app/ui/shared/CardGrid.tsx
new file mode 100644
index 000000000..0b571fb49
--- /dev/null
+++ b/packages/client/app/ui/shared/CardGrid.tsx
@@ -0,0 +1,101 @@
+import { type ReactNode } from 'react'
+import styled from 'styled-components'
+import { grid, th, H2, Link as UILink } from '@coko/client'
+
+import { ArrowRight } from '../base/Icons'
+
+// #region styled
+const Link = styled(UILink)`
+ display: block;
+`
+
+const Title = styled(H2)`
+ border-bottom: 2px solid ${th('colorPrimary')};
+ border-color: ${th('colorPrimary')};
+`
+
+const Description = styled.div`
+ flex-grow: 1;
+ color: ${th('colorText')};
+ padding-top: ${grid(2)};
+`
+
+const IconWrapper = styled.div`
+ align-self: end;
+ padding-bottom: ${grid(3)};
+ opacity: 0;
+ transition: opacity 0.3s ease;
+
+ > span[role='img'] {
+ font-size: 2rem;
+ color: ${th('colorPrimary')};
+ }
+`
+
+const Card = styled.div`
+ min-height: ${grid(38)};
+ border-radius: ${th('borderRadius')};
+ box-shadow: ${th('boxShadow200')};
+ padding: ${grid(6)} ${grid(4)} 0;
+ cursor: pointer;
+ background-color: ${th('colorBackground')};
+ transition: box-shadow 0.2s ease;
+
+ display: flex;
+ flex-direction: column;
+
+ &:hover,
+ ${Link}:focus & {
+ box-shadow: 0 0 0 3px ${th('colorPrimary')};
+ }
+
+ &:hover ${IconWrapper}, ${Link}:focus & ${IconWrapper} {
+ opacity: 1;
+ }
+`
+
+const Wrapper = styled.ul`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, ${grid(55)});
+ gap: ${grid(4)};
+ list-style: none;
+ margin: 0 auto;
+ padding: 0;
+`
+// #endregion styled
+
+type CardItem = {
+ title: string
+ description: string
+ url: string
+ key: string
+}
+
+type CardGridProps = {
+ items: CardItem[]
+}
+
+const CardGrid = ({ items }: CardGridProps): ReactNode => {
+ return (
+
+ {items.map(item => {
+ return (
+
+
+
+ {item.title}
+ {item.description}
+
+
+
+
+
+
+
+ )
+ })}
+
+ )
+}
+
+export default CardGrid
diff --git a/packages/client/app/ui/shared/Page.tsx b/packages/client/app/ui/shared/Page.tsx
new file mode 100644
index 000000000..374632f09
--- /dev/null
+++ b/packages/client/app/ui/shared/Page.tsx
@@ -0,0 +1,53 @@
+import { type ReactNode } from 'react'
+import styled from 'styled-components'
+
+import { grid, th, H1 } from '@coko/client'
+
+const Wrapper = styled.div`
+ background: ${th('colorWallpaper')};
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ min-height: 0;
+ overflow: auto;
+ padding: ${grid(3)};
+ font-family: ${th('fontInterface')};
+ line-height: ${th('lineHeightBase')};
+`
+
+const Header = styled(H1)`
+ /* stylelint-disable declaration-no-important */
+ margin-top: 0;
+ margin-bottom: ${grid(3)};
+ padding-bottom: ${grid(1)};
+ border-bottom: 2px solid ${th('colorPrimary')};
+
+ color: ${th('colorPrimary')} !important;
+ text-transform: capitalize;
+
+ font-family: ${th('fontHeading')};
+ font-weight: normal !important;
+`
+
+const Content = styled.div`
+ flex: 1;
+ min-height: 0;
+ font-size: ${th('fontSizeBase')};
+ padding-bottom: ${grid(3)};
+`
+
+type PageProps = {
+ title: string
+ children: ReactNode
+}
+
+const Page = ({ children, title }: PageProps): ReactNode => {
+ return (
+
+
+ {children}
+
+ )
+}
+
+export default Page
diff --git a/packages/client/app/ui/shared/PageDescription.tsx b/packages/client/app/ui/shared/PageDescription.tsx
new file mode 100644
index 000000000..2f75e2d63
--- /dev/null
+++ b/packages/client/app/ui/shared/PageDescription.tsx
@@ -0,0 +1,20 @@
+import { type ReactNode } from 'react'
+import styled from 'styled-components'
+
+import { grid, th } from '@coko/client'
+
+const StyledPageDescription = styled.p`
+ color: ${th('colorText')};
+ margin-bottom: ${grid(3)};
+ max-width: 60em;
+`
+
+type PageDescriptionProps = {
+ children: ReactNode
+}
+
+const PageDescription = ({ children }: PageDescriptionProps): ReactNode => {
+ return {children}
+}
+
+export default PageDescription
diff --git a/packages/client/package.json b/packages/client/package.json
index 773408eb4..26765233f 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -19,15 +19,17 @@
"prosemirror-view": "1.30.2"
},
"dependencies": {
+ "@ant-design/icons": "^6.2.5",
"@apollo/client": "4.1.9",
"@codemirror/lang-css": "^6.2.1",
"@codemirror/lang-html": "^6.4.6",
"@codemirror/lang-json": "^6.0.1",
- "@coko/client": "2.0.0",
+ "@coko/client": "2.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource/roboto": "^4.5.8",
+ "@fontsource/spectral-sc": "^5.2.8",
"@rjsf/core": "4.2.3",
"@uiw/react-codemirror": "^4.21.20",
"antd": "^6.3.7",
@@ -90,10 +92,11 @@
"yjs": "13.5.38"
},
"devDependencies": {
- "@storybook/addon-essentials": "8.6.14",
- "@storybook/react-vite": "^10.4.3",
+ "@faker-js/faker": "^10.5.0",
+ "@storybook/addon-docs": "^10.4.6",
+ "@storybook/react-vite": "^10.4.6",
"@types/react": "^19.2.14",
- "storybook": "^10.4.3"
+ "storybook": "^10.4.6"
},
"packageManager": "yarn@4.9.2"
}
diff --git a/packages/client/stories/assets/avatar_sample.png b/packages/client/stories/assets/avatar_sample.png
new file mode 100644
index 000000000..137241644
Binary files /dev/null and b/packages/client/stories/assets/avatar_sample.png differ
diff --git a/packages/client/stories/assets/avatar_sample_2.png b/packages/client/stories/assets/avatar_sample_2.png
new file mode 100644
index 000000000..a1b163cb2
Binary files /dev/null and b/packages/client/stories/assets/avatar_sample_2.png differ
diff --git a/packages/client/stories/base/Icons.stories.tsx b/packages/client/stories/base/Icons.stories.tsx
new file mode 100644
index 000000000..d3863e7d2
--- /dev/null
+++ b/packages/client/stories/base/Icons.stories.tsx
@@ -0,0 +1,101 @@
+import { type ComponentType } from 'react'
+import styled from 'styled-components'
+import { th } from '@coko/client'
+
+import preview from '../../.storybook/preview'
+import * as Icons from '../../app/ui/base/Icons'
+
+const Grid = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: 12px;
+`
+
+const IconTile = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ padding: 16px;
+ border: 1px solid ${th('colorBorder')};
+ border-radius: ${th('borderRadius')};
+
+ svg {
+ font-size: 2rem;
+ }
+`
+
+const SmallerIconTile = styled(IconTile)`
+ svg {
+ font-size: 1rem;
+ }
+`
+
+const ReverseIconTile = styled(IconTile)`
+ background-color: ${th('colorPrimary')};
+ color: ${th('colorTextReverse')};
+`
+
+const IconName = styled.span`
+ font-size: 12px;
+ text-align: center;
+ word-break: break-all;
+`
+
+const meta = preview.meta({})
+
+/**
+ * A combination of ant-design and custom icons. Make sure both types display
+ * consistently.
+ */
+export const AllIcons = meta.story({
+ render: () => (
+
+ {Object.entries(Icons).map(([name, IconComponent]) => {
+ const Component = IconComponent as ComponentType
+
+ return (
+
+
+ {name}
+
+ )
+ })}
+
+ ),
+})
+
+/** Adjust size via font-size. */
+export const Smaller = meta.story({
+ render: () => (
+
+ {Object.entries(Icons).map(([name, IconComponent]) => {
+ const Component = IconComponent as ComponentType
+
+ return (
+
+
+ {name}
+
+ )
+ })}
+
+ ),
+})
+
+/** Color should be defined by the current font color. */
+export const Reverse = meta.story({
+ render: () => (
+
+ {Object.entries(Icons).map(([name, IconComponent]) => {
+ const Component = IconComponent as ComponentType
+
+ return (
+
+
+ {name}
+
+ )
+ })}
+
+ ),
+})
diff --git a/packages/client/stories/base/Layout.stories.tsx b/packages/client/stories/base/Layout.stories.tsx
index ca7e57fdd..86c1e1fa4 100644
--- a/packages/client/stories/base/Layout.stories.tsx
+++ b/packages/client/stories/base/Layout.stories.tsx
@@ -1,11 +1,12 @@
import styled from 'styled-components'
+import { th } from '@coko/client'
import preview from '../../.storybook/preview'
import Layout from '../../app/ui/base/Layout'
const Left = styled.div`
color: white;
- background-color: forestgreen;
+ background-color: ${th('colorPrimary')};
width: 300px;
display: flex;
diff --git a/packages/client/stories/base/Menu.stories.tsx b/packages/client/stories/base/Menu.stories.tsx
new file mode 100644
index 000000000..4a0984439
--- /dev/null
+++ b/packages/client/stories/base/Menu.stories.tsx
@@ -0,0 +1,223 @@
+import styled from 'styled-components'
+
+import preview from '../../.storybook/preview'
+import Menu from '../../app/ui/base/Menu'
+import avatarImg from '../assets/avatar_sample.png'
+
+const Wrapper = styled.div`
+ height: 1000px;
+`
+
+const meta = preview.meta({
+ component: Menu,
+ parameters: {
+ router: { initialEntries: ['/kotahi/dashboard'], path: '/:groupName/*' },
+ },
+})
+
+export const Base = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const Admin = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const GroupAdmin = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const GroupManager = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const NoRoles = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const ManyRoles = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const StartCollapsed = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const MediumNames = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
+
+export const LongNames = meta.story({
+ render: () => {
+ return (
+
+
+ )
+ },
+})
diff --git a/packages/client/stories/pages/cms/CMS.stories.tsx b/packages/client/stories/pages/cms/CMS.stories.tsx
new file mode 100644
index 000000000..be940d9ce
--- /dev/null
+++ b/packages/client/stories/pages/cms/CMS.stories.tsx
@@ -0,0 +1,11 @@
+import preview from '../../../.storybook/preview'
+import CMS from '../../../app/ui/pages/cms/CMS'
+
+const meta = preview.meta({
+ component: CMS,
+ parameters: {
+ router: { initialEntries: ['/kotahi/cms'], path: '/:groupName/*' },
+ },
+})
+
+export const Base = meta.story({})
diff --git a/packages/client/stories/pages/formBuilder/FormBuilder.stories.tsx b/packages/client/stories/pages/formBuilder/FormBuilder.stories.tsx
new file mode 100644
index 000000000..bd6316f52
--- /dev/null
+++ b/packages/client/stories/pages/formBuilder/FormBuilder.stories.tsx
@@ -0,0 +1,11 @@
+import preview from '../../../.storybook/preview'
+import FormBuilder from '../../../app/ui/pages/formBuilder/FormBuilder'
+
+const meta = preview.meta({
+ component: FormBuilder,
+ parameters: {
+ router: { initialEntries: ['/kotahi/form-builder'], path: '/:groupName/*' },
+ },
+})
+
+export const Base = meta.story({})
diff --git a/packages/client/stories/shared/Avatar.stories.tsx b/packages/client/stories/shared/Avatar.stories.tsx
new file mode 100644
index 000000000..040a6f7b0
--- /dev/null
+++ b/packages/client/stories/shared/Avatar.stories.tsx
@@ -0,0 +1,56 @@
+import preview from '../../.storybook/preview'
+import Avatar from '../../app/ui/shared/Avatar'
+import avatarImg from '../assets/avatar_sample.png'
+import avatarImg2 from '../assets/avatar_sample_2.png'
+
+const meta = preview.meta({
+ component: Avatar,
+})
+
+export const Base = meta.story({
+ args: {
+ src: avatarImg,
+ showOnlineIndicator: true,
+ isUserOnline: true,
+ },
+})
+
+export const DifferentSize = meta.story({
+ args: {
+ src: avatarImg,
+ size: 20,
+ showOnlineIndicator: true,
+ isUserOnline: true,
+ },
+})
+
+export const DifferentImage = meta.story({
+ args: {
+ src: avatarImg2,
+ size: 20,
+ showOnlineIndicator: true,
+ isUserOnline: true,
+ },
+})
+
+export const Offline = meta.story({
+ args: {
+ src: avatarImg,
+ showOnlineIndicator: true,
+ isUserOnline: false,
+ },
+})
+
+export const FallbackImage = meta.story({
+ args: {
+ showOnlineIndicator: true,
+ isUserOnline: true,
+ },
+})
+
+export const HideOnlineIndicator = meta.story({
+ args: {
+ src: avatarImg,
+ showOnlineIndicator: false,
+ },
+})
diff --git a/packages/client/stories/shared/Breadcrumb.stories.tsx b/packages/client/stories/shared/Breadcrumb.stories.tsx
new file mode 100644
index 000000000..0b5616b95
--- /dev/null
+++ b/packages/client/stories/shared/Breadcrumb.stories.tsx
@@ -0,0 +1,18 @@
+import { Link } from 'react-router-dom'
+
+import preview from '../../.storybook/preview'
+import Breadcrumb from '../../app/ui/shared/Breadcrumb'
+
+const meta = preview.meta({
+ component: Breadcrumb,
+})
+
+export const Base = meta.story({
+ args: {
+ items: [
+ { title: First },
+ { title: Second },
+ { title: Third },
+ ],
+ },
+})
diff --git a/packages/client/stories/shared/CardGrid.stories.tsx b/packages/client/stories/shared/CardGrid.stories.tsx
new file mode 100644
index 000000000..852e24b03
--- /dev/null
+++ b/packages/client/stories/shared/CardGrid.stories.tsx
@@ -0,0 +1,35 @@
+import preview from '../../.storybook/preview'
+import CardGrid from '../../app/ui/shared/CardGrid'
+
+const meta = preview.meta({
+ component: CardGrid,
+})
+
+/** Supposed to be used with `Card` components as children. */
+export const Base = meta.story({
+ args: {
+ items: [
+ {
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a consectetur felis. Vestibulum hendrerit eleifend nulla, ac facilisis urna faucibus ac. Nunc at leo eget felis eleifend scelerisque.',
+ title: 'Test One',
+ key: 'one',
+ url: '/admin/one',
+ },
+ {
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a consectetur felis. Vestibulum hendrerit eleifend nulla, ac facilisis urna faucibus ac. Nunc at leo eget felis eleifend scelerisque.',
+ title: 'Test Two',
+ key: 'two',
+ url: '/admin/two',
+ },
+ {
+ description:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a consectetur felis. Vestibulum hendrerit eleifend nulla, ac facilisis urna faucibus ac. Nunc at leo eget felis eleifend scelerisque.',
+ title: 'Test Three',
+ key: 'three',
+ url: '/admin/three',
+ },
+ ],
+ },
+})
diff --git a/packages/client/stories/shared/Notification.stories.tsx b/packages/client/stories/shared/Notification.stories.tsx
new file mode 100644
index 000000000..921513be3
--- /dev/null
+++ b/packages/client/stories/shared/Notification.stories.tsx
@@ -0,0 +1,74 @@
+import { type ReactNode } from 'react'
+import { App, Button, Space } from 'antd'
+
+import preview from '../../.storybook/preview'
+
+const NotificationDemo = (): ReactNode => {
+ const { notification } = App.useApp()
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const meta = preview.meta({
+ component: NotificationDemo,
+})
+
+export const Base = meta.story({})
diff --git a/packages/client/stories/shared/Page.stories.tsx b/packages/client/stories/shared/Page.stories.tsx
new file mode 100644
index 000000000..25e4335f0
--- /dev/null
+++ b/packages/client/stories/shared/Page.stories.tsx
@@ -0,0 +1,17 @@
+import preview from '../../.storybook/preview'
+import Page from '../../app/ui/shared/Page'
+
+const meta = preview.meta({
+ component: Page,
+})
+
+export const Base = meta.story({
+ render: () => {
+ return (
+
+ some
+ test
+
+ )
+ },
+})
diff --git a/packages/client/stories/shared/PageDescription.stories.tsx b/packages/client/stories/shared/PageDescription.stories.tsx
new file mode 100644
index 000000000..f207ebe0b
--- /dev/null
+++ b/packages/client/stories/shared/PageDescription.stories.tsx
@@ -0,0 +1,24 @@
+/* eslint-disable import/no-extraneous-dependencies */
+
+import { faker } from '@faker-js/faker'
+
+import preview from '../../.storybook/preview'
+import PageDescription from '../../app/ui/shared/PageDescription'
+
+faker.seed(1)
+
+const meta = preview.meta({
+ component: PageDescription,
+})
+
+export const Base = meta.story({
+ args: {
+ children: faker.lorem.paragraph(),
+ },
+})
+
+export const LongText = meta.story({
+ args: {
+ children: faker.lorem.paragraphs(3),
+ },
+})
diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json
new file mode 100644
index 000000000..f47aa9b48
--- /dev/null
+++ b/packages/client/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": false,
+ "noEmit": true,
+ "types": ["vite/client"]
+ },
+ "include": ["app", "stories", ".storybook"]
+}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 7aecf737e..45c7e8d36 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -82,6 +82,21 @@ __metadata:
languageName: node
linkType: hard
+"@ant-design/icons@npm:^6.2.5":
+ version: 6.2.5
+ resolution: "@ant-design/icons@npm:6.2.5"
+ dependencies:
+ "@ant-design/colors": "npm:^8.0.1"
+ "@ant-design/icons-svg": "npm:^4.4.2"
+ "@rc-component/util": "npm:^1.11.0"
+ clsx: "npm:^2.1.1"
+ peerDependencies:
+ react: ">=16.0.0"
+ react-dom: ">=16.0.0"
+ checksum: 10c0/9fe43c491465276d3dbd1dfeb1d97a33253c7e0a1e80f18dac86e04fa6d11164d7d8d95d68b17fa89797327d585d5a97a6d219f5ce66a0b90a07148977ee7357
+ languageName: node
+ linkType: hard
+
"@ant-design/react-slick@npm:~2.0.0":
version: 2.0.0
resolution: "@ant-design/react-slick@npm:2.0.0"
@@ -354,13 +369,6 @@ __metadata:
languageName: node
linkType: hard
-"@babel/runtime@npm:^7.17.8":
- version: 7.29.7
- resolution: "@babel/runtime@npm:7.29.7"
- checksum: 10c0/ca11572f7146b21e0bde6a9ed4bb6a89eafbee5f0944c7eb54d0d8a2dac962c33638a1d611e14faa71dfbb92b4b5f9236232208568a6b7d5c6f3f39ddb91771e
- languageName: node
- linkType: hard
-
"@babel/runtime@npm:^7.28.4":
version: 7.29.2
resolution: "@babel/runtime@npm:7.29.2"
@@ -591,9 +599,9 @@ __metadata:
languageName: node
linkType: hard
-"@coko/client@npm:2.0.0":
- version: 2.0.0
- resolution: "@coko/client@npm:2.0.0"
+"@coko/client@npm:2.2.0":
+ version: 2.2.0
+ resolution: "@coko/client@npm:2.2.0"
dependencies:
"@ant-design/icons": "npm:^6.2.2"
"@dnd-kit/core": "npm:^6.3.1"
@@ -625,7 +633,7 @@ __metadata:
bin:
coko-client-build: ./scripts/coko-client-build.mjs
coko-client-dev: ./scripts/coko-client-dev.mjs
- checksum: 10c0/cead69bde056a3b68699f2f65f77a49e874b79a352197c58d4c367a3f505dec751c08bd2811579bca6464398c55a25783bc377bb7ef2cd4d03d29729fe61b971
+ checksum: 10c0/79c3af33858c454072ebfb0898d0fbadd461012e78abe62a3d42976c7007607b4a0a6bbb4e510da7299da494f7f12b4fc2d042547252687e746662a9270dca1c
languageName: node
linkType: hard
@@ -865,188 +873,195 @@ __metadata:
languageName: node
linkType: hard
-"@esbuild/aix-ppc64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/aix-ppc64@npm:0.27.7"
+"@esbuild/aix-ppc64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/aix-ppc64@npm:0.28.1"
conditions: os=aix & cpu=ppc64
languageName: node
linkType: hard
-"@esbuild/android-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/android-arm64@npm:0.27.7"
+"@esbuild/android-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/android-arm64@npm:0.28.1"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/android-arm@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/android-arm@npm:0.27.7"
+"@esbuild/android-arm@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/android-arm@npm:0.28.1"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
-"@esbuild/android-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/android-x64@npm:0.27.7"
+"@esbuild/android-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/android-x64@npm:0.28.1"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
-"@esbuild/darwin-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/darwin-arm64@npm:0.27.7"
+"@esbuild/darwin-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/darwin-arm64@npm:0.28.1"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/darwin-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/darwin-x64@npm:0.27.7"
+"@esbuild/darwin-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/darwin-x64@npm:0.28.1"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
-"@esbuild/freebsd-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/freebsd-arm64@npm:0.27.7"
+"@esbuild/freebsd-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/freebsd-arm64@npm:0.28.1"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/freebsd-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/freebsd-x64@npm:0.27.7"
+"@esbuild/freebsd-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/freebsd-x64@npm:0.28.1"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/linux-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-arm64@npm:0.27.7"
+"@esbuild/linux-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-arm64@npm:0.28.1"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/linux-arm@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-arm@npm:0.27.7"
+"@esbuild/linux-arm@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-arm@npm:0.28.1"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
-"@esbuild/linux-ia32@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-ia32@npm:0.27.7"
+"@esbuild/linux-ia32@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-ia32@npm:0.28.1"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
-"@esbuild/linux-loong64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-loong64@npm:0.27.7"
+"@esbuild/linux-loong64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-loong64@npm:0.28.1"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
-"@esbuild/linux-mips64el@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-mips64el@npm:0.27.7"
+"@esbuild/linux-mips64el@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-mips64el@npm:0.28.1"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
-"@esbuild/linux-ppc64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-ppc64@npm:0.27.7"
+"@esbuild/linux-ppc64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-ppc64@npm:0.28.1"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
-"@esbuild/linux-riscv64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-riscv64@npm:0.27.7"
+"@esbuild/linux-riscv64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-riscv64@npm:0.28.1"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
-"@esbuild/linux-s390x@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-s390x@npm:0.27.7"
+"@esbuild/linux-s390x@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-s390x@npm:0.28.1"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
-"@esbuild/linux-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/linux-x64@npm:0.27.7"
+"@esbuild/linux-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/linux-x64@npm:0.28.1"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
-"@esbuild/netbsd-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/netbsd-arm64@npm:0.27.7"
+"@esbuild/netbsd-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/netbsd-arm64@npm:0.28.1"
conditions: os=netbsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/netbsd-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/netbsd-x64@npm:0.27.7"
+"@esbuild/netbsd-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/netbsd-x64@npm:0.28.1"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/openbsd-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/openbsd-arm64@npm:0.27.7"
+"@esbuild/openbsd-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/openbsd-arm64@npm:0.28.1"
conditions: os=openbsd & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/openbsd-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/openbsd-x64@npm:0.27.7"
+"@esbuild/openbsd-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/openbsd-x64@npm:0.28.1"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
-"@esbuild/openharmony-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/openharmony-arm64@npm:0.27.7"
+"@esbuild/openharmony-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/openharmony-arm64@npm:0.28.1"
conditions: os=openharmony & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/sunos-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/sunos-x64@npm:0.27.7"
+"@esbuild/sunos-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/sunos-x64@npm:0.28.1"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
-"@esbuild/win32-arm64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/win32-arm64@npm:0.27.7"
+"@esbuild/win32-arm64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/win32-arm64@npm:0.28.1"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
-"@esbuild/win32-ia32@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/win32-ia32@npm:0.27.7"
+"@esbuild/win32-ia32@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/win32-ia32@npm:0.28.1"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
-"@esbuild/win32-x64@npm:0.27.7":
- version: 0.27.7
- resolution: "@esbuild/win32-x64@npm:0.27.7"
+"@esbuild/win32-x64@npm:0.28.1":
+ version: 0.28.1
+ resolution: "@esbuild/win32-x64@npm:0.28.1"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
+"@faker-js/faker@npm:^10.5.0":
+ version: 10.5.0
+ resolution: "@faker-js/faker@npm:10.5.0"
+ checksum: 10c0/2de3c7b8f41592d52cefc3ab4c99536656249bca78d5ad184a3b7ac43ecc9d946056cef2c4d37060373ef06ad992d82530de08479fabe16fdbe77b911d842446
+ languageName: node
+ linkType: hard
+
"@fontsource/roboto@npm:^4.5.8":
version: 4.5.8
resolution: "@fontsource/roboto@npm:4.5.8"
@@ -1054,6 +1069,13 @@ __metadata:
languageName: node
linkType: hard
+"@fontsource/spectral-sc@npm:^5.2.8":
+ version: 5.2.8
+ resolution: "@fontsource/spectral-sc@npm:5.2.8"
+ checksum: 10c0/8be7e8bcdb589c1d9495f1de7ca1e7cef70719e2815b8dbe8f3e8ebf210ea6554ac69a464b72bba3e39f8feff6f7bb6fca145a3e8fb3fa4ae27e7be3729d83b7
+ languageName: node
+ linkType: hard
+
"@graphql-typed-document-node/core@npm:^3.1.1":
version: 3.2.0
resolution: "@graphql-typed-document-node/core@npm:3.2.0"
@@ -2220,6 +2242,19 @@ __metadata:
languageName: node
linkType: hard
+"@rc-component/util@npm:^1.11.0":
+ version: 1.11.1
+ resolution: "@rc-component/util@npm:1.11.1"
+ dependencies:
+ is-mobile: "npm:^5.0.0"
+ react-is: "npm:^18.2.0"
+ peerDependencies:
+ react: ">=18.0.0"
+ react-dom: ">=18.0.0"
+ checksum: 10c0/a5b6a79089dc67a402669e24cb59776806fbebae746cc8d728632637a5f6f6b298a07a82ee24b0eb401cbeb384bc3c04ea82e9fbda4b8aa227f698e01641f62d
+ languageName: node
+ linkType: hard
+
"@rc-component/virtual-list@npm:^1.0.1":
version: 1.0.2
resolution: "@rc-component/virtual-list@npm:1.0.2"
@@ -2500,180 +2535,49 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/addon-actions@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-actions@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- "@types/uuid": "npm:^9.0.1"
- dequal: "npm:^2.0.2"
- polished: "npm:^4.2.2"
- uuid: "npm:^9.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/7a5d4faa15dd42f8f98335b6d817e1e2ffb50084001dc430aa3baf5c77edece16f0e866e8ce488e1c7a7a8f85465a6a71421373b87877375beafbe4729ada14a
- languageName: node
- linkType: hard
-
-"@storybook/addon-backgrounds@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-backgrounds@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- memoizerific: "npm:^1.11.3"
- ts-dedent: "npm:^2.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/e42befb774082313e7c0b94676dbbaa6a79dfd65b127dd474966d878fbd94e70a35651d5daccd1e09138775a1d275257279ae1092ddaaa840b7dc21839f0f5b9
- languageName: node
- linkType: hard
-
-"@storybook/addon-controls@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-controls@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- dequal: "npm:^2.0.2"
- ts-dedent: "npm:^2.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/a02a818be873ee7a630f5ca4a46fe16fc31edec2b16b0988ce4f8bcdd63650aaf1a2cd0356e1b42557e1a1bb2f46de4653e071e7b2b53a60c9a3e60a93fe19ef
- languageName: node
- linkType: hard
-
-"@storybook/addon-docs@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-docs@npm:8.6.14"
+"@storybook/addon-docs@npm:^10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/addon-docs@npm:10.4.6"
dependencies:
"@mdx-js/react": "npm:^3.0.0"
- "@storybook/blocks": "npm:8.6.14"
- "@storybook/csf-plugin": "npm:8.6.14"
- "@storybook/react-dom-shim": "npm:8.6.14"
+ "@storybook/csf-plugin": "npm:10.4.6"
+ "@storybook/icons": "npm:^2.0.2"
+ "@storybook/react-dom-shim": "npm:10.4.6"
react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/1668d40977624e3495b0cd3f009957994db04cec6de39645311e892cc1de99bf5a98bea9e9783d8063627c27b9545d222b1fdc490c23c6fadeead42ce0605fcb
- languageName: node
- linkType: hard
-
-"@storybook/addon-essentials@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-essentials@npm:8.6.14"
- dependencies:
- "@storybook/addon-actions": "npm:8.6.14"
- "@storybook/addon-backgrounds": "npm:8.6.14"
- "@storybook/addon-controls": "npm:8.6.14"
- "@storybook/addon-docs": "npm:8.6.14"
- "@storybook/addon-highlight": "npm:8.6.14"
- "@storybook/addon-measure": "npm:8.6.14"
- "@storybook/addon-outline": "npm:8.6.14"
- "@storybook/addon-toolbars": "npm:8.6.14"
- "@storybook/addon-viewport": "npm:8.6.14"
- ts-dedent: "npm:^2.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/77235e359d25267f7339e3c4a920329fccf7febbc5b81e13e7fc1f7d429e22385027dfaf30e2639865271c270d810df815a165e11a512735eedebb5c649c5adf
- languageName: node
- linkType: hard
-
-"@storybook/addon-highlight@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-highlight@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/db04b21cc3dc6185ce0051de08e0402899986d0b4b640ddbf1c8386bea5b1ed54b5a9b7e8a40e02973b73706839bcda1b324daf1eaafb465215247e6f548f817
- languageName: node
- linkType: hard
-
-"@storybook/addon-measure@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-measure@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- tiny-invariant: "npm:^1.3.1"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/a44f1c23e1665adf01684ce62b9c98e5b460aa96024c3c7be4bf9caa7c1073c4d9470b88bc5f814856594b7b90d9b877d5d58cfdbef2df77d483c1c3694ce37e
- languageName: node
- linkType: hard
-
-"@storybook/addon-outline@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-outline@npm:8.6.14"
- dependencies:
- "@storybook/global": "npm:^5.0.0"
- ts-dedent: "npm:^2.0.0"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/b54d71299a75588d7d424fb986433435d69974f4782986b7c4ef7db964bc9f85c75a07d5b0a2a0eec5b018284ddd4d4851e14fb4e131f41a6463fc16db234617
- languageName: node
- linkType: hard
-
-"@storybook/addon-toolbars@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-toolbars@npm:8.6.14"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/3fb0fe13fab65be101abda613c1c92e08c2aa0cba97ff2e37aa410af5fe2c1ebc15203627039c265e9667af294ea0cd7bb6e7bd84cb854bad5d0245a9ce66b79
- languageName: node
- linkType: hard
-
-"@storybook/addon-viewport@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/addon-viewport@npm:8.6.14"
- dependencies:
- memoizerific: "npm:^1.11.3"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/00a353ce87c79a7b08fd78cab87045c5d29996ea3fcd2a4072380e0f76a6e0bfc9385f743b98764aaff0740e8460b9d2a37864a1d0e91bf48f26e3d5a1984e51
- languageName: node
- linkType: hard
-
-"@storybook/blocks@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/blocks@npm:8.6.14"
- dependencies:
- "@storybook/icons": "npm:^1.2.12"
- ts-dedent: "npm:^2.0.0"
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- storybook: ^8.6.14
+ "@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ storybook: ^10.4.6
peerDependenciesMeta:
- react:
- optional: true
- react-dom:
+ "@types/react":
optional: true
- checksum: 10c0/3f87c1f224031dee8b5fa6e296022997d21956832fea1e4ffa3525931c05a8ef6450b67c728635297a08548d65ef5abde05d7740d4ea3a9a811f2ff08a5f0148
+ checksum: 10c0/427d1a0ef7a3c39205c1486b930628939ee5409f247623eee8c767c0cb041a62a3c853a27d650411aa6a8f5b5a881164e242fe963a5f6edbecd0283f4bf06e07
languageName: node
linkType: hard
-"@storybook/builder-vite@npm:10.4.3":
- version: 10.4.3
- resolution: "@storybook/builder-vite@npm:10.4.3"
+"@storybook/builder-vite@npm:10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/builder-vite@npm:10.4.6"
dependencies:
- "@storybook/csf-plugin": "npm:10.4.3"
+ "@storybook/csf-plugin": "npm:10.4.6"
ts-dedent: "npm:^2.0.0"
peerDependencies:
- storybook: ^10.4.3
+ storybook: ^10.4.6
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
- checksum: 10c0/e29d8cd3e1d68b49b9fe674e0c2a4216569908a9e2ec2a0c9522f124efa69c7d9f649e7f74dcb05d2c2eb6ff85bb36dc80720518bc382b695837ec83198dfbf1
+ checksum: 10c0/6238a6574485b979f698fe4979df4307a1a2554748c7a6530e7985f5df238cc6419b53ea6812f4c206ec39d96554a911ba302b9aa19460bacb6097af8c4be36e
languageName: node
linkType: hard
-"@storybook/csf-plugin@npm:10.4.3":
- version: 10.4.3
- resolution: "@storybook/csf-plugin@npm:10.4.3"
+"@storybook/csf-plugin@npm:10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/csf-plugin@npm:10.4.6"
dependencies:
unplugin: "npm:^2.3.5"
peerDependencies:
esbuild: "*"
rollup: "*"
- storybook: ^10.4.3
+ storybook: ^10.4.6
vite: "*"
webpack: "*"
peerDependenciesMeta:
@@ -2685,18 +2589,7 @@ __metadata:
optional: true
webpack:
optional: true
- checksum: 10c0/b5fcf3ccd8431967e0d8cac98692997571f364c6070205eddf8e91e06991681af8c1ad39acde94e8ed8cad78b65f5da510eb5eefa7463eaa85164c3b27f1ff8a
- languageName: node
- linkType: hard
-
-"@storybook/csf-plugin@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/csf-plugin@npm:8.6.14"
- dependencies:
- unplugin: "npm:^1.3.1"
- peerDependencies:
- storybook: ^8.6.14
- checksum: 10c0/3f2cebd6046aea554d2e740c4d262005c580018dfcbcca8d9a589f9e893d407da405ace235595b3e110fb6ed21b840ff1645508a98764017ca5d1d4c56027172
+ checksum: 10c0/73346ae0be948a2365b3fbc0f3b0fadfcb1c440accb40e012f3244417106c165baf83e3f4dff8c08ae7ca94ce6a26595d17fe09edd5e90c411c038ced0beba2b
languageName: node
linkType: hard
@@ -2707,16 +2600,6 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/icons@npm:^1.2.12":
- version: 1.6.0
- resolution: "@storybook/icons@npm:1.6.0"
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- checksum: 10c0/bbec9201a78a730195f9cf377b15856dc414a54d04e30d16c379d062425cc617bfd0d6586ba1716012cfbdab461f0c9693a6a52920f9bd09c7b4291fb116f59c
- languageName: node
- linkType: hard
-
"@storybook/icons@npm:^2.0.2":
version: 2.0.2
resolution: "@storybook/icons@npm:2.0.2"
@@ -2727,43 +2610,32 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/react-dom-shim@npm:10.4.3":
- version: 10.4.3
- resolution: "@storybook/react-dom-shim@npm:10.4.3"
+"@storybook/react-dom-shim@npm:10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/react-dom-shim@npm:10.4.6"
peerDependencies:
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
"@types/react-dom": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- storybook: ^10.4.3
+ storybook: ^10.4.6
peerDependenciesMeta:
"@types/react":
optional: true
"@types/react-dom":
optional: true
- checksum: 10c0/53a4efc9e19ec101c75cac630cbf2ca09ba9d293c498d6efff0c056589467b1bc9437e6128cc02cc10b4f868c437c1b03302c7e736fc8d843e0779a497a2b81d
- languageName: node
- linkType: hard
-
-"@storybook/react-dom-shim@npm:8.6.14":
- version: 8.6.14
- resolution: "@storybook/react-dom-shim@npm:8.6.14"
- peerDependencies:
- react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.6.14
- checksum: 10c0/6e54c05ab19be1c34084e8acd133d8d358c3b6824db01b4aff159be3f818bd12c3a17217fe1947244c4576d7d02875b486578f011edb0e2290cb62541a263c5f
+ checksum: 10c0/d7b2f86be48714cbe217628207a9c6de43839cf74c684501f107049983e6444d0de6b7d84d006a0fb5b63e42ab789ea3abc40e2450a3dee6b38ceba482cc7176
languageName: node
linkType: hard
-"@storybook/react-vite@npm:^10.4.3":
- version: 10.4.3
- resolution: "@storybook/react-vite@npm:10.4.3"
+"@storybook/react-vite@npm:^10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/react-vite@npm:10.4.6"
dependencies:
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:^0.7.0"
"@rollup/pluginutils": "npm:^5.0.2"
- "@storybook/builder-vite": "npm:10.4.3"
- "@storybook/react": "npm:10.4.3"
+ "@storybook/builder-vite": "npm:10.4.6"
+ "@storybook/react": "npm:10.4.6"
empathic: "npm:^2.0.0"
magic-string: "npm:^0.30.0"
react-docgen: "npm:^8.0.0"
@@ -2772,18 +2644,18 @@ __metadata:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- storybook: ^10.4.3
+ storybook: ^10.4.6
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
- checksum: 10c0/16813fca204871fa05b6b53672af571b0184e632b42afb7ef1b15b0c111155eedbcffa621966f1e6b48fa7e17e5bd0213d0f2a5eccb282909ab78e0d61632417
+ checksum: 10c0/5e85ce8c585c8cb9fee11bf2d0d574663818e806892cb72c8cab94c88445084b92632cabd2684178a64f53e2564cd8fb0ac586dabf1ff0c31279615c30d8c183
languageName: node
linkType: hard
-"@storybook/react@npm:10.4.3":
- version: 10.4.3
- resolution: "@storybook/react@npm:10.4.3"
+"@storybook/react@npm:10.4.6":
+ version: 10.4.6
+ resolution: "@storybook/react@npm:10.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
- "@storybook/react-dom-shim": "npm:10.4.3"
+ "@storybook/react-dom-shim": "npm:10.4.6"
react-docgen: "npm:^8.0.2"
react-docgen-typescript: "npm:^2.2.2"
peerDependencies:
@@ -2791,7 +2663,7 @@ __metadata:
"@types/react-dom": ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- storybook: ^10.4.3
+ storybook: ^10.4.6
typescript: ">= 4.9.x"
peerDependenciesMeta:
"@types/react":
@@ -2800,7 +2672,7 @@ __metadata:
optional: true
typescript:
optional: true
- checksum: 10c0/9cc542fec41868e56d2abb2bb6bb4929f507774aabd6289ab618559a647a2e1b68ceeed047aec98063420144b132dd3e36f240c3f4320574dcd2b28fb1b469ff
+ checksum: 10c0/8785d8665c47db86591964fdbde3601fa08f177cd81b85b99b01e2c89b3a0ef5635d71114836afc73944c1009a3fcb6157fe5fb6ef3a6b806eaaed601bb05ee0
languageName: node
linkType: hard
@@ -3088,13 +2960,6 @@ __metadata:
languageName: node
linkType: hard
-"@types/uuid@npm:^9.0.1":
- version: 9.0.8
- resolution: "@types/uuid@npm:9.0.8"
- checksum: 10c0/b411b93054cb1d4361919579ef3508a1f12bf15b5fdd97337d3d351bece6c921b52b6daeef89b62340fd73fd60da407878432a1af777f40648cbe53a01723489
- languageName: node
- linkType: hard
-
"@uiw/codemirror-extensions-basic-setup@npm:4.23.0":
version: 4.23.0
resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.23.0"
@@ -3325,7 +3190,7 @@ __metadata:
languageName: node
linkType: hard
-"acorn@npm:^8.14.0, acorn@npm:^8.15.0":
+"acorn@npm:^8.15.0":
version: 8.16.0
resolution: "acorn@npm:8.16.0"
bin:
@@ -4540,36 +4405,36 @@ __metadata:
languageName: node
linkType: hard
-"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0":
- version: 0.27.7
- resolution: "esbuild@npm:0.27.7"
- dependencies:
- "@esbuild/aix-ppc64": "npm:0.27.7"
- "@esbuild/android-arm": "npm:0.27.7"
- "@esbuild/android-arm64": "npm:0.27.7"
- "@esbuild/android-x64": "npm:0.27.7"
- "@esbuild/darwin-arm64": "npm:0.27.7"
- "@esbuild/darwin-x64": "npm:0.27.7"
- "@esbuild/freebsd-arm64": "npm:0.27.7"
- "@esbuild/freebsd-x64": "npm:0.27.7"
- "@esbuild/linux-arm": "npm:0.27.7"
- "@esbuild/linux-arm64": "npm:0.27.7"
- "@esbuild/linux-ia32": "npm:0.27.7"
- "@esbuild/linux-loong64": "npm:0.27.7"
- "@esbuild/linux-mips64el": "npm:0.27.7"
- "@esbuild/linux-ppc64": "npm:0.27.7"
- "@esbuild/linux-riscv64": "npm:0.27.7"
- "@esbuild/linux-s390x": "npm:0.27.7"
- "@esbuild/linux-x64": "npm:0.27.7"
- "@esbuild/netbsd-arm64": "npm:0.27.7"
- "@esbuild/netbsd-x64": "npm:0.27.7"
- "@esbuild/openbsd-arm64": "npm:0.27.7"
- "@esbuild/openbsd-x64": "npm:0.27.7"
- "@esbuild/openharmony-arm64": "npm:0.27.7"
- "@esbuild/sunos-x64": "npm:0.27.7"
- "@esbuild/win32-arm64": "npm:0.27.7"
- "@esbuild/win32-ia32": "npm:0.27.7"
- "@esbuild/win32-x64": "npm:0.27.7"
+"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0":
+ version: 0.28.1
+ resolution: "esbuild@npm:0.28.1"
+ dependencies:
+ "@esbuild/aix-ppc64": "npm:0.28.1"
+ "@esbuild/android-arm": "npm:0.28.1"
+ "@esbuild/android-arm64": "npm:0.28.1"
+ "@esbuild/android-x64": "npm:0.28.1"
+ "@esbuild/darwin-arm64": "npm:0.28.1"
+ "@esbuild/darwin-x64": "npm:0.28.1"
+ "@esbuild/freebsd-arm64": "npm:0.28.1"
+ "@esbuild/freebsd-x64": "npm:0.28.1"
+ "@esbuild/linux-arm": "npm:0.28.1"
+ "@esbuild/linux-arm64": "npm:0.28.1"
+ "@esbuild/linux-ia32": "npm:0.28.1"
+ "@esbuild/linux-loong64": "npm:0.28.1"
+ "@esbuild/linux-mips64el": "npm:0.28.1"
+ "@esbuild/linux-ppc64": "npm:0.28.1"
+ "@esbuild/linux-riscv64": "npm:0.28.1"
+ "@esbuild/linux-s390x": "npm:0.28.1"
+ "@esbuild/linux-x64": "npm:0.28.1"
+ "@esbuild/netbsd-arm64": "npm:0.28.1"
+ "@esbuild/netbsd-x64": "npm:0.28.1"
+ "@esbuild/openbsd-arm64": "npm:0.28.1"
+ "@esbuild/openbsd-x64": "npm:0.28.1"
+ "@esbuild/openharmony-arm64": "npm:0.28.1"
+ "@esbuild/sunos-x64": "npm:0.28.1"
+ "@esbuild/win32-arm64": "npm:0.28.1"
+ "@esbuild/win32-ia32": "npm:0.28.1"
+ "@esbuild/win32-x64": "npm:0.28.1"
dependenciesMeta:
"@esbuild/aix-ppc64":
optional: true
@@ -4625,7 +4490,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
- checksum: 10c0/ccd51f0555708bc9ff4ec9dc3ac92d3daacd45ecaac949ca8645984c5c323bf8cefe98c2df307418685e0b4ce37f9a3bdbfe8e3651fe632a0059a436195a17d4
+ checksum: 10c0/29cd456a79ce35ac2c7e05fe871330416b2c395c045d849653f843e51378d6e0d6e774d6dcd01b35f4e83238a29bf8decd04fcd34b3780c589a250b21e5f92bb
languageName: node
linkType: hard
@@ -5511,18 +5376,21 @@ __metadata:
version: 0.0.0-use.local
resolution: "kotahi@workspace:."
dependencies:
+ "@ant-design/icons": "npm:^6.2.5"
"@apollo/client": "npm:4.1.9"
"@codemirror/lang-css": "npm:^6.2.1"
"@codemirror/lang-html": "npm:^6.4.6"
"@codemirror/lang-json": "npm:^6.0.1"
- "@coko/client": "npm:2.0.0"
+ "@coko/client": "npm:2.2.0"
"@dnd-kit/core": "npm:^6.3.1"
"@dnd-kit/sortable": "npm:^10.0.0"
"@dnd-kit/utilities": "npm:^3.2.2"
+ "@faker-js/faker": "npm:^10.5.0"
"@fontsource/roboto": "npm:^4.5.8"
+ "@fontsource/spectral-sc": "npm:^5.2.8"
"@rjsf/core": "npm:4.2.3"
- "@storybook/addon-essentials": "npm:8.6.14"
- "@storybook/react-vite": "npm:^10.4.3"
+ "@storybook/addon-docs": "npm:^10.4.6"
+ "@storybook/react-vite": "npm:^10.4.6"
"@types/react": "npm:^19.2.14"
"@uiw/react-codemirror": "npm:^4.21.20"
antd: "npm:^6.3.7"
@@ -5572,7 +5440,7 @@ __metadata:
recharts: "npm:^3.8.1"
regenerator-runtime: "npm:^0.13.7"
split-pane-react: "npm:^0.1.3"
- storybook: "npm:^10.4.3"
+ storybook: "npm:^10.4.6"
striptags: "npm:^3.1.1"
styled-components: "npm:^6.4.1"
wax-prosemirror-core: "npm:0.10.2"
@@ -5941,13 +5809,6 @@ __metadata:
languageName: node
linkType: hard
-"map-or-similar@npm:^1.5.0":
- version: 1.5.0
- resolution: "map-or-similar@npm:1.5.0"
- checksum: 10c0/33c6ccfdc272992e33e4e99a69541a3e7faed9de3ac5bc732feb2500a9ee71d3f9d098980a70b7746e7eeb7f859ff7dfb8aa9b5ecc4e34170a32ab78cfb18def
- languageName: node
- linkType: hard
-
"mathjax-full@npm:3.1.4":
version: 3.1.4
resolution: "mathjax-full@npm:3.1.4"
@@ -5966,15 +5827,6 @@ __metadata:
languageName: node
linkType: hard
-"memoizerific@npm:^1.11.3":
- version: 1.11.3
- resolution: "memoizerific@npm:1.11.3"
- dependencies:
- map-or-similar: "npm:^1.5.0"
- checksum: 10c0/661bf69b7afbfad57f0208f0c63324f4c96087b480708115b78ee3f0237d86c7f91347f6db31528740b2776c2e34c709bcb034e1e910edee2270c9603a0a469e
- languageName: node
- linkType: hard
-
"merge-class-names@npm:^1.1.1":
version: 1.4.2
resolution: "merge-class-names@npm:1.4.2"
@@ -6592,15 +6444,6 @@ __metadata:
languageName: node
linkType: hard
-"polished@npm:^4.2.2":
- version: 4.3.1
- resolution: "polished@npm:4.3.1"
- dependencies:
- "@babel/runtime": "npm:^7.17.8"
- checksum: 10c0/45480d4c7281a134281cef092f6ecc202a868475ff66a390fee6e9261386e16f3047b4de46a2f2e1cf7fb7aa8f52d30b4ed631a1e3bcd6f303ca31161d4f07fe
- languageName: node
- linkType: hard
-
"postcss-value-parser@npm:^4.0.2":
version: 4.2.0
resolution: "postcss-value-parser@npm:4.2.0"
@@ -7935,9 +7778,9 @@ __metadata:
languageName: node
linkType: hard
-"storybook@npm:^10.4.3":
- version: 10.4.3
- resolution: "storybook@npm:10.4.3"
+"storybook@npm:^10.4.6":
+ version: 10.4.6
+ resolution: "storybook@npm:10.4.6"
dependencies:
"@storybook/global": "npm:^5.0.0"
"@storybook/icons": "npm:^2.0.2"
@@ -7946,7 +7789,7 @@ __metadata:
"@vitest/expect": "npm:3.2.4"
"@vitest/spy": "npm:3.2.4"
"@webcontainer/env": "npm:^1.1.1"
- esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0"
+ esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0"
open: "npm:^10.2.0"
oxc-parser: "npm:^0.127.0"
oxc-resolver: "npm:^11.19.1"
@@ -7967,7 +7810,7 @@ __metadata:
optional: true
bin:
storybook: ./dist/bin/dispatcher.js
- checksum: 10c0/79eb863bca38983bd4dca04df57ae29a3650031bf8d0beed3507fbe4f87547313fcc066b7b0ec4d16b386109b7142898ed1b07f3ea807cc3babc7aa23b6f628e
+ checksum: 10c0/d7083bee31a1e43aa88e198f5d833d249c14ac9ea01bb767f4dce8cb3264ea8bb970ca5428728f67fefba0daf1223b90a0edf790e8b028c4989e15f2df979159
languageName: node
linkType: hard
@@ -8186,7 +8029,7 @@ __metadata:
languageName: node
linkType: hard
-"tiny-invariant@npm:^1.3.1, tiny-invariant@npm:^1.3.3":
+"tiny-invariant@npm:^1.3.3":
version: 1.3.3
resolution: "tiny-invariant@npm:1.3.3"
checksum: 10c0/65af4a07324b591a059b35269cd696aba21bef2107f29b9f5894d83cc143159a204b299553435b03874ebb5b94d019afa8b8eff241c8a4cfee95872c2e1c1c4a
@@ -8390,16 +8233,6 @@ __metadata:
languageName: node
linkType: hard
-"unplugin@npm:^1.3.1":
- version: 1.16.1
- resolution: "unplugin@npm:1.16.1"
- dependencies:
- acorn: "npm:^8.14.0"
- webpack-virtual-modules: "npm:^0.6.2"
- checksum: 10c0/dd5f8c5727d0135847da73cf03fb199107f1acf458167034886fda3405737dab871ad3926431b4f70e1e82cdac482ac1383cea4019d782a68515c8e3e611b6cc
- languageName: node
- linkType: hard
-
"unplugin@npm:^2.3.5":
version: 2.3.11
resolution: "unplugin@npm:2.3.11"
@@ -8508,15 +8341,6 @@ __metadata:
languageName: node
linkType: hard
-"uuid@npm:^9.0.0":
- version: 9.0.1
- resolution: "uuid@npm:9.0.1"
- bin:
- uuid: dist/bin/uuid
- checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b
- languageName: node
- linkType: hard
-
"validate.io-array@npm:^1.0.3":
version: 1.0.6
resolution: "validate.io-array@npm:1.0.6"
diff --git a/packages/server/api/graphql/user/user.graphql b/packages/server/api/graphql/user/user.graphql
index e758103d7..0bf786494 100644
--- a/packages/server/api/graphql/user/user.graphql
+++ b/packages/server/api/graphql/user/user.graphql
@@ -29,7 +29,6 @@ extend type Mutation {
sendEmail(input: String!): SendEmailPayload!
updateEmail(id: ID!, email: String!): UpdateEmailResponse
updateRecentTab(tab: String): User
- updateMenuUI(expanded: Boolean!): User!
setGlobalRole(userId: ID!, role: String!, shouldEnable: Boolean!): User!
setGroupRole(userId: ID!, role: String!, shouldEnable: Boolean!): User!
expandChat(state: Boolean!): User!
@@ -70,7 +69,6 @@ type User {
isOnline: Boolean
recentTab: String
chatExpanded: Boolean!
- menuPinned: Boolean!
}
type CurrentRole {
diff --git a/packages/server/api/graphql/user/user.resolvers.js b/packages/server/api/graphql/user/user.resolvers.js
index 6c44a853b..d3c686ec1 100644
--- a/packages/server/api/graphql/user/user.resolvers.js
+++ b/packages/server/api/graphql/user/user.resolvers.js
@@ -14,7 +14,6 @@ const {
setGroupRole,
updateEmail,
updateLanguage,
- updateMenuUI,
updateRecentTab,
updateUser,
updateUsername,
@@ -93,10 +92,6 @@ module.exports = {
return updateLanguage(id, preferredLanguage)
},
- async updateMenuUI(_, { expanded }, ctx) {
- return updateMenuUI(ctx.userId, expanded)
- },
-
async updateRecentTab(_, { tab }, ctx) {
return updateRecentTab(ctx.userId, tab)
},
diff --git a/packages/server/api/rest/e2e/dumps/commons.bootstrap.sql b/packages/server/api/rest/e2e/dumps/commons.bootstrap.sql
index c5a958c27..d13dca74d 100644
--- a/packages/server/api/rest/e2e/dumps/commons.bootstrap.sql
+++ b/packages/server/api/rest/e2e/dumps/commons.bootstrap.sql
@@ -39,14 +39,14 @@ UPDATE configs SET form_data = '{"user": {"isAdmin": false}, "report": {"showInM
WHERE group_id = g_id;
-- -- Add users to the tests
-INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded", "menu_pinned") VALUES
-('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', NULL, 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true);
+INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded") VALUES
+('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', NULL, 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE);
INSERT INTO "public"."identities" ("id", "user_id", "created", "updated", "type", "identifier", "name", "aff", "oauth", "is_default") VALUES
diff --git a/packages/server/api/rest/e2e/dumps/commons.colab_bootstrap.sql b/packages/server/api/rest/e2e/dumps/commons.colab_bootstrap.sql
index d34055b8b..66b7448db 100644
--- a/packages/server/api/rest/e2e/dumps/commons.colab_bootstrap.sql
+++ b/packages/server/api/rest/e2e/dumps/commons.colab_bootstrap.sql
@@ -27,15 +27,15 @@ UPDATE configs SET form_data = '{"user": {"isAdmin": false, "kotahiApiTokens": "
WHERE group_id = g_id;
-- Add users to the tests
-INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded", "menu_pinned") VALUES
-('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', 't', 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.597+02', '2020-07-24 16:43:54.939+02', true, NULL, 'Test Account', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'profile', 'en-US', FALSE, true);
+INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded") VALUES
+('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', 't', 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.597+02', '2020-07-24 16:43:54.939+02', true, NULL, 'Test Account', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'profile', 'en-US', FALSE);
INSERT INTO "public"."identities" ("id", "user_id", "created", "updated", "type", "identifier", "name", "aff", "oauth", "is_default") VALUES
(gen_random_uuid(), '5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-07-29 05:15:21.654+00', '2022-07-29 05:15:21.624+00', 'orcid', '0000-0003-1838-2441', 'Joane Pilger' , '', '{"accesstoken": "26fbc6b6-4421-40c5-ba07-d8c665f6704b", "refreshtoken": "4211bbf5-85ae-4980-833a-3f3deabcec6a"}', 't'),
diff --git a/packages/server/api/rest/e2e/dumps/commons.elife_bootstrap.sql b/packages/server/api/rest/e2e/dumps/commons.elife_bootstrap.sql
index e0b707b15..dadf27259 100644
--- a/packages/server/api/rest/e2e/dumps/commons.elife_bootstrap.sql
+++ b/packages/server/api/rest/e2e/dumps/commons.elife_bootstrap.sql
@@ -27,14 +27,14 @@ UPDATE configs SET form_data = '{"user": {"isAdmin": false, "kotahiApiTokens": "
WHERE group_id = g_id;
-- Add users to the tests
-INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded", "menu_pinned") VALUES
-('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', NULL, 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true);
+INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded") VALUES
+('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', NULL, 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE);
INSERT INTO "public"."identities" ("id", "user_id", "created", "updated", "type", "identifier", "name", "aff", "oauth", "is_default") VALUES
(gen_random_uuid(), '5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-07-29 05:15:21.654+00', '2022-07-29 05:15:21.624+00', 'orcid', '0000-0003-1838-2441', 'Joane Pilger' , '', '{"accesstoken": "26fbc6b6-4421-40c5-ba07-d8c665f6704b", "refreshtoken": "4211bbf5-85ae-4980-833a-3f3deabcec6a"}', 't'),
diff --git a/packages/server/api/rest/e2e/dumps/initial_state_other.sql b/packages/server/api/rest/e2e/dumps/initial_state_other.sql
index 895b169af..d985792bb 100644
--- a/packages/server/api/rest/e2e/dumps/initial_state_other.sql
+++ b/packages/server/api/rest/e2e/dumps/initial_state_other.sql
@@ -30,15 +30,15 @@ SELECT id INTO channel_id FROM channels WHERE group_id=g_id AND topic='System-wi
UPDATE configs SET form_data = '{"user": {"isAdmin": false, "kotahiApiTokens": "test:123456"}, "report": {"showInMenu": true}, "review": {"showSummary": false}, "dashboard": {"showSections": ["editor"], "loginRedirectUrl": "/dashboard"}, "manuscript": {"tableColumns": "shortId, titleAndAbstract, created, updated, status, submission.$customStatus, author", "paginationCount": 10}, "publishing": {"webhook": {"ref": "test", "url": "https://someserver/webhook-address", "token": "test"}, "crossref": {"login": "test", "password": "test", "doiPrefix": "10.12345/", "licenseUrl": "test", "registrant": "test", "useSandbox": true, "journalName": "test", "depositorName": "test", "depositorEmail": "test@coko.foundation", "journalHomepage": "test", "publicationType": "article", "journalAbbreviatedName": "test", "publishedArticleLocationPrefix": "test"}, "hypothesis": {"group": null, "apiKey": null, "reverseFieldOrder": false, "shouldAllowTagging": false}}, "taskManager": {"teamTimezone": "Etc/UTC"}, "controlPanel": {"showTabs": ["Team", "Decision", "Reviews", "Manuscript text", "Metadata", "Tasks & Notifications"], "hideReview": true, "editorsCanPublish": true, "groupManagersCanPublish": true, "sharedReview": true, "displayManuscriptShortId": true}, "instanceName": "journal", "notification": {"gmailAuthEmail": null, "gmailSenderEmail": null, "gmailAuthPassword": null}, "groupIdentity": {"logoPath": "/logo-kotahi.png", "brandName": "preprint2", "primaryColor": "#3aae2a", "secondaryColor": "#9e9e9e"}}'
WHERE group_id = g_id;
-INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded", "menu_pinned") VALUES
-('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', 't', 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE, true),
-('231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.597+02', '2020-07-24 16:43:54.939+02', true, NULL, 'Test Account', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'profile', 'en-US', FALSE, true);
+INSERT INTO "public"."users" ("id", "created", "updated", "admin", "email", "username", "password_hash", "teams", "password_reset_token", "password_reset_timestamp", "type", "profile_picture", "online", "last_online", "recent_tab", "preferred_language", "chat_expanded") VALUES
+('5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-05-14 10:31:35.715+00', '2022-08-23 14:55:02.854+00', NULL, 'joanep@example.com' , 'Joane Pilger' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('85e1300e-003c-4e96-987b-23812f902477', '2020-07-21 14:35:38.381+00', '2022-08-23 14:55:16.435+00', 't', 'elaineb@example.com', 'Elaine Barnes', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('ba84de0d-d3d5-49e9-ae1b-e8a265789fbe', '2022-05-13 10:55:50.523+00', '2022-08-23 14:54:54.91+00' , NULL, 'emilyc@example.com' , 'Emily Clay' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('f9b1ed7f-f288-4c3f-898c-59e84b1c8e69', '2022-05-13 10:54:12.651+00', '2022-08-23 14:55:09.39+00' , 't' , 'sineads@example.com', 'Sinead Sullivan', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('41d52254-a2b8-4ea4-9ded-bfbfe9671578', '2022-09-14 02:51:58.817+00', '2022-09-14 02:53:20.544+00', NULL, 'sherry@example.com' , 'Sherry Crofoot', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('7f2fb549-51c0-49d5-844d-8a2fbbbbc0ad', '2022-09-14 02:50:09.737+00', '2022-09-14 02:50:25.118+00', NULL, 'gale@example.com' , 'Gale Davis' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('dcabc94f-eb6e-49bb-97d3-fc1a38f9408c', '2022-09-14 02:51:21.741+00', '2022-09-14 02:51:29.283+00', NULL, 'david@example.com' , 'David Miller' , NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'submissions', 'en-US', FALSE),
+('231717dd-ba09-43d4-ac98-9d5542b27a0c', '2020-07-22 14:18:36.597+02', '2020-07-24 16:43:54.939+02', true, NULL, 'Test Account', NULL, NULL, NULL, NULL, 'user', NULL, NULL, NULL, 'profile', 'en-US', FALSE);
INSERT INTO "public"."identities" ("id", "user_id", "created", "updated", "type", "identifier", "name", "aff", "oauth", "is_default") VALUES
(gen_random_uuid(), '5b861dfb-02df-4be1-bc67-41a21611f5e7', '2022-07-29 05:15:21.654+00', '2022-07-29 05:15:21.624+00', 'orcid', '0000-0003-1838-2441', 'Joane Pilger' , '', '{"accesstoken": "26fbc6b6-4421-40c5-ba07-d8c665f6704b", "refreshtoken": "4211bbf5-85ae-4980-833a-3f3deabcec6a"}', 't'),
diff --git a/packages/server/config/cmsTemplateFiles/layouts/article-preview.njk b/packages/server/config/cmsTemplateFiles/layouts/article-preview.njk
index 5cd10bcd3..e8c923a41 100644
--- a/packages/server/config/cmsTemplateFiles/layouts/article-preview.njk
+++ b/packages/server/config/cmsTemplateFiles/layouts/article-preview.njk
@@ -1,4 +1,4 @@
- {% extends '../layouts/base.njk' %}
+{% extends '../layouts/base.njk' %}
{% set currentPage = article %}
{% set title = article.meta.title %}
{% block content %}
diff --git a/packages/server/controllers/user.controllers.js b/packages/server/controllers/user.controllers.js
index 38c78629a..02bff2305 100644
--- a/packages/server/controllers/user.controllers.js
+++ b/packages/server/controllers/user.controllers.js
@@ -810,14 +810,6 @@ const updateLanguage = async (id, preferredLanguage) => {
return User.query().patchAndFetchById(id, { preferredLanguage })
}
-const updateMenuUI = async (userId, expanded) => {
- const user = await User.query().patchAndFetchById(userId, {
- menuPinned: expanded,
- })
-
- return user
-}
-
const updateRecentTab = async (userId, tab) => {
const user = await User.query().patchAndFetchById(userId, {
recentTab: tab,
@@ -873,7 +865,6 @@ module.exports = {
setGroupRole,
updateEmail,
updateLanguage,
- updateMenuUI,
updateRecentTab,
updateUser,
updateUsername,
diff --git a/packages/server/models/user/migrations/1783674872-remove-menu-pinned.ts b/packages/server/models/user/migrations/1783674872-remove-menu-pinned.ts
new file mode 100644
index 000000000..595c3d0db
--- /dev/null
+++ b/packages/server/models/user/migrations/1783674872-remove-menu-pinned.ts
@@ -0,0 +1,21 @@
+import { type Db } from '@coko/server'
+
+export async function up(db: Db): Promise {
+ const hasMenuPinnedColumn = await db.schema.hasColumn('users', 'menu_pinned')
+
+ if (hasMenuPinnedColumn) {
+ await db.schema.table('users', table => {
+ table.dropColumn('menu_pinned')
+ })
+ }
+}
+
+export async function down(db: Db): Promise {
+ const hasMenuPinnedColumn = await db.schema.hasColumn('users', 'menu_pinned')
+
+ if (!hasMenuPinnedColumn) {
+ await db.schema.table('users', table => {
+ table.boolean('menu_pinned').notNullable().defaultTo(true)
+ })
+ }
+}
diff --git a/packages/server/models/user/user.model.js b/packages/server/models/user/user.model.js
index 30f5ab4bc..a2d4ef881 100644
--- a/packages/server/models/user/user.model.js
+++ b/packages/server/models/user/user.model.js
@@ -115,7 +115,6 @@ class User extends BaseModel {
recentTab: { type: ['string', 'null'] },
preferredLanguage: { type: ['string', 'null'] },
chatExpanded: { type: 'boolean' },
- menuPinned: { type: 'boolean' },
},
}
}