aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/src/components/product-card.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/src/components/product-card.tsx')
-rw-r--r--frontend/src/components/product-card.tsx251
1 files changed, 251 insertions, 0 deletions
diff --git a/frontend/src/components/product-card.tsx b/frontend/src/components/product-card.tsx
new file mode 100644
index 0000000..f790c98
--- /dev/null
+++ b/frontend/src/components/product-card.tsx
@@ -0,0 +1,251 @@
+"use client";
+
+import { useState } from "react";
+import Link from "next/link";
+import { Card, CardContent } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Heart, Eye, ShoppingBag, Star } from "lucide-react";
+
+interface ProductCardProps {
+ id: string;
+ name: string;
+ price: number;
+ originalPrice?: number;
+ image: string;
+ images?: string[];
+ rating: number;
+ reviewCount: number;
+ isNew?: boolean;
+ isSale?: boolean;
+ isWishlisted?: boolean;
+ category: string;
+ colors?: string[];
+ sizes?: string[];
+}
+
+export function ProductCard({
+ id,
+ name,
+ price,
+ originalPrice,
+ image,
+ images = [],
+ rating,
+ reviewCount,
+ isNew = false,
+ isSale = false,
+ isWishlisted = false,
+ category,
+ colors = [],
+ sizes = [],
+}: ProductCardProps) {
+ const [currentImage, setCurrentImage] = useState(0);
+ const [isHovered, setIsHovered] = useState(false);
+ const [wishlisted, setWishlisted] = useState(isWishlisted);
+
+ const allImages = [image, ...images];
+ const discountPercentage = originalPrice
+ ? Math.round(((originalPrice - price) / originalPrice) * 100)
+ : 0;
+
+ const handleWishlistToggle = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setWishlisted(!wishlisted);
+ };
+
+ const handleQuickView = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // TODO: Implement quick view modal
+ console.log("Quick view for product:", id);
+ };
+
+ const handleAddToCart = (e: React.MouseEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ // TODO: Implement add to cart
+ console.log("Add to cart:", id);
+ };
+
+ return (
+ <Card
+ className="group relative overflow-hidden border-0 shadow-sm hover:shadow-xl transition-all duration-300 cursor-pointer"
+ onMouseEnter={() => setIsHovered(true)}
+ onMouseLeave={() => setIsHovered(false)}
+ >
+ <Link href={`/product/${id}`}>
+ <div className="relative overflow-hidden">
+ {/* Product Image */}
+ <div className="aspect-[3/4] bg-neutral-100 relative overflow-hidden">
+ <div
+ className="w-full h-full bg-gradient-to-br from-neutral-200 to-neutral-300 flex items-center justify-center transition-transform duration-300 group-hover:scale-105"
+ style={{
+ backgroundImage: `url(${allImages[currentImage]})`,
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ }}
+ >
+ {/* Placeholder for actual images */}
+ <div className="text-center text-neutral-500">
+ <div className="w-16 h-16 bg-neutral-400 rounded-full mx-auto mb-2 flex items-center justify-center">
+ <ShoppingBag className="w-8 h-8 text-white" />
+ </div>
+ <p className="text-sm font-medium">{name}</p>
+ </div>
+ </div>
+
+ {/* Badges */}
+ <div className="absolute top-3 left-3 flex flex-col gap-2">
+ {isNew && (
+ <Badge className="bg-blue-500 hover:bg-blue-600 text-white">
+ New
+ </Badge>
+ )}
+ {isSale && (
+ <Badge className="bg-red-500 hover:bg-red-600 text-white">
+ -{discountPercentage}%
+ </Badge>
+ )}
+ </div>
+
+ {/* Wishlist Button */}
+ <Button
+ variant="ghost"
+ size="icon"
+ className="absolute top-3 right-3 bg-white/80 hover:bg-white text-gray-600 hover:text-red-500 transition-colors"
+ onClick={handleWishlistToggle}
+ >
+ <Heart
+ className={`h-4 w-4 ${wishlisted ? 'fill-red-500 text-red-500' : ''}`}
+ />
+ </Button>
+
+ {/* Image dots for multiple images */}
+ {allImages.length > 1 && (
+ <div className="absolute bottom-3 left-1/2 transform -translate-x-1/2 flex gap-1">
+ {allImages.map((_, index) => (
+ <button
+ key={index}
+ className={`w-2 h-2 rounded-full transition-colors ${
+ index === currentImage ? 'bg-white' : 'bg-white/50'
+ }`}
+ onClick={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ setCurrentImage(index);
+ }}
+ />
+ ))}
+ </div>
+ )}
+
+ {/* Hover overlay with actions */}
+ <div className={`absolute inset-0 bg-black/20 flex items-center justify-center transition-opacity duration-300 ${
+ isHovered ? 'opacity-100' : 'opacity-0'
+ }`}>
+ <div className="flex gap-3">
+ <Button
+ variant="secondary"
+ size="sm"
+ onClick={handleQuickView}
+ className="bg-white/90 hover:bg-white text-gray-900"
+ >
+ <Eye className="h-4 w-4 mr-2" />
+ Quick View
+ </Button>
+ <Button
+ size="sm"
+ onClick={handleAddToCart}
+ className="bg-primary hover:bg-primary/90"
+ >
+ <ShoppingBag className="h-4 w-4 mr-2" />
+ Add to Cart
+ </Button>
+ </div>
+ </div>
+ </div>
+
+ {/* Product Info */}
+ <CardContent className="p-4 space-y-3">
+ {/* Category */}
+ <p className="text-xs text-muted-foreground uppercase tracking-wide">
+ {category}
+ </p>
+
+ {/* Product Name */}
+ <h3 className="font-semibold text-sm leading-tight line-clamp-2 group-hover:text-primary transition-colors">
+ {name}
+ </h3>
+
+ {/* Rating */}
+ <div className="flex items-center gap-2">
+ <div className="flex items-center">
+ {[1, 2, 3, 4, 5].map((star) => (
+ <Star
+ key={star}
+ className={`h-3 w-3 ${
+ star <= rating
+ ? 'fill-yellow-400 text-yellow-400'
+ : 'text-gray-300'
+ }`}
+ />
+ ))}
+ </div>
+ <span className="text-xs text-muted-foreground">
+ ({reviewCount})
+ </span>
+ </div>
+
+ {/* Colors */}
+ {colors.length > 0 && (
+ <div className="flex items-center gap-1">
+ {colors.slice(0, 4).map((color, index) => (
+ <div
+ key={index}
+ className="w-4 h-4 rounded-full border border-gray-300"
+ style={{ backgroundColor: color }}
+ />
+ ))}
+ {colors.length > 4 && (
+ <span className="text-xs text-muted-foreground ml-1">
+ +{colors.length - 4}
+ </span>
+ )}
+ </div>
+ )}
+
+ {/* Price */}
+ <div className="flex items-center gap-2">
+ <span className="font-bold text-lg">
+ ${price.toFixed(2)}
+ </span>
+ {originalPrice && (
+ <span className="text-sm text-muted-foreground line-through">
+ ${originalPrice.toFixed(2)}
+ </span>
+ )}
+ </div>
+
+ {/* Sizes */}
+ {sizes.length > 0 && (
+ <div className="flex flex-wrap gap-1">
+ {sizes.slice(0, 4).map((size) => (
+ <Badge key={size} variant="outline" className="text-xs px-2 py-1">
+ {size}
+ </Badge>
+ ))}
+ {sizes.length > 4 && (
+ <Badge variant="outline" className="text-xs px-2 py-1">
+ +{sizes.length - 4}
+ </Badge>
+ )}
+ </div>
+ )}
+ </CardContent>
+ </div>
+ </Link>
+ </Card>
+ );
+} \ No newline at end of file