1"use client";
2
3
8import { useEffect, useState } from "react";
9import { Wand2 } from "lucide-react";
10import { templateApi } from "@/lib/api";
11import { toast } from "@/stores/ui-store";
12import type { TemplateInfo } from "@/lib/types";
13
14export interface TemplateDialogProps {
15 workspaceId: string;
16 onClose: () => void;
17
18 onApply: (templateId: string, title: string) => Promise<void>;
19}
20
21export function TemplateDialog({ workspaceId, onClose, onApply }: TemplateDialogProps) {
22 const [templates, setTemplates] = useState<TemplateInfo[]>([]);
23 const [loading, setLoading] = useState(true);
24 const [busyId, setBusyId] = useState<string | null>(null);
25
26 useEffect(() => {
27 if (!workspaceId) {
28 setLoading(false);
29 return;
30 }
31 templateApi
32 .list(workspaceId)
33 .then(setTemplates)
34 .catch(() => setTemplates([]))
35 .finally(() => setLoading(false));
36 }, [workspaceId]);
37
38 useEffect(() => {
39 const onKey = (e: KeyboardEvent) => {
40 if (e.key === "Escape") onClose();
41 };
42 window.addEventListener("keydown", onKey);
43 return () => window.removeEventListener("keydown", onKey);
44 }, [onClose]);
45
46 const apply = async (t: TemplateInfo) => {
47 setBusyId(t.id);
48 try {
49 await onApply(t.id, t.name || "Untitled");
50 } catch (err) {
51 toast("error", "Could not insert template", err instanceof Error ? err.message : undefined);
52 } finally {
53 setBusyId(null);
54 }
55 };
56
57 return (
58 <div className="fixed inset-0 z-[85] flex items-center justify-center p-4">
59 <div className="absolute inset-0 bg-black/40" onClick={onClose} aria-hidden />
60 <div
61 role="dialog"
62 aria-modal="true"
63 aria-label="Insert template"
64 className="relative w-full max-w-md rounded-lg border border-border bg-card shadow-2xl"
65 >
66 <div className="flex items-center justify-between border-b border-border px-4 py-3">
67 <h2 className="flex items-center gap-2 text-base font-semibold">
68 <Wand2 className="size-4 text-muted-foreground" aria-hidden />
69 Insert template
70 </h2>
71 <button
72 type="button"
73 onClick={onClose}
74 className="rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground"
75 aria-label="Close template picker"
76 >
77 ✕
78 </button>
79 </div>
80 <div className="max-h-80 overflow-y-auto p-2">
81 {loading ? (
82 <p className="px-2 py-4 text-sm text-muted-foreground">Loading…</p>
83 ) : templates.length === 0 ? (
84 <p className="px-2 py-4 text-sm text-muted-foreground">
85 No templates yet — save one from the Templates page.
86 </p>
87 ) : (
88 <ul className="space-y-0.5">
89 {templates.map((t) => (
90 <li key={t.id}>
91 <button
92 type="button"
93 disabled={busyId !== null}
94 onClick={() => void apply(t)}
95 className="flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm hover:bg-accent disabled:opacity-50"
96 >
97 <span className="text-lg leading-none" aria-hidden>
98 {t.icon ?? "📄"}
99 </span>
100 <span className="min-w-0 flex-1 truncate font-medium">{t.name || "Untitled"}</span>
101 {busyId === t.id ? <span className="text-xs text-muted-foreground">Applying…</span> : null}
102 </button>
103 </li>
104 ))}
105 </ul>
106 )}
107 </div>
108 </div>
109 </div>
110 );
111}
112