| import React, { Component, ReactNode } from "react"; |
| import { Button } from "./ui/button"; |
| import { AlertTriangle, RefreshCw, Home, Code } from "lucide-react"; |
|
|
| interface Props { |
| children: ReactNode; |
| fallback?: ReactNode; |
| } |
|
|
| interface State { |
| hasError: boolean; |
| error: Error | null; |
| errorInfo: React.ErrorInfo | null; |
| } |
|
|
| export class ErrorBoundary extends Component<Props, State> { |
| constructor(props: Props) { |
| super(props); |
| this.state = { |
| hasError: false, |
| error: null, |
| errorInfo: null, |
| }; |
| } |
|
|
| static getDerivedStateFromError(error: Error): Partial<State> { |
| return { hasError: true }; |
| } |
|
|
| componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { |
| console.error("Error caught by boundary:", error, errorInfo); |
| this.setState({ |
| error, |
| errorInfo, |
| }); |
| } |
|
|
| handleReset = () => { |
| this.setState({ |
| hasError: false, |
| error: null, |
| errorInfo: null, |
| }); |
| }; |
|
|
| render() { |
| if (this.state.hasError) { |
| if (this.props.fallback) { |
| return this.props.fallback; |
| } |
|
|
| return ( |
| <div className="min-h-screen flex items-center justify-center bg-background px-4 py-8"> |
| <div className="max-w-3xl w-full"> |
| {/* Main Error Card */} |
| <div className="bg-white/[0.02] border border-white/10 rounded-xl overflow-hidden backdrop-blur-sm"> |
| {/* Header with Gradient */} |
| <div className="bg-gradient-to-r from-red-500/20 via-orange-500/20 to-yellow-500/20 border-b border-white/10 p-8"> |
| <div className="flex items-start gap-4"> |
| {/* Animated Icon */} |
| <div className="flex-shrink-0"> |
| <div className="w-16 h-16 bg-red-500/20 rounded-2xl flex items-center justify-center border border-red-500/30 backdrop-blur-sm animate-pulse"> |
| <AlertTriangle className="w-8 h-8 text-red-400" /> |
| </div> |
| </div> |
| |
| <div className="flex-1 min-w-0"> |
| <h1 className="text-3xl font-bold text-white mb-2 flex items-center gap-2"> |
| Oops! Something went wrong |
| </h1> |
| <p className="text-gray-400 text-base"> |
| Don't worry, your data is safe. The error has been logged and we'll look into it. |
| </p> |
| </div> |
| </div> |
| </div> |
| |
| {/* Error Details */} |
| <div className="p-8 space-y-6"> |
| {this.state.error && ( |
| <div className="space-y-4"> |
| {/* Error Message */} |
| <div> |
| <h3 className="text-sm font-semibold text-gray-400 mb-2 flex items-center gap-2"> |
| <Code className="w-4 h-4" /> |
| Error Message |
| </h3> |
| <div className="bg-black/50 border border-red-500/30 rounded-lg p-4 overflow-x-auto"> |
| <pre className="text-sm text-red-300 whitespace-pre-wrap break-words"> |
| {this.state.error.toString()} |
| </pre> |
| </div> |
| </div> |
| |
| {/* Stack Trace (Dev Only) */} |
| {import.meta.env.DEV && this.state.errorInfo && ( |
| <details className="group"> |
| <summary className="cursor-pointer text-sm font-semibold text-gray-400 hover:text-gray-300 transition-colors select-none flex items-center gap-2 mb-2"> |
| <svg |
| className="w-4 h-4 transition-transform group-open:rotate-90" |
| fill="none" |
| stroke="currentColor" |
| viewBox="0 0 24 24" |
| > |
| <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> |
| </svg> |
| Stack Trace (Development Mode) |
| </summary> |
| <div className="bg-black/50 border border-white/10 rounded-lg p-4 overflow-x-auto"> |
| <pre className="text-xs text-gray-400 whitespace-pre-wrap"> |
| {this.state.errorInfo.componentStack} |
| </pre> |
| </div> |
| </details> |
| )} |
| </div> |
| )} |
| |
| {/* Action Buttons */} |
| <div className="flex flex-wrap gap-3 pt-4"> |
| <Button |
| onClick={this.handleReset} |
| className="bg-purple-500 hover:bg-purple-600 text-white gap-2" |
| > |
| <RefreshCw className="w-4 h-4" /> |
| Try Again |
| </Button> |
| <Button |
| onClick={() => window.location.reload()} |
| variant="outline" |
| className="border-white/10 text-gray-300 hover:bg-white/5 gap-2" |
| > |
| <RefreshCw className="w-4 h-4" /> |
| Refresh Page |
| </Button> |
| <Button |
| onClick={() => (window.location.href = "/")} |
| variant="outline" |
| className="border-white/10 text-gray-300 hover:bg-white/5 gap-2" |
| > |
| <Home className="w-4 h-4" /> |
| Go Home |
| </Button> |
| </div> |
| |
| {/* Help Text */} |
| <div className="pt-4 border-t border-white/10"> |
| <p className="text-xs text-gray-500"> |
| If this problem persists, please contact support or check the console for more details. |
| </p> |
| </div> |
| </div> |
| </div> |
| |
| {/* Additional Help Card */} |
| <div className="mt-6 bg-white/[0.02] border border-white/10 rounded-xl p-6"> |
| <h3 className="text-sm font-semibold text-white mb-3">Quick Troubleshooting</h3> |
| <ul className="space-y-2 text-sm text-gray-400"> |
| <li className="flex items-start gap-2"> |
| <span className="text-purple-400 mt-0.5">•</span> |
| <span>Try clearing your browser cache and cookies</span> |
| </li> |
| <li className="flex items-start gap-2"> |
| <span className="text-purple-400 mt-0.5">•</span> |
| <span>Check your internet connection</span> |
| </li> |
| <li className="flex items-start gap-2"> |
| <span className="text-purple-400 mt-0.5">•</span> |
| <span>Make sure you're using a supported browser</span> |
| </li> |
| <li className="flex items-start gap-2"> |
| <span className="text-purple-400 mt-0.5">•</span> |
| <span>Disable browser extensions that might interfere</span> |
| </li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| ); |
| } |
|
|
| return this.props.children; |
| } |
| } |