x
This commit is contained in:
1 parent
f7ac94ca0d
commit
335f3b4f59
41 files changed
+809
-413
No files matched your search
@@ -1,11 +1,14 @@
|
||||
"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 {
|
||||
@@ -14,14 +17,27 @@ interface ProductCardProps {
|
||||
|
||||
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.scent.name
|
||||
const imageUrl = product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName)
|
||||
const firstPrice = product.variants[0]?.price ?? product.price
|
||||
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 (
|
||||
<article className="ui-card ui-card-hover group flex h-full flex-col overflow-hidden">
|
||||
<div className="relative aspect-[4/3] overflow-hidden bg-surface-page">
|
||||
<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
|
||||
@@ -32,47 +48,84 @@ export default function ProductCard({ product }: ProductCardProps) {
|
||||
className="h-full w-full object-cover transition duration-700 group-hover:scale-105"
|
||||
/>
|
||||
{categoryName && (
|
||||
<span className="ui-status-badge absolute left-4 top-4 border-line-primary/20 bg-surface-card/90 px-3 py-1 text-[10px] uppercase tracking-widest text-text-main shadow-sm">
|
||||
<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 gap-4 p-5">
|
||||
<div className="space-y-1">
|
||||
<h3 className="line-clamp-2 text-lg font-semibold leading-snug text-text-main">
|
||||
<Link href={`/products/${product.id}`} className="transition hover:text-brand-primary">
|
||||
<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>
|
||||
{product.scent.name && (
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-muted-text/70">
|
||||
{product.scent.name}
|
||||
{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="flex flex-wrap gap-2">
|
||||
{product.variants.slice(0, 3).map((variant) => (
|
||||
<span key={variant.id} className="rounded-full bg-surface-page px-3 py-1 text-xs font-semibold text-muted-text">
|
||||
{variant.size}
|
||||
</span>
|
||||
))}
|
||||
<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.size}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-auto flex items-center justify-between gap-4">
|
||||
<span className="text-lg font-bold text-brand-primary">{formatPrice(firstPrice)}</span>
|
||||
<Link
|
||||
href={`/products/${product.id}`}
|
||||
className="ui-button-primary px-4 py-2 text-[10px]"
|
||||
>
|
||||
View
|
||||
</Link>
|
||||
<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.size,
|
||||
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>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user