"use client"; import { Megaphone, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; type AnnouncementText = { title?: string; body?: string; }; type UpdateAnnouncementPayload = { enabled?: boolean; zh?: AnnouncementText; en?: AnnouncementText; updated_at?: string; }; type UpdateAnnouncementButtonProps = { isEn: boolean; }; function pickAnnouncementText(payload: UpdateAnnouncementPayload, isEn: boolean) { const primary = isEn ? payload.en : payload.zh; const fallback = isEn ? payload.zh : payload.en; return { title: String(primary?.title || fallback?.title || "").trim(), body: String(primary?.body || fallback?.body || "").trim(), }; } function formatUpdatedAt(value: string | undefined, isEn: boolean) { if (!value) return ""; const date = new Date(value); if (!Number.isFinite(date.getTime())) return ""; return date.toLocaleString(isEn ? "en-US" : "zh-CN", { hour12: false, month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }); } export function UpdateAnnouncementButton({ isEn }: UpdateAnnouncementButtonProps) { const [announcement, setAnnouncement] = useState(null); const [open, setOpen] = useState(false); const shellRef = useRef(null); useEffect(() => { let cancelled = false; async function loadAnnouncement() { try { const res = await fetch("/api/system/update-announcement", { cache: "no-store" }); if (!res.ok) return; const data = (await res.json()) as UpdateAnnouncementPayload; if (!cancelled) { setAnnouncement(data?.enabled ? data : null); } } catch { if (!cancelled) setAnnouncement(null); } } void loadAnnouncement(); return () => { cancelled = true; }; }, []); useEffect(() => { if (!open) return; const handlePointerDown = (event: PointerEvent) => { if (!shellRef.current?.contains(event.target as Node)) { setOpen(false); } }; window.addEventListener("pointerdown", handlePointerDown); return () => window.removeEventListener("pointerdown", handlePointerDown); }, [open]); const text = useMemo( () => (announcement ? pickAnnouncementText(announcement, isEn) : { title: "", body: "" }), [announcement, isEn], ); const updatedAt = useMemo( () => formatUpdatedAt(announcement?.updated_at, isEn), [announcement?.updated_at, isEn], ); if (!announcement || (!text.title && !text.body)) return null; return (
{open && (
{text.title || (isEn ? "PolyWeather update" : "PolyWeather 更新")}
{updatedAt && (
{isEn ? "Updated" : "更新"} {updatedAt}
)}
{text.body && (

{text.body}

)}
)}
); }