"use client" import { ArrowDownAZ, ArrowUpZA, Calendar, Filter, Search, SortAsc, X } from "lucide-react" import { usePathname, useRouter, useSearchParams } from "next/navigation" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { IconsGrid } from "@/components/icon-grid" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Card, CardContent } from "@/components/ui/card" import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { filterAndSortIcons, type SortOption } from "@/lib/utils" import type { IconWithName } from "@/types/icons" type IconWithStatus = IconWithName & { status: string } interface CommunityIconSearchProps { icons: IconWithStatus[] } const getStatusColor = (status: string) => { switch (status) { case "approved": return "bg-blue-500/10 text-blue-400 font-bold border-blue-500/20" case "rejected": return "bg-red-500/10 text-red-500 border-red-500/20" case "pending": return "bg-yellow-500/10 text-yellow-500 border-yellow-500/20" case "added_to_collection": return "bg-green-500/10 text-green-500 border-green-500/20" default: return "bg-gray-500/10 text-gray-500 border-gray-500/20" } } const getStatusDisplayName = (status: string) => { switch (status) { case "pending": return "Pending Review" case "approved": return "Approved" case "rejected": return "Rejected" case "added_to_collection": return "Added to Collection" default: return status } } export function CommunityIconSearch({ icons }: CommunityIconSearchProps) { const searchParams = useSearchParams() const initialQuery = searchParams.get("q") const initialCategories = searchParams.getAll("category") const initialSort = (searchParams.get("sort") as SortOption) || "relevance" const router = useRouter() const pathname = usePathname() const [searchQuery, setSearchQuery] = useState(initialQuery ?? "") const [debouncedQuery, setDebouncedQuery] = useState(initialQuery ?? "") const [selectedCategories, setSelectedCategories] = useState(initialCategories ?? []) const [sortOption, setSortOption] = useState(initialSort) const timeoutRef = useRef(null) useEffect(() => { const timer = setTimeout(() => { setDebouncedQuery(searchQuery) }, 200) return () => clearTimeout(timer) }, [searchQuery]) const allCategories = useMemo(() => { const categories = new Set() for (const icon of icons) { for (const category of icon.data.categories) { categories.add(category) } } return Array.from(categories).sort() }, [icons]) const matchedAliases = useMemo(() => { if (!searchQuery.trim()) return {} const q = searchQuery.toLowerCase() const matches: Record = {} for (const { name, data } of icons) { if (!name.toLowerCase().includes(q)) { const matchingAlias = data.aliases.find((alias) => alias.toLowerCase().includes(q)) if (matchingAlias) { matches[name] = matchingAlias } } } return matches }, [icons, searchQuery]) const filteredIcons = useMemo(() => { const result = filterAndSortIcons({ icons, query: debouncedQuery, categories: selectedCategories, sort: sortOption, }) as IconWithStatus[] return result }, [icons, debouncedQuery, selectedCategories, sortOption]) const groupedIcons = useMemo(() => { const statusPriority = { pending: 0, approved: 1, rejected: 2, added_to_collection: 3 } const groups: Record = {} for (const icon of filteredIcons) { const iconWithStatus = icon as IconWithStatus const status = iconWithStatus.status || 'pending' if (!groups[status]) { groups[status] = [] } groups[status].push(iconWithStatus) } return Object.entries(groups) .sort(([a], [b]) => { return (statusPriority[a as keyof typeof statusPriority] ?? 999) - (statusPriority[b as keyof typeof statusPriority] ?? 999) }) .map(([status, items]) => ({ status, items })) }, [filteredIcons]) const updateResults = useCallback( (query: string, categories: string[], sort: SortOption) => { const params = new URLSearchParams() if (query) params.set("q", query) for (const category of categories) { params.append("category", category) } if (sort !== "relevance" || initialSort !== "relevance") { params.set("sort", sort) } const newUrl = params.toString() ? `${pathname}?${params.toString()}` : pathname router.push(newUrl, { scroll: false }) }, [pathname, router, initialSort], ) const handleSearch = useCallback( (query: string) => { setSearchQuery(query) if (timeoutRef.current) { clearTimeout(timeoutRef.current) } timeoutRef.current = setTimeout(() => { updateResults(query, selectedCategories, sortOption) }, 200) }, [updateResults, selectedCategories, sortOption], ) const handleCategoryChange = useCallback( (category: string) => { let newCategories: string[] if (selectedCategories.includes(category)) { newCategories = selectedCategories.filter((c) => c !== category) } else { newCategories = [...selectedCategories, category] } setSelectedCategories(newCategories) updateResults(searchQuery, newCategories, sortOption) }, [updateResults, searchQuery, selectedCategories, sortOption], ) const handleSortChange = useCallback( (sort: SortOption) => { setSortOption(sort) updateResults(searchQuery, selectedCategories, sort) }, [updateResults, searchQuery, selectedCategories], ) const clearFilters = useCallback(() => { setSearchQuery("") setSelectedCategories([]) setSortOption("relevance") updateResults("", [], "relevance") }, [updateResults]) useEffect(() => { return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current) } } }, []) if (!searchParams) return null const getSortLabel = (sort: SortOption) => { switch (sort) { case "relevance": return "Best match" case "alphabetical-asc": return "A to Z" case "alphabetical-desc": return "Z to A" case "newest": return "Newest first" default: return "Sort" } } const getSortIcon = (sort: SortOption) => { switch (sort) { case "relevance": return case "alphabetical-asc": return case "alphabetical-desc": return case "newest": return default: return } } return ( <>
handleSearch(e.target.value)} />
Select Categories
{allCategories.map((category) => ( handleCategoryChange(category)} className="cursor-pointer capitalize" > {category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} ))}
{selectedCategories.length > 0 && ( <> { setSelectedCategories([]) updateResults(searchQuery, [], sortOption) }} className="cursor-pointer focus:bg-rose-50 dark:focus:bg-rose-950/20" > Clear categories )}
Sort By handleSortChange(value as SortOption)}> Relevance Name (A-Z) Name (Z-A) Newest first {(searchQuery || selectedCategories.length > 0 || sortOption !== "relevance") && ( )}
{selectedCategories.length > 0 && (
Selected:
{selectedCategories.map((category) => ( {category.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())} ))}
)}
{filteredIcons.length === 0 ? (

No icons found

Try adjusting your search or filters

) : (

Found {filteredIcons.length} icon {filteredIcons.length !== 1 ? "s" : ""}.

{getSortIcon(sortOption)} {getSortLabel(sortOption)}
{groupedIcons.map(({ status, items }) => (
{getStatusDisplayName(status)} {items.length} {items.length === 1 ? 'icon' : 'icons'}
))}
)} ) }