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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
1b5c16b135
commit
7fb8b075b8
@@ -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 type { DiagnosisRecord } from '@zeavis/shared';
|
||||||
import { Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -21,10 +21,25 @@ export function ImageClassificationForm({
|
|||||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Revoke object URL on component unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (previewUrl) {
|
||||||
|
URL.revokeObjectURL(previewUrl);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
|
function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
const selectedFile = event.target.files?.[0] ?? null;
|
const selectedFile = event.target.files?.[0] ?? null;
|
||||||
setError(null);
|
setError(null);
|
||||||
setFile(selectedFile);
|
setFile(selectedFile);
|
||||||
|
|
||||||
|
// Revoke previous preview URL before replacing it
|
||||||
|
if (previewUrl) {
|
||||||
|
URL.revokeObjectURL(previewUrl);
|
||||||
|
}
|
||||||
|
|
||||||
setPreviewUrl(selectedFile ? URL.createObjectURL(selectedFile) : null);
|
setPreviewUrl(selectedFile ? URL.createObjectURL(selectedFile) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +52,12 @@ export function ImageClassificationForm({
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await onSubmit(file);
|
await onSubmit(file);
|
||||||
|
|
||||||
|
// Revoke current preview URL after successful submit before setting null
|
||||||
|
if (previewUrl) {
|
||||||
|
URL.revokeObjectURL(previewUrl);
|
||||||
|
}
|
||||||
|
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setPreviewUrl(null);
|
setPreviewUrl(null);
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) {
|
||||||
@@ -80,7 +101,7 @@ export function ImageClassificationForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{previewUrl && (
|
{previewUrl && (
|
||||||
<img src={previewUrl} alt="Preview" className="h-48 rounded-lg object-cover" />
|
<img src={previewUrl} alt="Pratinjau gambar daun jagung untuk diagnosis" className="h-48 rounded-lg object-cover" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{error && <p className="text-sm text-red-600">{error}</p>}
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { apiClient } from '@/lib/api-client';
|
|||||||
|
|
||||||
export function DashboardPage() {
|
export function DashboardPage() {
|
||||||
const { user } = useAuthStore();
|
const { user } = useAuthStore();
|
||||||
|
const setUser = useAuthStore((state) => state.setUser);
|
||||||
const { dashboardCompact, toggleDashboardCompact } = useUiStore();
|
const { dashboardCompact, toggleDashboardCompact } = useUiStore();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -62,7 +63,7 @@ export function DashboardPage() {
|
|||||||
await apiClient.logout();
|
await apiClient.logout();
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
useAuthStore.setState({ user: null });
|
setUser(null);
|
||||||
queryClient.clear();
|
queryClient.clear();
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user