82 lines
2.5 KiB
JavaScript
82 lines
2.5 KiB
JavaScript
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 (
|
|
<StaticLayout footer={false}>
|
|
<Image width='500' height='375' className='rounded-1 shadow-sm' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/floating.gif`} fluid />
|
|
<h1 className={styles.status} style={{ fontSize: '48px' }}>something went wrong</h1>
|
|
{this.state.error && <CopyErrorButton errorDetails={errorDetails} />}
|
|
</StaticLayout>
|
|
)
|
|
}
|
|
|
|
// 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 <Button className='mt-3' onClick={onClick}>copy error information</Button>
|
|
}
|