Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/ui/ApiError.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="flex bg-red-950/90 items-start justify-between overflow-hidden gap-3 rounded-xl sm:text-xs md:text-base border border-red-500/70 p-3 text-white/80">

<AlertCircle className="mt-0.5 h-5 w-5 shrink-0" />

<div className="flex-1 text-sm break-words">
{message}
</div>

<button
onClick={() => setVisible(false)}
className="text-white/50 hover:text-red-900"
>
<X className="h-4 w-4" />
</button>
</div>
);
}
18 changes: 12 additions & 6 deletions frontend/src/pages/NewAnalysis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -33,6 +34,7 @@ function SectionLabel({ step, label }: { step: number; label: string }) {
}

export default function NewAnalysis() {
const [error, setError] = useState<string | null>(null)
const { showToast } = useToast()
const { googleMapsApiKey } = useApp()
const navigate = useNavigate()
Expand All @@ -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)
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -147,7 +152,7 @@ export default function NewAnalysis() {
</div>
</div>
</section>

{error && (<ApiError message={error}/>)}
{/* Submit */}
<button
onClick={handleSubmit}
Expand Down Expand Up @@ -185,6 +190,7 @@ export default function NewAnalysis() {
setResultPayload(null)
}}
/>

</ErrorBoundary>
</section>
)}
Expand Down
22 changes: 14 additions & 8 deletions frontend/src/pages/Upload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,6 +19,7 @@ function formatBytes(bytes: number) {
}

export default function Upload() {
const [error, setError] = useState<string | null>(null)
const { showToast } = useToast()
const { googleMapsApiKey } = useApp()
const navigate = useNavigate()
Expand Down Expand Up @@ -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,
Expand All @@ -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 (
<div className="max-w-3xl mx-auto px-6 py-8 space-y-6">

{/* Drop Zone */}
<div
onDragOver={(e) => { e.preventDefault(); setDragging(true) }}
Expand Down Expand Up @@ -186,9 +191,10 @@ export default function Upload() {
</div>

{/* Upload button */}
{error && <ApiError message={error} />}
<button
onClick={handleUpload}
disabled={!file || busy}
g disabled={!file || busy}
className="w-full h-12 rounded-xl font-semibold text-sm flex items-center justify-center gap-2 transition-all
bg-cv-primary text-white hover:bg-cv-primary-hover disabled:opacity-40 disabled:cursor-not-allowed shadow-glow"
>
Expand Down