'use client' 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 (

Changelog

Latest updates and release notes.

{content}
) }