507 lines
23 KiB
TypeScript
507 lines
23 KiB
TypeScript
import { Link, NavLink, useNavigate } from 'react-router-dom'
|
|
import { useState, useEffect } from 'react'
|
|
import type { Category } from '../types'
|
|
import { ShoppingBag, MapPin, Menu as MenuIcon, X, Heart } from 'lucide-react'
|
|
import { useCartStore } from '../store/cartStore'
|
|
import { useWishlistStore } from '../store/wishlistStore'
|
|
import { useAuthStore } from '../store/authStore'
|
|
import { logoutLiff, loginLiff } from '../lib/liff'
|
|
import { useTranslation } from 'react-i18next'
|
|
|
|
interface NavbarProps {
|
|
categories?: Category[]
|
|
topOffsetClassName?: string
|
|
}
|
|
|
|
export default function Navbar({ categories = [], topOffsetClassName = 'top-0' }: NavbarProps) {
|
|
const [searchOpen, setSearchOpen] = useState(false)
|
|
const [mobileOpen, setMobileOpen] = useState(false)
|
|
const [searchQuery, setSearchQuery] = useState('')
|
|
const [scrolled, setScrolled] = useState(false)
|
|
const [localOrdersCount, setLocalOrdersCount] = useState(0)
|
|
const [hasAnnouncement, setHasAnnouncement] = useState(false)
|
|
const navigate = useNavigate()
|
|
|
|
const { toggleCart, getCartCount } = useCartStore()
|
|
const cartCount = getCartCount()
|
|
const wishlistCount = useWishlistStore(state => state.items.length)
|
|
const { customer } = useAuthStore()
|
|
const { t, i18n } = useTranslation()
|
|
|
|
const toggleLanguage = () => {
|
|
const newLang = i18n.language.startsWith('en') ? 'th' : 'en'
|
|
i18n.changeLanguage(newLang)
|
|
localStorage.setItem('homaroma_lang', newLang)
|
|
}
|
|
|
|
// Detect scroll and announcement bar presence
|
|
useEffect(() => {
|
|
const checkAnnouncement = () => {
|
|
const el = document.querySelector('.announcement-bar')
|
|
setHasAnnouncement(!!el)
|
|
}
|
|
|
|
checkAnnouncement()
|
|
const timer = setTimeout(checkAnnouncement, 300)
|
|
|
|
const onScroll = () => {
|
|
setScrolled(window.scrollY > 20)
|
|
checkAnnouncement()
|
|
}
|
|
window.addEventListener('scroll', onScroll, { passive: true })
|
|
|
|
// Check local orders periodically (or just on mount)
|
|
const stored = localStorage.getItem('homaroma_local_orders')
|
|
if (stored) {
|
|
try { setLocalOrdersCount(JSON.parse(stored).length) } catch (e) {}
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener('scroll', onScroll)
|
|
clearTimeout(timer)
|
|
}
|
|
}, [])
|
|
|
|
// Close mobile menu on route change
|
|
useEffect(() => { setMobileOpen(false) }, [navigate])
|
|
// Prevent body scroll when overlays open
|
|
useEffect(() => {
|
|
document.body.style.overflow = (searchOpen || mobileOpen) ? 'hidden' : ''
|
|
return () => { document.body.style.overflow = '' }
|
|
}, [searchOpen, mobileOpen])
|
|
|
|
const handleSearch = (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
if (searchQuery.trim()) {
|
|
navigate(`/search?q=${encodeURIComponent(searchQuery.trim())}`)
|
|
setSearchOpen(false)
|
|
setMobileOpen(false)
|
|
setSearchQuery('')
|
|
}
|
|
}
|
|
|
|
const navLinkClass = ({ isActive }: { isActive: boolean }) =>
|
|
isActive ? 'text-spa-forest font-semibold' : 'text-spa-stone hover:text-spa-forest transition-colors'
|
|
|
|
const computedOffset = topOffsetClassName !== 'top-0'
|
|
? topOffsetClassName
|
|
: (hasAnnouncement ? 'top-[32px] md:top-[40px]' : 'top-0')
|
|
|
|
const finalTopClassName = scrolled ? 'top-0' : computedOffset
|
|
|
|
return (
|
|
<>
|
|
{/* ── NAVBAR BAR ── */}
|
|
<nav
|
|
className={`fixed ${finalTopClassName} w-full z-50 transition-all duration-300 px-4 md:px-6 py-3.5 md:py-4 flex justify-between items-center bg-white/80 backdrop-blur-xl border-b border-black/5 ${
|
|
scrolled ? 'shadow-lg shadow-black/5' : 'shadow-none'
|
|
}`}
|
|
>
|
|
{/* Logo */}
|
|
<Link to="/" onClick={() => setMobileOpen(false)} className="text-xl md:text-2xl font-bold tracking-widest text-spa-forest uppercase italic shrink-0">
|
|
HomAroma <span className="hidden md:inline text-spa-stone text-xs md:text-sm not-italic font-light ml-1">Essential</span>
|
|
</Link>
|
|
|
|
{/* ── DESKTOP NAV ── */}
|
|
<div className="hidden md:flex space-x-7 text-sm font-medium tracking-wide uppercase">
|
|
<NavLink to="/" end className={navLinkClass}>{t('nav.home')}</NavLink>
|
|
<NavLink to="/shop" className={navLinkClass}>{t('nav.shop')}</NavLink>
|
|
<NavLink to="/coupons" className={navLinkClass}>🔥 {t('nav.promotions') || 'Promotions'}</NavLink>
|
|
|
|
{/* Collections dropdown */}
|
|
{categories.length > 0 && (
|
|
<div className="relative group">
|
|
<button className="text-spa-stone hover:text-spa-forest transition-colors flex items-center gap-1 uppercase text-sm font-medium tracking-wide">
|
|
{t('nav.collections')}
|
|
<svg className="w-3 h-3 opacity-50 transition-transform duration-300 group-hover:rotate-180" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
</svg>
|
|
</button>
|
|
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-4 w-52 bg-white/95 backdrop-blur-xl border border-black/5 rounded-xl overflow-hidden opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300 shadow-2xl">
|
|
<div className="p-2 space-y-0.5">
|
|
{categories.map((cat) => (
|
|
<Link
|
|
key={cat.id}
|
|
to={`/category/${cat.id}`}
|
|
className="flex items-center justify-between px-4 py-2.5 text-xs uppercase tracking-widest text-spa-stone rounded-lg hover:bg-spa-sage hover:text-spa-forest transition-colors"
|
|
>
|
|
{cat.name}
|
|
<svg className="w-3 h-3 opacity-30" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<NavLink to="/scent-guide" className={navLinkClass}>{t('nav.scentGuide')}</NavLink>
|
|
<NavLink to="/about" className={navLinkClass}>{t('nav.about')}</NavLink>
|
|
</div>
|
|
|
|
{/* ── DESKTOP RIGHT ACTIONS ── */}
|
|
<div className="hidden md:flex items-center gap-3">
|
|
<Link
|
|
to="/track"
|
|
className="relative p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
|
aria-label="Track Orders"
|
|
title="Track Orders"
|
|
>
|
|
<MapPin className="w-4 h-4 group-hover:text-spa-forest transition-colors" />
|
|
{localOrdersCount > 0 && (
|
|
<span className="absolute top-0 right-0 w-2 h-2 bg-rose-500 rounded-full animate-pulse" />
|
|
)}
|
|
</Link>
|
|
|
|
<button
|
|
onClick={() => setSearchOpen(true)}
|
|
className="p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
|
aria-label="Search"
|
|
>
|
|
<svg className="w-4 h-4 group-hover:text-spa-forest transition-colors" 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>
|
|
</button>
|
|
|
|
<Link
|
|
to="/wishlist"
|
|
className="relative p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
|
aria-label="Wishlist"
|
|
>
|
|
<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">
|
|
{wishlistCount}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
|
|
<button
|
|
onClick={() => toggleCart(true)}
|
|
className="relative p-2.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all group"
|
|
aria-label="Cart"
|
|
>
|
|
<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">
|
|
{cartCount}
|
|
</span>
|
|
)}
|
|
</button>
|
|
<Link
|
|
to="/shop"
|
|
className="px-4 py-2 rounded-full text-xs font-bold uppercase tracking-widest bg-spa-forest text-white hover:bg-spa-stone transition-all duration-300 shadow-sm"
|
|
>
|
|
{t('nav.shopNow')}
|
|
</Link>
|
|
|
|
<button
|
|
onClick={toggleLanguage}
|
|
className="px-3 py-2 rounded-full text-xs font-bold transition-all text-spa-forest border border-spa-forest/30 hover:bg-spa-sage bg-white"
|
|
>
|
|
{i18n.language.startsWith('en') ? 'EN' : 'TH'}
|
|
</button>
|
|
|
|
{customer ? (
|
|
<div className="relative group flex items-center">
|
|
<Link to="/profile">
|
|
<img
|
|
src={customer.pictureUrl || `https://ui-avatars.com/api/?name=${customer.displayName}`}
|
|
alt="Profile"
|
|
className="w-9 h-9 rounded-full border-2 border-spa-forest/30 cursor-pointer object-cover animate-fade-in"
|
|
/>
|
|
</Link>
|
|
<div className="absolute top-full right-0 mt-4 w-36 bg-white/95 backdrop-blur-xl border border-spa-stone/10 rounded-xl overflow-hidden opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-all duration-300 shadow-xl">
|
|
<div className="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">Signed in as</p>
|
|
<p className="text-xs text-spa-forest font-bold truncate">{customer.displayName}</p>
|
|
</div>
|
|
<Link
|
|
to="/profile"
|
|
className="block px-3 py-2 text-xs uppercase tracking-widest text-spa-stone hover:text-spa-forest hover:bg-spa-light rounded-lg transition-colors font-bold"
|
|
>
|
|
My Profile
|
|
</Link>
|
|
<button
|
|
onClick={logoutLiff}
|
|
className="w-full text-left px-3 py-2 text-xs uppercase tracking-widest text-rose-500 hover:text-rose-600 hover:bg-rose-50 rounded-lg transition-colors font-bold"
|
|
>
|
|
Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
onClick={loginLiff}
|
|
className="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"
|
|
>
|
|
LINE Login
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{/* ── MOBILE RIGHT ACTIONS ── */}
|
|
<div className="flex md:hidden items-center gap-1.5">
|
|
{customer ? (
|
|
<div className="relative">
|
|
<Link to="/profile">
|
|
<img
|
|
src={customer.pictureUrl || `https://ui-avatars.com/api/?name=${customer.displayName}`}
|
|
alt="Profile"
|
|
className="w-7 h-7 rounded-full border-2 border-spa-forest/30 object-cover cursor-pointer"
|
|
/>
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<button
|
|
onClick={loginLiff}
|
|
className="p-1.5 rounded-full text-[#00B900] hover:bg-[#00B900]/10 transition-all border border-[#00B900]/30 bg-white"
|
|
aria-label="LINE Login"
|
|
>
|
|
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M24 10.304c0-5.369-5.383-9.738-12-9.738-6.616 0-12 4.369-12 9.738 0 4.814 3.932 8.946 9.539 9.615.375.056 1.155.176 1.321.603.149.387.049.991.023 1.258-.035.342-.165 1.011 1.05 1.503 1.216.492 4.417-2.602 6.012-4.423C21.841 16.208 24 13.524 24 10.304z"/>
|
|
</svg>
|
|
</button>
|
|
)}
|
|
|
|
<Link
|
|
to="/track"
|
|
onClick={() => setMobileOpen(false)}
|
|
className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
|
aria-label="Track Orders"
|
|
>
|
|
<MapPin className="w-4 h-4" />
|
|
{localOrdersCount > 0 && (
|
|
<span className="absolute top-0 right-0 w-2 h-2 bg-rose-500 rounded-full" />
|
|
)}
|
|
</Link>
|
|
<button
|
|
onClick={() => { setSearchOpen(true); setMobileOpen(false) }}
|
|
className="p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
|
aria-label="Search"
|
|
>
|
|
<svg className="w-4 h-4" 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>
|
|
</button>
|
|
|
|
<Link
|
|
to="/wishlist"
|
|
onClick={() => setMobileOpen(false)}
|
|
className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
|
aria-label="Wishlist"
|
|
>
|
|
<Heart className="w-4 h-4" />
|
|
{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">
|
|
{wishlistCount}
|
|
</span>
|
|
)}
|
|
</Link>
|
|
|
|
<button
|
|
onClick={() => { toggleCart(true); setMobileOpen(false) }}
|
|
className="relative p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
|
aria-label="Cart"
|
|
>
|
|
<ShoppingBag className="w-4 h-4" />
|
|
{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">
|
|
{cartCount}
|
|
</span>
|
|
)}
|
|
</button>
|
|
{/* Hamburger */}
|
|
<button
|
|
onClick={() => setMobileOpen(prev => !prev)}
|
|
className="p-1.5 rounded-full text-spa-stone hover:bg-spa-sage transition-all"
|
|
aria-label={mobileOpen ? 'Close menu' : 'Open menu'}
|
|
>
|
|
{mobileOpen ? (
|
|
<X className="w-5 h-5" />
|
|
) : (
|
|
<MenuIcon className="w-5 h-5" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
{/* ── MOBILE DRAWER ── */}
|
|
<div
|
|
className={`fixed inset-0 z-[60] md:hidden transition-all duration-300 ${mobileOpen ? 'visible' : 'invisible'}`}
|
|
>
|
|
{/* Backdrop */}
|
|
<div
|
|
className={`absolute inset-0 bg-spa-stone/40 backdrop-blur-sm transition-opacity duration-300 ${mobileOpen ? 'opacity-100' : 'opacity-0'}`}
|
|
onClick={() => setMobileOpen(false)}
|
|
/>
|
|
{/* Drawer */}
|
|
<div
|
|
className={`absolute top-0 right-0 h-full w-72 bg-white/98 backdrop-blur-2xl border-l border-spa-stone/10 flex flex-col transition-transform duration-300 shadow-2xl ${mobileOpen ? 'translate-x-0' : 'translate-x-full'}`}
|
|
>
|
|
{/* Drawer Header */}
|
|
<div className="flex items-center justify-between px-6 py-5 border-b border-spa-stone/10">
|
|
<span className="text-sm font-bold uppercase tracking-widest text-spa-forest">{t('nav.menu')}</span>
|
|
<div className="flex items-center gap-3">
|
|
{customer && (
|
|
<img src={customer.pictureUrl || ''} className="w-6 h-6 rounded-full border-2 border-spa-forest/30" />
|
|
)}
|
|
<button onClick={toggleLanguage} className="px-2.5 py-1 rounded-md text-[10px] font-bold text-spa-forest border border-spa-forest/30 uppercase bg-spa-sage">
|
|
{i18n.language.startsWith('en') ? 'EN' : 'TH'}
|
|
</button>
|
|
<button onClick={() => setMobileOpen(false)} className="p-1.5 rounded-full hover:bg-spa-sage transition-all text-spa-stone">
|
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Drawer Nav Links */}
|
|
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-1">
|
|
{[
|
|
{ to: '/', label: t('nav.home'), end: true },
|
|
{ to: '/shop', label: t('nav.shop') },
|
|
...(customer ? [{ to: '/profile', label: `👤 ${t('nav.profile') || 'My Profile'}` }] : []),
|
|
{ to: '/coupons', label: `🔥 ${t('nav.promotions') || 'Promotions'}` },
|
|
{ to: '/scent-guide', label: `✨ ${t('nav.scentGuide')}` },
|
|
{ to: '/about', label: t('nav.about') },
|
|
{ to: '/faq', label: t('nav.faq') },
|
|
{ to: '/contact', label: t('nav.contact') },
|
|
].map(item => (
|
|
<NavLink
|
|
key={item.to}
|
|
to={item.to}
|
|
end={item.end}
|
|
onClick={() => setMobileOpen(false)}
|
|
className={({ isActive }) =>
|
|
`block px-4 py-3 rounded-xl text-sm font-medium tracking-wide transition-all duration-200 ${
|
|
isActive ? 'bg-spa-forest text-white' : 'text-spa-stone hover:bg-spa-sage hover:text-spa-forest'
|
|
}`
|
|
}
|
|
>
|
|
{item.label}
|
|
</NavLink>
|
|
))}
|
|
|
|
{/* Collections sub-section */}
|
|
{categories.length > 0 && (
|
|
<div className="pt-4">
|
|
<p className="text-[10px] uppercase tracking-widest text-spa-stone/60 px-4 pb-2 font-semibold">{t('nav.collections')}</p>
|
|
{categories.map(cat => (
|
|
<Link
|
|
key={cat.id}
|
|
to={`/category/${cat.id}`}
|
|
onClick={() => setMobileOpen(false)}
|
|
className="flex items-center justify-between px-4 py-2.5 rounded-xl text-sm text-spa-stone hover:text-spa-forest hover:bg-spa-sage transition-all"
|
|
>
|
|
{cat.name}
|
|
<svg className="w-3 h-3 text-spa-stone/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
</svg>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Drawer Footer CTA */}
|
|
<div className="px-4 pb-8 pt-4 border-t border-spa-stone/10 space-y-3">
|
|
<Link
|
|
to="/shop"
|
|
onClick={() => setMobileOpen(false)}
|
|
className="block w-full py-3 bg-spa-forest text-white font-bold text-center rounded-xl text-xs uppercase tracking-widest hover:bg-spa-stone transition-colors duration-300"
|
|
>
|
|
{t('nav.shopNow')}
|
|
</Link>
|
|
<Link
|
|
to="/scent-guide"
|
|
onClick={() => setMobileOpen(false)}
|
|
className="block w-full py-3 text-center rounded-xl text-xs font-bold uppercase tracking-widest hover:bg-spa-sage transition-colors border-2 border-spa-forest text-spa-forest"
|
|
>
|
|
{t('nav.findMyScent')}
|
|
</Link>
|
|
|
|
{customer ? (
|
|
<button
|
|
onClick={() => { logoutLiff(); setMobileOpen(false); }}
|
|
className="block w-full py-3 glass text-center rounded-xl text-xs font-bold uppercase tracking-widest text-rose-400 hover:bg-rose-500/10 transition-colors border border-rose-500/20 mt-2"
|
|
>
|
|
Logout
|
|
</button>
|
|
) : (
|
|
<button
|
|
onClick={() => { loginLiff(); setMobileOpen(false); }}
|
|
className="block w-full py-3 glass text-center rounded-xl text-xs font-bold uppercase tracking-widest text-[#00B900] hover:bg-[#00B900]/10 transition-colors border border-[#00B900]/50 mt-2"
|
|
>
|
|
Login with LINE
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── SEARCH OVERLAY ── */}
|
|
{searchOpen && (
|
|
<div
|
|
className="fixed inset-0 z-[100] bg-spa-light/97 backdrop-blur-xl flex flex-col items-center pt-24 md:pt-32 px-4 md:px-6"
|
|
onClick={(e) => { if (e.target === e.currentTarget) setSearchOpen(false) }}
|
|
>
|
|
<div className="w-full max-w-2xl space-y-6 animate-fade-in">
|
|
<div className="flex justify-between items-center">
|
|
<p className="text-spa-stone text-xs uppercase tracking-widest font-semibold">{t('nav.search.title')}</p>
|
|
<button onClick={() => setSearchOpen(false)} className="p-2 rounded-full hover:bg-spa-sage transition-all text-spa-stone">
|
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
</svg>
|
|
</button>
|
|
</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">
|
|
<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>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
placeholder="ค้นหากลิ่น, หมวดหมู่..."
|
|
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">
|
|
Go
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div>
|
|
<p className="text-xs text-spa-stone/60 uppercase tracking-widest mb-3 font-semibold">Quick Links</p>
|
|
<div className="flex flex-wrap gap-2">
|
|
{[
|
|
...categories.map(c => ({ label: c.name, to: `/category/${c.id}` })),
|
|
{ label: '✨ Scent Guide', to: '/scent-guide' },
|
|
{ label: 'All Products', to: '/shop' },
|
|
].map(item => (
|
|
<Link
|
|
key={item.to}
|
|
to={item.to}
|
|
onClick={() => setSearchOpen(false)}
|
|
className="px-4 py-1.5 rounded-full text-xs font-medium text-spa-stone hover:bg-spa-forest hover:text-white transition-all border border-spa-stone/20 bg-white"
|
|
>
|
|
{item.label}
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</>
|
|
)
|
|
}
|