import * as Sentry from '@sentry/react-native'; import React from 'react'; import { ScrollView, StyleSheet, Text, View } from 'react-native'; import { theme } from './theme'; interface Props { children: React.ReactNode; /** Opcjonalny tag wysyłany do Sentry — pomaga rozróżnić top-level boundary od lokalnych. */ scope?: string; } interface State { error: Error | null; info: string | null; } export class ErrorBoundary extends React.Component { state: State = { error: null, info: null }; static getDerivedStateFromError(error: Error): State { return { error, info: null }; } componentDidCatch(error: Error, info: React.ErrorInfo): void { this.setState({ error, info: info.componentStack || null }); // Sentry.wrap()-owy boundary łapie tylko jeśli root nie jest manuanle wrapped. // Dodajemy explicit capture żeby ten boundary też wysyłał — z tagiem `scope` // żebyśmy w UI Sentry odróżnili global crash od per-tab crash. Sentry.captureException(error, { tags: { boundary: this.props.scope || 'global' }, contexts: { react: { componentStack: info.componentStack } }, }); // eslint-disable-next-line no-console console.error('App crashed:', error, info); } render() { if (this.state.error) { return ( App crashed Error {this.state.error.name}: {this.state.error.message} {this.state.error.stack ? ( <> Stack {this.state.error.stack} ) : null} {this.state.info ? ( <> Component stack {this.state.info} ) : null} ); } return <>{this.props.children}; } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.bg }, title: { color: theme.bad, fontSize: 24, fontWeight: '700', marginBottom: 16 }, label: { color: theme.muted, fontSize: 12, textTransform: 'uppercase', letterSpacing: 0.6, marginTop: 16, marginBottom: 4, }, body: { color: theme.fg, fontSize: 14 }, code: { color: theme.fg, fontSize: 11, fontFamily: 'monospace', }, });