"use client"; import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { Search } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { analytics } from "@/lib/analytics"; import { getXuluxTemplateAnalyticsId, useXuluxAnalytics, withXuluxContext, } from "@/lib/xulux/analytics-context"; import { cn } from "@/lib/utils"; import type { XuluxTemplate } from "../templates/types"; import { useXuluxTemplateCatalog } from "../templates/useXuluxTemplateCatalog"; import { TemplateDetailModal } from "./TemplateDetailModal"; import { Thumbnail } from "./Thumbnail"; type Props = { open: boolean; onOpenChange: (open: boolean) => void; openSurface: "landing_carousel" | "header"; initialCategoryId?: string | null | undefined; onSelect: (template: XuluxTemplate) => void; }; export function TemplatesModal({ open, onOpenChange, openSurface, initialCategoryId, onSelect, }: Props) { const analyticsCtx = useXuluxAnalytics(); const { categories, templates, isLoading, error } = useXuluxTemplateCatalog(); const [query, setQuery] = useState(""); const [activeCategory, setActiveCategory] = useState("all"); const [detailTemplate, setDetailTemplate] = useState( null, ); const openTrackedRef = useRef(false); useEffect(() => { if (open) { setActiveCategory(initialCategoryId ?? "all"); setQuery(""); } }, [open, initialCategoryId]); useEffect(() => { if (!open) { openTrackedRef.current = false; return; } if (openTrackedRef.current) return; openTrackedRef.current = true; analytics.xulux.templatesOpened( withXuluxContext(analyticsCtx, { surface: openSurface }), ); }, [analyticsCtx, open, openSurface]); const filtered = useMemo(() => { const q = query.trim().toLowerCase(); return templates.filter((template) => { if (activeCategory !== "all" && template.categoryId !== activeCategory) { return false; } if (!q) return true; return ( template.title.toLowerCase().includes(q) || template.description.toLowerCase().includes(q) || template.tags.some((tag) => tag.toLowerCase().includes(q)) ); }); }, [activeCategory, query, templates]); return ( <> Templates
setQuery(event.target.value)} placeholder="Search templates..." className="pl-9" />
setActiveCategory("all")} > All {categories.map((category) => ( setActiveCategory(category.id)} > {category.name} ))}
{isLoading ? (
Loading templates...
) : error ? (
Catalog unavailable
{error}
) : filtered.length === 0 ? (
{query ? `No templates match "${query}".` : "No templates available."}
) : (
{filtered.map((template) => ( ))}
)}
setDetailTemplate(null)} onSelect={(template) => { setDetailTemplate(null); onOpenChange(false); onSelect(template); }} /> ); } function CategoryChip({ active, onClick, children, }: { active: boolean; onClick: () => void; children: ReactNode; }) { return ( ); }