Spaces:
Running
Running
import { Link, useLocation } from "wouter"; | |
import { useCart } from "@/hooks/use-cart"; | |
import { useAuth } from "@/hooks/use-auth"; | |
import { Product } from "@/types"; | |
import { Button } from "@/components/ui/button"; | |
import { Badge } from "@/components/ui/badge"; | |
import { Heart } from "lucide-react"; | |
import { Card, CardContent } from "@/components/ui/card"; | |
import { ShoppingCart, Star, Store } from "lucide-react"; | |
import { useToast } from "@/hooks/use-toast"; | |
interface ProductCardProps { | |
product: Product; | |
} | |
export default function ProductCard({ product }: ProductCardProps) { | |
const { addItem } = useCart(); | |
const { toast } = useToast(); | |
const { isAuthenticated } = useAuth(); | |
const [, setLocation] = useLocation(); | |
const handleAddToCart = async (e: React.MouseEvent) => { | |
e.preventDefault(); | |
e.stopPropagation(); | |
if (!isAuthenticated) { | |
toast({ | |
title: "Login Required", | |
description: "Please sign in to add items to your cart.", | |
action: ( | |
<Button | |
variant="outline" | |
size="sm" | |
onClick={() => setLocation('/auth')} | |
> | |
Sign In | |
</Button> | |
), | |
}); | |
return; | |
} | |
try { | |
await addItem(product.id, 1); | |
toast({ | |
title: "Added to cart", | |
description: `${product.title} has been added to your cart.`, | |
}); | |
} catch (error) { | |
toast({ | |
variant: "destructive", | |
title: "Error", | |
description: "Failed to add item to cart. Please try again.", | |
}); | |
} | |
}; | |
const discountPercentage = product.originalPrice | |
? Math.round((1 - parseFloat(product.price) / parseFloat(product.originalPrice)) * 100) | |
: 0; | |
return ( | |
<Link href={`/product/${product.id}`}> | |
<Card className="group cursor-pointer bg-gradient-to-b from-white to-orange-50/20 rounded-2xl shadow-sm hover:shadow-xl transition-all duration-300 border-0 overflow-hidden h-full hover:scale-[1.02] transform" data-testid={`product-card-${product.id}`}> | |
{/* Product Image */} | |
<div className="aspect-[4/5] bg-gradient-to-br from-orange-50/50 to-amber-50/60 overflow-hidden relative rounded-xl m-3 mb-4"> | |
{/* Wishlist Heart */} | |
<button className="absolute top-3 right-3 z-10 p-2 bg-white/80 backdrop-blur-sm rounded-full shadow-sm hover:bg-white transition-all duration-200 hover:scale-110" onClick={(e) => { e.preventDefault(); e.stopPropagation(); }} data-testid={`wishlist-${product.id}`}> | |
<Heart className="w-4 h-4 text-gray-400 hover:text-red-500 transition-colors" /> | |
</button> | |
{product.images && product.images.length > 0 ? ( | |
<img | |
src={product.images[0]} | |
alt={product.title} | |
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" | |
data-testid={`product-image-${product.id}`} | |
/> | |
) : ( | |
<div className="w-full h-full bg-gradient-to-br from-orange-50 to-red-50 flex items-center justify-center"> | |
<span className="text-gray-400 text-sm">No Image</span> | |
</div> | |
)} | |
{/* Discount Badge */} | |
{discountPercentage > 0 && ( | |
<Badge | |
className="absolute top-3 left-3 bg-gradient-to-r from-red-500 to-pink-500 text-white border-0 rounded-full px-2 py-1 text-xs font-medium" | |
data-testid={`discount-badge-${product.id}`} | |
> | |
-{discountPercentage}% | |
</Badge> | |
)} | |
</div> | |
<CardContent className="p-4 pt-0 flex flex-col flex-grow"> | |
{/* Seller Info */} | |
{(product as any).seller && ( | |
<div className="flex items-center gap-2 mb-2"> | |
<Store className="w-3 h-3 text-gray-400" /> | |
<span className="text-xs text-gray-500 truncate">{(product as any).seller.username}</span> | |
</div> | |
)} | |
{/* Product Title */} | |
<h3 className="font-semibold text-gray-800 mb-2 line-clamp-2 text-sm leading-relaxed" data-testid={`product-title-${product.id}`}> | |
{product.title} | |
</h3> | |
{/* Rating */} | |
<div className="flex items-center gap-1 mb-3"> | |
<div className="flex items-center"> | |
<Star className="w-3 h-3 fill-yellow-400 text-yellow-400" /> | |
<Star className="w-3 h-3 fill-yellow-400 text-yellow-400" /> | |
<Star className="w-3 h-3 fill-yellow-400 text-yellow-400" /> | |
<Star className="w-3 h-3 fill-yellow-400 text-yellow-400" /> | |
<Star className="w-3 h-3 fill-gray-200 text-gray-200" /> | |
</div> | |
<span className="text-xs text-gray-500 ml-1">(4.0)</span> | |
</div> | |
{/* Price */} | |
<div className="mb-3 mt-auto"> | |
<div className="flex items-baseline space-x-2"> | |
<span className="text-lg font-bold text-gray-900" data-testid={`product-price-${product.id}`}> | |
₹{parseFloat(product.price).toFixed(2)} | |
</span> | |
{product.originalPrice && ( | |
<span | |
className="text-xs text-gray-400 line-through" | |
data-testid={`original-price-${product.id}`} | |
> | |
₹{parseFloat(product.originalPrice).toFixed(2)} | |
</span> | |
)} | |
</div> | |
{discountPercentage > 0 && ( | |
<p className="text-xs text-green-600 font-medium">Save ₹{(parseFloat(product.originalPrice!) - parseFloat(product.price)).toFixed(2)}</p> | |
)} | |
</div> | |
{/* Quick Actions */} | |
<div className="flex gap-2"> | |
<Button | |
size="sm" | |
onClick={handleAddToCart} | |
className="flex-1 bg-gradient-to-r from-orange-500 to-red-500 hover:from-orange-600 hover:to-red-600 text-white border-0 rounded-xl py-2 font-medium text-xs shadow-sm hover:shadow-md transition-all duration-300 hover:scale-105" | |
data-testid={`add-to-cart-${product.id}`} | |
> | |
<ShoppingCart className="w-3 h-3 mr-1" /> | |
Add to Cart | |
</Button> | |
</div> | |
{/* Trust Elements */} | |
<div className="flex items-center justify-between mt-2 text-xs text-gray-500"> | |
<span className="flex items-center gap-1"> | |
<div className="w-2 h-2 bg-green-500 rounded-full"></div> | |
In Stock | |
</span> | |
<span>Free Shipping</span> | |
</div> | |
{/* Stock Info */} | |
{product.stock <= 5 && product.stock > 0 && ( | |
<p className="text-xs text-orange-500 mt-2 font-medium" data-testid={`stock-warning-${product.id}`}> | |
Only {product.stock} left in stock | |
</p> | |
)} | |
{product.stock === 0 && ( | |
<p className="text-xs text-red-500 mt-2 font-medium" data-testid={`out-of-stock-${product.id}`}> | |
Out of stock | |
</p> | |
)} | |
</CardContent> | |
</Card> | |
</Link> | |
); | |
} | |