This commit is contained in:
Your Name committed 2026-06-21 02:09:26 -04:00
1 parent 3cacaacbeb
commit 8b73b8dd2f
52 files changed
+265 -265

No files matched your search

+4 -4
View File
@@ -32,7 +32,7 @@ export default function CartDrawer() {
/>
{/* Drawer */}
<div className="relative w-full max-w-md h-full bg-white shadow-2xl flex flex-col animate-slide-left">
<div className="relative w-full max-w-md h-full bg-spa-light shadow-2xl flex flex-col animate-slide-left">
{/* Header */}
<div className="p-6 border-b border-spa-stone/10 flex items-center justify-between bg-spa-light">
<h2 className="text-xl font-bold flex items-center gap-2 text-pop-black">
@@ -65,7 +65,7 @@ export default function CartDrawer() {
</div>
) : (
items.map((item) => (
<div key={item.cartItemId} className="flex gap-4 p-4 rounded-2xl bg-white border border-spa-stone/10 shadow-sm group relative">
<div key={item.cartItemId} className="flex gap-4 p-4 rounded-2xl bg-spa-light border border-spa-stone/10 shadow-sm group relative">
<div className="w-18 h-22 rounded-lg bg-spa-sage/30 overflow-hidden flex-shrink-0" style={{ width: '72px', height: '88px' }}>
<img
src={item.productImage || '/images/fallback-product.svg'}
@@ -225,7 +225,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose:
value={couponInput}
onChange={e => setCouponInput(e.target.value.toUpperCase())}
placeholder={t('cart.couponPlaceholder', 'Discount Code')}
className="flex-1 bg-white border border-spa-stone/20 rounded-lg px-3 py-2 text-sm text-pop-black focus:border-spa-forest outline-none uppercase tracking-widest placeholder:text-spa-stone/50"
className="flex-1 bg-spa-light border border-spa-stone/20 rounded-lg px-3 py-2 text-sm text-pop-black focus:border-spa-forest outline-none uppercase tracking-widest placeholder:text-spa-stone/50"
/>
<button
onClick={handleApplyCoupon}
@@ -292,7 +292,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose:
<Link
to="/checkout"
onClick={onClose}
className="flex items-center justify-center gap-2 w-full py-4 bg-spa-forest text-white font-bold rounded-xl uppercase tracking-widest text-sm hover:bg-spa-stone hover:shadow-lg transition-all duration-300 group"
className="flex items-center justify-center gap-2 w-full py-4 bg-spa-forest text-spa-light font-bold rounded-xl uppercase tracking-widest text-sm hover:bg-spa-stone hover:shadow-lg transition-all duration-300 group"
>
<ShoppingBag className="w-4 h-4" />
ดำเนินการชำระเงิน
+1 -1
View File
@@ -16,7 +16,7 @@ export default function DynamicBanner({ payload }: DynamicBannerProps) {
const buttonText = getLocalizedField(payload.buttonText, lang);
return (
<section className="ui-section-band bg-white relative overflow-hidden">
<section className="ui-section-band bg-spa-light relative overflow-hidden">
{linkUrl ? (
<Link to={linkUrl} className="ui-banner-shell group block cursor-pointer">
<div className="absolute inset-0 z-0">
@@ -26,7 +26,7 @@ export default function DynamicCategories({ payload, categories, loading }: Dyna
}, [categories, categoryIds]);
return (
<section id="categories" className="ui-section-band relative overflow-hidden bg-white">
<section id="categories" className="ui-section-band relative overflow-hidden bg-spa-light">
<div className="ui-section-inner space-y-16 relative z-10">
<div className="space-y-4 text-center">
<h2 className="text-4xl font-bold tracking-tight text-pop-black md:text-5xl">
@@ -40,7 +40,7 @@ export default function DynamicCategories({ payload, categories, loading }: Dyna
{loading ? (
<div className="grid grid-cols-2 gap-6 md:grid-cols-5">
{Array.from({ length: 5 }).map((_, i) => (
<div key={i} className="ui-shell-card h-80 animate-pulse bg-gray-50" />
<div key={i} className="ui-shell-card h-80 animate-pulse bg-spa-sage" />
))}
</div>
) : (
@@ -58,12 +58,12 @@ export default function DynamicCategories({ payload, categories, loading }: Dyna
alt={getLocalizedField(cat.name, lang)}
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none' }}
/>
<div className="absolute bottom-6 left-6 right-6 z-20 text-white">
<div className="absolute bottom-6 left-6 right-6 z-20 text-spa-light">
<span className="mb-1 block text-xl font-semibold">
{getLocalizedField(cat.name, localStorage.getItem('app_lang') || 'en')}
</span>
{cat._count && (
<span className="text-sm font-medium text-white/80">
<span className="text-sm font-medium text-spa-light">
{cat._count.products} Products
</span>
)}
@@ -100,7 +100,7 @@ export default function DynamicEditorialCarousel({ payload }: DynamicEditorialCa
<div className="pt-2">
<Link
to={slide.buttonLink}
className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-spa-gold hover:text-white transition-colors group/link"
className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-spa-gold hover:text-spa-light transition-colors group/link"
>
{slide.buttonText}
<span className="group-hover/link:translate-x-1 transition-transform">&rarr;</span>
@@ -73,7 +73,7 @@ export default function DynamicEditorialTile({ payload }: DynamicEditorialTilePr
<div className="pt-4">
<Link
to={buttonLink}
className="ui-chip border-current px-8 py-3.5 hover:bg-spa-gold hover:border-spa-gold hover:text-white"
className="ui-chip border-current px-8 py-3.5 hover:bg-spa-gold hover:border-spa-gold hover:text-spa-light"
>
{buttonText}
</Link>
@@ -28,7 +28,7 @@ export default function DynamicHeroArticle({ payload }: DynamicHeroArticleProps)
<div className="ui-hero-overlay" />
</div>
)}
<div className="ui-hero-content text-white">
<div className="ui-hero-content text-spa-light">
{category && (
<span className="mb-4 block text-sm font-bold uppercase tracking-widest text-spa-gold">
{category}
@@ -70,7 +70,7 @@ export default function DynamicNewsletter({ payload }: DynamicNewsletterProps) {
</div>
{status === 'success' ? (
<div className="p-6 bg-white/5 border border-spa-gold/30 rounded-2xl animate-pop-in space-y-2">
<div className="p-6 bg-spa-light border border-spa-gold/30 rounded-2xl animate-pop-in space-y-2">
<h4 className="text-spa-gold font-semibold text-lg">{t('newsletter.thankYou')}</h4>
<p className="text-xs text-spa-light/75">{t('newsletter.successMessage')}</p>
</div>
+3 -3
View File
@@ -26,13 +26,13 @@ export default function DynamicProducts({ payload, categories = [] }: DynamicPro
return (
<section className="ui-section-band relative bg-pop-bg">
<div className="ui-section-inner relative z-10 space-y-12">
<div className="flex items-end justify-between gap-6 border-b border-gray-200 pb-6 md:flex-row">
<div className="flex items-end justify-between gap-6 border-b border-spa-stone/20 pb-6 md:flex-row">
<div>
<h2 className="text-4xl font-bold text-pop-black md:text-5xl">
{title || "Featured Picks"}
</h2>
{subtitle && (
<p className="text-gray-500 mt-2">{subtitle}</p>
<p className="text-spa-stone mt-2">{subtitle}</p>
)}
</div>
<Link
@@ -45,7 +45,7 @@ export default function DynamicProducts({ payload, categories = [] }: DynamicPro
</div>
{products.length === 0 ? (
<div className="py-12 text-center text-gray-500">
<div className="py-12 text-center text-spa-stone">
No products found in this section
</div>
) : layout === 'carousel' ? (
@@ -14,7 +14,7 @@ export default function DynamicQuoteCallout({ payload }: DynamicQuoteCalloutProp
const role = getLocalizedField(payload.role, lang);
return (
<section className="ui-section-band bg-white">
<section className="ui-section-band bg-spa-light">
<div className="ui-section-inner text-center">
<svg className="w-12 h-12 mx-auto text-spa-gold/40 mb-8" fill="currentColor" viewBox="0 0 24 24">
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.638-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.638-3.996 5.849h3.983v10h-9.983z" />
+2 -2
View File
@@ -20,7 +20,7 @@ export default function DynamicText({ payload }: DynamicTextProps) {
}[textAlign] || 'text-left';
return (
<section className="ui-section-band bg-white relative">
<section className="ui-section-band bg-spa-light relative">
<div className={`ui-section-inner space-y-6 ${alignClass}`}>
{title && (
<h2 className="font-serif text-3xl font-semibold text-spa-forest md:text-4xl">
@@ -28,7 +28,7 @@ export default function DynamicText({ payload }: DynamicTextProps) {
</h2>
)}
<div
className="whitespace-pre-wrap text-base font-light leading-relaxed text-gray-600 md:text-lg"
className="whitespace-pre-wrap text-base font-light leading-relaxed text-spa-stone md:text-lg"
dangerouslySetInnerHTML={{ __html: content }}
/>
</div>
+2 -2
View File
@@ -17,13 +17,13 @@ export default function EmptyState({
actionClick
}: EmptyStateProps) {
const ButtonContent = () => (
<span className="inline-block mt-6 px-8 py-3 bg-spa-forest text-white rounded-xl text-sm font-bold uppercase tracking-widest hover:bg-spa-stone hover:shadow-lg transition-all cursor-pointer">
<span className="inline-block mt-6 px-8 py-3 bg-spa-forest text-spa-light rounded-xl text-sm font-bold uppercase tracking-widest hover:bg-spa-stone hover:shadow-lg transition-all cursor-pointer">
{actionText}
</span>
)
return (
<div className="flex flex-col items-center justify-center space-y-4 text-center p-12 bg-white border border-spa-stone/10 rounded-2xl shadow-sm max-w-lg mx-auto my-8">
<div className="flex flex-col items-center justify-center space-y-4 text-center p-12 bg-spa-light border border-spa-stone/10 rounded-2xl shadow-sm max-w-lg mx-auto my-8">
<div className="w-16 h-16 bg-spa-sage/30 rounded-full flex items-center justify-center mb-2">
<PackageOpen className="w-8 h-8 text-spa-forest" />
</div>
+2 -2
View File
@@ -13,7 +13,7 @@ export default function ErrorState({
showHomeButton = true
}: ErrorStateProps) {
const content = (
<div className="flex flex-col items-center justify-center space-y-4 text-center p-8 bg-white border border-rose-500/20 rounded-2xl shadow-sm max-w-md mx-auto">
<div className="flex flex-col items-center justify-center space-y-4 text-center p-8 bg-spa-light border border-rose-500/20 rounded-2xl shadow-sm max-w-md mx-auto">
<div className="w-12 h-12 bg-rose-500/10 rounded-full flex items-center justify-center">
<AlertCircle className="w-6 h-6 text-rose-500" />
</div>
@@ -21,7 +21,7 @@ export default function ErrorState({
{showHomeButton && (
<Link
to="/"
className="mt-4 px-6 py-2 bg-spa-forest text-white rounded-lg text-sm font-bold uppercase tracking-widest hover:bg-spa-stone transition-colors"
className="mt-4 px-6 py-2 bg-spa-forest text-spa-light rounded-lg text-sm font-bold uppercase tracking-widest hover:bg-spa-stone transition-colors"
>
Return Home
</Link>
+22 -22
View File
@@ -71,31 +71,31 @@ export default function Footer({ categories = [] }: FooterProps) {
}, [])
return (
<footer className="bg-night-900 px-6 py-20 border-t border-white/5">
<footer className="bg-night-900 px-6 py-20 border-t border-spa-light">
<div className="ui-section-inner grid grid-cols-1 gap-12 md:grid-cols-4">
<div className="space-y-6">
<div className="text-2xl font-bold tracking-widest">
{settings?.logoUrl ? <img src={settings.logoUrl} alt={brandName} className="h-8 md:h-10 object-contain" /> : brandName}
</div>
<p className="text-sm leading-relaxed text-slate-500">
<p className="text-sm leading-relaxed text-spa-stone">
{settings?.footerDescription || t('footer.catchphrase')}
</p>
<div className="flex gap-4">
{settings && (settings.facebookUrl || settings.instagramUrl || settings.tiktokUrl) ? (
<>
{settings.facebookUrl && (
<a href={settings.facebookUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-white/10 bg-white/5 text-[10px] text-white hover:bg-spa-gold hover:text-night-900">FB</a>
<a href={settings.facebookUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-spa-light bg-spa-light text-[10px] text-spa-light hover:bg-spa-gold hover:text-night-900">FB</a>
)}
{settings.instagramUrl && (
<a href={settings.instagramUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-white/10 bg-white/5 text-[10px] text-white hover:bg-spa-gold hover:text-night-900">IG</a>
<a href={settings.instagramUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-spa-light bg-spa-light text-[10px] text-spa-light hover:bg-spa-gold hover:text-night-900">IG</a>
)}
{settings.tiktokUrl && (
<a href={settings.tiktokUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-white/10 bg-white/5 text-[10px] text-white hover:bg-spa-gold hover:text-night-900">TT</a>
<a href={settings.tiktokUrl} target="_blank" rel="noreferrer" className="ui-chip h-9 w-9 border-spa-light bg-spa-light text-[10px] text-spa-light hover:bg-spa-gold hover:text-night-900">TT</a>
)}
</>
) : (
['IG', 'FB', 'TW', 'YT'].map((s) => (
<a key={s} href="#" className="ui-chip h-9 w-9 border-white/10 bg-white/5 text-[10px] text-white hover:bg-spa-gold hover:text-night-900">
<a key={s} href="#" className="ui-chip h-9 w-9 border-spa-light bg-spa-light text-[10px] text-spa-light hover:bg-spa-gold hover:text-night-900">
{s}
</a>
))
@@ -104,30 +104,30 @@ export default function Footer({ categories = [] }: FooterProps) {
</div>
<div className="space-y-4">
<h4 className="text-xs font-bold uppercase tracking-widest text-white">{t('footer.explore')}</h4>
<div className="flex flex-col space-y-2 text-sm text-slate-400">
<h4 className="text-xs font-bold uppercase tracking-widest text-spa-light">{t('footer.explore')}</h4>
<div className="flex flex-col space-y-2 text-sm text-spa-stone">
{exploreMenu.length > 0 ? (
exploreMenu.map(item => (
<Link key={item.id} to={item.url} className="hover:text-white transition-colors">{translateMenuLabel(item)}</Link>
<Link key={item.id} to={item.url} className="hover:text-spa-light transition-colors">{translateMenuLabel(item)}</Link>
))
) : (
<>
<Link to="/" className="hover:text-white transition-colors">{t('nav.home')}</Link>
<Link to="/shop" className="hover:text-white transition-colors">{t('nav.shop')}</Link>
<Link to="/scent-guide" className="hover:text-white transition-colors">{t('nav.scentGuide')}</Link>
<Link to="/about" className="hover:text-white transition-colors">{t('nav.about')}</Link>
<Link to="/contact" className="hover:text-white transition-colors">{t('nav.contact')}</Link>
<Link to="/faq" className="hover:text-white transition-colors">{t('nav.faq')}</Link>
<Link to="/" className="hover:text-spa-light transition-colors">{t('nav.home')}</Link>
<Link to="/shop" className="hover:text-spa-light transition-colors">{t('nav.shop')}</Link>
<Link to="/scent-guide" className="hover:text-spa-light transition-colors">{t('nav.scentGuide')}</Link>
<Link to="/about" className="hover:text-spa-light transition-colors">{t('nav.about')}</Link>
<Link to="/contact" className="hover:text-spa-light transition-colors">{t('nav.contact')}</Link>
<Link to="/faq" className="hover:text-spa-light transition-colors">{t('nav.faq')}</Link>
</>
)}
</div>
</div>
<div className="space-y-4">
<h4 className="text-xs font-bold uppercase tracking-widest text-white">{t('nav.collections')}</h4>
<div className="flex flex-col space-y-2 text-sm text-slate-400">
<h4 className="text-xs font-bold uppercase tracking-widest text-spa-light">{t('nav.collections')}</h4>
<div className="flex flex-col space-y-2 text-sm text-spa-stone">
{categories.map((c) => (
<Link key={c.id} to={`/category/${c.id}`} className="hover:text-white transition-colors">
<Link key={c.id} to={`/category/${c.id}`} className="hover:text-spa-light transition-colors">
{getLocalizedField(c.name, localStorage.getItem('app_lang') || 'en')}
</Link>
))}
@@ -135,17 +135,17 @@ export default function Footer({ categories = [] }: FooterProps) {
</div>
<div className="space-y-4">
<h4 className="text-xs font-bold uppercase tracking-widest text-white">
<h4 className="text-xs font-bold uppercase tracking-widest text-spa-light">
{settings?.footerSupportHeading || t('footer.support', 'Support & Legal')}
</h4>
<div className="flex flex-col space-y-2 text-sm text-slate-400">
<div className="flex flex-col space-y-2 text-sm text-spa-stone">
{supportMenu.map(item => (
<Link key={item.id} to={item.url} className="hover:text-white transition-colors">{translateMenuLabel(item)}</Link>
<Link key={item.id} to={item.url} className="hover:text-spa-light transition-colors">{translateMenuLabel(item)}</Link>
))}
</div>
<div className="pt-4">
<p className="text-[10px] text-slate-600 uppercase tracking-widest">
<p className="text-[10px] text-spa-stone uppercase tracking-widest">
{settings?.footerCopyrightText || `© ${new Date().getFullYear()} ${brandName}. ${t('footer.rights')}`}
</p>
</div>
+6 -6
View File
@@ -168,10 +168,10 @@ export function MobileDrawer({ isOpen, onClose, items, onOpenLang }: MobileDrawe
onClick={onClose}
/>
<div
className={`absolute top-0 right-0 h-full w-80 max-w-[85vw] bg-white shadow-2xl flex flex-col transition-transform duration-300 ${isOpen ? 'translate-x-0' : 'translate-x-full'}`}
className={`absolute top-0 right-0 h-full w-80 max-w-[85vw] bg-spa-light shadow-2xl flex flex-col transition-transform duration-300 ${isOpen ? 'translate-x-0' : 'translate-x-full'}`}
>
{/* Static Top Header */}
<div className="flex items-center justify-between px-6 py-5 border-b border-spa-stone/10 bg-white z-10 shrink-0">
<div className="flex items-center justify-between px-6 py-5 border-b border-spa-stone/10 bg-spa-light z-10 shrink-0">
<span className="text-sm font-bold uppercase tracking-widest text-spa-forest">Menu</span>
<div className="flex items-center gap-3">
<button onClick={onOpenLang} className="px-2.5 py-1 rounded-md text-[10px] font-bold text-spa-forest border border-spa-forest/30 uppercase bg-spa-sage flex items-center gap-1 shrink-0">
@@ -185,7 +185,7 @@ export function MobileDrawer({ isOpen, onClose, items, onOpenLang }: MobileDrawe
</div>
{/* Sliding Views Container */}
<div className="flex-1 relative overflow-hidden bg-white">
<div className="flex-1 relative overflow-hidden bg-spa-light">
{stack.map((view, index) => {
const isCurrent = index === stack.length - 1;
const isPast = index < stack.length - 1;
@@ -193,7 +193,7 @@ export function MobileDrawer({ isOpen, onClose, items, onOpenLang }: MobileDrawe
return (
<div
key={index}
className={`absolute inset-0 flex flex-col bg-white transition-transform duration-300 ease-in-out ${
className={`absolute inset-0 flex flex-col bg-spa-light transition-transform duration-300 ease-in-out ${
isCurrent ? 'translate-x-0' : isPast ? '-translate-x-full' : 'translate-x-full'
}`}
>
@@ -225,10 +225,10 @@ export function MobileDrawer({ isOpen, onClose, items, onOpenLang }: MobileDrawe
</div>
{/* Static Bottom Footer */}
<div className="px-6 pb-8 pt-6 border-t border-spa-stone/10 bg-white z-10 shrink-0 space-y-3">
<div className="px-6 pb-8 pt-6 border-t border-spa-stone/10 bg-spa-light z-10 shrink-0 space-y-3">
<button
onClick={() => handleLinkClick('/shop')}
className="block w-full py-3.5 bg-spa-forest text-white font-bold text-center rounded-xl text-xs uppercase tracking-widest hover:bg-spa-stone transition-colors duration-300 shadow-sm"
className="block w-full py-3.5 bg-spa-forest text-spa-light font-bold text-center rounded-xl text-xs uppercase tracking-widest hover:bg-spa-stone transition-colors duration-300 shadow-sm"
>
Shop Now
</button>
+10 -10
View File
@@ -153,7 +153,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
ref={navRef}
className={`fixed ${finalTopClassName} z-50 w-full border-b border-black/5 transition-all duration-300 ${
scrolled ? 'shadow-lg shadow-black/5' : 'shadow-none'
} bg-white/80 px-4 py-3.5 backdrop-blur-xl md:bg-spa-sage md:px-6 md:py-4 md:backdrop-blur-none`}
} bg-spa-light px-4 py-3.5 backdrop-blur-xl md:bg-spa-sage md:px-6 md:py-4 md:backdrop-blur-none`}
>
{/* Single-row layout: Logo (left) | Menu (center, desktop) | Actions (right) */}
<div className="w-full flex items-center justify-between gap-4">
@@ -193,7 +193,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
>
<Heart className="w-4 h-4 group-hover:text-rose-500 transition-colors" />
{wishlistCount > 0 && (
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
<span className="absolute -top-1 -right-1 bg-rose-500 text-spa-light text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
{wishlistCount}
</span>
)}
@@ -206,7 +206,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
>
<ShoppingBag className="w-4 h-4 group-hover:text-spa-forest transition-colors" />
{cartCount > 0 && (
<span className="absolute -top-1 -right-1 bg-rose-500 text-white text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
<span className="absolute -top-1 -right-1 bg-rose-500 text-spa-light text-[9px] font-bold w-4 h-4 rounded-full flex items-center justify-center">
{cartCount}
</span>
)}
@@ -215,12 +215,12 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
{isDesktop && (
<>
<div className="relative group flex items-center">
<button className="px-3 py-2 rounded-full text-[10px] font-bold transition-all text-spa-forest border border-spa-forest/30 hover:bg-spa-sage bg-white flex items-center gap-1.5 uppercase tracking-wider">
<button className="px-3 py-2 rounded-full text-[10px] font-bold transition-all text-spa-forest border border-spa-forest/30 hover:bg-spa-sage bg-spa-light flex items-center gap-1.5 uppercase tracking-wider">
<Globe className="w-3.5 h-3.5" strokeWidth={1.5} />
{LANGUAGES.find(l => i18n.language.startsWith(l.code))?.short || 'TH'}
</button>
<div className="absolute top-full right-0 pt-2 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-2xl p-1.5 space-y-0.5">
<div className="bg-spa-light backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-2xl p-1.5 space-y-0.5">
{LANGUAGES.map(lang => {
const isActive = i18n.language.startsWith(lang.code)
return (
@@ -248,7 +248,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
/>
</Link>
<div className="absolute top-full right-0 pt-3 w-36 opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300">
<div className="bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-xl p-1">
<div className="bg-spa-light backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden shadow-xl p-1">
<div className="px-3 py-2 border-b border-spa-stone/10 mb-1">
<p className="text-[10px] text-spa-stone/60 uppercase tracking-widest leading-tight truncate">{t('nav.signedInAs', 'Signed in as')}</p>
<p className="text-xs text-spa-forest font-bold truncate">{customer.displayName}</p>
@@ -271,7 +271,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
) : (
<button
onClick={loginLiff}
className="ml-2 px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest bg-[#00B900] text-white hover:bg-[#009900] transition-all duration-300 shadow-sm"
className="ml-2 px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest bg-[#00B900] text-spa-light hover:bg-[#009900] transition-all duration-300 shadow-sm"
>
{t('nav.login', 'LINE Login')}
</button>
@@ -317,7 +317,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
</div>
<form onSubmit={handleSearch}>
<div className="flex bg-white border-2 border-spa-stone/20 focus-within:border-spa-forest rounded-2xl overflow-hidden transition-colors shadow-sm">
<div className="flex bg-spa-light border-2 border-spa-stone/20 focus-within:border-spa-forest rounded-2xl overflow-hidden transition-colors shadow-sm">
<svg className="w-5 h-5 text-spa-stone/50 m-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
@@ -329,7 +329,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
className="bg-transparent border-none outline-none text-lg md:text-xl p-4 pl-0 w-full text-spa-forest placeholder-spa-stone/40"
autoFocus
/>
<button type="submit" className="bg-spa-forest text-white px-4 md:px-6 font-bold text-xs uppercase tracking-widest shrink-0 hover:bg-spa-stone transition-colors">
<button type="submit" className="bg-spa-forest text-spa-light px-4 md:px-6 font-bold text-xs uppercase tracking-widest shrink-0 hover:bg-spa-stone transition-colors">
Go
</button>
</div>
@@ -346,7 +346,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) {
onClick={() => setLangSheetOpen(false)}
/>
<div
className={`absolute bottom-0 left-0 right-0 bg-white rounded-t-3xl transition-transform duration-300 shadow-2xl pb-safe ${langSheetOpen ? 'translate-y-0' : 'translate-y-full'}`}
className={`absolute bottom-0 left-0 right-0 bg-spa-light rounded-t-3xl transition-transform duration-300 shadow-2xl pb-safe ${langSheetOpen ? 'translate-y-0' : 'translate-y-full'}`}
>
<div className="flex justify-center pt-3 pb-1">
<div className="w-12 h-1.5 bg-spa-stone/20 rounded-full" />
+4 -4
View File
@@ -57,13 +57,13 @@ export default function ProductCard({ product, enableQuickShop = false, category
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
<div className="ui-chip absolute left-3 top-3 border-white/20 bg-white/90 px-2.5 py-1 text-[10px] text-spa-forest shadow-sm backdrop-blur-sm">
<div className="ui-chip absolute left-3 top-3 border-spa-light bg-spa-light px-2.5 py-1 text-[10px] text-spa-forest shadow-sm backdrop-blur-sm">
{displayCategoryName}
</div>
<button
type="button"
onClick={handleWishlistClick}
className="absolute right-3 top-3 z-10 rounded-full bg-white/90 p-2 shadow-sm backdrop-blur-sm transition-transform hover:scale-110 cursor-pointer"
className="absolute right-3 top-3 z-10 rounded-full bg-spa-light p-2 shadow-sm backdrop-blur-sm transition-transform hover:scale-110 cursor-pointer"
>
<Heart
className={`w-4 h-4 transition-colors ${isWished ? 'fill-rose-500 text-rose-500' : 'text-spa-stone/60 hover:text-rose-500'}`}
@@ -96,8 +96,8 @@ export default function ProductCard({ product, enableQuickShop = false, category
}}
className={`rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-wider transition-all cursor-pointer ${
isSelected
? 'border-spa-forest bg-spa-forest text-white shadow-sm'
: 'border-spa-stone/10 bg-white text-spa-stone hover:border-spa-forest/30'
? 'border-spa-forest bg-spa-forest text-spa-light shadow-sm'
: 'border-spa-stone/10 bg-spa-light text-spa-stone hover:border-spa-forest/30'
}`}
>
{variant.size}
@@ -28,7 +28,7 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
return (
<div className="space-y-4">
{/* Main Image */}
<div className="relative rounded-2xl overflow-hidden h-[480px] group bg-white shadow-sm border border-spa-stone/10 flex items-center justify-center">
<div className="relative rounded-2xl overflow-hidden h-[480px] group bg-spa-light shadow-sm border border-spa-stone/10 flex items-center justify-center">
<img
src={currentImageUrl}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
@@ -38,7 +38,7 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
<div className="absolute inset-0 bg-gradient-to-t from-black/10 to-transparent pointer-events-none" />
{badgeText && (
<div className="absolute top-6 left-6 bg-white/90 backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-spa-forest shadow-sm pointer-events-none">
<div className="absolute top-6 left-6 bg-spa-light backdrop-blur-sm px-3 py-1.5 rounded-full text-[10px] font-bold uppercase tracking-widest text-spa-forest shadow-sm pointer-events-none">
{badgeText}
</div>
)}
@@ -48,7 +48,7 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
<>
<button
onClick={handlePrev}
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 backdrop-blur-md rounded-full flex items-center justify-center text-spa-forest opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white hover:scale-110 shadow-sm"
className="absolute left-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-spa-light backdrop-blur-md rounded-full flex items-center justify-center text-spa-forest opacity-0 group-hover:opacity-100 transition-opacity hover:bg-spa-light hover:scale-110 shadow-sm"
aria-label="Previous image"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -57,7 +57,7 @@ export default function ProductImageGallery({ images, fallback, alt = 'Product I
</button>
<button
onClick={handleNext}
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-white/80 backdrop-blur-md rounded-full flex items-center justify-center text-spa-forest opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white hover:scale-110 shadow-sm"
className="absolute right-4 top-1/2 -translate-y-1/2 w-10 h-10 bg-spa-light backdrop-blur-md rounded-full flex items-center justify-center text-spa-forest opacity-0 group-hover:opacity-100 transition-opacity hover:bg-spa-light hover:scale-110 shadow-sm"
aria-label="Next image"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+9 -9
View File
@@ -96,7 +96,7 @@ export default function ProductReviews({ productId }: { productId: number }) {
{/* List & Form */}
<div className="w-full md:w-2/3 space-y-8">
{customer ? (
<form onSubmit={handleSubmit} className="bg-white p-6 rounded-3xl border border-spa-stone/10 shadow-sm space-y-4">
<form onSubmit={handleSubmit} className="bg-spa-light p-6 rounded-3xl border border-spa-stone/10 shadow-sm space-y-4">
<h3 className="font-bold text-pop-black">Write a Review</h3>
<div className="flex gap-2">
{[1, 2, 3, 4, 5].map(star => (
@@ -115,7 +115,7 @@ export default function ProductReviews({ productId }: { productId: number }) {
<button
type="submit"
disabled={submitting}
className="px-6 py-2.5 bg-spa-forest text-white text-sm font-bold tracking-widest uppercase rounded-full hover:bg-spa-stone transition-all disabled:opacity-50"
className="px-6 py-2.5 bg-spa-forest text-spa-light text-sm font-bold tracking-widest uppercase rounded-full hover:bg-spa-stone transition-all disabled:opacity-50"
>
{submitting ? 'Submitting...' : 'Submit Review'}
</button>
@@ -135,8 +135,8 @@ export default function ProductReviews({ productId }: { productId: number }) {
onClick={() => { setFilterRating(null); setVisibleCount(3); }}
className={`flex-shrink-0 px-4 py-1.5 rounded-full text-xs font-bold transition-all border ${
filterRating === null
? 'bg-spa-stone text-white border-spa-stone'
: 'bg-white text-spa-stone border-spa-stone/20 hover:border-spa-stone/50'
? 'bg-spa-stone text-spa-light border-spa-stone'
: 'bg-spa-light text-spa-stone border-spa-stone/20 hover:border-spa-stone/50'
}`}
>
All ({reviews.length})
@@ -150,11 +150,11 @@ export default function ProductReviews({ productId }: { productId: number }) {
onClick={() => { setFilterRating(star); setVisibleCount(3); }}
className={`flex-shrink-0 flex items-center gap-1 px-4 py-1.5 rounded-full text-xs font-bold transition-all border ${
filterRating === star
? 'bg-spa-stone text-white border-spa-stone'
: 'bg-white text-spa-stone border-spa-stone/20 hover:border-spa-stone/50'
? 'bg-spa-stone text-spa-light border-spa-stone'
: 'bg-spa-light text-spa-stone border-spa-stone/20 hover:border-spa-stone/50'
}`}
>
{star} <Star className={`w-3 h-3 ${filterRating === star ? 'fill-white text-white' : 'fill-orange-400 text-orange-400'}`} /> ({count})
{star} <Star className={`w-3 h-3 ${filterRating === star ? 'fill-white text-spa-light' : 'fill-orange-400 text-orange-400'}`} /> ({count})
</button>
);
})}
@@ -174,7 +174,7 @@ export default function ProductReviews({ productId }: { productId: number }) {
{review.customer?.pictureUrl ? (
<img src={review.customer.pictureUrl} className="w-10 h-10 rounded-full object-cover" />
) : (
<div className="w-10 h-10 rounded-full bg-spa-stone/20 flex items-center justify-center text-white font-bold">
<div className="w-10 h-10 rounded-full bg-spa-stone/20 flex items-center justify-center text-spa-light font-bold">
{(review.customerName || 'A')[0].toUpperCase()}
</div>
)}
@@ -197,7 +197,7 @@ export default function ProductReviews({ productId }: { productId: number }) {
<div className="text-center pt-4">
<button
onClick={() => setVisibleCount(prev => prev + 5)}
className="px-6 py-2.5 border border-spa-stone/20 text-spa-stone text-xs font-bold tracking-widest uppercase rounded-full hover:border-spa-stone hover:text-pop-black transition-all bg-white"
className="px-6 py-2.5 border border-spa-stone/20 text-spa-stone text-xs font-bold tracking-widest uppercase rounded-full hover:border-spa-stone hover:text-pop-black transition-all bg-spa-light"
>
Show More Reviews
</button>
@@ -129,7 +129,7 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
</p>
</div>
<div className="bg-white rounded-3xl shadow-xl shadow-spa-sand/50 p-8 md:p-12 border border-spa-sage/10">
<div className="bg-spa-light rounded-3xl shadow-xl shadow-spa-sand/50 p-8 md:p-12 border border-spa-sage/10">
{isSuccess ? (
<div className="text-center py-12">
<div className="w-16 h-16 bg-green-100 text-green-600 rounded-full flex items-center justify-center mx-auto mb-6">
@@ -192,10 +192,10 @@ export default function ContactFormSection({ payload }: ContactFormSectionProps)
<button
type="submit"
disabled={isSubmitting}
className="w-full py-4 px-6 bg-spa-sage hover:bg-spa-sage-dark text-white rounded-xl font-medium flex items-center justify-center gap-2 transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
className="w-full py-4 px-6 bg-spa-sage hover:bg-spa-sage-dark text-spa-light rounded-xl font-medium flex items-center justify-center gap-2 transition-colors disabled:opacity-70 disabled:cursor-not-allowed"
>
{isSubmitting ? (
<span className="w-6 h-6 border-2 border-white/30 border-t-white rounded-full animate-spin" />
<span className="w-6 h-6 border-2 border-spa-light border-t-white rounded-full animate-spin" />
) : (
<>
<Send size={18} />
@@ -30,7 +30,7 @@ export default function ContactInfoSection({ payload }: ContactInfoSectionProps)
] as const;
return (
<section className="ui-section-band w-full bg-white px-4 md:px-8">
<section className="ui-section-band w-full bg-spa-light px-4 md:px-8">
<div className="ui-section-inner">
{title && (
<div className="mb-10 text-center">
@@ -78,7 +78,7 @@ export function ThaiAddressInput({ value, onChange }: ThaiAddressInputProps) {
if (focusedField !== field || results.length === 0) return null;
return (
<ul className="absolute z-50 w-full bg-white border border-spa-stone/20 rounded-lg mt-1 max-h-60 overflow-y-auto shadow-xl">
<ul className="absolute z-50 w-full bg-spa-light border border-spa-stone/20 rounded-lg mt-1 max-h-60 overflow-y-auto shadow-xl">
{results.map((item, idx) => (
<li
key={`${item.district}-${item.amphoe}-${item.province}-${item.zipcode}-${idx}`}