|
'use client'; |
|
|
|
import { useState, useEffect, useCallback, useMemo } from 'react'; |
|
import { Header } from '@/components/Header'; |
|
import { ProductCard } from '@/components/ProductCard'; |
|
import { ProductFilters } from '@/components/ProductFilters'; |
|
import { AiSuggestion } from '@/components/AiSuggestion'; |
|
import { Input } from '@/components/ui/input'; |
|
import { Button } from '@/components/ui/button'; |
|
import { Search, SlidersHorizontal, Loader2 } from 'lucide-react'; |
|
import { searchProducts } from '@/services/product-api'; |
|
import type { Product } from '@/lib/types'; |
|
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; |
|
|
|
export default function Home() { |
|
const [searchQuery, setSearchQuery] = useState(''); |
|
const [debouncedSearchQuery, setDebouncedSearchQuery] = useState(''); |
|
const [products, setProducts] = useState<Product[]>([]); |
|
const [isLoading, setIsLoading] = useState(false); |
|
|
|
const [priceRange, setPriceRange] = useState<{ min: number | undefined, max: number | undefined }>({ min: undefined, max: undefined }); |
|
const [minRating, setMinRating] = useState<number>(0); |
|
const [country, setCountry] = useState('US'); |
|
const [language, setLanguage] = useState('en'); |
|
|
|
|
|
useEffect(() => { |
|
const handler = setTimeout(() => { |
|
setDebouncedSearchQuery(searchQuery); |
|
}, 300); |
|
|
|
return () => { |
|
clearTimeout(handler); |
|
}; |
|
}, [searchQuery]); |
|
|
|
|
|
useEffect(() => { |
|
const fetchProducts = async () => { |
|
if (!debouncedSearchQuery) { |
|
setProducts([]); |
|
setIsLoading(false); |
|
return; |
|
} |
|
|
|
setIsLoading(true); |
|
try { |
|
const results = await searchProducts(debouncedSearchQuery, { priceRange, minRating }); |
|
setProducts(results); |
|
} catch (error) { |
|
console.error("Failed to fetch products:", error); |
|
setProducts([]); |
|
} finally { |
|
setIsLoading(false); |
|
} |
|
}; |
|
|
|
fetchProducts(); |
|
}, [debouncedSearchQuery, priceRange, minRating]); |
|
|
|
const onFiltersChange = useCallback( |
|
(newFilters: { |
|
priceRange: { min: number | undefined; max: number | undefined }; |
|
minRating: number; |
|
}) => { |
|
setPriceRange(newFilters.priceRange); |
|
setMinRating(newFilters.minRating); |
|
}, |
|
[] |
|
); |
|
|
|
const filtersComponent = ( |
|
<ProductFilters |
|
priceRange={priceRange} |
|
minRating={minRating} |
|
onFiltersChange={onFiltersChange} |
|
/> |
|
); |
|
|
|
const aiFilters = useMemo(() => ({ |
|
price: priceRange, |
|
rating: minRating, |
|
country, |
|
language, |
|
}), [priceRange, minRating, country, language]); |
|
|
|
return ( |
|
<div className="flex min-h-screen w-full flex-col bg-background"> |
|
<Header |
|
showControls={true} |
|
country={country} |
|
onCountryChange={setCountry} |
|
language={language} |
|
onLanguageChange={setLanguage} |
|
/> |
|
<main className="flex-1"> |
|
<div className="container mx-auto grid flex-1 gap-8 px-4 py-8 md:grid-cols-[280px_1fr]"> |
|
<aside className="hidden md:block">{filtersComponent}</aside> |
|
<div className="flex flex-col gap-8"> |
|
<div className="flex flex-col gap-4 sm:flex-row"> |
|
<div className="relative flex-1"> |
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-muted-foreground" /> |
|
<Input |
|
type="search" |
|
placeholder="Search for products... AI will suggest as you type" |
|
className="w-full rounded-full bg-white pl-10" |
|
value={searchQuery} |
|
onChange={(e) => setSearchQuery(e.target.value)} |
|
/> |
|
</div> |
|
<div className="md:hidden"> |
|
<Sheet> |
|
<SheetTrigger asChild> |
|
<Button variant="outline" size="icon" className="rounded-full"> |
|
<SlidersHorizontal className="h-5 w-5" /> |
|
<span className="sr-only">Open filters</span> |
|
</Button> |
|
</SheetTrigger> |
|
<SheetContent side="left" className="w-[300px] sm:w-[400px]"> |
|
{filtersComponent} |
|
</SheetContent> |
|
</Sheet> |
|
</div> |
|
</div> |
|
|
|
<AiSuggestion |
|
searchQuery={debouncedSearchQuery} |
|
filters={aiFilters} |
|
externalProductData={JSON.stringify(products)} |
|
/> |
|
|
|
{isLoading ? ( |
|
<div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-muted-foreground/30 bg-muted/20 py-20 text-center"> |
|
<Loader2 className="h-10 w-10 animate-spin text-primary" /> |
|
<h3 className="mt-4 text-xl font-semibold tracking-tight text-muted-foreground"> |
|
Searching for products... |
|
</h3> |
|
</div> |
|
) : products.length > 0 ? ( |
|
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"> |
|
{products.map((product) => ( |
|
<ProductCard key={product.id} product={product} /> |
|
))} |
|
</div> |
|
) : null} |
|
</div> |
|
</div> |
|
</main> |
|
</div> |
|
); |
|
} |
|
|