diff --git a/frontend/src/components/ui/ApiError.tsx b/frontend/src/components/ui/ApiError.tsx
new file mode 100644
index 0000000..3aca085
--- /dev/null
+++ b/frontend/src/components/ui/ApiError.tsx
@@ -0,0 +1,29 @@
+import { AlertCircle, X } from "lucide-react";
+
+
+interface ApiErrorProps {
+ message: string | null;
+ onDismiss: () => void;
+}
+
+export function ApiError({ message, onDismiss }: ApiErrorProps) {
+ if (!message) return null;
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/NewAnalysis.tsx b/frontend/src/pages/NewAnalysis.tsx
index a670bc8..bb23acf 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 },
@@ -44,6 +45,7 @@ export default function NewAnalysis() {
const [busy, setBusy] = useState(false)
const [resultRun, setResultRun] = useState(null)
const [resultPayload, setResultPayload] = useState | null>(null)
+ const [error, setError] = useState(null)
const canSubmit = bbox !== null && startDate && endDate && !busy
@@ -62,13 +64,16 @@ export default function NewAnalysis() {
return
}
- setBusy(true)
+
setResultRun(null)
setResultPayload(null)
try {
+ setBusy(true);
+ setError(null);
const res = await predictJson({ kind: 'bbox', analysis_type: analysisType, bbox: bbox!, start_date: startDate, end_date: endDate })
setResultPayload(res.result)
+
// Construct a minimal Run object for the results panel
setResultRun({
id: res.run_id,
@@ -85,8 +90,9 @@ export default function NewAnalysis() {
label: 'View in history',
onClick: () => navigate('/runs'),
})
- } catch (e) {
- showToast('error', String(e))
+ } catch (e:any) {
+ const message = e?.response?.data?.detail || e?.message || "Something went wrong";
+ setError(message);
} finally {
setBusy(false)
}
@@ -94,7 +100,8 @@ export default function NewAnalysis() {
return (
-
+
setError(null)} />
+
{/* Step 1 — Analysis Type */}
diff --git a/frontend/src/pages/Upload.tsx b/frontend/src/pages/Upload.tsx
index 5107689..7ba2be5 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
@@ -31,6 +32,7 @@ export default function Upload() {
const [startDate, setStartDate] = useState('')
const [endDate, setEndDate] = useState('')
const [busy, setBusy] = useState(false)
+ const [error, setError] = useState(null)
const [uploadProgress, setUploadProgress] = useState(null)
const fileInputRef = useRef(null)
@@ -57,6 +59,7 @@ export default function Upload() {
const handleUpload = async () => {
if (!file) return
setBusy(true)
+ setError(null);
setUploadProgress(0)
try {
@@ -75,9 +78,10 @@ export default function Upload() {
})
setFile(null)
setUploadProgress(null)
- } catch (e) {
- showToast('error', String(e))
- setUploadProgress(null)
+ } catch (e:any) {
+ const message = e?.response?.data?.detail || e?.message || "Upload failed";
+ setError(message);
+ setUploadProgress(null);
} finally {
setBusy(false)
}
@@ -85,7 +89,7 @@ export default function Upload() {
return (
-
+
setError(null)} />
{/* Drop Zone */}
{ e.preventDefault(); setDragging(true) }}