'use client'; import { useState, useEffect, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; import { motion, AnimatePresence } from 'framer-motion'; import { Search, TrendingUp, Clock, X, Command, Store } from 'lucide-react'; import { api, formatPrice, type Product } from '@/lib/api'; /** * Spotlight Search — premium search experience. * * - Glassmorphic blur backdrop (eliminates visual noise) * - Staggered product polaroids that scale in dynamically * - Cmd+K / Ctrl+K keyboard shortcut (desktop) * - Keyboard navigation (arrow keys + enter) * - Recent + trending searches when empty * - Product image previews as user types */ const TRENDING = ['Smart Watch', 'Wireless Earbuds', 'Ankara Dress', 'Phone Charger', 'Football', 'Cookware Set']; const CATEGORIES = ['Electronics', 'Fashion', 'Home', 'Beauty', 'Farm Fresh', 'Sports', 'Food', 'Toys', 'Books']; const QUICK_ACTIONS = [ { label: 'Flash Deals', href: '/categories?sort=flash', icon: '🔥' }, { label: 'Trending Now', href: '/categories?sort=trending', icon: '📈' }, { label: 'Live Shopping', href: '/live', icon: '🔴' }, { label: 'AI Assistant', href: '/ai-chat', icon: '✨' }, ]; export function SpotlightSearch({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) { const router = useRouter(); const [query, setQuery] = useState(''); const [products, setProducts] = useState([]); const [activeIndex, setActiveIndex] = useState(-1); const [recent, setRecent] = useState([]); const inputRef = useRef(null); const debounceRef = useRef(null); useEffect(() => { try { const stored = localStorage.getItem('cellex_recent_searches'); if (stored) setRecent(JSON.parse(stored).slice(0, 5)); } catch {} }, []); useEffect(() => { if (isOpen) { setTimeout(() => inputRef.current?.focus(), 100); } else { setQuery(''); setProducts([]); setActiveIndex(-1); } }, [isOpen]); // Search products with debounce useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); if (!query.trim()) { setProducts([]); return; } debounceRef.current = setTimeout(async () => { const result = await api.products.search(query, null); if (result.success) { setProducts((result.results || result.products || []).slice(0, 6)); } }, 200); }, [query]); // Keyboard shortcut useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === 'k') { e.preventDefault(); if (!isOpen) { // Trigger open via custom event window.dispatchEvent(new CustomEvent('open-spotlight')); } } if (e.key === 'Escape' && isOpen) { onClose(); } }; window.addEventListener('keydown', handler); return () => window.removeEventListener('keydown', handler); }, [isOpen, onClose]); const saveRecent = (q: string) => { const updated = [q, ...recent.filter(r => r !== q)].slice(0, 5); setRecent(updated); try { localStorage.setItem('cellex_recent_searches', JSON.stringify(updated)); } catch {} }; const go = (href: string, label?: string) => { if (label) saveRecent(label); onClose(); router.push(href); }; const handleKeyDown = (e: React.KeyboardEvent) => { const items = getAllItems(); if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIndex(i => Math.min(i + 1, items.length - 1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIndex(i => Math.max(i - 1, -1)); } else if (e.key === 'Enter') { if (activeIndex >= 0 && items[activeIndex]) { go(items[activeIndex].href, items[activeIndex].label); } else if (query.trim()) { go(`/search?q=${encodeURIComponent(query.trim())}`, query.trim()); } } }; const getAllItems = () => { const items: { label: string; href: string; type: string }[] = []; if (!query.trim()) { QUICK_ACTIONS.forEach(a => items.push({ label: a.label, href: a.href, type: 'quick' })); TRENDING.forEach(s => items.push({ label: s, href: `/search?q=${encodeURIComponent(s)}`, type: 'trending' })); CATEGORIES.slice(0, 4).forEach(c => items.push({ label: c, href: `/categories?category=${c}`, type: 'category' })); } else { items.push({ label: query, href: `/search?q=${encodeURIComponent(query)}`, type: 'search' }); CATEGORIES.forEach(c => { if (c.toLowerCase().includes(query.toLowerCase())) { items.push({ label: c, href: `/categories?category=${c}`, type: 'category' }); } }); } return items; }; const allItems = getAllItems(); let itemIndex = -1; return ( {isOpen && ( <> {/* Glassmorphic backdrop */} {/* Search panel */}
{/* Input */}
{ setQuery(e.target.value); setActiveIndex(-1); }} onKeyDown={handleKeyDown} placeholder="Search for products, categories..." className="flex-1 bg-transparent outline-none text-lg text-white placeholder:text-slate-500" /> {query ? ( ) : ( K )}
{/* Results */}
{/* Product polaroids (when searching) */} {query.trim() && products.length > 0 && (
Products
{products.map((p, i) => ( go(`/product?id=${p.id}`)} className="text-left group" >
{p.image_url ? ( {p.name} ) : (
)}
{p.name}
{formatPrice(p.price)}
))}
)} {/* Recent searches (when empty) */} {!query.trim() && recent.length > 0 && (
Recent
{recent.map((r, i) => { itemIndex++; const isActive = activeIndex === itemIndex; return ( go(`/search?q=${encodeURIComponent(r)}`, r)} onMouseEnter={() => setActiveIndex(itemIndex)} className={`w-full flex items-center gap-3 px-2 py-2.5 rounded-xl transition-colors ${ isActive ? 'bg-indigo-600' : 'hover:bg-white/5' }`} > {r} ); })}
)} {/* Trending / Quick actions / Categories */} {allItems.map((item, i) => { const realIndex = query.trim() ? i + (products.length > 0 ? -products.length : 0) : i + (recent.length > 0 ? recent.length : 0); const isActive = activeIndex === i; const isQuick = item.type === 'quick'; const isTrending = item.type === 'trending'; const isCategory = item.type === 'category'; const quickAction = QUICK_ACTIONS.find(a => a.label === item.label); return ( go(item.href, item.label)} onMouseEnter={() => setActiveIndex(i)} className={`w-full flex items-center gap-3 px-2 py-2.5 rounded-xl transition-colors ${ isActive ? 'bg-indigo-600' : 'hover:bg-white/5' }`} >
{isQuick && quickAction?.icon ? ( {quickAction.icon} ) : isTrending ? ( ) : isCategory ? ( 📂 ) : ( )}
{item.label}
{isQuick ? 'Quick action' : isTrending ? 'Trending' : isCategory ? 'Category' : 'Search'}
); })}
{/* Footer */}
↑↓ Navigate Select Esc Close
Cellex Search
)}
); } /** Hook to manage spotlight open/close state + listen for Cmd+K */ export function useSpotlight() { const [isOpen, setIsOpen] = useState(false); useEffect(() => { const handler = () => setIsOpen(true); window.addEventListener('open-spotlight', handler); return () => window.removeEventListener('open-spotlight', handler); }, []); return { isOpen, setIsOpen }; }