Files
maternal-app/maternal-web/components/common/ErrorFallbacks.tsx
Andrei 68e33712f1
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Add comprehensive error boundaries for graceful error handling
Implemented React error boundaries to catch and handle errors gracefully:

**Core Error Handling Components:**
- Created ErrorBoundary class component with error catching and logging
- Created specialized fallback UIs (MinimalErrorFallback, DataErrorFallback,
  ComponentErrorFallback, FormErrorFallback, ChartErrorFallback, ImageErrorFallback)
- Added withErrorBoundary HOC for easy component wrapping
- Created errorLogger service with Sentry integration placeholder

**Error Logging Service (errorLogger.ts):**
- Centralized error logging with severity levels (FATAL, ERROR, WARNING, INFO, DEBUG)
- Context enrichment (URL, userAgent, timestamp, environment)
- Local storage of last 10 errors in sessionStorage for debugging
- User context management (setUser, clearUser)
- Breadcrumb support for debugging trails

**App Integration:**
- Wrapped root layout with top-level ErrorBoundary for catastrophic errors
- Added NetworkStatusIndicator to main page for offline sync visibility
- Wrapped daily summary section with isolated DataErrorFallback
- Added error boundary to AI assistant page with ComponentErrorFallback
- Wrapped feeding tracking form with FormErrorFallback using withErrorBoundary HOC
- Protected analytics charts with isolated ChartErrorFallback boundaries

**Error Recovery Features:**
- Isolated error boundaries prevent cascade failures
- Retry buttons on all fallback UIs
- Error count tracking with user warnings
- Development-mode error details display
- Automatic error logging to service (when Sentry integrated)

Next: Integration with Sentry for production error tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 19:37:04 +00:00

237 lines
6.2 KiB
TypeScript

'use client';
import { Box, Button, Typography, Paper } from '@mui/material';
import { BrokenImage, CloudOff, BugReport, DataObject } from '@mui/icons-material';
import { ReactNode } from 'react';
interface FallbackProps {
error?: Error;
resetError?: () => void;
}
/**
* Generic minimal error fallback
*/
export function MinimalErrorFallback({ error, resetError }: FallbackProps) {
return (
<Box
sx={{
p: 2,
textAlign: 'center',
bgcolor: 'error.light',
borderRadius: 2,
border: '1px solid',
borderColor: 'error.main',
}}
>
<Typography variant="body2" color="error.dark" gutterBottom>
Something went wrong
</Typography>
{resetError && (
<Button size="small" onClick={resetError} variant="outlined" color="error">
Retry
</Button>
)}
</Box>
);
}
/**
* Fallback for image loading errors
*/
export function ImageErrorFallback() {
return (
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'grey.100',
borderRadius: 1,
}}
>
<BrokenImage sx={{ fontSize: 48, color: 'grey.400' }} />
</Box>
);
}
/**
* Fallback for API/data fetching errors
*/
export function DataErrorFallback({ error, resetError }: FallbackProps) {
return (
<Paper
sx={{
p: 3,
textAlign: 'center',
bgcolor: 'background.paper',
border: '1px dashed',
borderColor: 'divider',
}}
>
<CloudOff sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
<Typography variant="h6" gutterBottom>
Failed to Load Data
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{error?.message || 'Unable to fetch the requested data. Please try again.'}
</Typography>
{resetError && (
<Button variant="contained" onClick={resetError}>
Retry
</Button>
)}
</Paper>
);
}
/**
* Fallback for rendering errors in components
*/
export function ComponentErrorFallback({ error, resetError }: FallbackProps) {
return (
<Box
sx={{
p: 3,
border: '2px dashed',
borderColor: 'warning.main',
borderRadius: 2,
bgcolor: 'warning.light',
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<BugReport sx={{ color: 'warning.dark' }} />
<Typography variant="subtitle1" fontWeight="600" color="warning.dark">
Component Error
</Typography>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
This component failed to render. The rest of the page should still work.
</Typography>
{process.env.NODE_ENV === 'development' && error && (
<Typography
variant="caption"
component="pre"
sx={{
p: 1,
bgcolor: 'background.paper',
borderRadius: 1,
overflow: 'auto',
fontSize: '0.7rem',
}}
>
{error.message}
</Typography>
)}
{resetError && (
<Button size="small" variant="outlined" onClick={resetError} sx={{ mt: 1 }}>
Reload Component
</Button>
)}
</Box>
);
}
/**
* Fallback for form errors
*/
export function FormErrorFallback({ error, resetError }: FallbackProps) {
return (
<Paper
elevation={0}
sx={{
p: 2,
bgcolor: 'error.light',
border: '1px solid',
borderColor: 'error.main',
borderRadius: 2,
}}
>
<Typography variant="subtitle2" color="error.dark" gutterBottom>
Form Submission Error
</Typography>
<Typography variant="body2" color="error.dark" sx={{ mb: 2 }}>
{error?.message || 'Unable to process your form. Please try again.'}
</Typography>
{resetError && (
<Button size="small" variant="contained" color="error" onClick={resetError}>
Try Again
</Button>
)}
</Paper>
);
}
/**
* Fallback for data visualization/chart errors
*/
export function ChartErrorFallback({ resetError }: FallbackProps) {
return (
<Box
sx={{
height: 300,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
bgcolor: 'grey.50',
borderRadius: 2,
border: '1px dashed',
borderColor: 'divider',
}}
>
<DataObject sx={{ fontSize: 48, color: 'text.disabled', mb: 2 }} />
<Typography variant="body2" color="text.secondary" gutterBottom>
Unable to display chart
</Typography>
{resetError && (
<Button size="small" onClick={resetError} sx={{ mt: 1 }}>
Reload
</Button>
)}
</Box>
);
}
/**
* Wrapper component to easily apply error boundary with custom fallback
*/
interface ErrorBoundaryWrapperProps {
children: ReactNode;
fallbackType?: 'minimal' | 'component' | 'data' | 'form' | 'chart';
customFallback?: ReactNode;
}
export function withErrorBoundary<P extends object>(
Component: React.ComponentType<P>,
fallbackType: ErrorBoundaryWrapperProps['fallbackType'] = 'component'
) {
return function WithErrorBoundaryWrapper(props: P) {
const { ErrorBoundary } = require('./ErrorBoundary');
const getFallback = (error: Error, resetError: () => void) => {
switch (fallbackType) {
case 'minimal':
return <MinimalErrorFallback error={error} resetError={resetError} />;
case 'data':
return <DataErrorFallback error={error} resetError={resetError} />;
case 'form':
return <FormErrorFallback error={error} resetError={resetError} />;
case 'chart':
return <ChartErrorFallback resetError={resetError} />;
case 'component':
default:
return <ComponentErrorFallback error={error} resetError={resetError} />;
}
};
return (
<ErrorBoundary isolate fallback={(error: Error, reset: () => void) => getFallback(error, reset)}>
<Component {...props} />
</ErrorBoundary>
);
};
}