Files
BanGUI/frontend/src/components/ErrorBoundary.tsx

88 lines
2.1 KiB
TypeScript

/**
* React error boundary component.
*
* Catches render-time exceptions in child components and shows a fallback UI.
*/
import React from "react";
import { Button, makeStyles, Text, tokens } from "@fluentui/react-components";
interface ErrorBoundaryState {
hasError: boolean;
errorMessage: string | null;
}
interface ErrorBoundaryProps {
children: React.ReactNode;
}
interface ErrorBoundaryFallbackProps {
message: string;
onReload: () => void;
}
const useFallbackStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
minHeight: "100vh",
padding: tokens.spacingVerticalL,
textAlign: "center",
gap: tokens.spacingVerticalM,
},
message: {
maxWidth: "40rem",
},
});
function ErrorBoundaryFallback({ message, onReload }: ErrorBoundaryFallbackProps): React.ReactElement {
const styles = useFallbackStyles();
return (
<div className={styles.root} role="alert">
<Text as="h1" size={700} weight="semibold">
Something went wrong
</Text>
<Text size={300} className={styles.message}>
{message}
</Text>
<Button appearance="primary" onClick={onReload}>
Reload
</Button>
</div>
);
}
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, errorMessage: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, errorMessage: error.message || "Unknown error" };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
console.error("ErrorBoundary caught an error", { error, errorInfo });
}
handleReload = (): void => {
window.location.reload();
};
render(): React.ReactNode {
if (this.state.hasError) {
return (
<ErrorBoundaryFallback
message={this.state.errorMessage ?? "Please try reloading the page."}
onReload={this.handleReload}
/>
);
}
return this.props.children;
}
}