Skip to content

Commit 7708cf6

Browse files
Merge pull request #1789 from InformaticsMatters/motd
feat: add MOTD feature
2 parents a0f713f + 0abcc35 commit 7708cf6

7 files changed

Lines changed: 223 additions & 6 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ yarn-error.log*
3333
.env.test.local
3434
.env.production.local
3535

36+
# motd file
37+
motd.yaml
38+
3639
# vercel
3740
.vercel
3841

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
"sharp": "0.34.5",
9999
"typescript": "5.9.3",
100100
"use-immer": "0.11.0",
101+
"yaml": "2.8.2",
101102
"zod": "3.25.76"
102103
},
103104
"devDependencies": {

pnpm-lock.yaml

Lines changed: 43 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/Motd.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Alert, AlertTitle, Box, Button, Typography } from "@mui/material";
2+
import { useQuery } from "@tanstack/react-query";
3+
import { type z } from "zod";
4+
5+
import { type MotdEntrySchema } from "../pages/api/motd";
6+
7+
type MotdResponse = z.infer<typeof MotdEntrySchema>[];
8+
9+
const fetchMotd = async (): Promise<MotdResponse | null> => {
10+
const response = await fetch("/api/motd", { cache: "no-store" });
11+
12+
if (response.status === 204) {
13+
return null;
14+
}
15+
16+
if (!response.ok) {
17+
throw new Error("Failed to load MOTD");
18+
}
19+
20+
const data = (await response.json()) as MotdResponse;
21+
return data;
22+
};
23+
24+
function formatDate(dateStr?: string) {
25+
if (!dateStr) {
26+
return undefined;
27+
}
28+
const date = new Date(dateStr);
29+
if (Number.isNaN(date.getTime())) {
30+
return undefined;
31+
}
32+
return date.toLocaleString();
33+
}
34+
35+
export const Motd = () => {
36+
const { data, isError, isLoading } = useQuery({
37+
queryKey: ["motd"],
38+
queryFn: fetchMotd,
39+
staleTime: 0,
40+
refetchOnWindowFocus: true,
41+
refetchOnReconnect: true,
42+
retry: 1,
43+
});
44+
45+
if (isLoading || isError || !data) {
46+
return null;
47+
}
48+
49+
return data.map(({ title, message, url, begin, end }) => (
50+
<Alert
51+
action={
52+
url ? (
53+
<Button
54+
color="inherit"
55+
component="a"
56+
href={url}
57+
rel="noopener noreferrer"
58+
target="_blank"
59+
>
60+
More details
61+
</Button>
62+
) : undefined
63+
}
64+
key={`${title}-${message}`}
65+
severity="info"
66+
sx={{ mb: 2 }}
67+
>
68+
<Box>
69+
{!!title && <AlertTitle sx={{ mb: 0.5 }}>{title}</AlertTitle>}
70+
<Typography component="div" sx={{ whiteSpace: "pre-line", mb: 1 }}>
71+
{message}
72+
</Typography>
73+
{!!(begin ?? end) && (
74+
<Typography color="text.secondary" variant="caption">
75+
{!!begin && `From: ${formatDate(begin)} `}
76+
{!!end && `Until: ${formatDate(end)}`}
77+
</Typography>
78+
)}
79+
</Box>
80+
</Alert>
81+
));
82+
};

0 commit comments

Comments
 (0)