Spaces:
Running
Running
File size: 7,242 Bytes
b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e 1684141 b89a86e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
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>
);
}
|