483 lines
13 KiB
Markdown
483 lines
13 KiB
Markdown
# Toast Migration Analysis
|
|
|
|
This document identifies all places in the app where toast notifications should replace existing error handling mechanisms.
|
|
|
|
## Summary
|
|
|
|
**Total Locations Identified**: 7 pages/components
|
|
**Current Error Handling Methods**:
|
|
- `alert()` - Used in 3 locations (5+ instances)
|
|
- Inline error/success messages - Used in 2 locations
|
|
- Form input validation messages - Used in 1 location
|
|
- No error handling (TODO) - Used in 1 location
|
|
|
|
---
|
|
|
|
## Detailed Analysis
|
|
|
|
### 1. AdminPage.tsx ⚠️ HIGH PRIORITY
|
|
|
|
**File**: `src/pages/AdminPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
const [message, setMessage] = useState<string | null>(null);
|
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
|
|
// Multiple handlers use inline state
|
|
onSuccess: () => {
|
|
setMessage('Backup completed successfully');
|
|
setErrorMessage(null);
|
|
},
|
|
onError: (error) => {
|
|
setErrorMessage('Backup failed: ' + (error as any).message);
|
|
setMessage(null);
|
|
},
|
|
|
|
// Rendered inline in JSX
|
|
{errorMessage && (
|
|
<span className="text-red-400 text-xs">{errorMessage}</span>
|
|
)}
|
|
{message && (
|
|
<span className="text-green-400 text-xs">{message}</span>
|
|
)}
|
|
```
|
|
|
|
**Affected Actions**:
|
|
- `handleBackupSubmit` - Backup operation
|
|
- `handleRestoreSubmit` - Restore operation
|
|
- `handleMetadataMatch` - Metadata matching
|
|
- `handleCacheTables` - Cache tables
|
|
|
|
**Recommended Migration**:
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
|
|
const { showInfo, showError } = useToasts();
|
|
|
|
onSuccess: () => {
|
|
showInfo('Backup completed successfully');
|
|
},
|
|
onError: (error) => {
|
|
showError('Backup failed: ' + (error as any).message);
|
|
},
|
|
|
|
// Remove these from JSX:
|
|
// - {errorMessage && <span className="text-red-400 text-xs">{errorMessage}</span>}
|
|
// - {message && <span className="text-green-400 text-xs">{message}</span>}
|
|
// Remove state variables:
|
|
// - const [message, setMessage] = useState<string | null>(null);
|
|
// - const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
```
|
|
|
|
**Impact**: HIGH - 4 API operations with error/success feedback
|
|
|
|
---
|
|
|
|
### 2. AdminUsersPage.tsx ⚠️ HIGH PRIORITY
|
|
|
|
**File**: `src/pages/AdminUsersPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
// 4 instances of alert() calls
|
|
onError: (error: any) => {
|
|
alert('Failed to create user: ' + error.message);
|
|
},
|
|
// ... similar for delete, update password, update admin status
|
|
```
|
|
|
|
**Affected Operations**:
|
|
- User creation (line ~55)
|
|
- User deletion (line ~69)
|
|
- Password update (line ~85)
|
|
- Admin status toggle (line ~101)
|
|
|
|
**Recommended Migration**:
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
|
|
const { showInfo, showError } = useToasts();
|
|
|
|
onSuccess: () => {
|
|
showInfo('User created successfully');
|
|
setShowAddForm(false);
|
|
setNewUsername('');
|
|
setNewPassword('');
|
|
setNewIsAdmin(false);
|
|
refetch();
|
|
},
|
|
onError: (error: any) => {
|
|
showError('Failed to create user: ' + error.message);
|
|
},
|
|
|
|
// Similar pattern for other operations
|
|
```
|
|
|
|
**Impact**: HIGH - Critical user management operations
|
|
|
|
---
|
|
|
|
### 3. AdminImportPage.tsx ⚠️ HIGH PRIORITY
|
|
|
|
**File**: `src/pages/AdminImportPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
onError: (error) => {
|
|
console.error('Import failed:', error);
|
|
alert('Import failed: ' + (error as any).message);
|
|
},
|
|
|
|
// No success toast - just redirects
|
|
onSuccess: (response) => {
|
|
console.log('Import completed:', response.data);
|
|
window.location.href = '/admin/import-results';
|
|
},
|
|
```
|
|
|
|
**Recommended Migration**:
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
|
|
const { showInfo, showError } = useToasts();
|
|
|
|
onSuccess: (response) => {
|
|
showInfo('Import completed successfully');
|
|
setTimeout(() => {
|
|
window.location.href = '/admin/import-results';
|
|
}, 1500);
|
|
},
|
|
onError: (error) => {
|
|
showError('Import failed: ' + (error as any).message);
|
|
},
|
|
```
|
|
|
|
**Impact**: HIGH - Long-running import operation needs user feedback
|
|
|
|
---
|
|
|
|
### 4. SettingsPage.tsx ⚠️ MEDIUM PRIORITY (TODO)
|
|
|
|
**File**: `src/pages/SettingsPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
const handlePasswordSubmit = (e: FormEvent) => {
|
|
e.preventDefault();
|
|
// TODO: Call API to change password
|
|
};
|
|
|
|
const handleTimezoneSubmit = (e: FormEvent) => {
|
|
e.preventDefault();
|
|
// TODO: Call API to change timezone
|
|
};
|
|
```
|
|
|
|
**Recommended Migration** (when API calls are implemented):
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
import { useUpdatePassword, useUpdateTimezone } from '../generated/anthoLumeAPIV1';
|
|
|
|
const { showInfo, showError } = useToasts();
|
|
const updatePassword = useUpdatePassword();
|
|
const updateTimezone = useUpdateTimezone();
|
|
|
|
const handlePasswordSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
await updatePassword.mutateAsync({
|
|
data: { password, newPassword }
|
|
});
|
|
showInfo('Password updated successfully');
|
|
setPassword('');
|
|
setNewPassword('');
|
|
} catch (error: any) {
|
|
showError('Failed to update password: ' + error.message);
|
|
}
|
|
};
|
|
|
|
const handleTimezoneSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
try {
|
|
await updateTimezone.mutateAsync({
|
|
data: { timezone }
|
|
});
|
|
showInfo('Timezone updated successfully');
|
|
} catch (error: any) {
|
|
showError('Failed to update timezone: ' + error.message);
|
|
}
|
|
};
|
|
```
|
|
|
|
**Impact**: MEDIUM - User-facing settings need feedback when implemented
|
|
|
|
---
|
|
|
|
### 5. LoginPage.tsx ⚠️ MEDIUM PRIORITY
|
|
|
|
**File**: `src/pages/LoginPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
const [error, setError] = useState('');
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
// ...
|
|
try {
|
|
await login(username, password);
|
|
} catch (err) {
|
|
setError('Invalid credentials');
|
|
}
|
|
// ...
|
|
};
|
|
|
|
// Rendered inline under password input
|
|
<span className="absolute -bottom-5 text-red-400 text-xs">{error}</span>
|
|
```
|
|
|
|
**Recommended Migration**:
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
|
|
const { showError } = useToasts();
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
e.preventDefault();
|
|
setIsLoading(true);
|
|
|
|
try {
|
|
await login(username, password);
|
|
} catch (err) {
|
|
showError('Invalid credentials');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
// Remove from JSX:
|
|
// - <span className="absolute -bottom-5 text-red-400 text-xs">{error}</span>
|
|
// Remove state:
|
|
// - const [error, setError] = useState('');
|
|
```
|
|
|
|
**Impact**: MEDIUM - Login errors are important but less frequent
|
|
|
|
---
|
|
|
|
### 6. DocumentsPage.tsx ⚠️ LOW PRIORITY
|
|
|
|
**File**: `src/pages/DocumentsPage.tsx`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
if (!file.name.endsWith('.epub')) {
|
|
alert('Please upload an EPUB file');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await createMutation.mutateAsync({
|
|
data: { document_file: file }
|
|
});
|
|
alert('Document uploaded successfully!');
|
|
setUploadMode(false);
|
|
refetch();
|
|
} catch (error) {
|
|
console.error('Upload failed:', error);
|
|
alert('Failed to upload document');
|
|
}
|
|
};
|
|
```
|
|
|
|
**Recommended Migration**:
|
|
```typescript
|
|
import { useToasts } from '../components';
|
|
|
|
const { showInfo, showWarning, showError } = useToasts();
|
|
|
|
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
if (!file.name.endsWith('.epub')) {
|
|
showWarning('Please upload an EPUB file');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await createMutation.mutateAsync({
|
|
data: { document_file: file }
|
|
});
|
|
showInfo('Document uploaded successfully!');
|
|
setUploadMode(false);
|
|
refetch();
|
|
} catch (error: any) {
|
|
showError('Failed to upload document: ' + error.message);
|
|
}
|
|
};
|
|
```
|
|
|
|
**Impact**: LOW - Upload errors are less frequent, but good UX to have toasts
|
|
|
|
---
|
|
|
|
### 7. authInterceptor.ts ⚠️ OPTIONAL ENHANCEMENT
|
|
|
|
**File**: `src/auth/authInterceptor.ts`
|
|
|
|
**Current Implementation**:
|
|
```typescript
|
|
// Response interceptor to handle auth errors
|
|
axios.interceptors.response.use(
|
|
(response) => {
|
|
return response;
|
|
},
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
// Clear token on auth failure
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
// Optionally redirect to login
|
|
// window.location.href = '/login';
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
```
|
|
|
|
**Recommended Enhancement**:
|
|
```typescript
|
|
// Add a global error handler for 401 errors
|
|
// Note: This would need access to a toast context outside React
|
|
// Could be implemented via a global toast service or event system
|
|
|
|
axios.interceptors.response.use(
|
|
(response) => {
|
|
return response;
|
|
},
|
|
(error) => {
|
|
if (error.response?.status === 401) {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
// Could dispatch a global event here to show toast
|
|
window.dispatchEvent(new CustomEvent('auth-error', {
|
|
detail: { message: 'Session expired. Please log in again.' }
|
|
}));
|
|
} else if (error.response?.status >= 500) {
|
|
// Show toast for server errors
|
|
window.dispatchEvent(new CustomEvent('api-error', {
|
|
detail: { message: 'Server error. Please try again later.' }
|
|
}));
|
|
}
|
|
return Promise.reject(error);
|
|
}
|
|
);
|
|
```
|
|
|
|
**Note**: This would require a global toast service or event system. More complex to implement.
|
|
|
|
**Impact**: LOW - Optional enhancement for global error handling
|
|
|
|
---
|
|
|
|
## Priority Matrix
|
|
|
|
| Page | Priority | Complexity | Impact | Instances |
|
|
|------|----------|------------|--------|-----------|
|
|
| AdminPage.tsx | HIGH | LOW | HIGH | 4 actions |
|
|
| AdminUsersPage.tsx | HIGH | LOW | HIGH | 4 alerts |
|
|
| AdminImportPage.tsx | HIGH | LOW | HIGH | 1 alert |
|
|
| SettingsPage.tsx | MEDIUM | MEDIUM | MEDIUM | 2 TODOs |
|
|
| LoginPage.tsx | MEDIUM | LOW | MEDIUM | 1 error |
|
|
| DocumentsPage.tsx | LOW | LOW | LOW | 2 alerts |
|
|
| authInterceptor.ts | OPTIONAL | HIGH | LOW | N/A |
|
|
|
|
---
|
|
|
|
## Implementation Plan
|
|
|
|
### Phase 1: Quick Wins (1-2 hours)
|
|
1. **AdminPage.tsx** - Replace inline messages with toasts
|
|
2. **AdminUsersPage.tsx** - Replace all `alert()` calls
|
|
3. **AdminImportPage.tsx** - Replace `alert()` and add success toast
|
|
|
|
### Phase 2: Standard Migration (1 hour)
|
|
4. **LoginPage.tsx** - Replace inline error with toast
|
|
5. **DocumentsPage.tsx** - Replace `alert()` calls
|
|
|
|
### Phase 3: Future Implementation (when ready)
|
|
6. **SettingsPage.tsx** - Add toasts when API calls are implemented
|
|
|
|
### Phase 4: Optional Enhancement (if needed)
|
|
7. **authInterceptor.ts** - Global error handling with toasts
|
|
|
|
---
|
|
|
|
## Benefits of Migration
|
|
|
|
### User Experience
|
|
- ✅ Consistent error messaging across the app
|
|
- ✅ Less intrusive than `alert()` dialogs
|
|
- ✅ Auto-dismissing notifications (no need to click to dismiss)
|
|
- ✅ Better mobile experience (no modal blocking the UI)
|
|
- ✅ Stackable notifications for multiple events
|
|
|
|
### Developer Experience
|
|
- ✅ Remove state management for error/success messages
|
|
- ✅ Cleaner, more maintainable code
|
|
- ✅ Consistent API for showing notifications
|
|
- ✅ Theme-aware styling (automatic dark/light mode support)
|
|
|
|
### Code Quality
|
|
- ✅ Remove `alert()` calls (considered an anti-pattern in modern web apps)
|
|
- ✅ Remove inline error message rendering
|
|
- ✅ Follow React best practices
|
|
- ✅ Reduce component complexity
|
|
|
|
---
|
|
|
|
## Testing Checklist
|
|
|
|
After migrating each page, verify:
|
|
- [ ] Error toasts display correctly on API failures
|
|
- [ ] Success toasts display correctly on successful operations
|
|
- [ ] Toasts appear in top-right corner
|
|
- [ ] Toasts auto-dismiss after the specified duration
|
|
- [ ] Toasts can be manually dismissed via X button
|
|
- [ ] Multiple toasts stack correctly
|
|
- [ ] Theme colors are correct in light mode
|
|
- [ ] Theme colors are correct in dark mode
|
|
- [ ] No console errors related to toast functionality
|
|
- [ ] Previous functionality still works (e.g., redirects after success)
|
|
|
|
---
|
|
|
|
## Estimated Effort
|
|
|
|
| Phase | Pages | Time Estimate |
|
|
|-------|-------|---------------|
|
|
| Phase 1 | AdminPage, AdminUsersPage, AdminImportPage | 1-2 hours |
|
|
| Phase 2 | LoginPage, DocumentsPage | 1 hour |
|
|
| Phase 3 | SettingsPage (when API ready) | 30 minutes |
|
|
| Phase 4 | authInterceptor (optional) | 1-2 hours |
|
|
| **Total** | **7 pages** | **3-5 hours** |
|
|
|
|
---
|
|
|
|
## Notes
|
|
|
|
1. **SettingsPage**: API calls are not yet implemented (TODOs). Should migrate when those are added.
|
|
|
|
2. **authInterceptor**: Global error handling would require a different approach, possibly a global event system or toast service outside React context.
|
|
|
|
3. **Redirect behavior**: Some operations (like AdminImportPage) redirect on success. Consider showing a toast first, then redirecting after a short delay for better UX.
|
|
|
|
4. **Validation messages**: Some pages have inline validation messages (like "Please upload an EPUB file"). These could remain inline or be shown as warning toasts - consider UX tradeoffs.
|
|
|
|
5. **Loading states**: Ensure loading states are still displayed appropriately alongside toasts.
|
|
|
|
6. **Refetch behavior**: Pages that call `refetch()` after successful mutations should continue to do so; toasts are additive, not replacement for data refresh.
|