aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/product-page.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/product-page.tsx')
-rw-r--r--frontend/src/components/product-page.tsx567
1 files changed, 567 insertions, 0 deletions
diff --git a/frontend/src/components/product-page.tsx b/frontend/src/components/product-page.tsx
new file mode 100644
index 0000000..aff9d53
--- /dev/null
+++ b/frontend/src/components/product-page.tsx
@@ -0,0 +1,567 @@
+"use client";
+
+import { useState } from "react";
+import Image from "next/image";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+import { Separator } from "@/components/ui/separator";
+import {
+ Heart,
+ Share2,
+ ShoppingBag,
+ Star,
+ Truck,
+ RefreshCw,
+ Shield,
+ Ruler,
+ ChevronLeft,
+ ChevronRight,
+ Minus,
+ Plus,
+ Check,
+} from "lucide-react";
+
+interface ProductPageProps {
+ productId?: string;
+}
+
+export function ProductPage({ productId = "1" }: ProductPageProps) {
+ const [selectedImage, setSelectedImage] = useState(0);
+ const [selectedSize, setSelectedSize] = useState("");
+ const [selectedColor, setSelectedColor] = useState("");
+ const [quantity, setQuantity] = useState(1);
+ const [isWishlisted, setIsWishlisted] = useState(false);
+
+ // Mock product data - in real app, this would come from an API
+ const product = {
+ id: productId,
+ name: "Oversized Cotton Hoodie",
+ brand: "blcklst",
+ price: 89,
+ originalPrice: 129,
+ rating: 4.5,
+ reviewCount: 234,
+ description: "A premium oversized cotton hoodie crafted for ultimate comfort and style. Made from 100% organic cotton with a soft fleece lining, this hoodie features a relaxed fit perfect for layering or wearing solo.",
+ features: [
+ "100% organic cotton construction",
+ "Soft fleece interior lining",
+ "Kangaroo pocket with hidden phone compartment",
+ "Reinforced double-stitched seams",
+ "Pre-shrunk for lasting fit",
+ "Unisex design"
+ ],
+ images: [
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ "/api/placeholder/600/800",
+ ],
+ colors: [
+ { name: "Black", value: "#000000", available: true },
+ { name: "White", value: "#FFFFFF", available: true },
+ { name: "Gray", value: "#6B7280", available: true },
+ { name: "Navy", value: "#1E3A8A", available: false },
+ ],
+ sizes: [
+ { name: "XS", available: true },
+ { name: "S", available: true },
+ { name: "M", available: true },
+ { name: "L", available: true },
+ { name: "XL", available: true },
+ { name: "XXL", available: false },
+ ],
+ inStock: true,
+ stockCount: 12,
+ };
+
+ const relatedProducts = [
+ {
+ id: "2",
+ name: "Minimal T-Shirt",
+ price: 45,
+ originalPrice: 65,
+ image: "/api/placeholder/300/400",
+ rating: 4.3,
+ },
+ {
+ id: "3",
+ name: "Denim Jacket",
+ price: 129,
+ originalPrice: 179,
+ image: "/api/placeholder/300/400",
+ rating: 4.7,
+ },
+ {
+ id: "4",
+ name: "Cargo Pants",
+ price: 89,
+ originalPrice: 119,
+ image: "/api/placeholder/300/400",
+ rating: 4.2,
+ },
+ {
+ id: "5",
+ name: "Sneakers",
+ price: 149,
+ originalPrice: 199,
+ image: "/api/placeholder/300/400",
+ rating: 4.6,
+ },
+ ];
+
+ const reviews = [
+ {
+ id: 1,
+ name: "Sarah M.",
+ rating: 5,
+ date: "2024-01-15",
+ comment: "Amazing quality! The fit is perfect and the material feels premium. Definitely worth the price.",
+ verified: true,
+ },
+ {
+ id: 2,
+ name: "Alex K.",
+ rating: 4,
+ date: "2024-01-10",
+ comment: "Great hoodie, very comfortable. Only wish it came in more colors.",
+ verified: true,
+ },
+ {
+ id: 3,
+ name: "Jordan L.",
+ rating: 5,
+ date: "2024-01-05",
+ comment: "Best hoodie I've ever owned. The oversized fit is exactly what I wanted.",
+ verified: true,
+ },
+ ];
+
+ const nextImage = () => {
+ setSelectedImage((prev) => (prev + 1) % product.images.length);
+ };
+
+ const prevImage = () => {
+ setSelectedImage((prev) => (prev - 1 + product.images.length) % product.images.length);
+ };
+
+ const addToCart = () => {
+ if (!selectedSize || !selectedColor) {
+ alert("Please select size and color");
+ return;
+ }
+ // Add to cart logic here
+ console.log("Added to cart:", { product, selectedSize, selectedColor, quantity });
+ };
+
+ const renderStars = (rating: number) => {
+ return Array.from({ length: 5 }, (_, i) => (
+ <Star
+ key={i}
+ className={`h-4 w-4 ${
+ i < Math.floor(rating)
+ ? "fill-yellow-400 text-yellow-400"
+ : i < rating
+ ? "fill-yellow-400/50 text-yellow-400"
+ : "text-gray-300"
+ }`}
+ />
+ ));
+ };
+
+ return (
+ <div className="min-h-screen bg-background">
+ {/* Breadcrumb */}
+ <div className="border-b">
+ <div className="container mx-auto px-4 py-4">
+ <nav className="flex items-center space-x-2 text-sm text-muted-foreground">
+ <Link href="/" className="hover:text-foreground">Home</Link>
+ <span>/</span>
+ <Link href="/men" className="hover:text-foreground">Men</Link>
+ <span>/</span>
+ <Link href="/men/hoodies" className="hover:text-foreground">Hoodies</Link>
+ <span>/</span>
+ <span className="text-foreground">{product.name}</span>
+ </nav>
+ </div>
+ </div>
+
+ <div className="container mx-auto px-4 py-8">
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
+ {/* Product Images */}
+ <div className="space-y-4">
+ {/* Main Image */}
+ <div className="relative aspect-[3/4] overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-800">
+ <Image
+ src={product.images[selectedImage]}
+ alt={product.name}
+ fill
+ className="object-cover"
+ priority
+ />
+ {product.originalPrice && (
+ <Badge className="absolute top-4 left-4 bg-red-500 hover:bg-red-600">
+ {Math.round(((product.originalPrice - product.price) / product.originalPrice) * 100)}% OFF
+ </Badge>
+ )}
+
+ {/* Navigation Arrows */}
+ <button
+ onClick={prevImage}
+ className="absolute left-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-black/80 p-2 rounded-full hover:bg-white dark:hover:bg-black transition-colors"
+ >
+ <ChevronLeft className="h-5 w-5" />
+ </button>
+ <button
+ onClick={nextImage}
+ className="absolute right-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-black/80 p-2 rounded-full hover:bg-white dark:hover:bg-black transition-colors"
+ >
+ <ChevronRight className="h-5 w-5" />
+ </button>
+ </div>
+
+ {/* Thumbnail Images */}
+ <div className="grid grid-cols-4 gap-3">
+ {product.images.map((image, index) => (
+ <button
+ key={index}
+ onClick={() => setSelectedImage(index)}
+ className={`relative aspect-square overflow-hidden rounded-lg border-2 transition-colors ${
+ selectedImage === index
+ ? "border-primary"
+ : "border-transparent hover:border-neutral-300"
+ }`}
+ >
+ <Image
+ src={image}
+ alt={`${product.name} ${index + 1}`}
+ fill
+ className="object-cover"
+ />
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Product Details */}
+ <div className="space-y-6">
+ {/* Header */}
+ <div>
+ <p className="text-sm text-muted-foreground font-medium mb-2">{product.brand}</p>
+ <h1 className="text-3xl font-bold mb-4">{product.name}</h1>
+
+ {/* Rating */}
+ <div className="flex items-center space-x-2 mb-4">
+ <div className="flex items-center space-x-1">
+ {renderStars(product.rating)}
+ </div>
+ <span className="text-sm text-muted-foreground">
+ {product.rating} ({product.reviewCount} reviews)
+ </span>
+ </div>
+
+ {/* Price */}
+ <div className="flex items-center space-x-3 mb-6">
+ <span className="text-3xl font-bold">${product.price}</span>
+ {product.originalPrice && (
+ <span className="text-xl text-muted-foreground line-through">
+ ${product.originalPrice}
+ </span>
+ )}
+ </div>
+ </div>
+
+ {/* Color Selection */}
+ <div className="space-y-3">
+ <div className="flex items-center justify-between">
+ <h3 className="font-medium">Color</h3>
+ {selectedColor && (
+ <span className="text-sm text-muted-foreground capitalize">{selectedColor}</span>
+ )}
+ </div>
+ <div className="flex space-x-3">
+ {product.colors.map((color) => (
+ <button
+ key={color.name}
+ onClick={() => color.available && setSelectedColor(color.name)}
+ disabled={!color.available}
+ className={`relative w-10 h-10 rounded-full border-2 transition-all ${
+ selectedColor === color.name
+ ? "border-primary scale-110"
+ : "border-neutral-300 hover:border-neutral-400"
+ } ${!color.available && "opacity-50 cursor-not-allowed"}`}
+ style={{ backgroundColor: color.value }}
+ >
+ {selectedColor === color.name && (
+ <Check className="absolute inset-0 m-auto h-4 w-4 text-white" />
+ )}
+ {!color.available && (
+ <div className="absolute inset-0 bg-neutral-500/50 rounded-full" />
+ )}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Size Selection */}
+ <div className="space-y-3">
+ <div className="flex items-center justify-between">
+ <h3 className="font-medium">Size</h3>
+ <Button variant="link" className="h-auto p-0 text-sm">
+ <Ruler className="h-4 w-4 mr-1" />
+ Size Guide
+ </Button>
+ </div>
+ <div className="grid grid-cols-3 gap-3">
+ {product.sizes.map((size) => (
+ <button
+ key={size.name}
+ onClick={() => size.available && setSelectedSize(size.name)}
+ disabled={!size.available}
+ className={`py-3 px-4 border rounded-lg text-sm font-medium transition-colors ${
+ selectedSize === size.name
+ ? "border-primary bg-primary text-primary-foreground"
+ : size.available
+ ? "border-neutral-300 hover:border-neutral-400 hover:bg-white dark:hover:bg-white hover:text-black"
+ : "border-neutral-200 text-muted-foreground opacity-50 cursor-not-allowed"
+ }`}
+ >
+ {size.name}
+ </button>
+ ))}
+ </div>
+ </div>
+
+ {/* Quantity */}
+ <div className="space-y-3">
+ <h3 className="font-medium">Quantity</h3>
+ <div className="flex items-center space-x-3">
+ <div className="flex items-center border rounded-lg">
+ <button
+ onClick={() => setQuantity(Math.max(1, quantity - 1))}
+ className="p-3 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors focus:outline-none focus:ring-0 m-1"
+ >
+ <Minus className="h-4 w-4" />
+ </button>
+ <span className="px-4 py-3 min-w-[60px] text-center">{quantity}</span>
+ <button
+ onClick={() => setQuantity(Math.min(product.stockCount, quantity + 1))}
+ className="p-3 rounded-md hover:bg-neutral-100 dark:hover:bg-neutral-800 transition-colors focus:outline-none focus:ring-0 m-1"
+ >
+ <Plus className="h-4 w-4" />
+ </button>
+ </div>
+ <span className="text-sm text-muted-foreground">
+ {product.stockCount} items left
+ </span>
+ </div>
+ </div>
+
+ {/* Add to Cart */}
+ <div className="space-y-3">
+ <div className="flex space-x-3">
+ <Button
+ onClick={addToCart}
+ className="flex-1 h-12"
+ disabled={!product.inStock}
+ >
+ <ShoppingBag className="h-5 w-5 mr-2" />
+ {product.inStock ? "Add to Cart" : "Out of Stock"}
+ </Button>
+ <Button
+ variant="outline"
+ size="icon"
+ className="h-12 w-12"
+ onClick={() => setIsWishlisted(!isWishlisted)}
+ >
+ <Heart className={`h-5 w-5 ${isWishlisted ? "fill-red-500 text-red-500" : ""}`} />
+ </Button>
+ <Button variant="outline" size="icon" className="h-12 w-12">
+ <Share2 className="h-5 w-5" />
+ </Button>
+ </div>
+
+ {/* Features */}
+ <div className="grid grid-cols-3 gap-4 pt-4">
+ <div className="flex items-center space-x-2 text-sm">
+ <Truck className="h-4 w-4 text-muted-foreground" />
+ <span>Free Shipping</span>
+ </div>
+ <div className="flex items-center space-x-2 text-sm">
+ <RefreshCw className="h-4 w-4 text-muted-foreground" />
+ <span>Free Returns</span>
+ </div>
+ <div className="flex items-center space-x-2 text-sm">
+ <Shield className="h-4 w-4 text-muted-foreground" />
+ <span>2 Year Warranty</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ {/* Product Details Tabs */}
+ <div className="mt-16">
+ <Tabs defaultValue="description" className="w-full">
+ <TabsList className="grid w-full grid-cols-3">
+ <TabsTrigger value="description">Description</TabsTrigger>
+ <TabsTrigger value="specifications">Specifications</TabsTrigger>
+ <TabsTrigger value="reviews">Reviews ({product.reviewCount})</TabsTrigger>
+ </TabsList>
+
+ <TabsContent value="description" className="mt-8">
+ <div className="prose dark:prose-invert max-w-none">
+ <p className="text-lg text-muted-foreground mb-6">{product.description}</p>
+ <h3 className="text-xl font-semibold mb-4">Features</h3>
+ <ul className="space-y-2">
+ {product.features.map((feature, index) => (
+ <li key={index} className="flex items-start space-x-2">
+ <Check className="h-5 w-5 text-green-500 mt-0.5 flex-shrink-0" />
+ <span>{feature}</span>
+ </li>
+ ))}
+ </ul>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="specifications" className="mt-8">
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
+ <div className="space-y-4">
+ <h3 className="text-lg font-semibold">Material & Care</h3>
+ <div className="space-y-2">
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Material:</span>
+ <span>100% Organic Cotton</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Weight:</span>
+ <span>400 GSM</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Care:</span>
+ <span>Machine Wash Cold</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Origin:</span>
+ <span>Made in Portugal</span>
+ </div>
+ </div>
+ </div>
+ <div className="space-y-4">
+ <h3 className="text-lg font-semibold">Sizing</h3>
+ <div className="space-y-2">
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Fit:</span>
+ <span>Oversized</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Model Height:</span>
+ <span>6&apos;0&quot; / 183cm</span>
+ </div>
+ <div className="flex justify-between">
+ <span className="text-muted-foreground">Model Size:</span>
+ <span>Size M</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </TabsContent>
+
+ <TabsContent value="reviews" className="mt-8">
+ <div className="space-y-6">
+ <div className="flex items-center justify-between">
+ <div>
+ <div className="flex items-center space-x-2 mb-2">
+ <div className="flex items-center space-x-1">
+ {renderStars(product.rating)}
+ </div>
+ <span className="text-2xl font-bold">{product.rating}</span>
+ <span className="text-muted-foreground">({product.reviewCount} reviews)</span>
+ </div>
+ <p className="text-sm text-muted-foreground">Based on verified purchases</p>
+ </div>
+ <Button variant="outline">Write a Review</Button>
+ </div>
+
+ <Separator />
+
+ <div className="space-y-6">
+ {reviews.map((review) => (
+ <div key={review.id} className="space-y-3">
+ <div className="flex items-center justify-between">
+ <div className="flex items-center space-x-3">
+ <div>
+ <div className="flex items-center space-x-2">
+ <span className="font-medium">{review.name}</span>
+ {review.verified && (
+ <Badge variant="secondary" className="text-xs">
+ Verified Purchase
+ </Badge>
+ )}
+ </div>
+ <div className="flex items-center space-x-2 mt-1">
+ <div className="flex items-center space-x-1">
+ {renderStars(review.rating)}
+ </div>
+ <span className="text-sm text-muted-foreground">{review.date}</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ <p className="text-muted-foreground">{review.comment}</p>
+ <Separator />
+ </div>
+ ))}
+ </div>
+ </div>
+ </TabsContent>
+ </Tabs>
+ </div>
+
+ {/* Related Products */}
+ <div className="mt-16">
+ <h2 className="text-2xl font-bold mb-8">You might also like</h2>
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
+ {relatedProducts.map((item) => (
+ <Link key={item.id} href={`/products/${item.id}`} className="group">
+ <div className="space-y-3">
+ <div className="relative aspect-[3/4] overflow-hidden rounded-lg bg-neutral-100 dark:bg-neutral-800">
+ <Image
+ src={item.image}
+ alt={item.name}
+ fill
+ className="object-cover group-hover:scale-105 transition-transform duration-300"
+ />
+ {item.originalPrice && (
+ <Badge className="absolute top-3 left-3 bg-red-500 hover:bg-red-600">
+ {Math.round(((item.originalPrice - item.price) / item.originalPrice) * 100)}% OFF
+ </Badge>
+ )}
+ </div>
+ <div>
+ <h3 className="font-medium group-hover:text-primary transition-colors">
+ {item.name}
+ </h3>
+ <div className="flex items-center space-x-1 mt-1">
+ {renderStars(item.rating)}
+ <span className="text-sm text-muted-foreground">({item.rating})</span>
+ </div>
+ <div className="flex items-center space-x-2 mt-2">
+ <span className="font-semibold">${item.price}</span>
+ {item.originalPrice && (
+ <span className="text-sm text-muted-foreground line-through">
+ ${item.originalPrice}
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ </Link>
+ ))}
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+} \ No newline at end of file