x
This commit is contained in:
1 parent
87f4dac21f
commit
7b29a6f98e
2 files changed
+82
-17
No files matched your search
@@ -46,6 +46,11 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
const [selectedVariant, _setSelectedVariant] = useState(product.variants?.[0] || null);
|
const [selectedVariant, _setSelectedVariant] = useState(product.variants?.[0] || null);
|
||||||
const displayName = resolveStr(product.name, i18n.language) || (product.brand ? product.brand.name : '');
|
const displayName = resolveStr(product.name, i18n.language) || (product.brand ? product.brand.name : '');
|
||||||
const displayCategoryName = categoryNameOverride || resolveStr(product.category.name, i18n.language);
|
const displayCategoryName = categoryNameOverride || resolveStr(product.category.name, i18n.language);
|
||||||
|
const fallbackImage = getCategoryImage(displayCategoryName);
|
||||||
|
const images = (product.images?.length ?? 0) > 0
|
||||||
|
? product.images!.slice(0, 3).map(img => img.url)
|
||||||
|
: [fallbackImage];
|
||||||
|
const hasMultipleImages = images.length > 1;
|
||||||
|
|
||||||
const handleWishlistClick = (e: React.MouseEvent) => {
|
const handleWishlistClick = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -66,27 +71,54 @@ export default function ProductCard({ product, enableQuickShop = false, category
|
|||||||
onClick={handleCardClick}
|
onClick={handleCardClick}
|
||||||
className="ui-shell-card ui-shell-card-hover group flex cursor-pointer flex-col overflow-hidden p-2"
|
className="ui-shell-card ui-shell-card-hover group flex cursor-pointer flex-col overflow-hidden p-2"
|
||||||
>
|
>
|
||||||
<div className="relative aspect-square overflow-hidden rounded-xl bg-pop-black/5">
|
<div className="relative aspect-square overflow-hidden rounded-xl bg-pop-black/5 group/image">
|
||||||
<img
|
<div className="ui-chip absolute left-3 top-3 z-20 border-pop-bg bg-pop-bg/90 px-2.5 py-1 text-[10px] text-pop-black shadow-sm backdrop-blur-sm pointer-events-none">
|
||||||
src={product.images?.[0]?.url || getCategoryImage(displayCategoryName)}
|
|
||||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
|
||||||
alt={displayName}
|
|
||||||
onError={(e) => {
|
|
||||||
(e.target as HTMLImageElement).style.display = 'none';
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="ui-chip absolute left-3 top-3 border-pop-bg bg-pop-bg/90 px-2.5 py-1 text-[10px] text-pop-black shadow-sm backdrop-blur-sm">
|
|
||||||
{displayCategoryName}
|
{displayCategoryName}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleWishlistClick}
|
onClick={handleWishlistClick}
|
||||||
className="absolute right-3 top-3 z-10 rounded-full bg-pop-bg/90 p-2 shadow-sm backdrop-blur-sm transition-transform hover:scale-110 cursor-pointer"
|
className="absolute right-3 top-3 z-20 rounded-full bg-pop-bg/90 p-2 shadow-sm backdrop-blur-sm transition-transform hover:scale-110 cursor-pointer"
|
||||||
>
|
>
|
||||||
<Heart
|
<Heart
|
||||||
className={`w-4 h-4 transition-colors ${isWished ? 'fill-pop-pink text-pop-pink' : 'text-pop-black/40 hover:text-pop-pink'}`}
|
className={`w-4 h-4 transition-colors ${isWished ? 'fill-pop-pink text-pop-pink' : 'text-pop-black/40 hover:text-pop-pink'}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{hasMultipleImages ? (
|
||||||
|
<>
|
||||||
|
{/* Desktop Hover Effect */}
|
||||||
|
<div className="hidden md:block absolute inset-0 w-full h-full">
|
||||||
|
<img src={images[0]} alt={displayName} className="absolute inset-0 w-full h-full object-cover group-hover/image:opacity-0 transition-opacity duration-700" />
|
||||||
|
<img src={images[1]} alt={displayName} className="absolute inset-0 w-full h-full object-cover opacity-0 group-hover/image:opacity-100 group-hover/image:scale-105 transition-all duration-700" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Native Slider */}
|
||||||
|
<div className="md:hidden relative w-full h-full" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar">
|
||||||
|
{images.map((img, idx) => (
|
||||||
|
<div key={idx} className="relative w-full h-full flex-none snap-center block" onClick={handleCardClick}>
|
||||||
|
<img src={img} alt={`${displayName} ${idx + 1}`} className="absolute inset-0 w-full h-full object-cover" />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-3 left-0 right-0 flex justify-center gap-1.5 z-10 pointer-events-none">
|
||||||
|
{images.map((_, idx) => (
|
||||||
|
<div key={idx} className="w-1.5 h-1.5 rounded-full bg-white/80 shadow-[0_1px_2px_rgba(0,0,0,0.3)]" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<img
|
||||||
|
src={images[0]}
|
||||||
|
className="w-full h-full object-cover group-hover/image:scale-105 transition-transform duration-700"
|
||||||
|
alt={displayName}
|
||||||
|
onError={(e) => {
|
||||||
|
(e.target as HTMLImageElement).style.display = 'none';
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col p-4">
|
<div className="flex flex-1 flex-col p-4">
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export default function ProductCard({ product }: ProductCardProps) {
|
|||||||
const productName = resolveLocalized(product.name, lang) || product.brand.name
|
const productName = resolveLocalized(product.name, lang) || product.brand.name
|
||||||
const productSlug = generateProductSlug(productName, product.id)
|
const productSlug = generateProductSlug(productName, product.id)
|
||||||
const imageUrl = product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName)
|
const imageUrl = product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName)
|
||||||
|
const images = (product.images?.length ?? 0) > 0
|
||||||
|
? product.images!.slice(0, 3).map(img => img.url)
|
||||||
|
: [imageUrl];
|
||||||
|
const hasMultipleImages = images.length > 1;
|
||||||
const displayPrice = selectedVariant?.price ?? product.variants[0]?.price ?? 0
|
const displayPrice = selectedVariant?.price ?? product.variants[0]?.price ?? 0
|
||||||
|
|
||||||
const addToCartLabels: Record<string, string> = {
|
const addToCartLabels: Record<string, string> = {
|
||||||
@@ -59,21 +63,50 @@ export default function ProductCard({ product }: ProductCardProps) {
|
|||||||
return (
|
return (
|
||||||
<div className="ui-card ui-card-hover group flex h-full cursor-pointer flex-col overflow-hidden p-2">
|
<div className="ui-card ui-card-hover group flex h-full cursor-pointer flex-col overflow-hidden p-2">
|
||||||
<div className="relative aspect-square overflow-hidden rounded-xl bg-surface-page">
|
<div className="relative aspect-square overflow-hidden rounded-xl bg-surface-page">
|
||||||
|
<div className="absolute right-2 top-2 z-20">
|
||||||
<WishlistButton product={product} />
|
<WishlistButton product={product} />
|
||||||
|
</div>
|
||||||
|
{categoryName && (
|
||||||
|
<span className="ui-chip absolute left-3 top-3 z-20 border-line-primary/20 bg-surface-card/90 px-2.5 py-1 text-[10px] text-text-main shadow-sm backdrop-blur-sm pointer-events-none">
|
||||||
|
{categoryName}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{hasMultipleImages ? (
|
||||||
|
<>
|
||||||
|
{/* Desktop Hover Effect */}
|
||||||
|
<Link href={`/products/${productSlug}`} className="hidden md:block absolute inset-0 w-full h-full">
|
||||||
|
<Image src={images[0]} alt={productName} fill unoptimized className="absolute inset-0 h-full w-full object-cover transition duration-700 group-hover:opacity-0" />
|
||||||
|
<Image src={images[1]} alt={productName} fill unoptimized className="absolute inset-0 h-full w-full object-cover transition duration-700 opacity-0 group-hover:opacity-100 group-hover:scale-105" />
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* Mobile Native Slider */}
|
||||||
|
<div className="md:hidden relative w-full h-full">
|
||||||
|
<div className="flex w-full h-full overflow-x-auto snap-x snap-mandatory hide-scrollbar">
|
||||||
|
{images.map((img, idx) => (
|
||||||
|
<Link key={idx} href={`/products/${productSlug}`} className="relative w-full h-full flex-none snap-center block">
|
||||||
|
<Image src={img} alt={`${productName} ${idx + 1}`} fill unoptimized className="absolute inset-0 h-full w-full object-cover" />
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="absolute bottom-3 left-0 right-0 flex justify-center gap-1.5 z-10 pointer-events-none">
|
||||||
|
{images.map((_, idx) => (
|
||||||
|
<div key={idx} className="w-1.5 h-1.5 rounded-full bg-white/80 shadow-[0_1px_2px_rgba(0,0,0,0.3)]" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
<Link href={`/products/${productSlug}`} className="block h-full w-full">
|
<Link href={`/products/${productSlug}`} className="block h-full w-full">
|
||||||
<Image
|
<Image
|
||||||
src={imageUrl}
|
src={images[0]}
|
||||||
alt={productName}
|
alt={productName}
|
||||||
fill
|
fill
|
||||||
unoptimized
|
unoptimized
|
||||||
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
|
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
|
||||||
/>
|
/>
|
||||||
{categoryName && (
|
|
||||||
<span className="ui-chip absolute left-3 top-3 border-line-primary/20 bg-surface-card/90 px-2.5 py-1 text-[10px] text-text-main shadow-sm backdrop-blur-sm">
|
|
||||||
{categoryName}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-1 flex-col p-4">
|
<div className="flex flex-1 flex-col p-4">
|
||||||
|
|||||||
Reference in new issue
Block a user