'use client' import { useEffect, useState, useCallback } from 'react' import { BookMarked, Check, Globe, GlobeLock, MoreHorizontal, Pencil, Plus, Save, Trash2, Users, X } from 'lucide-react' import { emitter } from '@pascal-app/core' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/primitives/dropdown-menu' import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/primitives/popover' import { useAuth } from '@/features/community/lib/auth/hooks' import { cn } from '@/lib/utils' export type PresetType = 'door' | 'window' export interface PresetData { id: string type: string name: string data: Record thumbnail_url: string | null user_id: string | null is_community: boolean created_at: string } type Tab = 'community' | 'mine' interface PresetsPopoverProps { type: PresetType /** Apply preset data to the current node */ onApply: (data: Record) => void /** Save current node state as a new preset with the given name */ onSave: (name: string) => Promise /** Overwrite an existing preset's data with the current node state */ onOverwrite: (id: string) => Promise children: React.ReactNode } export function PresetsPopover({ type, onApply, onSave, onOverwrite, children }: PresetsPopoverProps) { const { isAuthenticated } = useAuth() const [open, setOpen] = useState(false) const [tab, setTab] = useState('community') const [presets, setPresets] = useState([]) const [loading, setLoading] = useState(false) // New preset save state const [showSaveInput, setShowSaveInput] = useState(false) const [saveName, setSaveName] = useState('') const [saving, setSaving] = useState(false) // Rename state const [renamingId, setRenamingId] = useState(null) const [renameValue, setRenameValue] = useState('') // Delete confirmation const [deletingId, setDeletingId] = useState(null) // Overwrite feedback (shows check icon briefly after overwrite) const [overwrittenId, setOverwrittenId] = useState(null) const fetchPresets = useCallback(async () => { setLoading(true) try { const res = await fetch(`/api/presets?type=${type}&tab=${tab}`) if (res.ok) { const json = await res.json() setPresets(json.presets ?? []) } } finally { setLoading(false) } }, [type, tab]) useEffect(() => { if (open) fetchPresets() }, [open, fetchPresets]) useEffect(() => { if (!isAuthenticated && tab === 'mine') setTab('community') }, [isAuthenticated, tab]) useEffect(() => { const handler = ({ presetId, thumbnailUrl }: { presetId: string; thumbnailUrl: string }) => { setPresets((prev) => prev.map((p) => (p.id === presetId ? { ...p, thumbnail_url: thumbnailUrl } : p)), ) } emitter.on('preset:thumbnail-updated', handler) return () => emitter.off('preset:thumbnail-updated', handler) }, []) const handleSaveNew = async () => { if (!saveName.trim()) return setSaving(true) try { await onSave(saveName.trim()) setSaveName('') setShowSaveInput(false) if (tab === 'mine') fetchPresets() else setTab('mine') } finally { setSaving(false) } } const handleRename = async (id: string) => { if (!renameValue.trim()) return const res = await fetch(`/api/presets/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: renameValue.trim() }), }) if (res.ok) { setPresets((prev) => prev.map((p) => (p.id === id ? { ...p, name: renameValue.trim() } : p))) setRenamingId(null) } } const handleDelete = async (id: string) => { const res = await fetch(`/api/presets/${id}`, { method: 'DELETE' }) if (res.ok) { setPresets((prev) => prev.filter((p) => p.id !== id)) setDeletingId(null) } } const handleOverwrite = async (id: string) => { await onOverwrite(id) setOverwrittenId(id) setTimeout(() => setOverwrittenId(null), 1500) } const handleToggleCommunity = async (id: string, current: boolean) => { const res = await fetch(`/api/presets/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ is_community: !current }), }) if (res.ok) { setPresets((prev) => prev.map((p) => (p.id === id ? { ...p, is_community: !current } : p))) } } return ( {children} {/* Header */}
{type === 'door' ? 'Door' : 'Window'} Presets
{isAuthenticated && ( )}
{/* New preset name input */} {showSaveInput && (
setSaveName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSaveNew() if (e.key === 'Escape') { setShowSaveInput(false); setSaveName('') } }} placeholder="Preset name…" className="flex-1 min-w-0 rounded-md border border-border/50 bg-background/50 px-2 py-1 text-xs text-foreground placeholder:text-muted-foreground/60 outline-none focus:border-ring focus:ring-1 focus:ring-ring/30" />
)} {/* Tabs */}
setTab('community')}> Community { if (isAuthenticated) setTab('mine') }} disabled={!isAuthenticated} > My presets
{/* Content */}
{loading ? (
) : presets.length === 0 ? ( ) : (
    {presets.map((preset) => ( { onApply(preset.data); setOpen(false) }} onOverwrite={() => handleOverwrite(preset.id)} onToggleCommunity={() => handleToggleCommunity(preset.id, preset.is_community)} onStartRename={() => { setRenamingId(preset.id); setRenameValue(preset.name) }} onRenameChange={setRenameValue} onRenameConfirm={() => handleRename(preset.id)} onRenameCancel={() => setRenamingId(null)} onDeleteRequest={() => setDeletingId(preset.id)} onDeleteConfirm={() => handleDelete(preset.id)} onDeleteCancel={() => setDeletingId(null)} /> ))}
)}
) } function TabButton({ active, onClick, disabled, children, }: { active: boolean onClick: () => void disabled?: boolean children: React.ReactNode }) { return ( ) } function EmptyState({ tab, isAuthenticated }: { tab: Tab; isAuthenticated: boolean }) { return (

{tab === 'community' ? 'No community presets yet.' : isAuthenticated ? 'No presets saved yet. Use "Save new" to save the current configuration.' : 'Sign in to save and view your presets.'}

) } interface PresetRowProps { preset: PresetData isMine: boolean renamingId: string | null renameValue: string deletingId: string | null overwrittenId: string | null onApply: () => void onOverwrite: () => void onToggleCommunity: () => void onStartRename: () => void onRenameChange: (v: string) => void onRenameConfirm: () => void onRenameCancel: () => void onDeleteRequest: () => void onDeleteConfirm: () => void onDeleteCancel: () => void } function PresetRow({ preset, isMine, renamingId, renameValue, deletingId, overwrittenId, onApply, onOverwrite, onToggleCommunity, onStartRename, onRenameChange, onRenameConfirm, onRenameCancel, onDeleteRequest, onDeleteConfirm, onDeleteCancel, }: PresetRowProps) { const isRenaming = renamingId === preset.id const isDeleting = deletingId === preset.id const justOverwritten = overwrittenId === preset.id if (isDeleting) { return (
  • Delete "{preset.name}"?
  • ) } if (isRenaming) { return (
  • onRenameChange(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') onRenameConfirm() if (e.key === 'Escape') onRenameCancel() }} className="flex-1 min-w-0 rounded-md border border-border/50 bg-background/50 px-2 py-1 text-xs text-foreground outline-none focus:border-ring focus:ring-1 focus:ring-ring/30" />
  • ) } return (
  • {/* Thumbnail */}
    {preset.thumbnail_url ? ( // eslint-disable-next-line @next/next/no-img-element {preset.name} ) : (
    )}
    {/* Name + date — clicking applies */} {/* Actions — 3-dot dropdown, only in "My presets" */} {isMine && ( Update with current {preset.is_community ? <>Remove from community : <>Share with community} Rename Delete )}
  • ) }