Files
hot-sauce-store/src/pages/ProductDetail.tsx
T
2026-05-21 12:19:54 +01:00

308 lines
16 KiB
TypeScript

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 { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { Separator } from "@/components/ui/separator";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
Star, Heart, Minus, Plus, ChevronRight, Truck, RotateCcw, ShieldCheck, ShoppingBag, Flame, ZoomIn
} from "lucide-react";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import { useCart } from "@/lib/cart-context";
const thumbnails = [
"from-rose-100 to-stone-200",
"from-stone-100 to-rose-100",
"from-amber-100 to-stone-100",
"from-stone-200 to-amber-100",
];
const colors = [
{ name: "Black", class: "bg-stone-900" },
{ name: "Cognac", class: "bg-amber-700" },
{ name: "Rose", class: "bg-rose-400" },
{ name: "Stone", class: "bg-stone-400" },
];
const sizes = ["XS", "S", "M", "L", "XL", "XXL"];
const reviews = [
{ name: "Sarah M.", date: "2 weeks ago", rating: 5, text: "Absolutely stunning quality. The leather is soft yet durable and the fit is perfect. Worth every penny!", avatar: "SM" },
{ name: "James K.", date: "1 month ago", rating: 4, text: "Great jacket overall. Runs slightly large so consider sizing down. The color is rich and beautiful.", avatar: "JK" },
{ name: "Emily R.", date: "1 month ago", rating: 5, text: "This is my third purchase from StyleHaus and they never disappoint. The craftsmanship is exceptional.", avatar: "ER" },
];
const relatedProducts = [
{ id: 10, name: "Satin Blouse", price: 89, gradient: "from-rose-50 to-rose-100", rating: 4 },
{ id: 11, name: "Tailored Wool Coat", price: 385, gradient: "from-stone-200 to-stone-400", rating: 5 },
{ id: 7, name: "Organic Cotton Tee", price: 45, gradient: "from-rose-100 to-rose-200", rating: 4 },
{ id: 8, name: "Velvet Midi Skirt", price: 115, gradient: "from-stone-300 to-stone-100", rating: 5 },
];
export default function ProductDetailPage() {
const [selectedImage, setSelectedImage] = useState(0);
const [selectedColor, setSelectedColor] = useState("Black");
const [selectedSize, setSelectedSize] = useState("M");
const [quantity, setQuantity] = useState(1);
const [wishlisted, setWishlisted] = useState(false);
const { addItem } = useCart();
const renderStars = (count: number) =>
Array.from({ length: 5 }, (_, i) => (
<Star key={i} className={\`h-4 w-4 \${i < count ? "fill-amber-400 text-amber-400" : "text-stone-300"}\`} />
));
const handleAddToCart = () => {
addItem({
id: "leather-jacket-1",
name: "Classic Leather Jacket",
price: 189,
quantity,
size: selectedSize,
color: selectedColor,
image: "",
});
toast.success("Added to cart!");
};
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">
{/* Breadcrumb */}
<nav className="flex items-center gap-2 text-sm text-stone-500 mb-8">
<Link to="/" className="hover:text-stone-900">Home</Link>
<ChevronRight className="h-3 w-3" />
<Link to="/products" className="hover:text-stone-900">Products</Link>
<ChevronRight className="h-3 w-3" />
<span className="text-stone-900">Classic Leather Jacket</span>
</nav>
{/* Product Layout */}
<div className="grid lg:grid-cols-2 gap-12">
{/* Left — Images */}
<div className="space-y-4">
<Dialog>
<DialogTrigger asChild>
<div className={\`aspect-square rounded-2xl bg-gradient-to-br \${thumbnails[selectedImage]} flex items-center justify-center text-stone-400 relative overflow-hidden animate-scale-in cursor-zoom-in group/zoom\`}>
<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-12 h-32 w-32 rounded-full bg-white/30 blur-2xl" />
<div className="absolute bottom-1/4 -right-12 h-28 w-28 rounded-full bg-white/20 blur-2xl" />
<span className="relative">Product Image</span>
<div className="absolute bottom-3 right-3 h-8 w-8 rounded-full bg-white/80 backdrop-blur flex items-center justify-center opacity-0 group-hover/zoom:opacity-100 transition-opacity shadow-sm">
<ZoomIn className="h-4 w-4 text-stone-700" />
</div>
</div>
</DialogTrigger>
<DialogContent className="max-w-4xl p-0 border-0 overflow-hidden">
<div className={\`w-full aspect-square bg-gradient-to-br \${thumbnails[selectedImage]} flex items-center justify-center text-stone-400 relative 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-12 h-32 w-32 rounded-full bg-white/30 blur-2xl" />
<div className="absolute bottom-1/4 -right-12 h-28 w-28 rounded-full bg-white/20 blur-2xl" />
<span className="relative text-lg">Product Image</span>
</div>
</DialogContent>
</Dialog>
<div className="grid grid-cols-4 gap-3">
{thumbnails.map((grad, i) => (
<button
key={i}
onClick={() => setSelectedImage(i)}
className={\`aspect-square rounded-lg bg-gradient-to-br \${grad} \${selectedImage === i ? "ring-2 ring-stone-900 ring-offset-2" : "opacity-60 hover:opacity-100"} transition-all\`}
/>
))}
</div>
</div>
{/* Right — Details */}
<div className="space-y-6">
<div>
<Badge className="bg-rose-100 text-rose-700 border-0 mb-3 animate-fade-in">Best Seller</Badge>
<h1 className="text-3xl font-bold tracking-tight animate-fade-up">Classic Leather Jacket</h1>
<div className="flex items-center gap-3 mt-3">
<div className="flex items-center gap-0.5">{renderStars(5)}</div>
<span className="text-sm text-stone-500">(128 reviews)</span>
</div>
</div>
<div className="flex items-baseline gap-3">
<span className="text-3xl font-bold">$189</span>
<span className="text-xl text-stone-400 line-through">$249</span>
<Badge className="bg-rose-500 text-white border-0">-24%</Badge>
</div>
<p className="text-stone-600 leading-relaxed">Timeless silhouette crafted from premium full-grain leather. Features a tailored fit with minimal hardware for a clean, modern aesthetic.</p>
<Separator />
{/* Color Selector */}
<div>
<h3 className="text-sm font-semibold mb-3">Color: <span className="font-normal text-stone-500">{selectedColor}</span></h3>
<div className="flex gap-3">
{colors.map((c) => (
<button
key={c.name}
onClick={() => setSelectedColor(c.name)}
className={\`h-9 w-9 rounded-full \${c.class} \${selectedColor === c.name ? "ring-2 ring-offset-2 ring-stone-900" : ""} transition-all\`}
title={c.name}
/>
))}
</div>
</div>
{/* Size Selector */}
<div>
<div className="flex items-center justify-between mb-3">
<h3 className="text-sm font-semibold">Size</h3>
<button className="text-xs text-rose-600 hover:underline">Size Guide</button>
</div>
<div className="flex flex-wrap gap-2">
{sizes.map((s) => (
<Button
key={s}
variant={selectedSize === s ? "default" : "outline"}
size="sm"
className={\`min-w-[3rem] \${selectedSize === s ? "bg-stone-900" : ""}\`}
onClick={() => setSelectedSize(s)}
>
{s}
</Button>
))}
</div>
</div>
{/* Scarcity Signal */}
<div className="flex items-center gap-2 text-sm text-amber-600 mb-3"><Flame className="h-4 w-4" /> Selling Fast — Only 5 left in stock</div>
{/* Quantity + Add to Cart */}
<div className="flex items-center gap-4">
<div className="flex items-center border rounded-full">
<Button variant="ghost" size="icon" className="h-10 w-10 rounded-full active:scale-90 transition-transform" onClick={() => setQuantity(Math.max(1, quantity - 1))}>
<Minus className="h-4 w-4" />
</Button>
<span className="w-10 text-center font-medium">{quantity}</span>
<Button variant="ghost" size="icon" className="h-10 w-10 rounded-full active:scale-90 transition-transform" onClick={() => setQuantity(quantity + 1)}>
<Plus className="h-4 w-4" />
</Button>
</div>
<Button size="lg" className="flex-1 bg-stone-900 hover:bg-stone-800 rounded-full active:scale-[0.98] transition-all" onClick={handleAddToCart}>
<ShoppingBag className="mr-2 h-4 w-4" /> Add to Cart
</Button>
<Button variant="outline" size="icon" className={\`h-11 w-11 rounded-full hover:scale-125 active:scale-90 transition-transform \${wishlisted ? "border-rose-300 bg-rose-50" : ""}\`} onClick={() => { if (!wishlisted) toast.success("Added to wishlist!"); setWishlisted(!wishlisted); }}>
<Heart className={\`h-4 w-4 \${wishlisted ? "fill-rose-500 text-rose-500" : ""}\`} />
</Button>
</div>
{/* Trust */}
<div className="grid grid-cols-3 gap-4 pt-4 animate-fade-in">
{[
{ icon: Truck, text: "Free Shipping" },
{ icon: RotateCcw, text: "Easy Returns" },
{ icon: ShieldCheck, text: "Secure Payment" },
].map((item) => (
<div key={item.text} className="flex items-center gap-2 text-xs text-stone-500">
<item.icon className="h-4 w-4 text-rose-500 shrink-0" />
{item.text}
</div>
))}
</div>
</div>
</div>
{/* Tabs */}
<Tabs defaultValue="description" className="mt-16">
<TabsList className="bg-stone-100 rounded-full p-1">
<TabsTrigger value="description" className="rounded-full text-sm">Description</TabsTrigger>
<TabsTrigger value="reviews" className="rounded-full text-sm">Reviews (128)</TabsTrigger>
<TabsTrigger value="shipping" className="rounded-full text-sm">Shipping Info</TabsTrigger>
</TabsList>
<TabsContent value="description" className="mt-6 prose prose-stone max-w-none">
<p className="text-stone-600 leading-relaxed">Our Classic Leather Jacket is the cornerstone of any wardrobe. Made from hand-selected full-grain leather that develops a beautiful patina over time, this jacket is designed to be your companion for years to come. The tailored fit flatters every body type while allowing comfortable layering.</p>
<ul className="mt-4 space-y-2 text-sm text-stone-600">
<li>Premium full-grain leather construction</li>
<li>YKK zippers with custom pulls</li>
<li>Interior and exterior pockets</li>
<li>Fully lined with satin finish</li>
<li>Available in 4 colors</li>
</ul>
</TabsContent>
<TabsContent value="reviews" className="mt-6 space-y-6">
<div className="flex items-center gap-4 mb-8">
<div className="text-4xl font-bold">4.8</div>
<div>
<div className="flex gap-0.5">{[...Array(5)].map((_, i) => <Star key={i} className="h-5 w-5 fill-amber-400 text-amber-400" />)}</div>
<p className="text-sm text-muted-foreground mt-1">Based on 128 reviews</p>
</div>
</div>
{reviews.map((review, i) => (
<div key={i} className="flex gap-4 pb-6 border-b border-stone-100 last:border-0">
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-rose-400 to-amber-300 flex items-center justify-center text-white text-sm font-bold shrink-0">{review.avatar}</div>
<div className="flex-1">
<div className="flex items-center gap-2">
<h4 className="font-medium text-sm">{review.name}</h4>
<Badge className="bg-green-50 text-green-700 border-0 text-[10px] px-1.5 py-0">Verified Purchase</Badge>
<span className="text-xs text-stone-400 ml-auto">{review.date}</span>
</div>
<div className="flex items-center gap-0.5 mt-1">{renderStars(review.rating)}</div>
<p className="mt-2 text-sm text-stone-600">{review.text}</p>
</div>
</div>
))}
<Button variant="outline" className="rounded-full mt-4">Write a Review</Button>
</TabsContent>
<TabsContent value="shipping" className="mt-6">
<div className="space-y-4 text-sm text-stone-600">
<div className="flex items-start gap-3">
<Truck className="h-5 w-5 text-rose-500 mt-0.5" />
<div>
<h4 className="font-medium text-stone-900">Free Standard Shipping</h4>
<p>On all orders over $100. Delivery in 5-7 business days.</p>
</div>
</div>
<div className="flex items-start gap-3">
<RotateCcw className="h-5 w-5 text-rose-500 mt-0.5" />
<div>
<h4 className="font-medium text-stone-900">30-Day Returns</h4>
<p>Not satisfied? Return within 30 days for a full refund.</p>
</div>
</div>
</div>
</TabsContent>
</Tabs>
{/* You Might Also Like */}
<section className="py-24">
<h2 className="text-2xl font-bold tracking-tight mb-8 animate-fade-up">You Might Also Like</h2>
<div className="flex gap-6 overflow-x-auto pb-4 snap-x">
{relatedProducts.map((product) => (
<Link to="/product" key={product.id} className="group snap-start min-w-[250px]">
<Card className="overflow-hidden border-0 shadow-sm hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
<div className={\`aspect-[3/4] bg-gradient-to-br \${product.gradient} flex items-center justify-center text-stone-400 text-xs relative overflow-hidden animate-scale-in\`}>
<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" />
<span className="relative">Image</span>
</div>
<CardContent className="p-4">
<h3 className="font-medium text-sm group-hover:text-rose-600 transition-colors">{product.name}</h3>
<div className="flex items-center gap-0.5 mt-1">{renderStars(product.rating)}</div>
<p className="mt-1.5 font-semibold">\${product.price}</p>
</CardContent>
</Card>
</Link>
))}
</div>
</section>
</div>
<Footer />
</div>
);
}