feat: broadcast
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
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<Broadcast | null>("/broadcasts/pending"),
|
||||
|
||||
/** Dismiss it. Idempotent, so a double-tap or two tabs are both fine. */
|
||||
markSeen: (id: string) =>
|
||||
call<void>(`/broadcasts/${id}/seen`, { method: "POST" }),
|
||||
};
|
||||
Reference in New Issue
Block a user