Files
homaroma/apps/website/src/components/ProductCard.tsx
T
2026-06-23 20:02:29 -04:00

132 lines
5.1 KiB
TypeScript

"use client"
import { useState } from 'react'
import Image from 'next/image'
import Link from './LocalizedLink'
import { ShoppingBag } from 'lucide-react'
import type { Product } from '@homaroma/types'
import { getCategoryImage, formatPrice } from '@homaroma/types'
import { resolveCategoryName, resolveLocalized } from '../lib/localization'
import { useLang } from '../hooks/useLang'
import { useCartStore } from '../store/cartStore'
import WishlistButton from './WishlistButton'
interface ProductCardProps {
product: Product
}
export default function ProductCard({ product }: ProductCardProps) {
const lang = useLang()
const { addToCart } = useCartStore()
const [selectedVariant, setSelectedVariant] = useState(product.variants?.[0] || null)
const categoryName = resolveCategoryName(product.category, lang)
const productName = resolveLocalized(product.name, lang) || product.brand.name
const imageUrl = product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName)
const displayPrice = selectedVariant?.price ?? product.variants[0]?.price ?? product.price
const addToCartLabels: Record<string, string> = {
th: 'เพิ่มลงตะกร้า',
en: 'Add to Cart',
ja: 'カートに追加',
zh: '加入购物车',
vi: 'Thêm vào giỏ',
ko: '장바구니에 담기'
}
const addToCartText = addToCartLabels[lang] || addToCartLabels.en
return (
<div className="ui-card ui-card-hover group flex h-full cursor-pointer flex-col overflow-hidden p-2">
<div className="relative aspect-[4/3] overflow-hidden rounded-xl bg-surface-page md:h-64 md:aspect-auto">
<WishlistButton product={product} />
<Link href={`/products/${product.id}`} className="block h-full w-full">
<Image
src={imageUrl}
alt={productName}
fill
unoptimized
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>
</div>
<div className="flex flex-1 flex-col p-4">
<div className="mb-2">
<h3 className="line-clamp-1 text-base font-bold leading-tight text-text-main transition-colors group-hover:text-brand-primary md:text-lg">
<Link href={`/products/${product.id}`}>
{productName}
</Link>
</h3>
{categoryName && (
<p className="mt-1 line-clamp-1 text-[10px] font-semibold uppercase tracking-widest text-muted-text md:text-xs">
{categoryName}
</p>
)}
</div>
{product.variants.length > 0 && (
<div className="mb-1 mt-2 flex flex-wrap gap-1.5">
{product.variants.map((variant) => {
const isSelected = selectedVariant?.id === variant.id
return (
<button
key={variant.id}
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedVariant(variant)
}}
className={`rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-wider transition-all cursor-pointer ${
isSelected
? 'border-brand-primary bg-brand-primary text-surface-card shadow-sm'
: 'border-line-primary/20 bg-surface-card text-text-main hover:border-brand-primary/50'
}`}
>
{[variant.option1, variant.option2, variant.option3].filter(Boolean).join(' / ')}
</button>
)
})}
</div>
)}
<div className="mt-auto flex flex-col gap-2 pt-3">
<div className="flex items-center justify-between">
<span className="text-sm font-black text-brand-primary md:text-lg">
{formatPrice(displayPrice)}
</span>
</div>
{selectedVariant && (
<button
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
addToCart({
productId: product.id,
variantId: selectedVariant.id,
quantity: 1,
price: selectedVariant.price || 0,
productName: productName,
variantSize: [selectedVariant.option1, selectedVariant.option2, selectedVariant.option3].filter(Boolean).join(' / '),
productImage: imageUrl
})
}}
className="ui-button-primary w-full gap-1.5 rounded-xl py-2.5 shadow-sm"
>
<ShoppingBag className="h-3.5 w-3.5" />
{addToCartText}
</button>
)}
</div>
</div>
</div>
)
}