63 lines
1.8 KiB
TypeScript
63 lines
1.8 KiB
TypeScript
import React from "react";
|
|
|
|
import { api } from "../api";
|
|
import { useI18n } from "../i18n/I18nProvider";
|
|
|
|
type Props = {
|
|
children: React.ReactNode;
|
|
};
|
|
|
|
type ErrorBoundaryInnerProps = Props & {
|
|
t: (key: "errorBoundaryTitle" | "errorBoundaryBody" | "errorBoundaryUnknown" | "errorBoundaryRefresh") => string;
|
|
};
|
|
|
|
type State = {
|
|
hasError: boolean;
|
|
errorId?: string;
|
|
};
|
|
|
|
class ErrorBoundaryInner extends React.Component<ErrorBoundaryInnerProps, State> {
|
|
state: State = { hasError: false };
|
|
|
|
static getDerivedStateFromError() {
|
|
return { hasError: true };
|
|
}
|
|
|
|
componentDidCatch(error: any, info: any) {
|
|
const errorId = `ui_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
this.setState({ errorId });
|
|
|
|
void api.post("/client-errors", {
|
|
errorId,
|
|
message: String(error?.message ?? error),
|
|
stack: String(error?.stack ?? ""),
|
|
componentStack: String(info?.componentStack ?? ""),
|
|
url: window.location.href,
|
|
userAgent: navigator.userAgent,
|
|
at: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
render() {
|
|
if (!this.state.hasError) return this.props.children;
|
|
|
|
return (
|
|
<div style={{ padding: 24, maxWidth: 820, margin: "0 auto" }}>
|
|
<h2 style={{ margin: "0 0 8px 0" }}>{this.props.t("errorBoundaryTitle")}</h2>
|
|
<div style={{ opacity: 0.8, marginBottom: 16 }}>
|
|
{this.props.t("errorBoundaryBody")}
|
|
<div style={{ fontFamily: "monospace", marginTop: 6 }}>
|
|
{this.state.errorId ?? this.props.t("errorBoundaryUnknown")}
|
|
</div>
|
|
</div>
|
|
<button onClick={() => window.location.reload()}>{this.props.t("errorBoundaryRefresh")}</button>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default function ErrorBoundary(props: Props) {
|
|
const { t } = useI18n();
|
|
return <ErrorBoundaryInner {...props} t={t} />;
|
|
}
|