x
This commit is contained in:
1 parent
943fd8f1a3
commit
9237c0eded
7 files changed
+776
-377
No files matched your search
@@ -14,7 +14,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
|
||||
interface ThemeLivePreviewProps {
|
||||
tokens: Record<string, string>
|
||||
tokens: Record<string, any>
|
||||
}
|
||||
|
||||
// ── WCAG Relative Luminance & Contrast Calculation ──────────────────────────
|
||||
@@ -401,39 +401,45 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
style={{ color: 'var(--p-text-main)' }}
|
||||
className="p-1 rounded-full hover:bg-black/5 transition"
|
||||
title="Search"
|
||||
>
|
||||
<Search className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
|
||||
<div className="relative">
|
||||
{tokens.headerShowSearch !== false && (
|
||||
<button
|
||||
type="button"
|
||||
style={{ color: 'var(--p-text-main)' }}
|
||||
className="p-1 rounded-full hover:bg-black/5 transition"
|
||||
title="Cart"
|
||||
title="Search"
|
||||
>
|
||||
<ShoppingCart className="w-3.5 h-3.5" />
|
||||
<Search className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<span
|
||||
style={{ backgroundColor: 'var(--p-brand-accent)', color: '#FFFFFF' }}
|
||||
className="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full text-[8px] font-bold flex items-center justify-center shadow-sm"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer"
|
||||
>
|
||||
<MessageCircle className="w-3 h-3" />
|
||||
<span>LINE</span>
|
||||
</button>
|
||||
{tokens.headerShowCart !== false && (
|
||||
<div className="relative">
|
||||
<button
|
||||
type="button"
|
||||
style={{ color: 'var(--p-text-main)' }}
|
||||
className="p-1 rounded-full hover:bg-black/5 transition"
|
||||
title="Cart"
|
||||
>
|
||||
<ShoppingCart className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<span
|
||||
style={{ backgroundColor: 'var(--p-brand-accent)', color: '#FFFFFF' }}
|
||||
className="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full text-[8px] font-bold flex items-center justify-center shadow-sm"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tokens.headerShowAccount !== false && (
|
||||
<button
|
||||
type="button"
|
||||
className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer"
|
||||
>
|
||||
<MessageCircle className="w-3 h-3" />
|
||||
<span>LINE</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -19,7 +19,13 @@ import {
|
||||
ArrowUpDown,
|
||||
Monitor,
|
||||
Smartphone,
|
||||
LayoutGrid
|
||||
LayoutGrid,
|
||||
Search,
|
||||
Heart,
|
||||
ShoppingBag,
|
||||
Truck,
|
||||
Globe,
|
||||
User
|
||||
} from 'lucide-react'
|
||||
|
||||
const defaultThemeTokens = {
|
||||
@@ -169,6 +175,12 @@ export default function ThemeSettingsPage() {
|
||||
const [mobileHeaderStyle, setMobileHeaderStyle] = useState<'logo_center' | 'logo_left' | 'search_bar_below'>('logo_center')
|
||||
const [mobileHeaderBehavior, setMobileHeaderBehavior] = useState<'always_sticky' | 'hide_on_scroll'>('hide_on_scroll')
|
||||
const [cardPreset, setCardPreset] = useState<'standard' | 'minimal' | 'swatch'>('standard')
|
||||
const [headerShowSearch, setHeaderShowSearch] = useState<boolean>(true)
|
||||
const [headerShowWishlist, setHeaderShowWishlist] = useState<boolean>(true)
|
||||
const [headerShowCart, setHeaderShowCart] = useState<boolean>(true)
|
||||
const [headerShowTrackOrder, setHeaderShowTrackOrder] = useState<boolean>(true)
|
||||
const [headerShowLanguage, setHeaderShowLanguage] = useState<boolean>(true)
|
||||
const [headerShowAccount, setHeaderShowAccount] = useState<boolean>(true)
|
||||
|
||||
// JSON Quick Copy / Paste State
|
||||
const [isJsonBoxOpen, setIsJsonBoxOpen] = useState(false)
|
||||
@@ -201,6 +213,12 @@ export default function ThemeSettingsPage() {
|
||||
setMobileHeaderBehavior((data as any).mobileHeaderBehavior || (data.themeTokens as any)?.mobileHeaderBehavior || pcBehavior)
|
||||
const cp = (data as any).cardPreset || (data.themeTokens as any)?.cardPreset || 'standard'
|
||||
setCardPreset(cp === 'minimal_swatch' ? 'swatch' : cp)
|
||||
setHeaderShowSearch((data as any).headerShowSearch ?? (data.themeTokens as any)?.headerShowSearch ?? true)
|
||||
setHeaderShowWishlist((data as any).headerShowWishlist ?? (data.themeTokens as any)?.headerShowWishlist ?? true)
|
||||
setHeaderShowCart((data as any).headerShowCart ?? (data.themeTokens as any)?.headerShowCart ?? true)
|
||||
setHeaderShowTrackOrder((data as any).headerShowTrackOrder ?? (data.themeTokens as any)?.headerShowTrackOrder ?? true)
|
||||
setHeaderShowLanguage((data as any).headerShowLanguage ?? (data.themeTokens as any)?.headerShowLanguage ?? true)
|
||||
setHeaderShowAccount((data as any).headerShowAccount ?? (data.themeTokens as any)?.headerShowAccount ?? true)
|
||||
})
|
||||
.catch(console.error)
|
||||
.finally(() => setLoading(false))
|
||||
@@ -221,6 +239,12 @@ export default function ThemeSettingsPage() {
|
||||
headerBehavior,
|
||||
mobileHeaderBehavior,
|
||||
mobileHeaderStyle,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
cardPreset,
|
||||
},
|
||||
cardPreset,
|
||||
@@ -231,6 +255,12 @@ export default function ThemeSettingsPage() {
|
||||
headerBehavior,
|
||||
mobileHeaderStyle,
|
||||
mobileHeaderBehavior,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +355,25 @@ export default function ThemeSettingsPage() {
|
||||
} else if (extractedTokens?.mobileHeaderBehavior === 'always_sticky' || extractedTokens?.mobileHeaderBehavior === 'hide_on_scroll') {
|
||||
setMobileHeaderBehavior(extractedTokens.mobileHeaderBehavior)
|
||||
}
|
||||
|
||||
// Extract Header Action Icons Visibility
|
||||
if (typeof parsed.headerShowSearch === 'boolean') setHeaderShowSearch(parsed.headerShowSearch)
|
||||
else if (typeof extractedTokens?.headerShowSearch === 'boolean') setHeaderShowSearch(extractedTokens.headerShowSearch)
|
||||
|
||||
if (typeof parsed.headerShowWishlist === 'boolean') setHeaderShowWishlist(parsed.headerShowWishlist)
|
||||
else if (typeof extractedTokens?.headerShowWishlist === 'boolean') setHeaderShowWishlist(extractedTokens.headerShowWishlist)
|
||||
|
||||
if (typeof parsed.headerShowCart === 'boolean') setHeaderShowCart(parsed.headerShowCart)
|
||||
else if (typeof extractedTokens?.headerShowCart === 'boolean') setHeaderShowCart(extractedTokens.headerShowCart)
|
||||
|
||||
if (typeof parsed.headerShowTrackOrder === 'boolean') setHeaderShowTrackOrder(parsed.headerShowTrackOrder)
|
||||
else if (typeof extractedTokens?.headerShowTrackOrder === 'boolean') setHeaderShowTrackOrder(extractedTokens.headerShowTrackOrder)
|
||||
|
||||
if (typeof parsed.headerShowLanguage === 'boolean') setHeaderShowLanguage(parsed.headerShowLanguage)
|
||||
else if (typeof extractedTokens?.headerShowLanguage === 'boolean') setHeaderShowLanguage(extractedTokens.headerShowLanguage)
|
||||
|
||||
if (typeof parsed.headerShowAccount === 'boolean') setHeaderShowAccount(parsed.headerShowAccount)
|
||||
else if (typeof extractedTokens?.headerShowAccount === 'boolean') setHeaderShowAccount(extractedTokens.headerShowAccount)
|
||||
}
|
||||
|
||||
// ── Paste JSON Text Handlers ──────────────────────────────────────────────
|
||||
@@ -450,6 +499,12 @@ export default function ThemeSettingsPage() {
|
||||
headerBehavior,
|
||||
mobileHeaderBehavior,
|
||||
mobileHeaderStyle,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
cardPreset,
|
||||
},
|
||||
cardPreset,
|
||||
@@ -460,6 +515,12 @@ export default function ThemeSettingsPage() {
|
||||
headerBehavior,
|
||||
mobileHeaderStyle,
|
||||
mobileHeaderBehavior,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
}),
|
||||
})
|
||||
if (res.ok) {
|
||||
@@ -1262,6 +1323,141 @@ export default function ThemeSettingsPage() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Header Action Icons (Corner Icons) */}
|
||||
<div className="space-y-4 pt-6 border-t border-white/5">
|
||||
<div>
|
||||
<label className="text-xs uppercase font-bold tracking-widest text-slate-400 flex items-center gap-1.5">
|
||||
<ShoppingBag className="w-3.5 h-3.5 text-amber-400" />
|
||||
<span>Header Action Icons (ไอคอนมุมขวาบนของ Header)</span>
|
||||
</label>
|
||||
<p className="text-xs text-slate-400 mt-1">
|
||||
กำหนดเปิด/ซ่อนไอคอนการทำงานที่มุมขวาบนของ Header (ทั้งบน PC และ Mobile) เหมาะสำหรับร้านที่ไม่มีระบบตะกร้าสินค้า หรือต้องการซ่อนบางฟังก์ชัน
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{/* Cart */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowCart ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowCart ? 'bg-amber-400/20 text-amber-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<ShoppingBag className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">ตะกร้าสินค้า (Cart)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ซ่อนปุ่มตะกร้า หากเป็นร้านโชว์สินค้า/ไม่มีระบบสั่งซื้อ</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowCart(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowCart ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowCart ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Wishlist */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowWishlist ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowWishlist ? 'bg-rose-500/20 text-rose-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<Heart className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">รายการโปรด (Wishlist)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ไอคอนหัวใจบันทึกรายการโปรด</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowWishlist(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowWishlist ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowWishlist ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Search */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowSearch ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowSearch ? 'bg-cyan-500/20 text-cyan-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<Search className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">ค้นหา (Search)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่มเปิดหน้าต่างค้นหาสินค้า</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowSearch(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowSearch ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowSearch ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Track Order */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowTrackOrder ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowTrackOrder ? 'bg-emerald-500/20 text-emerald-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<Truck className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">ติดตามพัสดุ (Track Order)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่มตรวจสอบสถานะจัดส่งสินค้า</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowTrackOrder(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowTrackOrder ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowTrackOrder ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Language Switcher */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowLanguage ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowLanguage ? 'bg-violet-500/20 text-violet-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<Globe className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">เปลี่ยนภาษา (Language)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">เมนูเลือกภาษา (TH, EN, JA ฯลฯ)</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowLanguage(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowLanguage ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowLanguage ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Account / Login */}
|
||||
<div className={`flex items-start justify-between p-4 rounded-xl border transition-all ${headerShowAccount ? 'bg-white/5 border-amber-500/40' : 'bg-night-950/40 border-white/5 opacity-70'}`}>
|
||||
<div className="flex items-start gap-3 min-w-0">
|
||||
<div className={`p-2 rounded-lg shrink-0 ${headerShowAccount ? 'bg-lime-500/20 text-lime-300' : 'bg-white/5 text-slate-500'}`}>
|
||||
<User className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs font-bold text-white block">บัญชี / ล็อกอิน (Account)</span>
|
||||
<span className="text-[10px] text-slate-400 block mt-0.5 leading-tight">ปุ่ม LINE Login หรือโปรไฟล์ผู้ใช้</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHeaderShowAccount(v => !v)}
|
||||
className={`relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors cursor-pointer ml-2 ${headerShowAccount ? 'bg-amber-500' : 'bg-slate-700'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${headerShowAccount ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 5. Mobile Header Settings ── */}
|
||||
@@ -1487,7 +1683,15 @@ export default function ThemeSettingsPage() {
|
||||
|
||||
{/* ── Right Column: Live Interactive UI Preview (Sticky) ── */}
|
||||
<div className="xl:col-span-5 xl:sticky xl:top-20 space-y-6">
|
||||
<ThemeLivePreview tokens={themeTokens} />
|
||||
<ThemeLivePreview tokens={{
|
||||
...themeTokens,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
}} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -189,6 +189,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
const mobileHeaderBehavior = (settings.themeTokens as any)?.mobileHeaderBehavior ?? headerBehavior;
|
||||
const mobileHeaderStyle = (settings.themeTokens as any)?.mobileHeaderStyle ?? 'logo_center';
|
||||
const cardPreset = (settings.themeTokens as any)?.cardPreset ?? 'standard';
|
||||
const headerShowSearch = (settings.themeTokens as any)?.headerShowSearch ?? true;
|
||||
const headerShowWishlist = (settings.themeTokens as any)?.headerShowWishlist ?? true;
|
||||
const headerShowCart = (settings.themeTokens as any)?.headerShowCart ?? true;
|
||||
const headerShowTrackOrder = (settings.themeTokens as any)?.headerShowTrackOrder ?? true;
|
||||
const headerShowLanguage = (settings.themeTokens as any)?.headerShowLanguage ?? true;
|
||||
const headerShowAccount = (settings.themeTokens as any)?.headerShowAccount ?? true;
|
||||
return {
|
||||
...settingsData,
|
||||
footerLayout: normalizeFooterLayoutSettings((settingsData as any).footerLayout),
|
||||
@@ -197,6 +203,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
mobileHeaderBehavior,
|
||||
mobileHeaderStyle,
|
||||
cardPreset,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
footerDescription: localizedDescription ?? settingsData.footerDescription,
|
||||
defaultSeoTitle: localizedSeoTitle ?? settingsData.defaultSeoTitle,
|
||||
defaultSeoDescription: localizedSeoDescription ?? settingsData.defaultSeoDescription,
|
||||
@@ -371,15 +383,33 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
}, { beforeHandle: requireAdmin, body: t.Any() })
|
||||
.put("/", async ({ body, set }: { body: any; set: any }) => {
|
||||
try {
|
||||
const { footerLayout, headerBehavior, mobileHeaderBehavior, mobileHeaderStyle, cardPreset, translations, ...data } = body as any;
|
||||
const {
|
||||
footerLayout,
|
||||
headerBehavior,
|
||||
mobileHeaderBehavior,
|
||||
mobileHeaderStyle,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
cardPreset,
|
||||
translations,
|
||||
...data
|
||||
} = body as any;
|
||||
if (footerLayout !== undefined) {
|
||||
set.status = 400;
|
||||
return { error: "Use PATCH /settings/footer-layout to update footer layout" };
|
||||
}
|
||||
const currentTokens = (typeof data.themeTokens === 'object' && data.themeTokens !== null)
|
||||
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
|
||||
const existingTokens = (typeof (settings as any)?.themeTokens === 'object' && (settings as any)?.themeTokens !== null)
|
||||
? (settings as any).themeTokens
|
||||
: {};
|
||||
const passedTokens = (typeof data.themeTokens === 'object' && data.themeTokens !== null)
|
||||
? data.themeTokens
|
||||
: {};
|
||||
const updatedTokens = { ...currentTokens };
|
||||
const updatedTokens = { ...existingTokens, ...passedTokens };
|
||||
if (headerBehavior !== undefined) {
|
||||
updatedTokens.headerBehavior = headerBehavior;
|
||||
}
|
||||
@@ -389,12 +419,29 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
if (mobileHeaderStyle !== undefined) {
|
||||
updatedTokens.mobileHeaderStyle = mobileHeaderStyle;
|
||||
}
|
||||
if (headerShowSearch !== undefined) {
|
||||
updatedTokens.headerShowSearch = headerShowSearch;
|
||||
}
|
||||
if (headerShowWishlist !== undefined) {
|
||||
updatedTokens.headerShowWishlist = headerShowWishlist;
|
||||
}
|
||||
if (headerShowCart !== undefined) {
|
||||
updatedTokens.headerShowCart = headerShowCart;
|
||||
}
|
||||
if (headerShowTrackOrder !== undefined) {
|
||||
updatedTokens.headerShowTrackOrder = headerShowTrackOrder;
|
||||
}
|
||||
if (headerShowLanguage !== undefined) {
|
||||
updatedTokens.headerShowLanguage = headerShowLanguage;
|
||||
}
|
||||
if (headerShowAccount !== undefined) {
|
||||
updatedTokens.headerShowAccount = headerShowAccount;
|
||||
}
|
||||
if (cardPreset !== undefined) {
|
||||
updatedTokens.cardPreset = cardPreset;
|
||||
}
|
||||
data.themeTokens = updatedTokens;
|
||||
|
||||
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
|
||||
if (!settings) {
|
||||
settings = await prisma.storeSettings.create({ data, include: { translations: true } });
|
||||
} else {
|
||||
@@ -422,6 +469,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
mobileHeaderBehavior: returnedMobileBehavior,
|
||||
mobileHeaderStyle: returnedMobileStyle,
|
||||
cardPreset: returnedCardPreset,
|
||||
headerShowSearch: (settings?.themeTokens as any)?.headerShowSearch ?? true,
|
||||
headerShowWishlist: (settings?.themeTokens as any)?.headerShowWishlist ?? true,
|
||||
headerShowCart: (settings?.themeTokens as any)?.headerShowCart ?? true,
|
||||
headerShowTrackOrder: (settings?.themeTokens as any)?.headerShowTrackOrder ?? true,
|
||||
headerShowLanguage: (settings?.themeTokens as any)?.headerShowLanguage ?? true,
|
||||
headerShowAccount: (settings?.themeTokens as any)?.headerShowAccount ?? true,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("[Settings] Error:", err);
|
||||
@@ -456,6 +509,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
headerBehavior: t.Optional(t.String()),
|
||||
mobileHeaderBehavior: t.Optional(t.String()),
|
||||
mobileHeaderStyle: t.Optional(t.String()),
|
||||
headerShowSearch: t.Optional(t.Boolean()),
|
||||
headerShowWishlist: t.Optional(t.Boolean()),
|
||||
headerShowCart: t.Optional(t.Boolean()),
|
||||
headerShowTrackOrder: t.Optional(t.Boolean()),
|
||||
headerShowLanguage: t.Optional(t.Boolean()),
|
||||
headerShowAccount: t.Optional(t.Boolean()),
|
||||
cardPreset: t.Optional(t.String()),
|
||||
defaultSeoTitle: t.Optional(t.Nullable(t.String())),
|
||||
defaultSeoDescription: t.Optional(t.Nullable(t.String())),
|
||||
@@ -467,15 +526,33 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
})
|
||||
.post("/", async ({ body, set }: { body: any; set: any }) => {
|
||||
try {
|
||||
const { footerLayout, headerBehavior, mobileHeaderBehavior, mobileHeaderStyle, cardPreset, translations, ...data } = body as any;
|
||||
const {
|
||||
footerLayout,
|
||||
headerBehavior,
|
||||
mobileHeaderBehavior,
|
||||
mobileHeaderStyle,
|
||||
headerShowSearch,
|
||||
headerShowWishlist,
|
||||
headerShowCart,
|
||||
headerShowTrackOrder,
|
||||
headerShowLanguage,
|
||||
headerShowAccount,
|
||||
cardPreset,
|
||||
translations,
|
||||
...data
|
||||
} = body as any;
|
||||
if (footerLayout !== undefined) {
|
||||
set.status = 400;
|
||||
return { error: "Use PATCH /settings/footer-layout to update footer layout" };
|
||||
}
|
||||
const currentTokens = (typeof data.themeTokens === 'object' && data.themeTokens !== null)
|
||||
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
|
||||
const existingTokens = (typeof (settings as any)?.themeTokens === 'object' && (settings as any)?.themeTokens !== null)
|
||||
? (settings as any).themeTokens
|
||||
: {};
|
||||
const passedTokens = (typeof data.themeTokens === 'object' && data.themeTokens !== null)
|
||||
? data.themeTokens
|
||||
: {};
|
||||
const updatedTokens = { ...currentTokens };
|
||||
const updatedTokens = { ...existingTokens, ...passedTokens };
|
||||
if (headerBehavior !== undefined) {
|
||||
updatedTokens.headerBehavior = headerBehavior;
|
||||
}
|
||||
@@ -485,12 +562,29 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
if (mobileHeaderStyle !== undefined) {
|
||||
updatedTokens.mobileHeaderStyle = mobileHeaderStyle;
|
||||
}
|
||||
if (headerShowSearch !== undefined) {
|
||||
updatedTokens.headerShowSearch = headerShowSearch;
|
||||
}
|
||||
if (headerShowWishlist !== undefined) {
|
||||
updatedTokens.headerShowWishlist = headerShowWishlist;
|
||||
}
|
||||
if (headerShowCart !== undefined) {
|
||||
updatedTokens.headerShowCart = headerShowCart;
|
||||
}
|
||||
if (headerShowTrackOrder !== undefined) {
|
||||
updatedTokens.headerShowTrackOrder = headerShowTrackOrder;
|
||||
}
|
||||
if (headerShowLanguage !== undefined) {
|
||||
updatedTokens.headerShowLanguage = headerShowLanguage;
|
||||
}
|
||||
if (headerShowAccount !== undefined) {
|
||||
updatedTokens.headerShowAccount = headerShowAccount;
|
||||
}
|
||||
if (cardPreset !== undefined) {
|
||||
updatedTokens.cardPreset = cardPreset;
|
||||
}
|
||||
data.themeTokens = updatedTokens;
|
||||
|
||||
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
|
||||
if (!settings) {
|
||||
settings = await prisma.storeSettings.create({ data, include: { translations: true } });
|
||||
} else {
|
||||
@@ -518,6 +612,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
mobileHeaderBehavior: returnedMobileBehavior,
|
||||
mobileHeaderStyle: returnedMobileStyle,
|
||||
cardPreset: returnedCardPreset,
|
||||
headerShowSearch: (settings?.themeTokens as any)?.headerShowSearch ?? true,
|
||||
headerShowWishlist: (settings?.themeTokens as any)?.headerShowWishlist ?? true,
|
||||
headerShowCart: (settings?.themeTokens as any)?.headerShowCart ?? true,
|
||||
headerShowTrackOrder: (settings?.themeTokens as any)?.headerShowTrackOrder ?? true,
|
||||
headerShowLanguage: (settings?.themeTokens as any)?.headerShowLanguage ?? true,
|
||||
headerShowAccount: (settings?.themeTokens as any)?.headerShowAccount ?? true,
|
||||
};
|
||||
} catch (err) {
|
||||
console.error("[Settings] Error:", err);
|
||||
@@ -552,6 +652,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
|
||||
headerBehavior: t.Optional(t.String()),
|
||||
mobileHeaderBehavior: t.Optional(t.String()),
|
||||
mobileHeaderStyle: t.Optional(t.String()),
|
||||
headerShowSearch: t.Optional(t.Boolean()),
|
||||
headerShowWishlist: t.Optional(t.Boolean()),
|
||||
headerShowCart: t.Optional(t.Boolean()),
|
||||
headerShowTrackOrder: t.Optional(t.Boolean()),
|
||||
headerShowLanguage: t.Optional(t.Boolean()),
|
||||
headerShowAccount: t.Optional(t.Boolean()),
|
||||
cardPreset: t.Optional(t.String()),
|
||||
defaultSeoTitle: t.Optional(t.Nullable(t.String())),
|
||||
defaultSeoDescription: t.Optional(t.Nullable(t.String())),
|
||||
|
||||
@@ -199,50 +199,62 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
|
||||
|
||||
const isTwoTier = settings?.headerStyle === 'two_tier'
|
||||
|
||||
const showSearch = (settings?.themeTokens as any)?.headerShowSearch ?? settings?.headerShowSearch ?? true
|
||||
const showWishlist = (settings?.themeTokens as any)?.headerShowWishlist ?? settings?.headerShowWishlist ?? true
|
||||
const showCart = (settings?.themeTokens as any)?.headerShowCart ?? settings?.headerShowCart ?? true
|
||||
const showLanguage = (settings?.themeTokens as any)?.headerShowLanguage ?? settings?.headerShowLanguage ?? true
|
||||
const showAccount = (settings?.themeTokens as any)?.headerShowAccount ?? settings?.headerShowAccount ?? true
|
||||
|
||||
const renderActionButtons = () => (
|
||||
<div className="flex items-center gap-1.5 md:gap-2 shrink-0">
|
||||
{/* Search */}
|
||||
<button
|
||||
onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
|
||||
className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
aria-label="Search"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
{showSearch && (
|
||||
<button
|
||||
onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
|
||||
className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
aria-label="Search"
|
||||
>
|
||||
<svg className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
)}
|
||||
|
||||
{/* Wishlist */}
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
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>
|
||||
{showWishlist && (
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
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>
|
||||
)}
|
||||
|
||||
{/* Cart */}
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false); }}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
{showCart && (
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false); }}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
)}
|
||||
|
||||
{/* Language switcher (desktop) */}
|
||||
{isClientReady && isDesktop && (
|
||||
{showLanguage && isClientReady && isDesktop && (
|
||||
<div className="relative group flex items-center">
|
||||
<button className="px-3 py-2 rounded-full text-[10px] font-bold transition-all text-pop-black border border-pop-black/20 hover:bg-pop-black/5 bg-pop-bg flex items-center gap-1.5 uppercase tracking-wider">
|
||||
<Globe className="w-3.5 h-3.5" strokeWidth={1.5} />
|
||||
@@ -269,7 +281,7 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
|
||||
)}
|
||||
|
||||
{/* Auth / Profile (desktop) */}
|
||||
{isClientReady && isDesktop && (
|
||||
{showAccount && isClientReady && isDesktop && (
|
||||
customer ? (
|
||||
<div className="relative group flex items-center ml-1">
|
||||
<Link to="/profile">
|
||||
@@ -314,7 +326,7 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
|
||||
|
||||
const renderMobileActionButtons = (includeSearch = true) => (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{includeSearch && (
|
||||
{includeSearch && showSearch && (
|
||||
<button
|
||||
onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
|
||||
className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
|
||||
@@ -326,32 +338,36 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
|
||||
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>
|
||||
{showWishlist && (
|
||||
<Link
|
||||
to="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
|
||||
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); setMobileOpen(false); }}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
{showCart && (
|
||||
<button
|
||||
onClick={() => { toggleCart(true); setMobileOpen(false); }}
|
||||
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="w-4 h-4 group-hover:text-pop-black 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>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -265,9 +265,16 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
|
||||
const isTwoTier = settings?.headerStyle === 'two_tier'
|
||||
|
||||
const showSearch = (settings?.themeTokens as any)?.headerShowSearch ?? (settings as any)?.headerShowSearch ?? true
|
||||
const showWishlist = (settings?.themeTokens as any)?.headerShowWishlist ?? (settings as any)?.headerShowWishlist ?? true
|
||||
const showCart = (settings?.themeTokens as any)?.headerShowCart ?? (settings as any)?.headerShowCart ?? true
|
||||
const showTrackOrder = (settings?.themeTokens as any)?.headerShowTrackOrder ?? (settings as any)?.headerShowTrackOrder ?? true
|
||||
const showLanguage = (settings?.themeTokens as any)?.headerShowLanguage ?? (settings as any)?.headerShowLanguage ?? true
|
||||
const showAccount = (settings?.themeTokens as any)?.headerShowAccount ?? (settings as any)?.headerShowAccount ?? true
|
||||
|
||||
const renderMobileActionButtons = (includeSearch = true) => (
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
{includeSearch && (
|
||||
{includeSearch && showSearch && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
@@ -280,45 +287,51 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
{showWishlist && (
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{showCart && (
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={iconButtonClass}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
{showTrackOrder && (
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={iconButtonClass}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -451,231 +464,67 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-1.5 md:gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={iconButtonClass}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search className="h-4 w-4 transition-colors" />
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
|
||||
<div className="hidden md:flex group relative items-center">
|
||||
<button className="ui-nav-chip">
|
||||
<Globe className="h-3.5 w-3.5" strokeWidth={1.5} />
|
||||
{activeLanguage.short}
|
||||
{showSearch && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={iconButtonClass}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search className="h-4 w-4 transition-colors" />
|
||||
</button>
|
||||
<div className="pointer-events-none absolute right-0 top-full w-36 pt-2 opacity-0 transition-all duration-300 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||
<div className="ui-nav-panel space-y-0.5 p-1.5">
|
||||
{LANGUAGES.map((language) => {
|
||||
const isActive = lang.startsWith(language.code) || language.code.startsWith(lang)
|
||||
return (
|
||||
<button
|
||||
key={language.code}
|
||||
onClick={() => changeLanguage(language.code)}
|
||||
className={`flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-xs font-medium transition-colors ${
|
||||
isActive ? 'bg-surface-page font-bold text-text-main' : 'text-text-main/65 hover:bg-surface-page hover:text-brand-primary'
|
||||
}`}
|
||||
>
|
||||
<span>{language.label}</span>
|
||||
{isActive && <Check className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="hidden md:flex relative items-center">
|
||||
{customer ? (
|
||||
<Link href="/profile" className="ml-1">
|
||||
{customer.pictureUrl ? (
|
||||
<Image
|
||||
src={customer.pictureUrl}
|
||||
alt={customer.displayName || 'Profile'}
|
||||
width={36}
|
||||
height={36}
|
||||
unoptimized
|
||||
className="h-9 w-9 rounded-full border-2 border-brand-primary/30 object-cover"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-full border-2 border-brand-primary/30 text-text-main/65">
|
||||
<User className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={loginWithLine}
|
||||
className="ui-button-line ml-2"
|
||||
>
|
||||
{t('nav.login', 'เข้าสู่ระบบด้วย LINE')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Tier: Centered Navigation (Desktop only) */}
|
||||
<div className={`hidden md:flex w-full items-center justify-between transition-all duration-300 ${
|
||||
scrolled ? 'py-0.5' : 'border-t border-black/5 pt-2 mt-1'
|
||||
}`}>
|
||||
<div className={`transition-all duration-300 ${scrolled ? 'w-auto opacity-100' : 'w-0 opacity-0 overflow-hidden'}`}>
|
||||
<Link href="/" className="ui-brand-wordmark text-sm font-bold tracking-widest">
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={120} height={28} unoptimized className="h-6 w-auto object-contain" />
|
||||
) : (
|
||||
brandName
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-center">
|
||||
<DesktopMegaMenu items={menuItems} lang={lang} currentPath={pathname} />
|
||||
</div>
|
||||
|
||||
<div className={`flex items-center gap-1.5 transition-all duration-300 ${scrolled ? 'w-auto opacity-100 md:gap-2' : 'w-0 opacity-0 overflow-hidden'}`}>
|
||||
<button onClick={() => setSearchOpen(true)} className={iconButtonClass} aria-label="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
</button>
|
||||
<Link href="/wishlist" className={`relative ${iconButtonClass}`} aria-label="Wishlist">
|
||||
<Heart className="h-4 w-4" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<button onClick={() => toggleCart(true)} className={`relative ${iconButtonClass}`} aria-label="Cart">
|
||||
<ShoppingBag className="h-4 w-4" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden md:flex w-full items-center justify-between gap-4">
|
||||
<div className="flex shrink-0 items-center">
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="ui-brand-wordmark"
|
||||
>
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={160} height={40} unoptimized className="h-8 w-auto object-contain md:h-10" />
|
||||
) : (
|
||||
brandName
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-center">
|
||||
{canRenderDesktopMenu && (
|
||||
<DesktopMegaMenu items={menuItems} lang={lang} currentPath={pathname} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-1.5 md:gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={iconButtonClass}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search className="h-4 w-4 transition-colors" />
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
{showWishlist && (
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
{showCart && (
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
{showTrackOrder && (
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{canRenderDesktopMenu && (
|
||||
<>
|
||||
<div className="group relative flex items-center">
|
||||
{showLanguage && (
|
||||
<div className="hidden md:flex group relative items-center">
|
||||
<button className="ui-nav-chip">
|
||||
<Globe className="h-3.5 w-3.5" strokeWidth={1.5} />
|
||||
{activeLanguage.short}
|
||||
@@ -700,8 +549,10 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative flex items-center">
|
||||
{showAccount && (
|
||||
<div className="hidden md:flex relative items-center">
|
||||
{customer ? (
|
||||
<Link href="/profile" className="ml-1">
|
||||
{customer.pictureUrl ? (
|
||||
@@ -728,6 +579,198 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Tier: Centered Navigation (Desktop only) */}
|
||||
<div className={`hidden md:flex w-full items-center justify-between transition-all duration-300 ${
|
||||
scrolled ? 'py-0.5' : 'border-t border-black/5 pt-2 mt-1'
|
||||
}`}>
|
||||
<div className={`transition-all duration-300 ${scrolled ? 'w-auto opacity-100' : 'w-0 opacity-0 overflow-hidden'}`}>
|
||||
<Link href="/" className="ui-brand-wordmark text-sm font-bold tracking-widest">
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={120} height={28} unoptimized className="h-6 w-auto object-contain" />
|
||||
) : (
|
||||
brandName
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-center">
|
||||
<DesktopMegaMenu items={menuItems} lang={lang} currentPath={pathname} />
|
||||
</div>
|
||||
|
||||
<div className={`flex items-center gap-1.5 transition-all duration-300 ${scrolled ? 'w-auto opacity-100 md:gap-2' : 'w-0 opacity-0 overflow-hidden'}`}>
|
||||
{showSearch && (
|
||||
<button onClick={() => setSearchOpen(true)} className={iconButtonClass} aria-label="Search">
|
||||
<Search className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
{showWishlist && (
|
||||
<Link href="/wishlist" className={`relative ${iconButtonClass}`} aria-label="Wishlist">
|
||||
<Heart className="h-4 w-4" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
{showCart && (
|
||||
<button onClick={() => toggleCart(true)} className={`relative ${iconButtonClass}`} aria-label="Cart">
|
||||
<ShoppingBag className="h-4 w-4" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="hidden md:flex w-full items-center justify-between gap-4">
|
||||
<div className="flex shrink-0 items-center">
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className="ui-brand-wordmark"
|
||||
>
|
||||
{settings?.logoUrl ? (
|
||||
<Image src={settings.logoUrl} alt={brandName} width={160} height={40} unoptimized className="h-8 w-auto object-contain md:h-10" />
|
||||
) : (
|
||||
brandName
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 justify-center">
|
||||
{canRenderDesktopMenu && (
|
||||
<DesktopMegaMenu items={menuItems} lang={lang} currentPath={pathname} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-1.5 md:gap-2">
|
||||
{showSearch && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setSearchOpen(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={iconButtonClass}
|
||||
aria-label="Search"
|
||||
>
|
||||
<Search className="h-4 w-4 transition-colors" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{showWishlist && (
|
||||
<Link
|
||||
href="/wishlist"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Wishlist"
|
||||
>
|
||||
<Heart className="h-4 w-4 transition-colors" />
|
||||
{wishlistCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{wishlistCount}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{showCart && (
|
||||
<button
|
||||
onClick={() => {
|
||||
toggleCart(true)
|
||||
setMobileOpen(false)
|
||||
}}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label="Cart"
|
||||
>
|
||||
<ShoppingBag className="h-4 w-4 transition-colors" />
|
||||
{cartCount > 0 && (
|
||||
<span className="absolute -right-1 -top-1 flex h-4 w-4 items-center justify-center rounded-full bg-brand-primary text-[9px] font-bold text-surface-card">
|
||||
{cartCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{showTrackOrder && (
|
||||
<Link
|
||||
href="/track"
|
||||
onClick={() => setMobileOpen(false)}
|
||||
className={`relative ${iconButtonClass}`}
|
||||
aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')}
|
||||
>
|
||||
<Truck className="h-4 w-4 transition-colors" />
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{canRenderDesktopMenu && (
|
||||
<>
|
||||
{showLanguage && (
|
||||
<div className="group relative flex items-center">
|
||||
<button className="ui-nav-chip">
|
||||
<Globe className="h-3.5 w-3.5" strokeWidth={1.5} />
|
||||
{activeLanguage.short}
|
||||
</button>
|
||||
<div className="pointer-events-none absolute right-0 top-full w-36 pt-2 opacity-0 transition-all duration-300 group-hover:pointer-events-auto group-hover:opacity-100">
|
||||
<div className="ui-nav-panel space-y-0.5 p-1.5">
|
||||
{LANGUAGES.map((language) => {
|
||||
const isActive = lang.startsWith(language.code) || language.code.startsWith(lang)
|
||||
return (
|
||||
<button
|
||||
key={language.code}
|
||||
onClick={() => changeLanguage(language.code)}
|
||||
className={`flex w-full items-center justify-between rounded-lg px-3 py-2.5 text-xs font-medium transition-colors ${
|
||||
isActive ? 'bg-surface-page font-bold text-text-main' : 'text-text-main/65 hover:bg-surface-page hover:text-brand-primary'
|
||||
}`}
|
||||
>
|
||||
<span>{language.label}</span>
|
||||
{isActive && <Check className="h-3.5 w-3.5" />}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showAccount && (
|
||||
<div className="relative flex items-center">
|
||||
{customer ? (
|
||||
<Link href="/profile" className="ml-1">
|
||||
{customer.pictureUrl ? (
|
||||
<Image
|
||||
src={customer.pictureUrl}
|
||||
alt={customer.displayName || 'Profile'}
|
||||
width={36}
|
||||
height={36}
|
||||
unoptimized
|
||||
className="h-9 w-9 rounded-full border-2 border-brand-primary/30 object-cover"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-full border-2 border-brand-primary/30 text-text-main/65">
|
||||
<User className="h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
) : (
|
||||
<button
|
||||
onClick={loginWithLine}
|
||||
className="ui-button-line ml-2"
|
||||
>
|
||||
{t('nav.login', 'เข้าสู่ระบบด้วย LINE')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -745,6 +788,10 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
|
||||
onOpenLang={() => setLangSheetOpen(true)}
|
||||
onLogin={loginWithLine}
|
||||
onLogout={logout}
|
||||
showLanguage={showLanguage}
|
||||
showAccount={showAccount}
|
||||
showTrackOrder={showTrackOrder}
|
||||
showCart={showCart}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@ interface MobileDrawerProps {
|
||||
onOpenLang: () => void
|
||||
onLogin: () => void
|
||||
onLogout: () => void
|
||||
showLanguage?: boolean
|
||||
showAccount?: boolean
|
||||
showTrackOrder?: boolean
|
||||
showCart?: boolean
|
||||
}
|
||||
|
||||
export function MobileDrawer({
|
||||
@@ -31,6 +35,10 @@ export function MobileDrawer({
|
||||
onOpenLang,
|
||||
onLogin,
|
||||
onLogout,
|
||||
showLanguage = true,
|
||||
showAccount = true,
|
||||
showTrackOrder = true,
|
||||
showCart = true,
|
||||
}: MobileDrawerProps) {
|
||||
const [stack, setStack] = useState<MenuNode[]>([{ type: 'root', data: items, title: 'Menu' }])
|
||||
|
||||
@@ -81,18 +89,20 @@ export function MobileDrawer({
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{customerName && (
|
||||
{showAccount && customerName && (
|
||||
<div className="border-b border-text-main/10">
|
||||
<Link href="/profile" onClick={onClose} className={linkClass}>
|
||||
My Profile
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
<div className="border-b border-text-main/10">
|
||||
<Link href="/track" onClick={onClose} className={linkClass}>
|
||||
{lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'}
|
||||
</Link>
|
||||
</div>
|
||||
{showTrackOrder && (
|
||||
<div className="border-b border-text-main/10">
|
||||
<Link href="/track" onClick={onClose} className={linkClass}>
|
||||
{lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'}
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -167,13 +177,15 @@ export function MobileDrawer({
|
||||
<div className="z-10 flex shrink-0 items-center justify-between border-b border-text-main/10 bg-surface-card px-6 py-5">
|
||||
<span className="text-sm font-bold uppercase tracking-widest text-text-main">Menu</span>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={onOpenLang}
|
||||
className="flex shrink-0 items-center gap-1 rounded-md border border-brand-primary/30 bg-surface-page px-2.5 py-1 text-[10px] font-bold uppercase text-brand-primary"
|
||||
>
|
||||
<Globe className="h-3 w-3" />
|
||||
{lang.toUpperCase()}
|
||||
</button>
|
||||
{showLanguage && (
|
||||
<button
|
||||
onClick={onOpenLang}
|
||||
className="flex shrink-0 items-center gap-1 rounded-md border border-brand-primary/30 bg-surface-page px-2.5 py-1 text-[10px] font-bold uppercase text-brand-primary"
|
||||
>
|
||||
<Globe className="h-3 w-3" />
|
||||
{lang.toUpperCase()}
|
||||
</button>
|
||||
)}
|
||||
<button onClick={onClose} className="rounded-full p-1.5 text-text-main/65 transition hover:bg-surface-page hover:text-brand-primary">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
@@ -215,14 +227,16 @@ export function MobileDrawer({
|
||||
</div>
|
||||
|
||||
<div className="z-10 shrink-0 space-y-3 border-t border-text-main/10 bg-surface-card px-6 pb-8 pt-6">
|
||||
<Link
|
||||
href="/shop"
|
||||
onClick={onClose}
|
||||
className="block w-full rounded-xl bg-text-main py-3.5 text-center text-xs font-bold uppercase tracking-widest text-surface-card shadow-sm transition-colors hover:bg-brand-primary"
|
||||
>
|
||||
Shop Now
|
||||
</Link>
|
||||
{customerName ? (
|
||||
{showCart && (
|
||||
<Link
|
||||
href="/shop"
|
||||
onClick={onClose}
|
||||
className="block w-full rounded-xl bg-text-main py-3.5 text-center text-xs font-bold uppercase tracking-widest text-surface-card shadow-sm transition-colors hover:bg-brand-primary"
|
||||
>
|
||||
Shop Now
|
||||
</Link>
|
||||
)}
|
||||
{showAccount && (customerName ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
onLogout()
|
||||
@@ -242,7 +256,7 @@ export function MobileDrawer({
|
||||
>
|
||||
Login with LINE
|
||||
</button>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user