Skip to content

Commit 0ae310d

Browse files
committed
feat: add React Error Boundary to prevent white-screen crashes
Wraps AppRoutes in ErrorBoundary so any unhandled component error shows a recovery UI instead of a blank white screen. Displays the error message, a 'Try again' button (resets error state) and a 'Go home' button (navigates to /). Class component because React doesn't support error boundaries with hooks yet. Closes OPE-15
1 parent 56694d1 commit 0ae310d

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

frontend/src/App.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ArchitecturePage } from './pages/ArchitecturePage';
1818
import { ContributingPage } from './pages/ContributingPage';
1919
import { GitHubCallbackPage } from './pages/GitHubCallbackPage';
2020
import { ScrollToTop } from './components/ScrollToTop';
21+
import { ErrorBoundary } from './components/ErrorBoundary';
2122

2223
function ProtectedRoute({ children }: { children: React.ReactNode }) {
2324
const { user, loading } = useAuth();
@@ -126,9 +127,11 @@ export function App() {
126127
<TooltipProvider>
127128
<BrowserRouter>
128129
<ScrollToTop />
129-
<AuthProvider>
130-
<AppRoutes />
131-
</AuthProvider>
130+
<ErrorBoundary>
131+
<AuthProvider>
132+
<AppRoutes />
133+
</AuthProvider>
134+
</ErrorBoundary>
132135
</BrowserRouter>
133136
</TooltipProvider>
134137
</ThemeProvider>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Component, type ReactNode } from 'react'
2+
import { Button } from './ui/button'
3+
4+
interface Props {
5+
children: ReactNode
6+
}
7+
8+
interface State {
9+
hasError: boolean
10+
error: Error | null
11+
}
12+
13+
// class component required -- React doesn't support error boundaries with hooks yet
14+
export class ErrorBoundary extends Component<Props, State> {
15+
state: State = { hasError: false, error: null }
16+
17+
static getDerivedStateFromError(error: Error): State {
18+
return { hasError: true, error }
19+
}
20+
21+
componentDidCatch(error: Error, info: React.ErrorInfo) {
22+
console.error('ErrorBoundary caught:', error, info.componentStack)
23+
}
24+
25+
render() {
26+
if (!this.state.hasError) return this.props.children
27+
28+
return (
29+
<div className="min-h-screen flex items-center justify-center bg-background p-6">
30+
<div className="max-w-md w-full text-center space-y-4">
31+
<h1 className="text-2xl font-bold text-foreground">
32+
Something went wrong
33+
</h1>
34+
<p className="text-muted-foreground">
35+
An unexpected error occurred. This has been logged.
36+
</p>
37+
{this.state.error && (
38+
<pre className="text-xs text-left bg-muted border border-border rounded-lg p-3 overflow-auto max-h-[120px] text-muted-foreground">
39+
{this.state.error.message}
40+
</pre>
41+
)}
42+
<div className="flex gap-3 justify-center pt-2">
43+
<Button
44+
variant="outline"
45+
onClick={() => this.setState({ hasError: false, error: null })}
46+
>
47+
Try again
48+
</Button>
49+
<Button onClick={() => window.location.assign('/')}>
50+
Go home
51+
</Button>
52+
</div>
53+
</div>
54+
</div>
55+
)
56+
}
57+
}

0 commit comments

Comments
 (0)