feat: highlight newer versions in notifications
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"compare-versions": "^4.1.3",
|
||||
"graphql": "^15.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^2.3.1",
|
||||
|
@@ -5,6 +5,7 @@ import Toolbar from '@mui/material/Toolbar';
|
||||
import List from '@mui/material/List';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Badge from '@mui/material/Badge';
|
||||
|
||||
import ListItemLink from 'components/ListItemLink';
|
||||
import HideOnScroll from 'components/HideOnScroll';
|
||||
@@ -17,6 +18,7 @@ type DrawerLink = {
|
||||
Icon: React.ElementType;
|
||||
primary: string;
|
||||
to: string;
|
||||
badgeContent?: React.ReactNode;
|
||||
};
|
||||
|
||||
type DrawerProps = {
|
||||
@@ -65,10 +67,14 @@ export default function Drawer(props: DrawerProps): React.ReactElement {
|
||||
</div>
|
||||
|
||||
<List sx={{ py: 0, mt: 3 }}>
|
||||
{bottomLinks.map(({ Icon, primary, to }, index) => (
|
||||
{bottomLinks.map(({ Icon, badgeContent, primary, to }, index) => (
|
||||
<ListItemLink
|
||||
key={`${to}-${index}`}
|
||||
icon={<Icon htmlColor={theme.palette.primary.main} />}
|
||||
icon={(
|
||||
<Badge badgeContent={badgeContent} color="secondary" max={99}>
|
||||
<Icon htmlColor={theme.palette.primary.main} />
|
||||
</Badge>
|
||||
)}
|
||||
primary={formatMessage(primary)}
|
||||
to={to}
|
||||
onClick={closeOnClick}
|
||||
|
@@ -9,6 +9,7 @@ import HistoryIcon from '@mui/icons-material/History';
|
||||
import NotificationsIcon from '@mui/icons-material/Notifications';
|
||||
|
||||
import * as URLS from 'config/urls';
|
||||
import useVersion from 'hooks/useVersion';
|
||||
import AppBar from 'components/AppBar';
|
||||
import Drawer from 'components/Drawer';
|
||||
|
||||
@@ -34,15 +35,17 @@ const drawerLinks = [
|
||||
},
|
||||
];
|
||||
|
||||
const drawerBottomLinks = [
|
||||
const generateDrawerBottomLinks = ({ notificationBadgeContent = 0 }) => [
|
||||
{
|
||||
Icon: NotificationsIcon,
|
||||
primary: 'settingsDrawer.notifications',
|
||||
to: URLS.UPDATES,
|
||||
badgeContent: notificationBadgeContent,
|
||||
},
|
||||
]
|
||||
|
||||
export default function PublicLayout({ children }: PublicLayoutProps): React.ReactElement {
|
||||
const version = useVersion();
|
||||
const theme = useTheme();
|
||||
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true });
|
||||
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
|
||||
@@ -50,6 +53,10 @@ export default function PublicLayout({ children }: PublicLayoutProps): React.Rea
|
||||
const openDrawer = () => setDrawerOpen(true);
|
||||
const closeDrawer = () => setDrawerOpen(false);
|
||||
|
||||
const drawerBottomLinks = generateDrawerBottomLinks({
|
||||
notificationBadgeContent: version.newVersionCount,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBar drawerOpen={isDrawerOpen} onDrawerOpen={openDrawer} onDrawerClose={closeDrawer} />
|
||||
|
9
packages/web/src/graphql/queries/healthcheck.ts
Normal file
9
packages/web/src/graphql/queries/healthcheck.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const HEALTHCHECK = gql`
|
||||
query Healthcheck {
|
||||
healthcheck {
|
||||
version
|
||||
}
|
||||
}
|
||||
`;
|
26
packages/web/src/hooks/useNotifications.ts
Normal file
26
packages/web/src/hooks/useNotifications.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import * as React from 'react';
|
||||
import appConfig from 'config/app';
|
||||
|
||||
interface INotification {
|
||||
name: string;
|
||||
createdAt: string;
|
||||
documentationUrl: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export default function useNotifications(): INotification[] {
|
||||
const [notifications, setNotifications] = React.useState<INotification[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetch(`${appConfig.notificationsUrl}/notifications.json`)
|
||||
.then((response) => response.json())
|
||||
.then((notifications) => {
|
||||
if (Array.isArray(notifications) && notifications.length) {
|
||||
setNotifications(notifications);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
|
||||
return notifications;
|
||||
}
|
35
packages/web/src/hooks/useVersion.ts
Normal file
35
packages/web/src/hooks/useVersion.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { compare } from 'compare-versions';
|
||||
|
||||
|
||||
import { HEALTHCHECK } from 'graphql/queries/healthcheck';
|
||||
import useNotifications from 'hooks/useNotifications';
|
||||
|
||||
type TVersionInfo = {
|
||||
version: string;
|
||||
newVersionCount: number;
|
||||
}
|
||||
|
||||
export default function useVersion(): TVersionInfo {
|
||||
const notifications = useNotifications();
|
||||
const { data } = useQuery(HEALTHCHECK, { fetchPolicy: 'cache-and-network' });
|
||||
const version = data?.healthcheck.version;
|
||||
|
||||
const newVersionCount = notifications.reduce((count, notification) => {
|
||||
if (!version) return 0;
|
||||
|
||||
// an unexpectedly invalid version would throw and thus, try-catch.
|
||||
try {
|
||||
const isNewer = compare(version, notification.name, '<');
|
||||
|
||||
return isNewer ? count + 1 : count;
|
||||
} catch {
|
||||
return count;
|
||||
}
|
||||
}, 0);
|
||||
|
||||
return {
|
||||
version,
|
||||
newVersionCount,
|
||||
};
|
||||
}
|
@@ -2,13 +2,13 @@ import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Stack from '@mui/material/Stack';
|
||||
|
||||
import appConfig from 'config/app';
|
||||
import useNotifications from 'hooks/useNotifications';
|
||||
import Container from 'components/Container';
|
||||
import NotificationCard from 'components/NotificationCard';
|
||||
import PageTitle from 'components/PageTitle';
|
||||
import useFormatMessage from 'hooks/useFormatMessage';
|
||||
|
||||
interface IUpdate {
|
||||
interface INotification {
|
||||
name: string;
|
||||
createdAt: string;
|
||||
documentationUrl: string;
|
||||
@@ -17,18 +17,7 @@ interface IUpdate {
|
||||
|
||||
export default function Updates(): React.ReactElement {
|
||||
const formatMessage = useFormatMessage();
|
||||
const [updates, setUpdates] = React.useState<IUpdate[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetch(`${appConfig.notificationsUrl}/notifications.json`)
|
||||
.then((response) => response.json())
|
||||
.then((updates) => {
|
||||
if (Array.isArray(updates) && updates.length) {
|
||||
setUpdates(updates);
|
||||
}
|
||||
})
|
||||
.catch(console.error);
|
||||
}, []);
|
||||
const notifications = useNotifications();
|
||||
|
||||
return (
|
||||
<Box sx={{ py: 3 }}>
|
||||
@@ -40,13 +29,13 @@ export default function Updates(): React.ReactElement {
|
||||
<Stack
|
||||
gap={2}
|
||||
>
|
||||
{updates.map((update: IUpdate) => (
|
||||
{notifications.map((notification: INotification) => (
|
||||
<NotificationCard
|
||||
key={update.name}
|
||||
name={`Version ${update.name}`}
|
||||
description={update.description}
|
||||
createdAt={update.createdAt}
|
||||
documentationUrl={update.documentationUrl}
|
||||
key={notification.name}
|
||||
name={`Version ${notification.name}`}
|
||||
description={notification.description}
|
||||
createdAt={notification.createdAt}
|
||||
documentationUrl={notification.documentationUrl}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
@@ -7184,6 +7184,11 @@ compare-func@^2.0.0:
|
||||
array-ify "^1.0.0"
|
||||
dot-prop "^5.1.0"
|
||||
|
||||
compare-versions@^4.1.3:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4"
|
||||
integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg==
|
||||
|
||||
component-emitter@^1.2.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
|
||||
|
Reference in New Issue
Block a user