import { Component } from 'react' import { StaticLayout } from './layout' import styles from '@/styles/error.module.css' import Image from 'react-bootstrap/Image' import copy from 'clipboard-copy' import { LoggerContext } from './logger' import Button from 'react-bootstrap/Button' import { useToast } from './toast' import { decodeMinifiedStackTrace } from '@/lib/stacktrace' class ErrorBoundary extends Component { constructor (props) { super(props) // Define a state variable to track whether is an error or not this.state = { hasError: false, error: undefined, errorInfo: undefined } } static getDerivedStateFromError (error) { // Update state so the next render will show the fallback UI return { hasError: true, error } } getErrorDetails () { let details = this.state.error.stack if (this.state.errorInfo?.componentStack) { details += `\n\nComponent stack:\n ${this.state.errorInfo.componentStack}` } return details } componentDidCatch (error, errorInfo) { // You can use your own error logging service here console.log({ error, errorInfo }) this.setState({ errorInfo }) const logger = this.context logger?.error(this.getErrorDetails()) } render () { // Check if the error is thrown if (this.state.hasError) { // You can render any custom fallback UI const errorDetails = this.getErrorDetails() return (

something went wrong

{this.state.error && }
) } // Return children components in case of no error return this.props.children } } ErrorBoundary.contextType = LoggerContext export default ErrorBoundary // This button is a functional component so we can use `useToast` hook, which // can't be easily done in a class component that already consumes a context const CopyErrorButton = ({ errorDetails }) => { const toaster = useToast() const onClick = async () => { try { const decodedDetails = await decodeMinifiedStackTrace(errorDetails) await copy(decodedDetails) toaster?.success?.('copied') } catch (err) { console.error(err) toaster?.danger?.('failed to copy') } } return }