Files
biiproject-kit-v1/mobile/components/ErrorBoundary.tsx
T

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' },
});