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