'use client' import * as React from 'react' import { useRouter } from 'next/navigation' import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from '@/components/ui/command' import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog' import { Search, Star, BookOpen, Home, FileText, Code } from 'lucide-react' interface CommandMenuProps { open: boolean setOpen: (open: boolean) => void } interface SearchResult { repository_id: number repository_name: string repository_url: string description: string | null stars: number | null language: string | null topics: string | null awesome_list_name: string | null awesome_list_category: string | null snippet: string | null } interface SearchResponse { results: SearchResult[] total: number page: number pageSize: number totalPages: number } export function CommandMenu({ open, setOpen }: CommandMenuProps) { const router = useRouter() const [search, setSearch] = React.useState('') const [results, setResults] = React.useState([]) const [loading, setLoading] = React.useState(false) React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && (e.metaKey || e.ctrlKey)) { e.preventDefault() setOpen(!open) } } document.addEventListener('keydown', down) return () => document.removeEventListener('keydown', down) }, [open, setOpen]) React.useEffect(() => { // Clear results if search is empty if (!search || search.trim() === '') { setResults([]) setLoading(false) return } // Debounce search const timer = setTimeout(async () => { setLoading(true) try { // Match the search page API call with same parameters const params = new URLSearchParams({ q: search, page: '1', sortBy: 'relevance', limit: '10' // Limit to 10 results for command menu }) const response = await fetch(`/api/search?${params}`) if (!response.ok) { console.error('Search API error:', response.status, response.statusText) setResults([]) return } const data: SearchResponse = await response.json() // Check if response has error or invalid data if (!data.results) { console.error('Invalid search response:', data) setResults([]) return } console.log('Search results:', data.results.length, 'results for:', search) setResults(data.results) } catch (error) { console.error('Search error:', error) setResults([]) } finally { setLoading(false) } }, 300) return () => clearTimeout(timer) }, [search]) const runCommand = React.useCallback((command: () => void) => { setOpen(false) command() }, [setOpen]) const pages = [ { id: 'home', type: 'page', title: 'Home', url: '/', }, { id: 'browse', type: 'page', title: 'Browse Collections', url: '/browse', }, { id: 'search', type: 'page', title: 'Search', url: '/search', }, ] const getIcon = (type: string) => { switch (type) { case 'list': return case 'repo': return case 'page': return default: return } } return ( Search {loading ? (
) : (
No results found for "{search}"
)} {!search && ( {pages.map((page) => ( runCommand(() => router.push(page.url))} > {getIcon(page.type)} {page.title} ))} )} {results.length > 0 && ( {results.map((result) => ( runCommand(() => router.push(`/repository/${result.repository_id}`))} >
{result.repository_name} {result.stars !== null && ( {result.stars.toLocaleString()} )}
{result.description && ( {result.description} )}
{result.language && ( {result.language} )} {result.awesome_list_category && ( {result.awesome_list_category} )}
))}
)}
) }