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 */}
+
)}
diff --git a/frontend/src/pages/Upload.tsx b/frontend/src/pages/Upload.tsx
index 5107689..e446861 100644
--- a/frontend/src/pages/Upload.tsx
+++ b/frontend/src/pages/Upload.tsx
@@ -8,6 +8,7 @@ import { MapBBoxPicker } from '../components/Map/MapBBoxPicker'
import { ErrorBoundary } from '../components/ui/ErrorBoundary'
import { useToast } from '../contexts/ToastContext'
import { useApp } from '../contexts/AppContext'
+import { ApiError } from '../components/ui/ApiError'
const ACCEPTED = ['.tif', '.tiff', '.geotiff', '.nc', '.hdf5']
const MAX_MB = 500
@@ -18,6 +19,7 @@ function formatBytes(bytes: number) {
}
export default function Upload() {
+ const [error, setError] = useState(null)
const { showToast } = useToast()
const { googleMapsApiKey } = useApp()
const navigate = useNavigate()
@@ -55,10 +57,10 @@ export default function Upload() {
}, [])
const handleUpload = async () => {
- if (!file) return
+ if (!file) return
setBusy(true)
setUploadProgress(0)
-
+ setError(null)
try {
const res = await predictUpload({
file,
@@ -75,17 +77,20 @@ export default function Upload() {
})
setFile(null)
setUploadProgress(null)
- } catch (e) {
- showToast('error', String(e))
- setUploadProgress(null)
- } finally {
+ } catch (e: any) {
+ const message = e?.response?.data?.message || e?.message || 'Upload failed'
+
+ setError(message)
+ showToast('error', message)
+
+ setUploadProgress(null)}
+ finally {
setBusy(false)
}
}
return (
-
{/* Drop Zone */}
{ e.preventDefault(); setDragging(true) }}
@@ -186,9 +191,10 @@ export default function Upload() {
{/* Upload button */}
+ {error &&
}