"use client"; import PageHeading from "../ui/PageHeading"; import { useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { authFetch, clearToken, getApiBase, getToken } from "../lib/auth"; type SiteInfo = { changelog?: string; }; type ChangelogGroup = { date: string; entries: string[]; }; const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/; const parseChangelog = (raw: string): ChangelogGroup[] => { const groups: ChangelogGroup[] = []; for (const rawLine of raw.split("\n")) { const line = rawLine.trim(); if (!line) continue; const [candidateDate, ...messageParts] = line.split("|"); if (DATE_PATTERN.test(candidateDate) && messageParts.length > 0) { const message = messageParts.join("|").trim(); if (!message) continue; const currentGroup = groups[groups.length - 1]; if (currentGroup?.date === candidateDate) { currentGroup.entries.push(message); } else { groups.push({ date: candidateDate, entries: [message] }); } continue; } if (groups.length === 0) { groups.push({ date: "Updates", entries: [line] }); } else { groups[groups.length - 1].entries.push(line); } } return groups; }; export default function ChangelogPage() { const router = useRouter(); const [groups, setGroups] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const token = getToken(); if (!token) { router.push("/login"); return; } let active = true; const load = async () => { try { const baseUrl = getApiBase(); const response = await authFetch(`${baseUrl}/site/info`); if (!response.ok) { if (response.status === 401) { clearToken(); router.push("/login"); return; } throw new Error("Failed to load changelog"); } const data: SiteInfo = await response.json(); if (!active) return; setGroups(parseChangelog(data?.changelog ?? "")); } catch (err) { console.error(err); if (!active) return; setGroups([]); } finally { if (active) setLoading(false); } }; void load(); return () => { active = false; }; }, [router]); const content = useMemo(() => { if (loading) { return
Loading changelog...
; } if (groups.length === 0) { return
No updates posted yet.
; } return (
{groups.map((group) => (

{group.date}

    {group.entries.map((entry, index) => (
  • {entry}
  • ))}
))}
); }, [groups, loading]); return (
{content}
); }