From 2c5a48674981ff9900920bb21c8294f39c92b049 Mon Sep 17 00:00:00 2001 From: liaakin Date: Fri, 8 May 2026 18:10:12 +0200 Subject: [PATCH] Add reusable ApiError component for prediction and upload failures --- frontend/src/api.ts | 1 + frontend/src/components/ui/ApiError.tsx | 31 +++++++++++++++++++++++++ frontend/src/pages/NewAnalysis.tsx | 18 +++++++++----- frontend/src/pages/Upload.tsx | 22 +++++++++++------- 4 files changed, 58 insertions(+), 14 deletions(-) create mode 100644 frontend/src/components/ui/ApiError.tsx diff --git a/frontend/src/api.ts b/frontend/src/api.ts index e72eb07..5991563 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -190,6 +190,7 @@ export async function predictJson(payload: PredictJsonRequest): Promise<{ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) + if (!res.ok) throw new Error('Prediction failed') return res.json() } diff --git a/frontend/src/components/ui/ApiError.tsx b/frontend/src/components/ui/ApiError.tsx new file mode 100644 index 0000000..bc0b5ee --- /dev/null +++ b/frontend/src/components/ui/ApiError.tsx @@ -0,0 +1,31 @@ +// frontend/src/components/ui/ApiError.tsx +import { AlertCircle, X } from "lucide-react"; +import { useState } from "react"; + +type ApiErrorProps = { + message: string; +}; + +export function ApiError({ message }: ApiErrorProps) { + const [visible, setVisible] = useState(true); + + if (!visible) return null; + + return ( +
+ + + +
+ {message} +
+ + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/NewAnalysis.tsx b/frontend/src/pages/NewAnalysis.tsx index a670bc8..0898b45 100644 --- a/frontend/src/pages/NewAnalysis.tsx +++ b/frontend/src/pages/NewAnalysis.tsx @@ -10,6 +10,7 @@ import { ErrorBoundary } from '../components/ui/ErrorBoundary' import { useToast } from '../contexts/ToastContext' import { useApp } from '../contexts/AppContext' import type { Run } from '../api' +import { ApiError } from '../components/ui/ApiError' const PRESETS = [ { label: 'Last 30d', days: 30 }, @@ -33,6 +34,7 @@ function SectionLabel({ step, label }: { step: number; label: string }) { } export default function NewAnalysis() { + const [error, setError] = useState(null) const { showToast } = useToast() const { googleMapsApiKey } = useApp() const navigate = useNavigate() @@ -56,12 +58,12 @@ export default function NewAnalysis() { } const handleSubmit = async () => { - if (!canSubmit) return + if (!canSubmit) return if (startDate > endDate) { showToast('error', 'Start date must be before end date.') return } - + setError(null) setBusy(true) setResultRun(null) setResultPayload(null) @@ -85,9 +87,12 @@ export default function NewAnalysis() { label: 'View in history', onClick: () => navigate('/runs'), }) - } catch (e) { - showToast('error', String(e)) - } finally { + } catch (e: any) { + const message = e?.response?.data?.message || e?.message || 'Prediction failed' + setError(message) + showToast('error', message) + } + finally { setBusy(false) } } @@ -147,7 +152,7 @@ export default function NewAnalysis() { - + {error && ()} {/* Submit */}