- 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.
48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
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;
|
|
}
|
|
}
|