diff --git a/src/web-check-live/components/Form/Nav.tsx b/src/web-check-live/components/Form/Nav.tsx index 7987d58..ec2dcc4 100644 --- a/src/web-check-live/components/Form/Nav.tsx +++ b/src/web-check-live/components/Form/Nav.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled'; import type { ReactNode } from 'react'; +import { Link } from 'react-router-dom'; import { StyledCard } from 'web-check-live/components/Form/Card'; import Heading from 'web-check-live/components/Form/Heading'; @@ -21,7 +22,7 @@ const Nav = (props: { children?: ReactNode}) => {
Web Check Icon - Web Check + Web Check {props.children && props.children}
diff --git a/src/web-check-live/components/boundaries/PageError.tsx b/src/web-check-live/components/boundaries/PageError.tsx new file mode 100644 index 0000000..9d5df22 --- /dev/null +++ b/src/web-check-live/components/boundaries/PageError.tsx @@ -0,0 +1,132 @@ +import React from 'react'; +import styled from '@emotion/styled'; + +import colors from 'web-check-live/styles/colors'; +import Heading from 'web-check-live/components/Form/Heading'; +import Footer from 'web-check-live/components/misc/Footer'; +import Nav from 'web-check-live/components/Form/Nav'; +import Button from 'web-check-live/components/Form/Button'; +import { StyledCard } from 'web-check-live/components/Form/Card'; +import { Link } from 'react-router-dom'; + +interface ErrorBoundaryState { + hasError: boolean; + errorCount: number; + errorMessage: string | null; +} + +interface ErrorBoundaryProps { + children: React.ReactNode; +} + +const ErrorPageContainer = styled.div` +width: 95vw; +max-width: 1000px; +margin: 2rem auto; +padding-bottom: 1rem; +header { + margin 1rem 0; + width: auto; +} +section { + width: auto; + .inner-heading { display: none; } +} +`; + +const HeaderLinkContainer = styled.nav` + display: flex; + flex-wrap: wrap; + gap: 1rem; + a { + text-decoration: none; + } +`; + +const ErrorInner = styled(StyledCard)` + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + h3 { font-size: 6rem; } +`; + +const ErrorDetails = styled.div` + background: ${colors.primaryTransparent}; + padding: 1rem; + border-radius: 0.5rem; +`; + +const ErrorMessageText = styled.p` + color: ${colors.danger}; +`; + +class ErrorBoundary extends React.Component { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, errorCount: 0, errorMessage: null }; + } + + static getDerivedStateFromError(err: Error): ErrorBoundaryState { + return { hasError: true, errorCount: 0, errorMessage: err.message }; + } + + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + console.error( + `%cCritical Error%c\n\nRoute or component failed to mount%c:%c\n` + +`${this.state.errorCount < 1? 'Will attempt a page reload' : ''}. ` + + `Error Details:\n${error}\n\n${JSON.stringify(errorInfo || {})}`, + `background: ${colors.danger}; color:${colors.background}; padding: 4px 8px; font-size: 16px;`, + `font-weight: bold; color: ${colors.danger};`, + `color: ${colors.danger};`, + `color: ${colors.warning};`, + ); + if (this.state.errorCount < 1) { + this.setState(prevState => ({ errorCount: prevState.errorCount + 1 })); + window.location.reload(); + } + } + + render() { + if (this.state.hasError) { + return ( + + + + Something's gone wrong + An unexpected error occurred. + 🤯 + +

+ We're sorry this happened. + Usually reloading the page will resolve this, but if it doesn't, please raise a bug report. +

+ {this.state.errorMessage && ( +

+ Below is the error message we received:

+ {this.state.errorMessage} +

+ )} +
+ + + Report Issue + +
+