Files
geniusrun/frontend/src/banner.ts
Christophe Vila f9b1d454bc fix(frontend): dedupe identical banners in banner.ts
Multiple independent requests that fail with the same message (e.g.
Activities.tsx's mount effect firing loadFirstPage/listWorkoutKinds/getProfile
separately) each call showError() independently, previously stacking
2-3 textually-identical banners for a single root cause (backend down,
brief network blip). show() now skips adding a banner if one with the
same severity+message is already visible, and skips scheduling a
redundant auto-dismiss timer for it -- the original banner's timer
keeps running untouched.
2026-07-27 10:34:57 +02:00

60 lines
1.3 KiB
TypeScript

export type BannerSeverity = "error" | "warning" | "notice" | "success";
export interface BannerEntry {
id: number;
severity: BannerSeverity;
message: string;
}
const DISMISS_AFTER_MS = 8000;
let banners: BannerEntry[] = [];
let nextId = 1;
const listeners = new Set<() => void>();
function notify() {
for (const listener of listeners) listener();
}
function show(severity: BannerSeverity, message: string): void {
const isDuplicate = banners.some(
(b) => b.severity === severity && b.message === message,
);
if (isDuplicate) return;
const id = nextId++;
banners = [{ id, severity, message }, ...banners];
notify();
setTimeout(() => dismiss(id), DISMISS_AFTER_MS);
}
export function showError(message: string): void {
show("error", message);
}
export function showWarning(message: string): void {
show("warning", message);
}
export function showNotice(message: string): void {
show("notice", message);
}
export function showSuccess(message: string): void {
show("success", message);
}
export function dismiss(id: number): void {
banners = banners.filter((b) => b.id !== id);
notify();
}
export function subscribe(listener: () => void): () => void {
listeners.add(listener);
return () => listeners.delete(listener);
}
export function getSnapshot(): BannerEntry[] {
return banners;
}