Skip to content

Commit 28de34e

Browse files
authored
Merge pull request #42 from shamoo53/Implement-Proper-Error-Boundary-Strategy
Implement proper error boundary strategy
2 parents 1d80a04 + e39d9ba commit 28de34e

14 files changed

Lines changed: 3287 additions & 19 deletions

docs/ERROR_HANDLING.md

Lines changed: 470 additions & 0 deletions
Large diffs are not rendered by default.

src/app/api/errors/route.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { ErrorReportingData } from '@/types/errors';
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
const body: ErrorReportingData = await request.json();
7+
8+
// Validate required fields
9+
if (!body.errorId || !body.category || !body.message) {
10+
return NextResponse.json(
11+
{ error: 'Missing required fields' },
12+
{ status: 400 }
13+
);
14+
}
15+
16+
// Log error (in production, this would go to your analytics service)
17+
console.error('Error Report:', {
18+
id: body.errorId,
19+
category: body.category,
20+
severity: body.severity,
21+
message: body.message,
22+
userAgent: body.userAgent,
23+
url: body.url,
24+
timestamp: body.timestamp,
25+
context: body.context,
26+
});
27+
28+
// Store in database (placeholder for actual implementation)
29+
// await db.errors.create({ ...body });
30+
31+
// Send to external service (placeholder for actual implementation)
32+
// await analyticsService.trackError(body);
33+
34+
return NextResponse.json(
35+
{ success: true, message: 'Error reported successfully' },
36+
{ status: 200 }
37+
);
38+
39+
} catch (error) {
40+
console.error('Error reporting failed:', error);
41+
return NextResponse.json(
42+
{ error: 'Internal server error' },
43+
{ status: 500 }
44+
);
45+
}
46+
}
47+
48+
export async function GET() {
49+
return NextResponse.json(
50+
{ message: 'Error reporting endpoint. Use POST to report errors.' },
51+
{ status: 200 }
52+
);
53+
}

src/app/error-test/page.tsx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
'use client';
2+
3+
import { ErrorTestSuite } from '@/components/error/ErrorTestSuite';
4+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5+
import { EnhancedErrorBoundary, ErrorBoundaryPresets } from '@/components/error/EnhancedErrorBoundary';
6+
import { Button } from '@/components/ui/button';
7+
import { ErrorCategory } from '@/types/errors';
8+
import { WalletConnector } from '@/components/WalletConnector';
9+
import { LanguageSwitcher } from '@/components/LanguageSwitcher';
10+
11+
function ErrorDemo() {
12+
return (
13+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
14+
<header className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
15+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
16+
<div className="flex justify-between items-center h-16">
17+
<div className="flex items-center gap-3">
18+
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
19+
<span className="text-white font-bold text-sm">PC</span>
20+
</div>
21+
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
22+
Error Boundary Demo
23+
</h1>
24+
</div>
25+
<div className="flex items-center gap-3">
26+
<LanguageSwitcher />
27+
<WalletConnector />
28+
</div>
29+
</div>
30+
</div>
31+
</header>
32+
33+
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
34+
<div className="text-center mb-12">
35+
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
36+
Error Handling Demonstration
37+
</h2>
38+
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
39+
Test comprehensive error boundaries with contextual error handling and recovery strategies
40+
</p>
41+
</div>
42+
43+
{/* Error Test Suite */}
44+
<ErrorTestSuite />
45+
46+
{/* Individual Error Boundary Examples */}
47+
<div className="mt-16 space-y-12">
48+
<div>
49+
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
50+
Individual Error Boundary Examples
51+
</h3>
52+
53+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
54+
{/* Web3 Error Boundary */}
55+
<Card>
56+
<CardHeader>
57+
<CardTitle>Web3 Error Boundary</CardTitle>
58+
</CardHeader>
59+
<CardContent>
60+
<p className="text-gray-600 dark:text-gray-300 mb-4">
61+
Handles blockchain-related errors with wallet reconnection options.
62+
</p>
63+
<ErrorBoundaryPresets.web3 enableRetry maxRetries={3}>
64+
<Button
65+
onClick={() => {
66+
throw new Error('Simulated Web3 connection failure');
67+
}}
68+
className="w-full"
69+
>
70+
Trigger Web3 Error
71+
</Button>
72+
</ErrorBoundaryPresets.web3>
73+
</CardContent>
74+
</Card>
75+
76+
{/* Network Error Boundary */}
77+
<Card>
78+
<CardHeader>
79+
<CardTitle>Network Error Boundary</CardTitle>
80+
</CardHeader>
81+
<CardContent>
82+
<p className="text-gray-600 dark:text-gray-300 mb-4">
83+
Handles network connectivity issues with retry mechanisms.
84+
</p>
85+
<ErrorBoundaryPresets.network enableRetry maxRetries={5}>
86+
<Button
87+
onClick={() => {
88+
throw new Error('Simulated network timeout');
89+
}}
90+
className="w-full"
91+
>
92+
Trigger Network Error
93+
</Button>
94+
</ErrorBoundaryPresets.network>
95+
</CardContent>
96+
</Card>
97+
98+
{/* AR Error Boundary */}
99+
<Card>
100+
<CardHeader>
101+
<CardTitle>AR Error Boundary</CardTitle>
102+
</CardHeader>
103+
<CardContent>
104+
<p className="text-gray-600 dark:text-gray-300 mb-4">
105+
Handles AR/VR feature errors with device capability checks.
106+
</p>
107+
<ErrorBoundaryPresets.ar enableRetry maxRetries={2}>
108+
<Button
109+
onClick={() => {
110+
throw new Error('Simulated AR camera access denied');
111+
}}
112+
className="w-full"
113+
>
114+
Trigger AR Error
115+
</Button>
116+
</ErrorBoundaryPresets.ar>
117+
</CardContent>
118+
</Card>
119+
120+
{/* UI Error Boundary */}
121+
<Card>
122+
<CardHeader>
123+
<CardTitle>UI Error Boundary</CardTitle>
124+
</CardHeader>
125+
<CardContent>
126+
<p className="text-gray-600 dark:text-gray-300 mb-4">
127+
Handles general UI component errors with graceful degradation.
128+
</p>
129+
<ErrorBoundaryPresets.ui enableRetry maxRetries={3}>
130+
<Button
131+
onClick={() => {
132+
throw new Error('Simulated UI component failure');
133+
}}
134+
className="w-full"
135+
>
136+
Trigger UI Error
137+
</Button>
138+
</ErrorBoundaryPresets.ui>
139+
</CardContent>
140+
</Card>
141+
</div>
142+
</div>
143+
144+
{/* Graceful Degradation Example */}
145+
<div>
146+
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
147+
Graceful Degradation Example
148+
</h3>
149+
<Card>
150+
<CardHeader>
151+
<CardTitle>Fallback Component</CardTitle>
152+
</CardHeader>
153+
<CardContent>
154+
<p className="text-gray-600 dark:text-gray-300 mb-4">
155+
Shows how non-critical features can degrade gracefully.
156+
</p>
157+
<ErrorBoundaryPresets.ui
158+
gracefulDegradation={{
159+
fallbackComponent: (
160+
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
161+
<h4 className="font-medium text-yellow-800 dark:text-yellow-200 mb-2">
162+
Feature Unavailable
163+
</h4>
164+
<p className="text-sm text-yellow-700 dark:text-yellow-300">
165+
This feature is currently unavailable, but you can continue using other parts of the application.
166+
</p>
167+
</div>
168+
)
169+
}}
170+
>
171+
<Button
172+
onClick={() => {
173+
throw new Error('Non-critical feature error');
174+
}}
175+
className="w-full"
176+
>
177+
Trigger Graceful Degradation
178+
</Button>
179+
</ErrorBoundaryPresets.ui>
180+
</CardContent>
181+
</Card>
182+
</div>
183+
</div>
184+
</main>
185+
</div>
186+
);
187+
}
188+
189+
export default ErrorDemo;

src/app/page.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
TransactionButton,
2424
} from "@/components/ChainAwareProps";
2525
import { LoadingState } from "@/components/LoadingSpinner";
26-
import { ErrorBoundary } from "@/components/ErrorBoundary";
26+
import { ErrorBoundaryPresets } from "@/components/error/EnhancedErrorBoundary";
2727

2828
function HomeContent() {
2929
const { t } = useTranslation("common");
@@ -275,8 +275,10 @@ function HomeContent() {
275275

276276
export default function Home() {
277277
return (
278-
<ErrorBoundary>
279-
<HomeContent />
280-
</ErrorBoundary>
278+
<ErrorBoundaryPresets.ui enableRetry maxRetries={3}>
279+
<ChainAwareProvider>
280+
<HomeContent />
281+
</ChainAwareProvider>
282+
</ErrorBoundaryPresets.ui>
281283
);
282284
}

src/components/ErrorBoundary.tsx

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,62 @@
1-
'use client';
1+
"use client";
22

3-
import React, { Component } from 'react';
4-
import type { ErrorInfo, ReactNode } from 'react';
5-
import { getWalletErrorMessage } from '@/utils/errorHandling';
3+
import React, { Component, ReactNode } from "react";
4+
import {
5+
EnhancedErrorBoundary,
6+
ErrorBoundaryPresets,
7+
} from "./error/EnhancedErrorBoundary";
8+
import { AppError, ErrorCategory } from "@/types/errors";
9+
import { getWalletErrorMessage } from "@/utils/errorHandling";
610

711
interface Props {
812
children: ReactNode;
913
fallback?: ReactNode;
14+
onError?: (error: AppError) => void;
15+
category?: ErrorCategory;
16+
enableRetry?: boolean;
17+
maxRetries?: number;
1018
}
1119

1220
interface State {
1321
hasError: boolean;
14-
error: Error | null;
22+
error: AppError | null;
1523
}
1624

25+
/**
26+
* @deprecated Use EnhancedErrorBoundary or specific error boundaries instead
27+
* This component is kept for backward compatibility
28+
*/
1729
export class ErrorBoundary extends Component<Props, State> {
1830
constructor(props: Props) {
1931
super(props);
2032
this.state = { hasError: false, error: null };
2133
}
2234

23-
static getDerivedStateFromError(error: Error): State {
35+
static getDerivedStateFromError(error: Error): Partial<State> {
2436
return { hasError: true, error };
2537
}
2638

27-
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
28-
console.error('ErrorBoundary caught an error:', error, errorInfo);
39+
componentDidCatch(error: Error, errorInfo: React.ComponentDidCatchInfo) {
40+
console.error("ErrorBoundary caught an error:", error, errorInfo);
2941
}
3042

3143
render() {
44+
// Use the new enhanced error boundary if category is specified
45+
if (this.props.category) {
46+
return (
47+
<EnhancedErrorBoundary
48+
category={this.props.category}
49+
fallback={this.props.fallback}
50+
onError={this.props.onError}
51+
enableRetry={this.props.enableRetry}
52+
maxRetries={this.props.maxRetries}
53+
>
54+
{this.props.children}
55+
</EnhancedErrorBoundary>
56+
);
57+
}
58+
59+
// Fallback to old behavior for backward compatibility
3260
if (this.state.hasError) {
3361
if (this.props.fallback) {
3462
return this.props.fallback;
@@ -39,26 +67,38 @@ export class ErrorBoundary extends Component<Props, State> {
3967
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
4068
<div className="text-center">
4169
<div className="w-16 h-16 bg-red-100 dark:bg-red-900/20 rounded-full flex items-center justify-center mx-auto mb-4">
42-
<svg className="w-8 h-8 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
43-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" />
70+
<svg
71+
className="w-8 h-8 text-red-600 dark:text-red-400"
72+
fill="none"
73+
stroke="currentColor"
74+
viewBox="0 0 24 24"
75+
>
76+
<path
77+
strokeLinecap="round"
78+
strokeLinejoin="round"
79+
strokeWidth={2}
80+
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z"
81+
/>
4482
</svg>
4583
</div>
46-
84+
4785
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
4886
Something went wrong
4987
</h2>
50-
88+
5189
<p className="text-gray-600 dark:text-gray-300 mb-6">
52-
{getWalletErrorMessage(this.state.error)}
90+
{this.state.error
91+
? getWalletErrorMessage(this.state.error)
92+
: "An unexpected error occurred"}
5393
</p>
54-
94+
5595
<button
5696
onClick={() => window.location.reload()}
5797
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
5898
>
5999
Reload Page
60100
</button>
61-
101+
62102
<button
63103
onClick={() => this.setState({ hasError: false, error: null })}
64104
className="ml-3 px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-300 font-medium rounded-lg transition-colors"
@@ -74,3 +114,7 @@ export class ErrorBoundary extends Component<Props, State> {
74114
return this.props.children;
75115
}
76116
}
117+
118+
// Export the enhanced error boundary and presets for easy usage
119+
export { EnhancedErrorBoundary, ErrorBoundaryPresets };
120+
export default ErrorBoundary;

0 commit comments

Comments
 (0)