Files
karma/ui/src/ErrorBoundary.tsx
2020-07-21 17:58:08 +01:00

105 lines
2.8 KiB
TypeScript

import React, {
Component,
StatelessComponent,
ReactNode,
ErrorInfo,
} from "react";
import { captureException } from "@sentry/browser";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBomb } from "@fortawesome/free-solid-svg-icons/faBomb";
interface InternalErrorProps {
message: ReactNode;
secondsLeft: number;
progressLeft: number;
}
const InternalError: StatelessComponent<InternalErrorProps> = (props) => (
<div className="text-placeholder screen-center">
<div className="container-fluid text-center">
<h1 className="display-1">
<FontAwesomeIcon className="text-danger mr-4" icon={faBomb} />
<span className="text-muted">Internal error</span>
</h1>
<p className="lead text-white bg-secondary p-3 rounded text-wrap text-break">
{props.message}
</p>
<p className="text-muted d-inline-block">
This page will auto refresh in {props.secondsLeft}s
<span
className="progress bg-secondary mx-auto"
style={{ height: "2px" }}
>
<span
className="progress-bar bg-info"
role="progressbar"
style={{ width: `${props.progressLeft}%` }}
aria-valuenow={props.progressLeft}
aria-valuemin={0}
aria-valuemax={100}
></span>
</span>
</p>
</div>
</div>
);
interface ErrorBoundaryProps {
children: React.ReactNode;
}
interface ErrorBoundaryState {
cachedError: Error | null;
reloadSeconds: number;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
timer: ReturnType<typeof setInterval> | null;
state: Readonly<ErrorBoundaryState> = {
cachedError: null,
reloadSeconds: 60,
};
constructor(props: ErrorBoundaryProps) {
super(props);
this.timer = null;
}
reloadApp = (): void => {
if (this.state.reloadSeconds <= 1) {
window.location.reload();
} else {
this.setState({ reloadSeconds: this.state.reloadSeconds - 1 });
}
};
componentDidCatch(error: Error, { componentStack }: ErrorInfo): void {
if (this.state.cachedError === null) {
this.setState({ cachedError: error });
captureException(error, { contexts: { react: { componentStack } } });
}
// reload after 60s, this is to fix wall monitors automatically
// but only if the timer isn't set yet
if (this.timer === null) {
this.timer = setInterval(this.reloadApp, 1000);
}
}
render(): ReactNode {
if (this.state.cachedError !== null) {
return (
<InternalError
message={this.state.cachedError.toString()}
secondsLeft={this.state.reloadSeconds}
progressLeft={(this.state.reloadSeconds / 60.0) * 100.0}
/>
);
}
return this.props.children;
}
}
export { ErrorBoundary, InternalError };