Full website
This commit is contained in:
1 parent
807c43ece5
commit
ea9f33f60e
40 files changed
+4503
-1202
No files matched your search
@@ -1,15 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { getCategoryImage, formatPrice, type Product, type TranslatableString } from '@homaroma/types'
|
||||
import { getCategoryImage, formatPrice, type Product } from '@homaroma/types'
|
||||
import { useCartStore } from '../store/cartStore'
|
||||
|
||||
/** Resolve TranslatableString → plain string */
|
||||
function resolveStr(v: TranslatableString | null | undefined, lang = 'th'): string {
|
||||
if (!v) return ''
|
||||
if (typeof v === 'string') return v
|
||||
return v[lang] ?? v['th'] ?? v['en'] ?? Object.values(v)[0] ?? ''
|
||||
}
|
||||
import { resolveLocalized } from '../lib/localization'
|
||||
|
||||
interface Props {
|
||||
product: Product
|
||||
@@ -20,129 +15,134 @@ export default function AddToCartSection({ product }: Props) {
|
||||
const { addToCart, toggleCart } = useCartStore()
|
||||
|
||||
const currentVariant = product.variants[selectedVariant]
|
||||
const categoryName = resolveLocalized(product.category.name)
|
||||
const productName = resolveLocalized(product.name) || product.scent.name
|
||||
const description = resolveLocalized(product.description)
|
||||
|
||||
const handleAddToCart = () => {
|
||||
if (!currentVariant) return
|
||||
|
||||
addToCart({
|
||||
productId: product.id,
|
||||
variantId: currentVariant.id,
|
||||
quantity: 1,
|
||||
price: currentVariant.price || product.price || 0,
|
||||
productName: product.scent.name,
|
||||
productName,
|
||||
variantSize: currentVariant.size,
|
||||
productImage: product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.th || (product.category.name as any)?.en || ''),
|
||||
productImage: product.images?.[0]?.url || product.category.imageUrl || getCategoryImage(categoryName),
|
||||
})
|
||||
toggleCart(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Desktop Info Panel */}
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-2">
|
||||
<p className="text-cyan-glow uppercase tracking-[0.3em] text-xs font-semibold opacity-80">
|
||||
{typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.th || (product.category.name as any)?.en || ''}
|
||||
{categoryName && (
|
||||
<p className="text-xs font-bold uppercase tracking-[0.28em] text-pop-pink">
|
||||
{categoryName}
|
||||
</p>
|
||||
)}
|
||||
<h1 className="text-4xl font-semibold leading-tight text-pop-black md:text-6xl">
|
||||
{productName}
|
||||
</h1>
|
||||
<p className="text-sm font-semibold uppercase tracking-widest text-gray-500">
|
||||
Premium Collection
|
||||
</p>
|
||||
<h1 className="text-5xl font-bold leading-tight">{product.scent.name}</h1>
|
||||
<p className="text-slate-400 text-sm uppercase tracking-widest">Premium Collection</p>
|
||||
</div>
|
||||
|
||||
{resolveStr(product.description) && (
|
||||
<p className="text-slate-300 leading-relaxed">{resolveStr(product.description)}</p>
|
||||
{description && <p className="leading-relaxed text-gray-600">{description}</p>}
|
||||
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-4xl font-bold text-pop-pink">
|
||||
{formatPrice(currentVariant?.price ?? product.price)}
|
||||
</span>
|
||||
{currentVariant && <span className="text-sm text-gray-500">/ {currentVariant.size}</span>}
|
||||
</div>
|
||||
|
||||
{product.variants.length > 0 && (
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs font-bold uppercase tracking-widest text-gray-500">Select Size</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{product.variants.map((variant, index) => (
|
||||
<button
|
||||
key={variant.id}
|
||||
onClick={() => setSelectedVariant(index)}
|
||||
className={`rounded-xl border px-5 py-3 text-sm font-bold uppercase tracking-widest transition ${
|
||||
selectedVariant === index
|
||||
? 'border-pop-pink bg-pop-pink text-white shadow-brutal'
|
||||
: 'border-gray-200 bg-white text-gray-600 hover:border-pop-pink hover:text-pop-pink'
|
||||
}`}
|
||||
>
|
||||
{variant.size}
|
||||
<span className={`mt-1 block text-[10px] ${selectedVariant === index ? 'text-white/80' : 'text-gray-400'}`}>
|
||||
{formatPrice(variant.price)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Price */}
|
||||
<div className="flex items-baseline gap-3">
|
||||
<span className="text-4xl font-bold text-cyan-glow">
|
||||
{formatPrice(currentVariant?.price)}
|
||||
</span>
|
||||
{currentVariant && (
|
||||
<span className="text-slate-400 text-sm">/ {currentVariant.size}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Variant Selector */}
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs uppercase tracking-widest text-slate-400 font-semibold">Select Size</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{product.variants.map((v, i) => (
|
||||
<button
|
||||
key={v.id}
|
||||
onClick={() => setSelectedVariant(i)}
|
||||
className={`px-5 py-2.5 rounded-lg text-sm font-bold uppercase tracking-widest transition-all duration-300 ${
|
||||
selectedVariant === i
|
||||
? 'bg-cyan-glow text-night-900 shadow-[0_0_15px_rgba(102,252,241,0.4)]'
|
||||
: 'glass hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
{v.size}
|
||||
<span className={`block text-[10px] mt-0.5 ${selectedVariant === i ? 'text-night-800' : 'text-slate-400'}`}>
|
||||
{formatPrice(v.price)}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex gap-4">
|
||||
<div className="hidden gap-4 md:flex">
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-4 bg-white text-night-900 font-bold rounded-lg tracking-widest uppercase hover:bg-cyan-glow hover:shadow-[0_0_25px_rgba(102,252,241,0.4)] transition-all duration-500"
|
||||
className="flex-1 rounded-full bg-pop-black px-8 py-4 font-bold uppercase tracking-widest text-white transition hover:bg-pop-pink"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
<button className="glass p-4 rounded-lg hover:bg-white/10 transition-all">
|
||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
|
||||
</svg>
|
||||
</button>
|
||||
{categoryName && (
|
||||
<Link
|
||||
href={`/category/${product.category.id}`}
|
||||
className="rounded-full border border-gray-200 bg-white px-8 py-4 font-bold uppercase tracking-widest text-gray-600 transition hover:border-pop-pink hover:text-pop-pink"
|
||||
>
|
||||
{categoryName}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Meta */}
|
||||
<div className="border-t border-white/5 pt-6 grid grid-cols-2 gap-4 text-sm text-slate-400">
|
||||
<div className="grid grid-cols-2 gap-4 border-t border-gray-200 pt-6 text-sm text-gray-500">
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1">Scent</span>
|
||||
<span className="text-white font-medium">{product.scent.name}</span>
|
||||
<span className="mb-1 block text-xs font-bold uppercase tracking-widest text-gray-400">Scent</span>
|
||||
<span className="font-semibold text-pop-black">{product.scent.name}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs uppercase tracking-widest block mb-1">Category</span>
|
||||
<Link href={`/category/${product.category.id}`} className="text-white font-medium hover:text-cyan-glow transition-colors">
|
||||
{typeof product.category.name === 'string' ? product.category.name : (product.category.name as any)?.th || (product.category.name as any)?.en || ''}
|
||||
<span className="mb-1 block text-xs font-bold uppercase tracking-widest text-gray-400">Category</span>
|
||||
<Link href={`/category/${product.category.id}`} className="font-semibold text-pop-black transition hover:text-pop-pink">
|
||||
{categoryName}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Sticky Add to Cart Bar */}
|
||||
<div className="md:hidden fixed bottom-0 left-0 right-0 p-4 bg-night-950/95 backdrop-blur-xl border-t border-white/10 z-40 animate-fade-in flex flex-col gap-3 shadow-[0_-10px_30px_rgba(0,0,0,0.5)]">
|
||||
<div className="fixed bottom-0 left-0 right-0 z-40 border-t border-gray-200 bg-white/95 p-4 shadow-[0_-10px_30px_rgba(0,0,0,0.08)] backdrop-blur md:hidden">
|
||||
{product.variants.length > 1 && (
|
||||
<div className="flex gap-2 overflow-x-auto pb-1" style={{ scrollbarWidth: 'none' }}>
|
||||
{product.variants.map((v, i) => (
|
||||
<div className="mb-3 flex gap-2 overflow-x-auto pb-1" style={{ scrollbarWidth: 'none' }}>
|
||||
{product.variants.map((variant, index) => (
|
||||
<button
|
||||
key={`mobile-v-${v.id}`}
|
||||
onClick={() => setSelectedVariant(i)}
|
||||
className={`flex-shrink-0 px-4 py-2 rounded-lg text-[10px] font-bold uppercase tracking-widest transition-all border whitespace-nowrap ${
|
||||
selectedVariant === i
|
||||
? 'bg-cyan-glow/10 border-cyan-glow text-cyan-glow'
|
||||
: 'bg-white/5 border-white/10 text-slate-400'
|
||||
key={`mobile-v-${variant.id}`}
|
||||
onClick={() => setSelectedVariant(index)}
|
||||
className={`flex-shrink-0 rounded-full border px-4 py-2 text-[10px] font-bold uppercase tracking-widest transition ${
|
||||
selectedVariant === index
|
||||
? 'border-pop-pink bg-pop-pink text-white'
|
||||
: 'border-gray-200 bg-white text-gray-500'
|
||||
}`}
|
||||
>
|
||||
{v.size}
|
||||
{variant.size}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[10px] uppercase tracking-widest text-slate-400">
|
||||
<span className="text-[10px] font-bold uppercase tracking-widest text-gray-400">
|
||||
{product.variants.length > 1 ? currentVariant?.size : 'Total Price'}
|
||||
</span>
|
||||
<span className="text-xl font-bold text-cyan-glow">{formatPrice(currentVariant?.price)}</span>
|
||||
<span className="text-xl font-bold text-pop-pink">{formatPrice(currentVariant?.price ?? product.price)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleAddToCart}
|
||||
className="flex-1 py-3.5 bg-cyan-glow text-night-900 font-bold rounded-lg tracking-widest uppercase hover:brightness-110 shadow-[0_0_15px_rgba(102,252,241,0.3)] transition-all"
|
||||
className="flex-1 rounded-full bg-pop-black px-5 py-3.5 font-bold uppercase tracking-widest text-white transition hover:bg-pop-pink"
|
||||
>
|
||||
Add to Cart
|
||||
</button>
|
||||
|
||||
Reference in new issue
Block a user