79 lines
2.9 KiB
TypeScript
79 lines
2.9 KiB
TypeScript
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
import { View, Text, StyleSheet, TouchableOpacity, SafeAreaView } from 'react-native';
|
|
import { Feather } from '@expo/vector-icons';
|
|
|
|
interface Props {
|
|
children: ReactNode;
|
|
}
|
|
|
|
interface State {
|
|
hasError: boolean;
|
|
error: Error | null;
|
|
}
|
|
|
|
export class ErrorBoundary extends Component<Props, State> {
|
|
public state: State = {
|
|
hasError: false,
|
|
error: null,
|
|
};
|
|
|
|
public static getDerivedStateFromError(error: Error): State {
|
|
return { hasError: true, error };
|
|
}
|
|
|
|
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
|
console.error('Uncaught error:', error, errorInfo);
|
|
|
|
// Technical fix: Report error to backend logs
|
|
const { ApiService } = require('../services/api');
|
|
ApiService.reportError(error.message, 'critical', {
|
|
componentStack: errorInfo.componentStack,
|
|
platform: require('react-native').Platform.OS,
|
|
});
|
|
}
|
|
|
|
private handleReset = () => {
|
|
this.setState({ hasError: false, error: null });
|
|
};
|
|
|
|
public render() {
|
|
if (this.state.hasError) {
|
|
return (
|
|
<View key="error-fallback" style={[styles.container, { paddingTop: 60 }]}>
|
|
<View style={styles.content}>
|
|
<View style={styles.iconBox}>
|
|
<Feather name="alert-triangle" size={60} color="#FF4B4B" />
|
|
</View>
|
|
<Text style={styles.title}>Oops! Something went wrong</Text>
|
|
<Text style={styles.desc}>
|
|
An unexpected error occurred. Don't worry, your data is safe.
|
|
</Text>
|
|
{__DEV__ && (
|
|
<View style={styles.errorBox}>
|
|
<Text style={styles.errorText}>{this.state.error?.toString()}</Text>
|
|
</View>
|
|
)}
|
|
<TouchableOpacity style={styles.btn} onPress={this.handleReset}>
|
|
<Text style={styles.btnText}>Try Again</Text>
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
return this.props.children;
|
|
}
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: '#FFFFFF' },
|
|
content: { flex: 1, alignItems: 'center', justifyContent: 'center', padding: 40 },
|
|
iconBox: { width: 120, height: 120, borderRadius: 40, backgroundColor: '#FFF0F0', alignItems: 'center', justifyContent: 'center', marginBottom: 30 },
|
|
title: { fontSize: 24, fontFamily: 'Outfit_800ExtraBold', color: '#1A1A1A', textAlign: 'center', marginBottom: 12 },
|
|
desc: { fontSize: 15, fontFamily: 'Outfit_400Regular', color: '#666', textAlign: 'center', lineHeight: 22, marginBottom: 40 },
|
|
errorBox: { width: '100%', padding: 16, backgroundColor: '#F5F5F5', borderRadius: 12, marginBottom: 30 },
|
|
errorText: { fontSize: 12, fontFamily: 'Monaco', color: '#888' },
|
|
btn: { width: '100%', height: 56, borderRadius: 16, backgroundColor: '#1A1A1A', alignItems: 'center', justifyContent: 'center' },
|
|
btnText: { color: '#FFFFFF', fontSize: 16, fontFamily: 'Outfit_700Bold' },
|
|
});
|