From 9237c0eded5fb0517419b71f976a2b05626db9ad Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 10 Oct 2026 08:00:56 +0700 Subject: [PATCH] x --- .../admin/src/components/ThemeLivePreview.tsx | 60 +- apps/admin/src/pages/ThemeSettingsPage.tsx | 208 ++++++- apps/backend/src/routes/settings.ts | 122 +++- apps/spa/src/components/Navbar.tsx | 140 +++-- apps/website/src/components/Navbar.tsx | 559 ++++++++++-------- .../components/navigation/MobileDrawer.tsx | 58 +- packages/types/index.ts | 6 + 7 files changed, 776 insertions(+), 377 deletions(-) diff --git a/apps/admin/src/components/ThemeLivePreview.tsx b/apps/admin/src/components/ThemeLivePreview.tsx index a2c9ac9..5094529 100644 --- a/apps/admin/src/components/ThemeLivePreview.tsx +++ b/apps/admin/src/components/ThemeLivePreview.tsx @@ -14,7 +14,7 @@ import { } from 'lucide-react' interface ThemeLivePreviewProps { - tokens: Record + tokens: Record } // ── WCAG Relative Luminance & Contrast Calculation ────────────────────────── @@ -401,39 +401,45 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
- - -
+ {tokens.headerShowSearch !== false && ( - - 2 - -
+ )} - + {tokens.headerShowCart !== false && ( +
+ + + 2 + +
+ )} + + {tokens.headerShowAccount !== false && ( + + )}
diff --git a/apps/admin/src/pages/ThemeSettingsPage.tsx b/apps/admin/src/pages/ThemeSettingsPage.tsx index f7fbbb3..522c035 100644 --- a/apps/admin/src/pages/ThemeSettingsPage.tsx +++ b/apps/admin/src/pages/ThemeSettingsPage.tsx @@ -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(true) + const [headerShowWishlist, setHeaderShowWishlist] = useState(true) + const [headerShowCart, setHeaderShowCart] = useState(true) + const [headerShowTrackOrder, setHeaderShowTrackOrder] = useState(true) + const [headerShowLanguage, setHeaderShowLanguage] = useState(true) + const [headerShowAccount, setHeaderShowAccount] = useState(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() { + + {/* Header Action Icons (Corner Icons) */} +
+
+ +

+ กำหนดเปิด/ซ่อนไอคอนการทำงานที่มุมขวาบนของ Header (ทั้งบน PC และ Mobile) เหมาะสำหรับร้านที่ไม่มีระบบตะกร้าสินค้า หรือต้องการซ่อนบางฟังก์ชัน +

+
+ +
+ {/* Cart */} +
+
+
+ +
+
+ ตะกร้าสินค้า (Cart) + ซ่อนปุ่มตะกร้า หากเป็นร้านโชว์สินค้า/ไม่มีระบบสั่งซื้อ +
+
+ +
+ + {/* Wishlist */} +
+
+
+ +
+
+ รายการโปรด (Wishlist) + ไอคอนหัวใจบันทึกรายการโปรด +
+
+ +
+ + {/* Search */} +
+
+
+ +
+
+ ค้นหา (Search) + ปุ่มเปิดหน้าต่างค้นหาสินค้า +
+
+ +
+ + {/* Track Order */} +
+
+
+ +
+
+ ติดตามพัสดุ (Track Order) + ปุ่มตรวจสอบสถานะจัดส่งสินค้า +
+
+ +
+ + {/* Language Switcher */} +
+
+
+ +
+
+ เปลี่ยนภาษา (Language) + เมนูเลือกภาษา (TH, EN, JA ฯลฯ) +
+
+ +
+ + {/* Account / Login */} +
+
+
+ +
+
+ บัญชี / ล็อกอิน (Account) + ปุ่ม LINE Login หรือโปรไฟล์ผู้ใช้ +
+
+ +
+
+
{/* ── 5. Mobile Header Settings ── */} @@ -1487,7 +1683,15 @@ export default function ThemeSettingsPage() { {/* ── Right Column: Live Interactive UI Preview (Sticky) ── */}
- +
diff --git a/apps/backend/src/routes/settings.ts b/apps/backend/src/routes/settings.ts index c9fdc98..d0e4359 100644 --- a/apps/backend/src/routes/settings.ts +++ b/apps/backend/src/routes/settings.ts @@ -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())), diff --git a/apps/spa/src/components/Navbar.tsx b/apps/spa/src/components/Navbar.tsx index cc8dbfd..94a97cf 100644 --- a/apps/spa/src/components/Navbar.tsx +++ b/apps/spa/src/components/Navbar.tsx @@ -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 = () => (
{/* Search */} - + {showSearch && ( + + )} {/* Wishlist */} - 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" - > - - {wishlistCount > 0 && ( - - {wishlistCount} - - )} - + {showWishlist && ( + 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" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} {/* Cart */} - + {showCart && ( + + )} {/* Language switcher (desktop) */} - {isClientReady && isDesktop && ( + {showLanguage && isClientReady && isDesktop && (
)} - setMobileOpen(false)} - className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5" - aria-label="Wishlist" - > - - {wishlistCount > 0 && ( - - {wishlistCount} - - )} - + {showWishlist && ( + setMobileOpen(false)} + className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5" + aria-label="Wishlist" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} - + {showCart && ( + + )}
) diff --git a/apps/website/src/components/Navbar.tsx b/apps/website/src/components/Navbar.tsx index 746f36c..a27dcbc 100644 --- a/apps/website/src/components/Navbar.tsx +++ b/apps/website/src/components/Navbar.tsx @@ -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) => (
- {includeSearch && ( + {includeSearch && showSearch && ( )} - setMobileOpen(false)} - className={`relative ${iconButtonClass}`} - aria-label="Wishlist" - > - - {wishlistCount > 0 && ( - - {wishlistCount} - - )} - + {showWishlist && ( + setMobileOpen(false)} + className={`relative ${iconButtonClass}`} + aria-label="Wishlist" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} - + {showCart && ( + + )} - setMobileOpen(false)} - className={iconButtonClass} - aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - > - - + {showTrackOrder && ( + setMobileOpen(false)} + className={iconButtonClass} + aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + > + + + )}
) @@ -451,231 +464,67 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
- - - setMobileOpen(false)} - className={`relative ${iconButtonClass}`} - aria-label="Wishlist" - > - - {wishlistCount > 0 && ( - - {wishlistCount} - - )} - - - - - setMobileOpen(false)} - className={`relative ${iconButtonClass}`} - aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - > - - - -
- -
-
- {LANGUAGES.map((language) => { - const isActive = lang.startsWith(language.code) || language.code.startsWith(lang) - return ( - - ) - })} -
-
-
- -
- {customer ? ( - - {customer.pictureUrl ? ( - {customer.displayName - ) : ( - - - - )} - - ) : ( - - )} -
-
- - - {/* Bottom Tier: Centered Navigation (Desktop only) */} -
-
- - {settings?.logoUrl ? ( - {brandName} - ) : ( - brandName - )} - -
- -
- -
- -
- - - - {wishlistCount > 0 && ( - - {wishlistCount} - - )} - - -
-
- - ) : ( -
-
- setMobileOpen(false)} - className="ui-brand-wordmark" - > - {settings?.logoUrl ? ( - {brandName} - ) : ( - brandName )} - -
-
- {canRenderDesktopMenu && ( - - )} -
- -
- - - setMobileOpen(false)} - className={`relative ${iconButtonClass}`} - aria-label="Wishlist" - > - - {wishlistCount > 0 && ( - - {wishlistCount} - + {showWishlist && ( + setMobileOpen(false)} + className={`relative ${iconButtonClass}`} + aria-label="Wishlist" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + )} - - )} - - setMobileOpen(false)} - className={`relative ${iconButtonClass}`} - aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} - > - - + {showTrackOrder && ( + setMobileOpen(false)} + className={`relative ${iconButtonClass}`} + aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + > + + + )} - {canRenderDesktopMenu && ( - <> -
+ {showLanguage && ( +
+ )} -
+ {showAccount && ( +
{customer ? ( {customer.pictureUrl ? ( @@ -728,6 +579,198 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n )}
+ )} +
+
+ + {/* Bottom Tier: Centered Navigation (Desktop only) */} +
+
+ + {settings?.logoUrl ? ( + {brandName} + ) : ( + brandName + )} + +
+ +
+ +
+ +
+ {showSearch && ( + + )} + {showWishlist && ( + + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} + {showCart && ( + + )} +
+
+ + ) : ( +
+
+ setMobileOpen(false)} + className="ui-brand-wordmark" + > + {settings?.logoUrl ? ( + {brandName} + ) : ( + brandName + )} + +
+ +
+ {canRenderDesktopMenu && ( + + )} +
+ +
+ {showSearch && ( + + )} + + {showWishlist && ( + setMobileOpen(false)} + className={`relative ${iconButtonClass}`} + aria-label="Wishlist" + > + + {wishlistCount > 0 && ( + + {wishlistCount} + + )} + + )} + + {showCart && ( + + )} + + {showTrackOrder && ( + setMobileOpen(false)} + className={`relative ${iconButtonClass}`} + aria-label={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + title={t('nav.trackOrder', 'ติดตามคำสั่งซื้อ')} + > + + + )} + + {canRenderDesktopMenu && ( + <> + {showLanguage && ( +
+ +
+
+ {LANGUAGES.map((language) => { + const isActive = lang.startsWith(language.code) || language.code.startsWith(lang) + return ( + + ) + })} +
+
+
+ )} + + {showAccount && ( +
+ {customer ? ( + + {customer.pictureUrl ? ( + {customer.displayName + ) : ( + + + + )} + + ) : ( + + )} +
+ )} )}
@@ -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} /> )} diff --git a/apps/website/src/components/navigation/MobileDrawer.tsx b/apps/website/src/components/navigation/MobileDrawer.tsx index ce3f5c5..9ff8a3c 100644 --- a/apps/website/src/components/navigation/MobileDrawer.tsx +++ b/apps/website/src/components/navigation/MobileDrawer.tsx @@ -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([{ type: 'root', data: items, title: 'Menu' }]) @@ -81,18 +89,20 @@ export function MobileDrawer({ )}
))} - {customerName && ( + {showAccount && customerName && (
My Profile
)} -
- - {lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'} - -
+ {showTrackOrder && ( +
+ + {lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'} + +
+ )} ) } @@ -167,13 +177,15 @@ export function MobileDrawer({
Menu
- + {showLanguage && ( + + )} @@ -215,14 +227,16 @@ export function MobileDrawer({
- - Shop Now - - {customerName ? ( + {showCart && ( + + Shop Now + + )} + {showAccount && (customerName ? ( - )} + ))}
diff --git a/packages/types/index.ts b/packages/types/index.ts index afae778..f704d33 100644 --- a/packages/types/index.ts +++ b/packages/types/index.ts @@ -417,6 +417,12 @@ export interface StoreSettings { headerBehavior?: 'always_sticky' | 'hide_on_scroll' | string | null mobileHeaderStyle?: 'logo_center' | 'logo_left' | 'search_bar_below' | string | null mobileHeaderBehavior?: 'always_sticky' | 'hide_on_scroll' | string | null + headerShowSearch?: boolean | null + headerShowWishlist?: boolean | null + headerShowCart?: boolean | null + headerShowTrackOrder?: boolean | null + headerShowLanguage?: boolean | null + headerShowAccount?: boolean | null cardPreset?: 'standard' | 'minimal' | 'swatch' | string | null defaultSeoTitle?: string | null defaultSeoDescription?: string | null