import { useState, type ReactNode } from "react"; import { useQuery } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { Bug, Sparkles } from "lucide-react"; import { loadReleaseNotes, notesLang, visibleNotes } from "../lib/releaseNotes"; import { qk } from "../lib/queryKeys"; import { formatDate } from "../lib/format"; import { Button } from "./ui/Button"; import Modal from "./ui/Modal"; import { LoadingState, StateMessage } from "./ui/QueryState"; // The release date in the UI language — `toLocaleDateString(undefined, …)` formats in the BROWSER's // locale, which put Hungarian-shaped dates in an English release-notes list. Unparseable input falls // back to the raw string (a hand-written note could carry anything). function fmtDate(d: string, lang: string): string { return isNaN(new Date(d).getTime()) ? d : formatDate(d, lang); } // The newest releases are what a user opens this for; the whole history (150+ versions) rendered on // every open was what made the dialog slow to appear. The rest are one click away. const RECENT = 10; // Public release notes in the UI language. `highlight` rings the version the user just updated to // when opened from the new-version banner. export default function ReleaseNotes({ onClose, highlight, }: { onClose: () => void; highlight?: string; }) { const { t, i18n } = useTranslation(); const lang = notesLang(i18n.language); const q = useQuery({ queryKey: qk.releaseNotes(lang), queryFn: () => loadReleaseNotes(lang), staleTime: Infinity, }); const [showAll, setShowAll] = useState(false); const notes = visibleNotes(q.data ?? []); // Ring the newest note if `highlight` isn't one of the listed versions — e.g. a Vite dev build // where the caller's FRONTEND_VERSION is "dev", or a VERSION bump whose note hasn't landed yet — // so the banner always rings *something* current instead of nothing. const at = notes.findIndex((r) => r.version === highlight); const active = at >= 0 ? highlight : notes[0]?.version; const shown = showAll || at >= RECENT ? notes : notes.slice(0, RECENT); return ( {q.isError ? ( q.refetch()}> {t("about.notesError")} ) : !q.data ? ( ) : (
{shown.map((r) => (

v{r.version}

{fmtDate(r.date, i18n.language)}
} title={t("about.new")} accent> {r.new} } title={t("about.fixes")}> {r.fixed}
))} {shown.length < notes.length && ( )}
)}
); } function Items({ icon, title, accent, children, }: { icon: ReactNode; title: string; accent?: boolean; children: string[]; }) { if (children.length === 0) return null; const tone = accent ? "text-accent" : "text-muted"; return (
{icon} {title}
); }