Update src/app/product/[id]/page.tsx
This commit is contained in:
@@ -20,30 +20,28 @@ const TikTok = (props: React.SVGProps<SVGSVGElement>) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const products = [
|
||||
{
|
||||
id: "1", name: "Classic Black Hoodie", price: 89.99,
|
||||
description: "Premium oversized hoodie crafted from 100% organic cotton. Perfect for layering or wearing solo. Features reinforced seams and a comfortable fit that works with any style.", image: "http://img.b2bpic.net/free-photo/medium-shot-man-posing-with-hoodie-indoors_23-2149359859.jpg", sizes: ["XS", "S", "M", "L", "XL", "XXL"],
|
||||
const products: Record<string, any> = {
|
||||
"1": {
|
||||
id: "1", name: "Classic Black Hoodie", price: "$89.99", originalPrice: "$119.99", description: "Our signature black hoodie - a timeless essential for any streetwear collection. Premium 100% cotton blend with soft interior lining. Features a kangaroo pocket, drawstring with metal tips, and embroidered Umbra logo on the chest.", images: [
|
||||
"http://img.b2bpic.net/free-photo/medium-shot-man-posing-with-hoodie-indoors_23-2149359859.jpg", "http://img.b2bpic.net/free-photo/medium-shot-man-posing-with-hoodie-indoors_23-2149359859.jpg"],
|
||||
sizes: ["XS", "S", "M", "L", "XL", "2XL"],
|
||||
colors: ["Black", "Charcoal", "Navy"],
|
||||
rating: 4.8,
|
||||
reviews: 342,
|
||||
inStock: true,
|
||||
},
|
||||
{
|
||||
id: "2", name: "Vintage Denim Jacket", price: 129.99,
|
||||
description: "Timeless denim jacket with authentic vintage wash. Perfect for any occasion. Heavy-duty construction with durable hardware and reinforced stitching.", image: "http://img.b2bpic.net/free-photo/fashionable-woman-wearing-denim-jacket_23-2148859621.jpg", sizes: ["XS", "S", "M", "L", "XL"],
|
||||
colors: ["Indigo", "Light Wash", "Medium Wash"],
|
||||
"2": {
|
||||
id: "2", name: "Vintage Denim Jacket", price: "$129.99", description: "Classic denim jacket with a vintage washed finish. Perfect layering piece for any outfit. Features button closure, flap pockets, and adjustable waist tabs.", images: [
|
||||
"http://img.b2bpic.net/free-photo/fashionable-woman-wearing-denim-jacket_23-2148859621.jpg"],
|
||||
sizes: ["XS", "S", "M", "L", "XL", "2XL"],
|
||||
colors: ["Indigo", "Light Wash", "Dark Wash"],
|
||||
rating: 4.6,
|
||||
reviews: 218,
|
||||
inStock: true,
|
||||
},
|
||||
{
|
||||
id: "3", name: "Cargo Pants - Khaki", price: 79.99,
|
||||
description: "Versatile cargo pants with multiple pockets. Made from durable cotton blend. Perfect for everyday wear and outdoor adventures.", image: "http://img.b2bpic.net/free-photo/young-woman-wearing-trucker-hat_23-2149432334.jpg", sizes: ["28", "30", "32", "34", "36", "38"],
|
||||
colors: ["Khaki", "Olive", "Black"],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function ProductPage({ params }: { params: { id: string } }) {
|
||||
const product = products.find((p) => p.id === params.id);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [selectedSize, setSelectedSize] = useState(product?.sizes[0]);
|
||||
const [selectedColor, setSelectedColor] = useState(product?.colors[0]);
|
||||
|
||||
const navItems = [
|
||||
{ name: "Shop", id: "products" },
|
||||
{ name: "Collections", id: "collections" },
|
||||
@@ -52,6 +50,12 @@ export default function ProductPage({ params }: { params: { id: string } }) {
|
||||
{ name: "Contact", id: "contact" },
|
||||
];
|
||||
|
||||
const product = products[params.id] || products["1"];
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [selectedSize, setSelectedSize] = useState(product.sizes[2]);
|
||||
const [selectedColor, setSelectedColor] = useState(product.colors[0]);
|
||||
const [selectedImage, setSelectedImage] = useState(0);
|
||||
|
||||
const socialLinks = [
|
||||
{ icon: Instagram, href: "https://instagram.com/umbra", ariaLabel: "Instagram" },
|
||||
{ icon: Twitter, href: "https://twitter.com/umbra", ariaLabel: "Twitter" },
|
||||
@@ -59,46 +63,6 @@ export default function ProductPage({ params }: { params: { id: string } }) {
|
||||
{ icon: Youtube, href: "https://youtube.com/@umbra", ariaLabel: "YouTube" },
|
||||
];
|
||||
|
||||
if (!product) {
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="shift-hover"
|
||||
defaultTextAnimation="reveal-blur"
|
||||
borderRadius="soft"
|
||||
contentWidth="smallMedium"
|
||||
sizing="medium"
|
||||
background="circleGradient"
|
||||
cardStyle="glass-elevated"
|
||||
primaryButtonStyle="gradient"
|
||||
secondaryButtonStyle="glass"
|
||||
headingFontWeight="normal"
|
||||
>
|
||||
<div id="nav" data-section="nav">
|
||||
<NavbarStyleCentered
|
||||
navItems={navItems}
|
||||
button={{ text: "View Cart", href: "/cart" }}
|
||||
brandName="Umbra"
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-semibold text-foreground mb-4">Product not found</h1>
|
||||
<Link href="/shop" className="text-accent hover:text-background-accent">
|
||||
Back to Shop
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer" data-section="footer">
|
||||
<FooterCard
|
||||
logoText="Umbra"
|
||||
copyrightText="© 2025 | Umbra Streetwear. All rights reserved."
|
||||
socialLinks={socialLinks}
|
||||
/>
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
defaultButtonVariant="shift-hover"
|
||||
@@ -120,104 +84,141 @@ export default function ProductPage({ params }: { params: { id: string } }) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div id="product" data-section="product" className="mx-auto px-4 md:px-6 py-20">
|
||||
<div id="product-detail" data-section="product-detail" className="mx-auto px-4 md:px-6 py-20">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
<Link href="/shop" className="text-accent hover:text-background-accent mb-8 inline-block">
|
||||
← Back to Shop
|
||||
</Link>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div className="flex items-center justify-center bg-card/50 border border-accent/20 rounded-soft p-8">
|
||||
<img
|
||||
src={product.image}
|
||||
alt={product.name}
|
||||
className="w-full h-auto rounded-soft object-cover"
|
||||
/>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||
{/* Product Images */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="w-full aspect-square bg-card rounded-soft overflow-hidden">
|
||||
<img
|
||||
src={product.images[selectedImage]}
|
||||
alt={product.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
{product.images.map((img: string, idx: number) => (
|
||||
<button
|
||||
key={idx}
|
||||
onClick={() => setSelectedImage(idx)}
|
||||
className={`w-20 h-20 rounded-soft overflow-hidden border-2 transition-colors ${
|
||||
selectedImage === idx
|
||||
? "border-background-accent"
|
||||
: "border-accent/20 hover:border-accent/50"
|
||||
}`}
|
||||
>
|
||||
<img src={img} alt={`${product.name} ${idx}`} className="w-full h-full object-cover" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-center">
|
||||
<h1 className="text-4xl font-light text-foreground mb-4">{product.name}</h1>
|
||||
<p className="text-3xl font-semibold text-background-accent mb-6">
|
||||
${product.price.toFixed(2)}
|
||||
</p>
|
||||
<p className="text-foreground/70 mb-8 leading-relaxed">{product.description}</p>
|
||||
{/* Product Details */}
|
||||
<div>
|
||||
<h1 className="text-4xl font-light text-foreground mb-2">{product.name}</h1>
|
||||
|
||||
<div className="space-y-6 mb-8">
|
||||
<div>
|
||||
<label className="block text-foreground font-semibold mb-3">Size</label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{product.sizes.map((size) => (
|
||||
<button
|
||||
key={size}
|
||||
onClick={() => setSelectedSize(size)}
|
||||
className={`px-4 py-2 rounded-soft border transition-colors ${
|
||||
selectedSize === size
|
||||
? "bg-background-accent text-primary-cta-text border-background-accent"
|
||||
: "border-accent/30 text-foreground hover:border-accent"
|
||||
}`}
|
||||
>
|
||||
{size}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-foreground font-semibold mb-3">Color</label>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{product.colors.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
onClick={() => setSelectedColor(color)}
|
||||
className={`px-4 py-2 rounded-soft border transition-colors ${
|
||||
selectedColor === color
|
||||
? "bg-background-accent text-primary-cta-text border-background-accent"
|
||||
: "border-accent/30 text-foreground hover:border-accent"
|
||||
}`}
|
||||
>
|
||||
{color}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-foreground font-semibold mb-3">Quantity</label>
|
||||
<div className="flex items-center gap-3 w-fit bg-accent/10 rounded-soft p-2">
|
||||
<button
|
||||
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
||||
className="px-3 py-1 hover:bg-accent/20 rounded transition-colors"
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="flex items-center gap-1">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={i < Math.floor(product.rating) ? "text-background-accent" : "text-accent/30"}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span className="w-8 text-center font-semibold text-foreground">{quantity}</span>
|
||||
★
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<span className="text-sm text-foreground/70">({product.reviews} reviews)</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-baseline gap-4 mb-6">
|
||||
<span className="text-3xl font-bold text-background-accent">{product.price}</span>
|
||||
{product.originalPrice && (
|
||||
<span className="text-lg text-foreground/50 line-through">{product.originalPrice}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-foreground/70 mb-8">{product.description}</p>
|
||||
|
||||
{/* Color Selection */}
|
||||
<div className="mb-8">
|
||||
<label className="block text-sm font-semibold text-foreground mb-3">Color</label>
|
||||
<div className="flex gap-3">
|
||||
{product.colors.map((color: string) => (
|
||||
<button
|
||||
onClick={() => setQuantity(quantity + 1)}
|
||||
className="px-3 py-1 hover:bg-accent/20 rounded transition-colors"
|
||||
key={color}
|
||||
onClick={() => setSelectedColor(color)}
|
||||
className={`px-4 py-2 rounded-soft border-2 transition-colors ${
|
||||
selectedColor === color
|
||||
? "border-background-accent bg-background-accent/10"
|
||||
: "border-accent/20 hover:border-accent/50"
|
||||
}`}
|
||||
>
|
||||
+
|
||||
{color}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button className="flex-1 px-8 py-3 bg-background-accent text-primary-cta-text font-semibold rounded-soft hover:bg-primary-cta hover:text-primary-cta-text transition-colors">
|
||||
Add to Cart
|
||||
</button>
|
||||
<button className="flex-1 px-8 py-3 border border-accent/50 text-foreground font-semibold rounded-soft hover:bg-accent/10 transition-colors">
|
||||
Save for Later
|
||||
</button>
|
||||
{/* Size Selection */}
|
||||
<div className="mb-8">
|
||||
<label className="block text-sm font-semibold text-foreground mb-3">Size</label>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{product.sizes.map((size: string) => (
|
||||
<button
|
||||
key={size}
|
||||
onClick={() => setSelectedSize(size)}
|
||||
className={`py-2 rounded-soft border-2 transition-colors ${
|
||||
selectedSize === size
|
||||
? "border-background-accent bg-background-accent/10"
|
||||
: "border-accent/20 hover:border-accent/50"
|
||||
}`}
|
||||
>
|
||||
{size}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 pt-8 border-t border-accent/20">
|
||||
<p className="text-foreground/70 text-sm">
|
||||
<span className="font-semibold text-foreground">Free Shipping:</span> On orders over $150
|
||||
</p>
|
||||
<p className="text-foreground/70 text-sm mt-2">
|
||||
<span className="font-semibold text-foreground">Easy Returns:</span> 30-day money-back guarantee
|
||||
</p>
|
||||
{/* Quantity */}
|
||||
<div className="mb-8">
|
||||
<label className="block text-sm font-semibold text-foreground mb-3">Quantity</label>
|
||||
<div className="flex items-center gap-4 w-fit bg-accent/10 rounded-soft p-2">
|
||||
<button
|
||||
onClick={() => setQuantity(Math.max(1, quantity - 1))}
|
||||
className="px-3 py-1 hover:bg-accent/20 rounded transition-colors"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<span className="w-8 text-center font-semibold">{quantity}</span>
|
||||
<button
|
||||
onClick={() => setQuantity(quantity + 1)}
|
||||
className="px-3 py-1 hover:bg-accent/20 rounded transition-colors"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Add to Cart Button */}
|
||||
<button className="w-full px-6 py-4 bg-background-accent text-primary-cta-text font-semibold rounded-soft hover:bg-primary-cta hover:text-primary-cta-text transition-colors mb-4">
|
||||
Add to Cart
|
||||
</button>
|
||||
|
||||
<button className="w-full px-6 py-3 border border-accent/50 text-foreground font-semibold rounded-soft hover:bg-accent/10 transition-colors">
|
||||
Save for Later
|
||||
</button>
|
||||
|
||||
{/* Stock Status */}
|
||||
{product.inStock ? (
|
||||
<p className="text-sm text-background-accent font-semibold mt-4">✓ In Stock - Ships within 24 hours</p>
|
||||
) : (
|
||||
<p className="text-sm text-foreground/50 mt-4">Out of Stock - Notify me when available</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user