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 01960d3bb7
commit 2c51e4434b
9 changed files with 403 additions and 68 deletions

View file

@ -0,0 +1,48 @@
import { Component, type ReactNode } from "react";
import { notify } from "../lib/notifications";
interface Props {
children: ReactNode;
}
interface State {
crashed: boolean;
}
// Catches render-time crashes anywhere in the tree, logs a fatal notification (kept
// in the center across the reload) and offers a recovery action.
export default class ErrorBoundary extends Component<Props, State> {
state: State = { crashed: false };
static getDerivedStateFromError(): State {
return { crashed: true };
}
componentDidCatch(error: Error) {
notify({
level: "fatal",
title: "Something broke",
message: error.message || "Unexpected error",
requiresInteraction: true,
});
}
render() {
if (this.state.crashed) {
return (
<div className="min-h-screen grid place-items-center text-muted p-6 text-center">
<div>
<div className="text-lg font-semibold text-fg mb-1">Something went wrong.</div>
<div className="text-sm mb-3">The app hit an unexpected error.</div>
<button
onClick={() => location.reload()}
className="text-accent hover:underline text-sm font-semibold"
>
Reload
</button>
</div>
</div>
);
}
return this.props.children;
}
}