diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 71a43b74..cf57d557 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -1,5 +1,6 @@ import axios, { type AxiosError, type InternalAxiosRequestConfig } from 'axios' import { useAuthStore } from '@/store/authStore' +import { toast } from '@/lib/toast' const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000' @@ -10,6 +11,44 @@ export const apiClient = axios.create({ }, }) +// Global error handler - shows toast for common API errors +// Pages can still catch errors explicitly if they need custom handling +function handleGlobalError(error: AxiosError) { + // Network error (no response from server) + if (!error.response) { + if (error.code === 'ECONNABORTED') { + toast.error('Request timeout - please try again') + } else { + toast.error('Network error - please check your connection') + } + return + } + + const status = error.response.status + const data = error.response.data as { detail?: string } + + // Don't show toast for 401 (handled by refresh interceptor) + if (status === 401) { + return + } + + // Client errors (4xx) + if (status >= 400 && status < 500) { + const message = data?.detail || 'Invalid request' + // Only show generic messages - pages handle specific errors + if (!data?.detail) { + toast.error(message) + } + return + } + + // Server errors (5xx) + if (status >= 500) { + toast.error('Server error - please try again later') + return + } +} + // Request interceptor - add auth token apiClient.interceptors.request.use( (config: InternalAxiosRequestConfig) => { @@ -48,6 +87,9 @@ apiClient.interceptors.response.use( // Only handle 401s that haven't already been retried if (error.response?.status !== 401 || originalRequest._retry) { + // Show global error toast for non-401 errors + // Pages can still catch errors explicitly for custom handling + handleGlobalError(error) return Promise.reject(error) } diff --git a/frontend/src/components/library/FolderEditModal.tsx b/frontend/src/components/library/FolderEditModal.tsx index 27d9a566..7f1c6166 100644 --- a/frontend/src/components/library/FolderEditModal.tsx +++ b/frontend/src/components/library/FolderEditModal.tsx @@ -3,6 +3,7 @@ import { X } from 'lucide-react' import { foldersApi } from '@/api' import type { FolderListItem, FolderCreate, FolderUpdate } from '@/types' import { cn } from '@/lib/utils' +import { toast } from '@/lib/toast' // Predefined color options const FOLDER_COLORS = [ @@ -66,7 +67,6 @@ export function FolderEditModal({ const [color, setColor] = useState(FOLDER_COLORS[0]) const [parentId, setParentId] = useState(null) const [isSubmitting, setIsSubmitting] = useState(false) - const [error, setError] = useState(null) const isEditMode = folder !== null @@ -127,15 +127,13 @@ export function FolderEditModal({ setColor(FOLDER_COLORS[0]) setParentId(initialParentId || null) } - setError(null) }, [folder, initialParentId, isOpen]) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - setError(null) if (!name.trim()) { - setError('Folder name is required') + toast.error('Folder name is required') return } @@ -148,12 +146,14 @@ export function FolderEditModal({ updateData.parent_id = parentId } await foldersApi.update(folder.id, updateData) + toast.success('Folder updated successfully') } else { const createData: FolderCreate = { name, color } if (parentId) { createData.parent_id = parentId } await foldersApi.create(createData) + toast.success('Folder created successfully') } onSave() onClose() @@ -163,7 +163,7 @@ export function FolderEditModal({ const errorMessage = err instanceof Error && 'response' in err ? (err as { response?: { data?: { detail?: string } } }).response?.data?.detail : undefined - setError(errorMessage || 'Failed to save folder') + toast.error(errorMessage || 'Failed to save folder') } finally { setIsSubmitting(false) } @@ -257,13 +257,6 @@ export function FolderEditModal({ - {/* Error message */} - {error && ( -
- {error} -
- )} - {/* Actions */}