diff options
Diffstat (limited to 'frontend/src/components/product-card.tsx')
-rw-r--r-- | frontend/src/components/product-card.tsx | 251 |
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 |