/** * Browser-side client for admin announcements. * * Deliberately thin: an announcement is fetched, shown once, and dismissed. * Nothing is cached client-side — "have I seen this?" is the server's answer, * so it survives a new device, a cleared browser, and a second tab. */ import type { MessageEntity } from "@/lib/chat"; const BFF_BASE = process.env.NEXT_PUBLIC_BFF_URL || "http://localhost:8080"; async function call(path: string, init?: RequestInit): Promise { const res = await fetch(`${BFF_BASE}/api/v1${path}`, { credentials: "include", headers: { "Content-Type": "application/json" }, ...init, }); if (!res.ok) throw new Error(`broadcast request failed: ${res.status}`); if (res.status === 204) return undefined as T; const json = await res.json(); return (json?.data ?? json) as T; } export type Broadcast = { id: string; title: string; body: string; /** Formatting spans over `body`, same shape chat messages use. */ entities: MessageEntity[]; banner_url?: string | null; cta_label?: string | null; cta_url?: string | null; published_at?: string | null; expires_at?: string | null; }; export const broadcasts = { /** The announcement waiting for this user, or null. */ pending: () => call("/broadcasts/pending"), /** Dismiss it. Idempotent, so a double-tap or two tabs are both fine. */ markSeen: (id: string) => call(`/broadcasts/${id}/seen`, { method: "POST" }), };