From 7fb8b075b88e0daea565e5426fe09d1231af0ccf Mon Sep 17 00:00:00 2001 From: Asep Haryana Saputra <90584806+MythEclipse@users.noreply.github.com> Date: Fri, 22 May 2026 17:07:18 +0000 Subject: [PATCH] fix: clean up dashboard upload state - Prevent object URL leaks in image-classification-form by revoking previous preview URL before replacing it, revoking after successful submit, and revoking on component unmount - Improve preview image alt text from generic "Preview" to descriptive Indonesian text "Pratinjau gambar daun jagung untuk diagnosis" - Use auth store setter consistently in dashboard-page logout instead of direct setState call Co-Authored-By: Claude Opus 4.7 --- .../components/image-classification-form.tsx | 25 +++++++++++++++++-- apps/web/src/pages/dashboard-page.tsx | 3 ++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/image-classification-form.tsx b/apps/web/src/components/image-classification-form.tsx index e55b914..1f73ffb 100644 --- a/apps/web/src/components/image-classification-form.tsx +++ b/apps/web/src/components/image-classification-form.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, FormEvent, useRef, useState } from 'react'; +import { ChangeEvent, FormEvent, useRef, useState, useEffect } from 'react'; import type { DiagnosisRecord } from '@zeavis/shared'; import { Upload } from 'lucide-react'; import { Button } from '@/components/ui/button'; @@ -21,10 +21,25 @@ export function ImageClassificationForm({ const [previewUrl, setPreviewUrl] = useState(null); const [error, setError] = useState(null); + // Revoke object URL on component unmount + useEffect(() => { + return () => { + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + }; + }, []); + function handleFileChange(event: ChangeEvent) { const selectedFile = event.target.files?.[0] ?? null; setError(null); setFile(selectedFile); + + // Revoke previous preview URL before replacing it + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + setPreviewUrl(selectedFile ? URL.createObjectURL(selectedFile) : null); } @@ -37,6 +52,12 @@ export function ImageClassificationForm({ try { await onSubmit(file); + + // Revoke current preview URL after successful submit before setting null + if (previewUrl) { + URL.revokeObjectURL(previewUrl); + } + setFile(null); setPreviewUrl(null); if (fileInputRef.current) { @@ -80,7 +101,7 @@ export function ImageClassificationForm({ {previewUrl && ( - Preview + Pratinjau gambar daun jagung untuk diagnosis )} {error &&

{error}

} diff --git a/apps/web/src/pages/dashboard-page.tsx b/apps/web/src/pages/dashboard-page.tsx index a352735..68748ab 100644 --- a/apps/web/src/pages/dashboard-page.tsx +++ b/apps/web/src/pages/dashboard-page.tsx @@ -12,6 +12,7 @@ import { apiClient } from '@/lib/api-client'; export function DashboardPage() { const { user } = useAuthStore(); + const setUser = useAuthStore((state) => state.setUser); const { dashboardCompact, toggleDashboardCompact } = useUiStore(); const queryClient = useQueryClient(); const navigate = useNavigate(); @@ -62,7 +63,7 @@ export function DashboardPage() { await apiClient.logout(); }, onSuccess: () => { - useAuthStore.setState({ user: null }); + setUser(null); queryClient.clear(); navigate('/login'); },