diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 536ef8a..14dd9f8 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -64,15 +64,27 @@ client.interceptors.response.use( } } - // Extract error message from response + // Extract error message — detail can be a string or a structured object + const rawDetail = error.response?.data?.detail; const message = - error.response?.data?.detail || - error.message || - "An unexpected error occurred"; + typeof rawDetail === "string" + ? rawDetail + : typeof rawDetail === "object" && rawDetail !== null && (rawDetail as { message?: string }).message + ? (rawDetail as { message: string }).message + : error.message || "An unexpected error occurred"; - const enhancedError = new Error(message); - (enhancedError as Error & { status?: number }).status = status; - (enhancedError as Error & { code?: string }).code = error.response?.data?.code; + const enhancedError = new Error(message) as Error & { + status?: number; + code?: string; + detail?: unknown; + }; + enhancedError.status = status; + // Preserve the full detail object so consumers can inspect it (e.g. for modal flows) + enhancedError.detail = rawDetail; + enhancedError.code = + typeof rawDetail === "object" && rawDetail !== null + ? (rawDetail as { code?: string }).code + : error.response?.data?.code; return Promise.reject(enhancedError); }, diff --git a/frontend/src/pages/CampaignDetailPage.tsx b/frontend/src/pages/CampaignDetailPage.tsx index 3a9e4e4..41f9af9 100644 --- a/frontend/src/pages/CampaignDetailPage.tsx +++ b/frontend/src/pages/CampaignDetailPage.tsx @@ -98,29 +98,23 @@ export default function CampaignDetailPage() { showToast("Campaign activated", "success"); }, onError: (err: unknown) => { - // FastAPI wraps error bodies as: { detail: string | object } - type AxiosLike = { - response?: { - status?: number; - data?: { detail?: { code?: string; message?: string } | string }; - }; + // The Axios interceptor (client.ts) transforms errors into enhanced Error objects + // with .status (HTTP status), .detail (raw FastAPI detail), and .message (readable string) + type EnhancedError = Error & { + status?: number; + detail?: { code?: string; message?: string } | string; }; - const axiosErr = err as AxiosLike; - const status = axiosErr?.response?.status; - const detail = axiosErr?.response?.data?.detail; + const e = err as EnhancedError; - if (status === 409 && detail && typeof detail === "object" && detail.message) { - // Future start_date → show confirmation modal - setStartDateWarning(detail.message); + if (e.status === 409) { + // Future start_date — show confirmation modal using the message from detail + const warningMsg = + typeof e.detail === "object" && e.detail?.message + ? e.detail.message + : e.message; + setStartDateWarning(warningMsg); } else { - // Any other error → extract readable message from FastAPI detail - const msg = - typeof detail === "string" - ? detail - : typeof detail === "object" && detail?.message - ? detail.message - : "Failed to activate campaign"; - showToast(msg, "error"); + showToast(e.message || "Failed to activate campaign", "error"); } }, });