Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
470 changes: 470 additions & 0 deletions docs/ERROR_HANDLING.md

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions src/app/api/errors/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextRequest, NextResponse } from 'next/server';
import { ErrorReportingData } from '@/types/errors';

export async function POST(request: NextRequest) {
try {
const body: ErrorReportingData = await request.json();

// Validate required fields
if (!body.errorId || !body.category || !body.message) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}

// Log error (in production, this would go to your analytics service)
console.error('Error Report:', {
id: body.errorId,
category: body.category,
severity: body.severity,
message: body.message,
userAgent: body.userAgent,
url: body.url,
timestamp: body.timestamp,
context: body.context,
});

// Store in database (placeholder for actual implementation)
// await db.errors.create({ ...body });

// Send to external service (placeholder for actual implementation)
// await analyticsService.trackError(body);

return NextResponse.json(
{ success: true, message: 'Error reported successfully' },
{ status: 200 }
);

} catch (error) {
console.error('Error reporting failed:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}

export async function GET() {
return NextResponse.json(
{ message: 'Error reporting endpoint. Use POST to report errors.' },
{ status: 200 }
);
}
189 changes: 189 additions & 0 deletions src/app/error-test/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
'use client';

import { ErrorTestSuite } from '@/components/error/ErrorTestSuite';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { EnhancedErrorBoundary, ErrorBoundaryPresets } from '@/components/error/EnhancedErrorBoundary';
import { Button } from '@/components/ui/button';
import { ErrorCategory } from '@/types/errors';
import { WalletConnector } from '@/components/WalletConnector';
import { LanguageSwitcher } from '@/components/LanguageSwitcher';

function ErrorDemo() {
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800">
<header className="bg-white dark:bg-gray-800 shadow-sm border-b border-gray-200 dark:border-gray-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">PC</span>
</div>
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
Error Boundary Demo
</h1>
</div>
<div className="flex items-center gap-3">
<LanguageSwitcher />
<WalletConnector />
</div>
</div>
</div>
</header>

<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Error Handling Demonstration
</h2>
<p className="text-lg text-gray-600 dark:text-gray-300 max-w-2xl mx-auto">
Test comprehensive error boundaries with contextual error handling and recovery strategies
</p>
</div>

{/* Error Test Suite */}
<ErrorTestSuite />

{/* Individual Error Boundary Examples */}
<div className="mt-16 space-y-12">
<div>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
Individual Error Boundary Examples
</h3>

<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* Web3 Error Boundary */}
<Card>
<CardHeader>
<CardTitle>Web3 Error Boundary</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Handles blockchain-related errors with wallet reconnection options.
</p>
<ErrorBoundaryPresets.web3 enableRetry maxRetries={3}>
<Button
onClick={() => {
throw new Error('Simulated Web3 connection failure');
}}
className="w-full"
>
Trigger Web3 Error
</Button>
</ErrorBoundaryPresets.web3>
</CardContent>
</Card>

{/* Network Error Boundary */}
<Card>
<CardHeader>
<CardTitle>Network Error Boundary</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Handles network connectivity issues with retry mechanisms.
</p>
<ErrorBoundaryPresets.network enableRetry maxRetries={5}>
<Button
onClick={() => {
throw new Error('Simulated network timeout');
}}
className="w-full"
>
Trigger Network Error
</Button>
</ErrorBoundaryPresets.network>
</CardContent>
</Card>

{/* AR Error Boundary */}
<Card>
<CardHeader>
<CardTitle>AR Error Boundary</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Handles AR/VR feature errors with device capability checks.
</p>
<ErrorBoundaryPresets.ar enableRetry maxRetries={2}>
<Button
onClick={() => {
throw new Error('Simulated AR camera access denied');
}}
className="w-full"
>
Trigger AR Error
</Button>
</ErrorBoundaryPresets.ar>
</CardContent>
</Card>

{/* UI Error Boundary */}
<Card>
<CardHeader>
<CardTitle>UI Error Boundary</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Handles general UI component errors with graceful degradation.
</p>
<ErrorBoundaryPresets.ui enableRetry maxRetries={3}>
<Button
onClick={() => {
throw new Error('Simulated UI component failure');
}}
className="w-full"
>
Trigger UI Error
</Button>
</ErrorBoundaryPresets.ui>
</CardContent>
</Card>
</div>
</div>

{/* Graceful Degradation Example */}
<div>
<h3 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
Graceful Degradation Example
</h3>
<Card>
<CardHeader>
<CardTitle>Fallback Component</CardTitle>
</CardHeader>
<CardContent>
<p className="text-gray-600 dark:text-gray-300 mb-4">
Shows how non-critical features can degrade gracefully.
</p>
<ErrorBoundaryPresets.ui
gracefulDegradation={{
fallbackComponent: (
<div className="p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
<h4 className="font-medium text-yellow-800 dark:text-yellow-200 mb-2">
Feature Unavailable
</h4>
<p className="text-sm text-yellow-700 dark:text-yellow-300">
This feature is currently unavailable, but you can continue using other parts of the application.
</p>
</div>
)
}}
>
<Button
onClick={() => {
throw new Error('Non-critical feature error');
}}
className="w-full"
>
Trigger Graceful Degradation
</Button>
</ErrorBoundaryPresets.ui>
</CardContent>
</Card>
</div>
</div>
</main>
</div>
);
}

export default ErrorDemo;
10 changes: 6 additions & 4 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
TransactionButton,
} from "@/components/ChainAwareProps";
import { LoadingState } from "@/components/LoadingSpinner";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { ErrorBoundaryPresets } from "@/components/error/EnhancedErrorBoundary";

function HomeContent() {
const { t } = useTranslation("common");
Expand Down Expand Up @@ -275,8 +275,10 @@ function HomeContent() {

export default function Home() {
return (
<ErrorBoundary>
<HomeContent />
</ErrorBoundary>
<ErrorBoundaryPresets.ui enableRetry maxRetries={3}>
<ChainAwareProvider>
<HomeContent />
</ChainAwareProvider>
</ErrorBoundaryPresets.ui>
);
}
74 changes: 59 additions & 15 deletions src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,62 @@
'use client';
"use client";

import React, { Component } from 'react';
import type { ErrorInfo, ReactNode } from 'react';
import { getWalletErrorMessage } from '@/utils/errorHandling';
import React, { Component, ReactNode } from "react";
import {
EnhancedErrorBoundary,
ErrorBoundaryPresets,
} from "./error/EnhancedErrorBoundary";
import { AppError, ErrorCategory } from "@/types/errors";
import { getWalletErrorMessage } from "@/utils/errorHandling";

interface Props {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: AppError) => void;
category?: ErrorCategory;
enableRetry?: boolean;
maxRetries?: number;
}

interface State {
hasError: boolean;
error: Error | null;
error: AppError | null;
}

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

static getDerivedStateFromError(error: Error): State {
static getDerivedStateFromError(error: Error): Partial<State> {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('ErrorBoundary caught an error:', error, errorInfo);
componentDidCatch(error: Error, errorInfo: React.ComponentDidCatchInfo) {
console.error("ErrorBoundary caught an error:", error, errorInfo);
}

render() {
// Use the new enhanced error boundary if category is specified
if (this.props.category) {
return (
<EnhancedErrorBoundary
category={this.props.category}
fallback={this.props.fallback}
onError={this.props.onError}
enableRetry={this.props.enableRetry}
maxRetries={this.props.maxRetries}
>
{this.props.children}
</EnhancedErrorBoundary>
);
}

// Fallback to old behavior for backward compatibility
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
Expand All @@ -39,26 +67,38 @@ export class ErrorBoundary extends Component<Props, State> {
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6">
<div className="text-center">
<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">
<svg className="w-8 h-8 text-red-600 dark:text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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" />
<svg
className="w-8 h-8 text-red-600 dark:text-red-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<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"
/>
</svg>
</div>

<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Something went wrong
</h2>

<p className="text-gray-600 dark:text-gray-300 mb-6">
{getWalletErrorMessage(this.state.error)}
{this.state.error
? getWalletErrorMessage(this.state.error)
: "An unexpected error occurred"}
</p>

<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white font-medium rounded-lg transition-colors"
>
Reload Page
</button>

<button
onClick={() => this.setState({ hasError: false, error: null })}
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"
Expand All @@ -74,3 +114,7 @@ export class ErrorBoundary extends Component<Props, State> {
return this.props.children;
}
}

// Export the enhanced error boundary and presets for easy usage
export { EnhancedErrorBoundary, ErrorBoundaryPresets };
export default ErrorBoundary;
Loading
Loading