303 lines
6.2 KiB
Markdown
303 lines
6.2 KiB
Markdown
|
|
# ErrorDisplay Component API Design
|
||
|
|
|
||
|
|
**Date**: 2025-01-27
|
||
|
|
**Action**: 3.1.1.1 - Design ErrorDisplay component API
|
||
|
|
**Status**: ✅ Complete
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This document defines the API for a reusable `ErrorDisplay` component that standardizes error presentation across the application, replacing inconsistent toast notifications and inline error divs.
|
||
|
|
|
||
|
|
## Component Purpose
|
||
|
|
|
||
|
|
The `ErrorDisplay` component provides:
|
||
|
|
- **Consistent error UI** across the application
|
||
|
|
- **Contextual error information** (error type, message, details)
|
||
|
|
- **Recovery actions** (retry, dismiss, show details)
|
||
|
|
- **Accessibility** (ARIA labels, keyboard navigation)
|
||
|
|
- **Flexible placement** (inline, modal, banner)
|
||
|
|
|
||
|
|
## Component API
|
||
|
|
|
||
|
|
### Props Interface
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
interface ErrorDisplayProps {
|
||
|
|
/**
|
||
|
|
* The error to display
|
||
|
|
* Can be Error object, ApiError, string, or error-like object
|
||
|
|
*/
|
||
|
|
error: Error | ApiError | string | {
|
||
|
|
message: string;
|
||
|
|
code?: string | number;
|
||
|
|
status?: number;
|
||
|
|
details?: Record<string, any>;
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Callback when user clicks retry button
|
||
|
|
* If not provided, retry button is hidden
|
||
|
|
*/
|
||
|
|
onRetry?: () => void | Promise<void>;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Callback when user dismisses the error
|
||
|
|
* If not provided, dismiss button is hidden
|
||
|
|
*/
|
||
|
|
onDismiss?: () => void;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether to show detailed error information
|
||
|
|
* Default: false (only shows user-friendly message)
|
||
|
|
* In development, defaults to true
|
||
|
|
*/
|
||
|
|
showDetails?: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Additional context about where/why the error occurred
|
||
|
|
* Used for logging and debugging
|
||
|
|
*/
|
||
|
|
context?: {
|
||
|
|
action?: string; // e.g., "fetching tracks", "uploading file"
|
||
|
|
resource?: string; // e.g., "tracks", "playlist"
|
||
|
|
resourceId?: string; // e.g., track ID, playlist ID
|
||
|
|
[key: string]: any; // Additional context
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Variant/style of error display
|
||
|
|
* - "inline": Inline error within content (default)
|
||
|
|
* - "banner": Full-width banner at top of page
|
||
|
|
* - "modal": Modal dialog overlay
|
||
|
|
* - "card": Card-style error display
|
||
|
|
*/
|
||
|
|
variant?: 'inline' | 'banner' | 'modal' | 'card';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Error severity/category
|
||
|
|
* Affects visual styling (color, icon)
|
||
|
|
* - "error": Critical errors (default)
|
||
|
|
* - "warning": Warnings that don't block functionality
|
||
|
|
* - "info": Informational messages
|
||
|
|
*/
|
||
|
|
severity?: 'error' | 'warning' | 'info';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Size of the error display
|
||
|
|
* - "sm": Small, compact
|
||
|
|
* - "md": Medium (default)
|
||
|
|
* - "lg": Large, prominent
|
||
|
|
*/
|
||
|
|
size?: 'sm' | 'md' | 'lg';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Custom className for styling
|
||
|
|
*/
|
||
|
|
className?: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Whether error is dismissible
|
||
|
|
* Default: true (if onDismiss provided)
|
||
|
|
*/
|
||
|
|
dismissible?: boolean;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Custom title override
|
||
|
|
* If not provided, uses default title based on error type
|
||
|
|
*/
|
||
|
|
title?: string;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Custom icon override
|
||
|
|
* If not provided, uses default icon based on severity
|
||
|
|
*/
|
||
|
|
icon?: React.ReactNode;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Additional actions to display alongside retry/dismiss
|
||
|
|
* Array of { label, onClick, variant } objects
|
||
|
|
*/
|
||
|
|
actions?: Array<{
|
||
|
|
label: string;
|
||
|
|
onClick: () => void;
|
||
|
|
variant?: 'default' | 'outline' | 'ghost';
|
||
|
|
}>;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Error Type Handling
|
||
|
|
|
||
|
|
### Error Object Normalization
|
||
|
|
|
||
|
|
The component normalizes different error types:
|
||
|
|
|
||
|
|
1. **Error Object**:
|
||
|
|
```typescript
|
||
|
|
new Error('Failed to fetch tracks')
|
||
|
|
```
|
||
|
|
- Message: `error.message`
|
||
|
|
- Stack: Available in details (dev mode)
|
||
|
|
|
||
|
|
2. **ApiError** (from generated types):
|
||
|
|
```typescript
|
||
|
|
{ code: 404, message: 'Track not found', ... }
|
||
|
|
```
|
||
|
|
- Message: `error.message`
|
||
|
|
- Code: `error.code`
|
||
|
|
- Status: `error.status`
|
||
|
|
|
||
|
|
3. **String**:
|
||
|
|
```typescript
|
||
|
|
'Network error occurred'
|
||
|
|
```
|
||
|
|
- Message: The string itself
|
||
|
|
|
||
|
|
4. **Axios Error**:
|
||
|
|
```typescript
|
||
|
|
axiosError.response.data.error
|
||
|
|
```
|
||
|
|
- Extracts error message from response
|
||
|
|
- Includes HTTP status code
|
||
|
|
|
||
|
|
## Default Behavior
|
||
|
|
|
||
|
|
### Error Message Extraction
|
||
|
|
|
||
|
|
1. **Priority order**:
|
||
|
|
- `error.message` (if Error object)
|
||
|
|
- `error.error?.message` (if ApiError)
|
||
|
|
- `error.toString()` (fallback)
|
||
|
|
- `String(error)` (final fallback)
|
||
|
|
|
||
|
|
2. **User-friendly messages**:
|
||
|
|
- Maps technical error codes to user-friendly messages
|
||
|
|
- Example: `ERR_NETWORK` → "Unable to connect to server. Please check your internet connection."
|
||
|
|
|
||
|
|
### Visual Styling
|
||
|
|
|
||
|
|
- **Error severity**: Red/error colors
|
||
|
|
- **Warning severity**: Yellow/warning colors
|
||
|
|
- **Info severity**: Blue/info colors
|
||
|
|
|
||
|
|
### Icons
|
||
|
|
|
||
|
|
- **Error**: `AlertTriangle` (red)
|
||
|
|
- **Warning**: `AlertCircle` (yellow)
|
||
|
|
- **Info**: `Info` (blue)
|
||
|
|
|
||
|
|
## Usage Examples
|
||
|
|
|
||
|
|
### Basic Inline Error
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
onRetry={() => refetch()}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Error with Context
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
context={{
|
||
|
|
action: 'fetching tracks',
|
||
|
|
resource: 'tracks',
|
||
|
|
}}
|
||
|
|
onRetry={handleRetry}
|
||
|
|
showDetails={isDev}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Banner Error
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
variant="banner"
|
||
|
|
onDismiss={() => setError(null)}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Modal Error
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
variant="modal"
|
||
|
|
onRetry={handleRetry}
|
||
|
|
onDismiss={() => setShowError(false)}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Error with Custom Actions
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
onRetry={handleRetry}
|
||
|
|
actions={[
|
||
|
|
{
|
||
|
|
label: 'Report Issue',
|
||
|
|
onClick: () => reportIssue(error),
|
||
|
|
variant: 'outline',
|
||
|
|
},
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
## Integration Points
|
||
|
|
|
||
|
|
### Replace Toast Errors
|
||
|
|
|
||
|
|
**Before**:
|
||
|
|
```typescript
|
||
|
|
toast.error('Failed to fetch tracks');
|
||
|
|
```
|
||
|
|
|
||
|
|
**After**:
|
||
|
|
```typescript
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
variant="inline"
|
||
|
|
onRetry={() => refetch()}
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Replace Inline Error Divs
|
||
|
|
|
||
|
|
**Before**:
|
||
|
|
```typescript
|
||
|
|
{error && (
|
||
|
|
<div className="text-red-500">{error.message}</div>
|
||
|
|
)}
|
||
|
|
```
|
||
|
|
|
||
|
|
**After**:
|
||
|
|
```typescript
|
||
|
|
{error && (
|
||
|
|
<ErrorDisplay
|
||
|
|
error={error}
|
||
|
|
variant="inline"
|
||
|
|
size="sm"
|
||
|
|
/>
|
||
|
|
)}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Accessibility
|
||
|
|
|
||
|
|
- **ARIA labels**: `role="alert"`, `aria-live="polite"`
|
||
|
|
- **Keyboard navigation**: Tab to retry/dismiss buttons
|
||
|
|
- **Screen reader support**: Announces error message
|
||
|
|
- **Focus management**: Focuses retry button on mount
|
||
|
|
|
||
|
|
## Validation
|
||
|
|
|
||
|
|
✅ Component API designed
|
||
|
|
✅ Props interface defined
|
||
|
|
✅ Error type handling specified
|
||
|
|
✅ Usage examples provided
|
||
|
|
✅ Integration points identified
|
||
|
|
⏭️ Next: Action 3.1.1.2 - Implement ErrorDisplay component
|