import { Component, type ErrorInfo, type ReactNode } from "react"; interface Props { children: ReactNode; /** What to show instead of the children. Without it, a full-window notice with a Reload button. */ fallback?: (error: Error, reset: () => void) => ReactNode; } interface State { error: Error | null; } /** * React unmounts the whole tree when a component throws and nothing catches it, which leaves a blank window that * looks like a hang. This keeps the failure to the part that threw (or, at the top, shows what happened and a way back). * Plain elements on purpose: the component library may be what threw. */ export default class ErrorBoundary extends Component { state: State = { error: null }; static getDerivedStateFromError(error: Error): State { return { error }; } componentDidCatch(error: Error, info: ErrorInfo) { console.error("Voiced UI error:", error, info.componentStack); } reset = () => this.setState({ error: null }); render() { const { error } = this.state; if (!error) return this.props.children; if (this.props.fallback) return this.props.fallback(error, this.reset); return (

Something went wrong

Voiced hit an unexpected error. Your invoices and drafts are saved. Reload to carry on.

{error.message}
); } }