'use client' import { useState, useCallback, useEffect } from 'react' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Link2, Plus, X } from 'lucide-react' import { toast } from 'sonner' import { invalidateReference } from '@/lib/reference-data' interface Props { open: boolean onClose: () => void onCreated: () => void } export function AddBookmarkDialog({ open, onClose, onCreated }: Props) { const [url, setUrl] = useState('') const [title, setTitle] = useState('') const [description, setDescription] = useState('') const [notes, setNotes] = useState('') const [dueDate, setDueDate] = useState('') const [kanban, setKanban] = useState('INBOX') const [loading, setLoading] = useState(false) const [tags, setTags] = useState<{ id: string; name: string; color: string }[]>([]) const [allTags, setAllTags] = useState([]) const [newTagName, setNewTagName] = useState('') useEffect(() => { if (open) { fetch('/api/tags').then(r => r.json()).then(setAllTags).catch(() => {}) const timer = setTimeout(() => { setUrl(''); setTitle(''); setDescription(''); setNotes(''); setDueDate(''); setKanban('INBOX'); setTags([]) }, 0) return () => clearTimeout(timer) } }, [open]) // Review M-10: no client-side preview fetch. The server fetches preview // metadata once during create (and fills title/description defaults), so a // blur followed by Save no longer spends the preview budget twice. const handleSubmit = useCallback(async () => { if (!url) { toast.error('URL is required'); return } setLoading(true) try { const res = await fetch('/api/bookmarks', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url, title: title || undefined, description: description || undefined, notes: notes || undefined, dueDate: dueDate || undefined, kanbanStatus: kanban, tagIds: tags.map(t => t.id), }), }) if (res.ok) { toast.success('Bookmark created') onCreated() } else { const data = await res.json().catch(() => ({})) toast.error(data?.error ?? 'Failed') } } catch { toast.error('Error creating bookmark') } setLoading(false) }, [url, title, description, notes, dueDate, kanban, tags, onCreated]) const createTag = useCallback(async () => { if (!newTagName) return try { const res = await fetch('/api/tags', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: newTagName }), }) if (res.ok) { const tag = await res.json() setTags(prev => [...prev, { id: tag.id, name: tag.name, color: tag.color }]) setAllTags(prev => [...prev, tag]) setNewTagName('') invalidateReference('tags') } } catch { /* ignore */ } }, [newTagName]) return ( onClose()}> Add Bookmark
) => setUrl(e.target.value)} />
) => setTitle(e.target.value)} placeholder="Auto-filled from URL" />
) => setDescription(e.target.value)} />