feat: highlight newer versions in notifications

This commit is contained in:
Ali BARIN
2022-07-27 15:15:52 +02:00
parent e7c734c55e
commit ff09a836b4
8 changed files with 101 additions and 23 deletions

View File

@@ -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",

View File

@@ -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}

View File

@@ -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} />

View File

@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const HEALTHCHECK = gql`
query Healthcheck {
healthcheck {
version
}
}
`;

View 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;
}

View 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,
};
}

View File

@@ -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>

View File

@@ -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"