import type { CategoryInfo, Tool } from "@snapotter/shared"; import { ANALYTICS_EVENTS, CATEGORIES, TOOLS } from "@snapotter/shared"; import { Eye, EyeOff, FileImage, LayoutGrid, List, Search } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { OtterLogo } from "@/components/common/otter-logo"; import { track } from "@/lib/analytics"; import { apiGet } from "@/lib/api"; import { ICON_MAP } from "@/lib/icon-map"; import { cn } from "@/lib/utils"; import { useFeaturesStore } from "@/stores/features-store"; export function FullscreenGridPage() { const [search, setSearch] = useState(""); const [showDetails, setShowDetails] = useState(true); const navigate = useNavigate(); const [disabledTools, setDisabledTools] = useState([]); const [experimentalEnabled, setExperimentalEnabled] = useState(false); const fetchFeatures = useFeaturesStore((s) => s.fetch); useEffect(() => { fetchFeatures(); }, [fetchFeatures]); useEffect(() => { apiGet<{ settings: Record }>("/v1/settings") .then((data) => { setDisabledTools( data.settings.disabledTools ? JSON.parse(data.settings.disabledTools) : [], ); setExperimentalEnabled(data.settings.enableExperimentalTools === "true"); }) .catch(() => {}); }, []); const visibleTools = useMemo(() => { return TOOLS.filter((t) => { if (disabledTools.includes(t.id)) return false; if (t.experimental && !experimentalEnabled) return false; return true; }); }, [disabledTools, experimentalEnabled]); const filteredTools = useMemo(() => { if (!search) return visibleTools; const q = search.toLowerCase(); return visibleTools.filter( (t) => t.name.toLowerCase().includes(q) || t.description.toLowerCase().includes(q) || t.category.toLowerCase().includes(q), ); }, [search, visibleTools]); useEffect(() => { if (!search) return; const timer = setTimeout(() => { track(ANALYTICS_EVENTS.SEARCH, { query: search, results_count: filteredTools.length, }); }, 1000); return () => clearTimeout(timer); }, [search, filteredTools.length]); const groupedTools = useMemo(() => { const groups = new Map(); for (const tool of filteredTools) { const list = groups.get(tool.category) || []; list.push(tool); groups.set(tool.category, list); } return groups; }, [filteredTools]); const activeCategories = CATEGORIES.filter((cat) => groupedTools.has(cat.id)); return (
{/* Top bar */}
SnapOtter {/* Search */}
setSearch(e.target.value)} placeholder="Search tools..." className="w-full pl-10 pr-4 py-2 rounded-lg border border-border bg-background text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20" />
{/* Toggle details */} {/* Switch to sidebar view */}
{/* Grid */}
{activeCategories.length === 0 ? (

No tools found

Try a different search term

) : (
{activeCategories.map((category) => ( ))}
)}
); } function CategoryCard({ category, tools, showDetails, }: { category: CategoryInfo; tools: Tool[]; showDetails: boolean; }) { const CategoryIcon = (ICON_MAP[category.icon] as React.ComponentType<{ className?: string }>) ?? LayoutGrid; return (
{/* Header */}

{category.name}

{tools.length}
{/* Tool list */}
{tools.map((tool) => { const ToolIcon = (ICON_MAP[tool.icon] as React.ComponentType<{ className?: string }>) ?? FileImage; return (

{tool.name}

{showDetails && (

{tool.description}

)}
{tool.experimental && ( Experimental )} ); })}
); }