feat(ui): notification center with leveled toasts and error capture

- Replace the toast store with a notification store: levels (info/success/
  warning/error/fatal), requiresInteraction, and a persisted history.
- Move toasts to the top-right, styled per level, with manual dismiss.
- Add a bell in the header opening a Notification Center (history, unread badge,
  'needs attention' vs info, clear all).
- Capture network failures and 5xx responses (api layer) and render crashes
  (ErrorBoundary) as notifications.
- Sound + server-sourced events + per-account settings remain for 6b.
This commit is contained in:
npeter83 2026-06-11 19:26:34 +02:00
parent 1a01657c1f
commit 42150edb92
9 changed files with 403 additions and 68 deletions

View file

@ -1,3 +1,5 @@
import { notify } from "./notifications";
export interface Me {
id: number;
email: string;
@ -67,12 +69,34 @@ class HttpError extends Error {
}
async function req(url: string, opts: RequestInit = {}): Promise<any> {
const r = await fetch(url, {
credentials: "include",
headers: { "Content-Type": "application/json" },
...opts,
});
if (!r.ok) throw new HttpError(r.status);
const method = opts.method ?? "GET";
let r: Response;
try {
r = await fetch(url, {
credentials: "include",
headers: { "Content-Type": "application/json" },
...opts,
});
} catch (e) {
// Network/connection failure — surface it in the notification center.
notify({
level: "error",
title: "Network error",
message: `Couldn't reach the server (${method} ${url}).`,
});
throw e;
}
if (!r.ok) {
// Server faults are worth logging; 401/403/404 etc. are handled by callers.
if (r.status >= 500) {
notify({
level: "error",
title: `Server error ${r.status}`,
message: `${method} ${url}`,
});
}
throw new HttpError(r.status);
}
return r.status === 204 ? null : r.json();
}