13 KiB
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:
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 operationhandleRestoreSubmit- Restore operationhandleMetadataMatch- Metadata matchinghandleCacheTables- Cache tables
Recommended Migration:
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:
// 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:
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:
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:
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:
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):
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:
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:
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:
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:
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:
// 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:
// 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)
- AdminPage.tsx - Replace inline messages with toasts
- AdminUsersPage.tsx - Replace all
alert()calls - AdminImportPage.tsx - Replace
alert()and add success toast
Phase 2: Standard Migration (1 hour)
- LoginPage.tsx - Replace inline error with toast
- DocumentsPage.tsx - Replace
alert()calls
Phase 3: Future Implementation (when ready)
- SettingsPage.tsx - Add toasts when API calls are implemented
Phase 4: Optional Enhancement (if needed)
- 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
-
SettingsPage: API calls are not yet implemented (TODOs). Should migrate when those are added.
-
authInterceptor: Global error handling would require a different approach, possibly a global event system or toast service outside React context.
-
Redirect behavior: Some operations (like AdminImportPage) redirect on success. Consider showing a toast first, then redirecting after a short delay for better UX.
-
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.
-
Loading states: Ensure loading states are still displayed appropriately alongside toasts.
-
Refetch behavior: Pages that call
refetch()after successful mutations should continue to do so; toasts are additive, not replacement for data refresh.