import React, { Component, ReactNode } from 'react'; import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'; import { debugLogger } from '@/services/DebugLogger'; import { AppColors, Spacing, FontSizes, BorderRadius } from '@/constants/theme'; interface Props { children: ReactNode; fallback?: ReactNode; onError?: (error: Error, errorInfo: React.ErrorInfo) => void; category?: string; } interface State { hasError: boolean; error: Error | null; errorInfo: React.ErrorInfo | null; } /** * ErrorBoundary - catches JavaScript errors in child components * Logs errors to DebugLogger and shows fallback UI */ export class ErrorBoundary extends Component { constructor(props: Props) { super(props); this.state = { hasError: false, error: null, errorInfo: null, }; } static getDerivedStateFromError(error: Error): State { return { hasError: true, error, errorInfo: null, }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { const category = this.props.category || 'ErrorBoundary'; // Log to debug console debugLogger.error( category, `💥 Component crashed: ${error.message}`, { error: error.toString(), stack: error.stack, componentStack: errorInfo.componentStack, } ); // Update state with error info this.setState({ errorInfo, }); // Call optional error handler this.props.onError?.(error, errorInfo); } handleReset = () => { this.setState({ hasError: false, error: null, errorInfo: null, }); }; render() { if (this.state.hasError) { // Custom fallback UI if (this.props.fallback) { return this.props.fallback; } // Default fallback UI return ( Something went wrong {this.state.error?.message || 'Unknown error'} {this.state.error?.stack && ( Stack trace: {this.state.error.stack} )} Try Again Check Debug tab for full error details ); } return this.props.children; } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: AppColors.background, padding: Spacing.lg, }, errorCard: { backgroundColor: AppColors.surface, borderRadius: BorderRadius.lg, padding: Spacing.lg, maxWidth: 400, width: '100%', borderLeftWidth: 4, borderLeftColor: AppColors.error || '#E53935', }, errorTitle: { fontSize: FontSizes.xl, fontWeight: '700', color: AppColors.error || '#E53935', marginBottom: Spacing.sm, }, errorMessage: { fontSize: FontSizes.base, color: AppColors.textPrimary, marginBottom: Spacing.md, lineHeight: 22, }, stackContainer: { backgroundColor: AppColors.background, borderRadius: BorderRadius.md, padding: Spacing.sm, marginBottom: Spacing.md, }, stackLabel: { fontSize: FontSizes.xs, color: AppColors.textSecondary, fontWeight: '600', marginBottom: Spacing.xs, }, stackTrace: { fontSize: FontSizes.xs, color: AppColors.textMuted, fontFamily: 'monospace', lineHeight: 16, }, resetButton: { backgroundColor: AppColors.primary, borderRadius: BorderRadius.md, paddingVertical: Spacing.sm, paddingHorizontal: Spacing.md, alignItems: 'center', marginBottom: Spacing.sm, }, resetButtonText: { color: AppColors.white, fontSize: FontSizes.base, fontWeight: '600', }, debugHint: { fontSize: FontSizes.xs, color: AppColors.textMuted, textAlign: 'center', marginTop: Spacing.xs, }, });