From ca3ae0db6e8e3f2cf99423797c60f5c2cc66a780 Mon Sep 17 00:00:00 2001
From: Biswa Kalyan Bhuyan <biswa@surgot.in>
Date: Thu, 29 May 2025 20:57:31 +0530
Subject: feat: added the product page and enhanced the UI

- added the product page where the product details is shown
- enhanced the nav bar to handle the rendering
- added the loading screen to the page
- fixed the rendering method to the navigation-menu
---
 frontend/src/components/product-page.tsx | 567 +++++++++++++++++++++++++++++++
 1 file changed, 567 insertions(+)
 create mode 100644 frontend/src/components/product-page.tsx

(limited to 'frontend/src/components/product-page.tsx')

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
-- 
cgit v1.2.3-59-g8ed1b

