'use client'; import { useEffect, useRef, useState } from 'react'; import { Counter } from './CounterCard'; export interface Group { id: number; name: string; order_index: number; } interface Props { open: boolean; initial?: Counter | null; groups: Group[]; onClose: () => void; onSave: (data: { name: string; value: number; group_id: number | null; image_path: string | null; }) => void; onDelete?: (id: number) => void; } export default function CounterModal({ open, initial, groups, onClose, onSave, onDelete }: Props) { const [confirmDelete, setConfirmDelete] = useState(false); const deleteTimer = useRef | null>(null); const [name, setName] = useState(''); const [value, setValue] = useState(0); const [groupId, setGroupId] = useState(null); const [imagePath, setImagePath] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); const [uploading, setUploading] = useState(false); const [error, setError] = useState(''); const nameRef = useRef(null); // Revoke blob URLs when previewUrl changes (prevents memory leaks) useEffect(() => { const url = previewUrl; return () => { if (url?.startsWith('blob:')) URL.revokeObjectURL(url); }; }, [previewUrl]); useEffect(() => { if (open) { setName(initial?.name ?? ''); setValue(initial?.value ?? 0); setGroupId(initial?.group_id ?? null); setImagePath(initial?.image_path ?? null); setPreviewUrl(initial?.image_path ? `/api/uploads/${initial.image_path}` : null); setError(''); setConfirmDelete(false); if (deleteTimer.current) clearTimeout(deleteTimer.current); setTimeout(() => nameRef.current?.focus(), 50); } }, [open, initial]); async function handleFileChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setPreviewUrl(URL.createObjectURL(file)); setUploading(true); try { const fd = new FormData(); fd.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body: fd }); if (!res.ok) { const body = await res.json(); setError(body.error ?? 'Upload failed'); return; } const { filename } = await res.json(); setImagePath(filename); } catch { setError('Upload failed'); } finally { setUploading(false); } } function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!name.trim()) { setError('Name is required'); return; } if (uploading) { setError('Please wait for the image to finish uploading'); return; } onSave({ name: name.trim(), value, group_id: groupId, image_path: imagePath }); } function handleBackdrop(e: React.MouseEvent) { if (e.target === e.currentTarget) onClose(); } useEffect(() => { if (!open) return; function onKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); } window.addEventListener('keydown', onKeyDown); return () => window.removeEventListener('keydown', onKeyDown); }, [open, onClose]); if (!open) return null; return (

{initial ? 'Edit Counter' : 'New Counter'}

{error && (

{error}

)} {/* Name */}
setName(e.target.value)} className="w-full rounded-lg border border-ctp-surface1 bg-ctp-surface0 text-ctp-text px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ctp-mauve" placeholder="Counter name" />
{/* Initial value */}
setValue(Number(e.target.value))} className="w-full rounded-lg border border-ctp-surface1 bg-ctp-surface0 text-ctp-text px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ctp-mauve" />
{/* Group */}
{/* Image upload */}
{previewUrl && (
{/* eslint-disable-next-line @next/next/no-img-element */} Preview
)}
{/* Delete — only shown when editing an existing counter */} {initial && onDelete ? ( ) : }
); }