119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import React, { Component, ErrorInfo, ReactNode } from "react";
|
|
import { mdiAlertCircle } from "@mdi/js";
|
|
|
|
import BaseIcon from "./BaseIcon";
|
|
|
|
type ErrorBoundaryProps = {
|
|
children: ReactNode;
|
|
};
|
|
|
|
type ErrorBoundaryState = {
|
|
hasError: boolean;
|
|
error: Error | null;
|
|
errorInfo: ErrorInfo | null;
|
|
showStack: boolean;
|
|
};
|
|
|
|
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
constructor(props: ErrorBoundaryProps) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
hasError: false,
|
|
error: null,
|
|
errorInfo: null,
|
|
showStack: false,
|
|
};
|
|
}
|
|
|
|
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
|
|
return {
|
|
hasError: true,
|
|
error,
|
|
};
|
|
}
|
|
|
|
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
console.error("Error caught in boundary:", error, errorInfo);
|
|
|
|
this.setState({
|
|
errorInfo,
|
|
});
|
|
}
|
|
|
|
toggleStack = () => {
|
|
this.setState((prevState) => ({
|
|
showStack: !prevState.showStack,
|
|
}));
|
|
};
|
|
|
|
resetError = () => {
|
|
this.setState({
|
|
hasError: false,
|
|
error: null,
|
|
errorInfo: null,
|
|
showStack: false,
|
|
});
|
|
};
|
|
|
|
render() {
|
|
if (!this.state.hasError) {
|
|
return this.props.children;
|
|
}
|
|
|
|
const { error, errorInfo, showStack } = this.state;
|
|
const errorMessage = error?.message || "An unexpected error occurred";
|
|
const stackTrace =
|
|
errorInfo?.componentStack || error?.stack || "No stack trace available";
|
|
|
|
return (
|
|
<div className="flex min-h-screen items-center justify-center bg-slate-100 p-6">
|
|
<div className="w-full max-w-xl rounded-2xl border border-rose-200 bg-white p-8 shadow-sm">
|
|
<div className="flex items-center gap-3">
|
|
<div className="rounded-2xl bg-rose-50 p-3 text-rose-600">
|
|
<BaseIcon path={mdiAlertCircle} size={28} />
|
|
</div>
|
|
<div>
|
|
<h1 className="text-xl font-semibold text-slate-950">
|
|
Something went wrong
|
|
</h1>
|
|
<p className="mt-1 text-sm text-slate-600">
|
|
The app stopped on a visible frontend error.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 rounded-xl bg-rose-50 p-4 font-mono text-sm text-rose-800">
|
|
{errorMessage}
|
|
</div>
|
|
|
|
{showStack && (
|
|
<pre className="mt-4 max-h-72 overflow-auto rounded-xl bg-slate-950 p-4 text-xs text-slate-100">
|
|
{stackTrace}
|
|
</pre>
|
|
)}
|
|
|
|
<div className="mt-6 flex flex-wrap gap-3">
|
|
<button
|
|
type="button"
|
|
onClick={this.resetError}
|
|
className="rounded-xl bg-blue-600 px-4 py-2 text-sm font-semibold text-white hover:bg-blue-500"
|
|
>
|
|
Try again
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={this.toggleStack}
|
|
className="rounded-xl border border-slate-200 px-4 py-2 text-sm font-semibold text-slate-700 hover:bg-slate-50"
|
|
>
|
|
{showStack ? "Hide details" : "Show details"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default ErrorBoundary;
|