"use client"; import { useState, useRef, useEffect, useCallback } from "react"; import Image from "next/image"; import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useToast } from "@/components/ui/use-toast"; import { Upload, X, Loader2, Pencil, Check, LayoutTemplate } from "lucide-react"; interface SketchTemplate { id: string; name: string; fileUrl: string; fileName: string; sortOrder: number; } export function SketchTemplatesSection() { const { toast } = useToast(); const fileInputRef = useRef(null); const [templates, setTemplates] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [pendingName, setPendingName] = useState(""); const [editingId, setEditingId] = useState(null); const [editName, setEditName] = useState(""); const fetchTemplates = useCallback(async () => { try { const res = await fetch("/api/sketch-templates"); if (res.ok) { const data = await res.json(); setTemplates(data.templates); } } finally { setLoading(false); } }, []); useEffect(() => { fetchTemplates(); }, [fetchTemplates]); const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; setUploading(true); try { const fd = new FormData(); fd.append("file", file); if (pendingName.trim()) fd.append("name", pendingName.trim()); const res = await fetch("/api/sketch-templates", { method: "POST", body: fd }); if (!res.ok) { const err = await res.json(); throw new Error(err.error ?? "Upload failed"); } const { template } = await res.json(); setTemplates((prev) => [...prev, template]); setPendingName(""); } catch (err) { toast({ title: "Upload failed", description: err instanceof Error ? err.message : undefined, variant: "destructive" }); } finally { setUploading(false); if (fileInputRef.current) fileInputRef.current.value = ""; } }; const handleDelete = async (id: string) => { try { const res = await fetch(`/api/sketch-templates/${id}`, { method: "DELETE" }); if (!res.ok) throw new Error("Delete failed"); setTemplates((prev) => prev.filter((t) => t.id !== id)); } catch { toast({ title: "Failed to delete template", variant: "destructive" }); } }; const startEdit = (t: SketchTemplate) => { setEditingId(t.id); setEditName(t.name); }; const saveEdit = async (id: string) => { if (!editName.trim()) return; try { const res = await fetch(`/api/sketch-templates/${id}`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: editName }), }); if (!res.ok) throw new Error("Rename failed"); setTemplates((prev) => prev.map((t) => t.id === id ? { ...t, name: editName.trim() } : t)); } catch { toast({ title: "Failed to rename template", variant: "destructive" }); } finally { setEditingId(null); } }; return (
Sketch Templates

Upload background images (body diagrams, scene layouts, storyboard frames, etc.) that artists can load as a canvas background when sketching on a take.

{loading ? (
Loading…
) : ( <> {/* Template grid */} {templates.length > 0 && (
{templates.map((t) => (
{t.name}
{editingId === t.id ? ( <> setEditName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") saveEdit(t.id); if (e.key === "Escape") setEditingId(null); }} className="h-6 text-xs px-1 bg-zinc-800 border-zinc-600" autoFocus /> ) : ( <> {t.name} )}
))}
)} {/* Upload */}
setPendingName(e.target.value)} className="h-8 text-sm max-w-[200px]" />
{templates.length === 0 && (

No templates yet.

)} )}
); }