Seed: ecommerce template (24 files)
This commit is contained in:
@@ -0,0 +1,137 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Search as SearchIcon, Star, Heart, X, Sparkles } from "lucide-react";
|
||||||
|
import Header from "@/components/Header";
|
||||||
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
|
const allProducts = [
|
||||||
|
{ id: 1, name: "Classic Leather Jacket", price: 189, gradient: "from-rose-100 to-stone-200", rating: 5, category: "Outerwear" },
|
||||||
|
{ id: 2, name: "Silk Evening Dress", price: 245, gradient: "from-amber-100 to-orange-200", rating: 5, category: "Dresses" },
|
||||||
|
{ id: 3, name: "Cashmere Knit Sweater", price: 128, gradient: "from-stone-100 to-stone-300", rating: 4, category: "Knitwear" },
|
||||||
|
{ id: 4, name: "Linen Wide-Leg Trousers", price: 95, gradient: "from-rose-50 to-amber-100", rating: 4, category: "Bottoms" },
|
||||||
|
{ id: 5, name: "Italian Leather Handbag", price: 320, gradient: "from-stone-200 to-rose-100", rating: 5, category: "Accessories" },
|
||||||
|
{ id: 6, name: "Merino Wool Blazer", price: 165, gradient: "from-amber-50 to-stone-200", rating: 4, category: "Outerwear" },
|
||||||
|
{ id: 7, name: "Organic Cotton Tee", price: 45, gradient: "from-rose-100 to-rose-200", rating: 4, category: "Tops" },
|
||||||
|
{ id: 8, name: "Velvet Midi Skirt", price: 115, gradient: "from-stone-300 to-stone-100", rating: 5, category: "Bottoms" },
|
||||||
|
{ id: 9, name: "Suede Ankle Boots", price: 198, gradient: "from-amber-200 to-stone-200", rating: 5, category: "Footwear" },
|
||||||
|
{ id: 10, name: "Satin Blouse", price: 89, gradient: "from-rose-50 to-rose-100", rating: 4, category: "Tops" },
|
||||||
|
{ id: 11, name: "Tailored Wool Coat", price: 385, gradient: "from-stone-200 to-stone-400", rating: 5, category: "Outerwear" },
|
||||||
|
{ id: 12, name: "Pearl Drop Earrings", price: 68, gradient: "from-amber-100 to-rose-100", rating: 4, category: "Accessories" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterChips = ["All", "Outerwear", "Dresses", "Tops", "Bottoms", "Accessories", "Footwear", "Knitwear"];
|
||||||
|
|
||||||
|
export default function SearchPage() {
|
||||||
|
const [query, setQuery] = useState("");
|
||||||
|
const [activeFilter, setActiveFilter] = useState("All");
|
||||||
|
|
||||||
|
const filtered = allProducts.filter((p) => {
|
||||||
|
const matchesQuery = query.length === 0 || p.name.toLowerCase().includes(query.toLowerCase());
|
||||||
|
const matchesFilter = activeFilter === "All" || p.category === activeFilter;
|
||||||
|
return matchesQuery && matchesFilter;
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderStars = (count: number) =>
|
||||||
|
Array.from({ length: 5 }, (_, i) => (
|
||||||
|
<Star key={i} className={`h-3 w-3 \${i < count ? "fill-amber-400 text-amber-400" : "text-stone-300"}`} />
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-white text-stone-900 antialiased">
|
||||||
|
<Header />
|
||||||
|
|
||||||
|
<div className="mx-auto max-w-7xl px-6 pt-28 pb-20">
|
||||||
|
{/* Search Input */}
|
||||||
|
<div className="max-w-2xl mx-auto mb-10">
|
||||||
|
<div className="relative">
|
||||||
|
<SearchIcon className="absolute left-4 top-1/2 -translate-y-1/2 h-5 w-5 text-stone-400" />
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search for products..."
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
autoFocus
|
||||||
|
className="pl-12 pr-10 h-14 text-lg rounded-full border-stone-200 focus-visible:ring-rose-500 focus-visible:ring-2 focus-visible:border-rose-400 transition-shadow"
|
||||||
|
/>
|
||||||
|
{query && (
|
||||||
|
<button onClick={() => setQuery("")} className="absolute right-4 top-1/2 -translate-y-1/2 text-stone-400 hover:text-stone-600">
|
||||||
|
<X className="h-5 w-5" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Results heading */}
|
||||||
|
{query && (
|
||||||
|
<h2 className="text-xl font-bold mb-6">
|
||||||
|
{filtered.length} result{filtered.length !== 1 ? "s" : ""} for "<span className="text-rose-600">{query}</span>"
|
||||||
|
</h2>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Filter Chips */}
|
||||||
|
<div className="flex flex-wrap gap-2 mb-8">
|
||||||
|
{filterChips.map((chip) => (
|
||||||
|
<Button
|
||||||
|
key={chip}
|
||||||
|
variant={activeFilter === chip ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
className={`rounded-full text-sm \${activeFilter === chip ? "bg-stone-900" : ""}`}
|
||||||
|
onClick={() => setActiveFilter(chip)}
|
||||||
|
>
|
||||||
|
{chip}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Results */}
|
||||||
|
{filtered.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 stagger">
|
||||||
|
{filtered.map((product) => (
|
||||||
|
<Link to="/product" key={product.id} className="group">
|
||||||
|
<Card className="overflow-hidden border-0 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<div className={`relative aspect-[3/4] bg-gradient-to-br \${product.gradient} overflow-hidden`}>
|
||||||
|
<div className="absolute inset-0" style={{ backgroundImage: "radial-gradient(circle, rgba(0,0,0,0.06) 1px, transparent 1px)", backgroundSize: "16px 16px" }} />
|
||||||
|
<div className="absolute top-1/4 -left-8 h-24 w-24 rounded-full bg-white/30 blur-2xl" />
|
||||||
|
<div className="absolute bottom-1/4 -right-8 h-20 w-20 rounded-full bg-white/20 blur-2xl" />
|
||||||
|
<div className="absolute inset-0 flex items-center justify-center text-stone-400 text-xs">Image</div>
|
||||||
|
<button className="absolute top-2 right-2 h-7 w-7 rounded-full bg-white/80 backdrop-blur-sm flex items-center justify-center opacity-0 group-hover:opacity-100 transition-all hover:scale-110 active:scale-95">
|
||||||
|
<Heart className="h-3.5 w-3.5 text-stone-600" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<CardContent className="p-3">
|
||||||
|
<h3 className="font-medium text-xs truncate">{product.name}</h3>
|
||||||
|
<div className="flex items-center gap-0.5 mt-1">{renderStars(product.rating)}</div>
|
||||||
|
<p className="mt-1.5 font-semibold text-sm">\${product.price}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-20">
|
||||||
|
<div className="h-16 w-16 mx-auto rounded-full bg-stone-100 flex items-center justify-center mb-4">
|
||||||
|
<SearchIcon className="h-8 w-8 text-stone-400" />
|
||||||
|
</div>
|
||||||
|
<h3 className="text-xl font-bold mb-2">No results found</h3>
|
||||||
|
<p className="text-stone-500 mb-6 max-w-md mx-auto">
|
||||||
|
We couldn't find anything matching your search. Try different keywords or browse our categories.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
|
<Badge variant="secondary" className="cursor-pointer hover:bg-stone-200">Summer Dresses</Badge>
|
||||||
|
<Badge variant="secondary" className="cursor-pointer hover:bg-stone-200">Leather Jackets</Badge>
|
||||||
|
<Badge variant="secondary" className="cursor-pointer hover:bg-stone-200">Cashmere</Badge>
|
||||||
|
<Badge variant="secondary" className="cursor-pointer hover:bg-stone-200">Accessories</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user