This commit is contained in:
Your Name committed 2026-10-10 08:00:56 +07:00
1 parent 943fd8f1a3
commit 9237c0eded
7 files changed
+417 -18

No files matched your search

@@ -14,7 +14,7 @@ import {
} from 'lucide-react' } from 'lucide-react'
interface ThemeLivePreviewProps { interface ThemeLivePreviewProps {
tokens: Record<string, string> tokens: Record<string, any>
} }
// ── WCAG Relative Luminance & Contrast Calculation ────────────────────────── // ── WCAG Relative Luminance & Contrast Calculation ──────────────────────────
@@ -401,6 +401,7 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{tokens.headerShowSearch !== false && (
<button <button
type="button" type="button"
style={{ color: 'var(--p-text-main)' }} style={{ color: 'var(--p-text-main)' }}
@@ -409,7 +410,9 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
> >
<Search className="w-3.5 h-3.5" /> <Search className="w-3.5 h-3.5" />
</button> </button>
)}
{tokens.headerShowCart !== false && (
<div className="relative"> <div className="relative">
<button <button
type="button" type="button"
@@ -426,7 +429,9 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
2 2
</span> </span>
</div> </div>
)}
{tokens.headerShowAccount !== false && (
<button <button
type="button" type="button"
className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer" className="preview-btn-line px-2.5 py-1 text-[10px] font-bold flex items-center gap-1 cursor-pointer"
@@ -434,6 +439,7 @@ export default function ThemeLivePreview({ tokens }: ThemeLivePreviewProps) {
<MessageCircle className="w-3 h-3" /> <MessageCircle className="w-3 h-3" />
<span>LINE</span> <span>LINE</span>
</button> </button>
)}
</div> </div>
</header> </header>
+206 -2
View File
@@ -19,7 +19,13 @@ import {
ArrowUpDown, ArrowUpDown,
Monitor, Monitor,
Smartphone, Smartphone,
LayoutGrid LayoutGrid,
Search,
Heart,
ShoppingBag,
Truck,
Globe,
User
} from 'lucide-react' } from 'lucide-react'
const defaultThemeTokens = { const defaultThemeTokens = {
@@ -169,6 +175,12 @@ export default function ThemeSettingsPage() {
const [mobileHeaderStyle, setMobileHeaderStyle] = useState<'logo_center' | 'logo_left' | 'search_bar_below'>('logo_center') 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 [mobileHeaderBehavior, setMobileHeaderBehavior] = useState<'always_sticky' | 'hide_on_scroll'>('hide_on_scroll')
const [cardPreset, setCardPreset] = useState<'standard' | 'minimal' | 'swatch'>('standard') 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 // JSON Quick Copy / Paste State
const [isJsonBoxOpen, setIsJsonBoxOpen] = useState(false) const [isJsonBoxOpen, setIsJsonBoxOpen] = useState(false)
@@ -201,6 +213,12 @@ export default function ThemeSettingsPage() {
setMobileHeaderBehavior((data as any).mobileHeaderBehavior || (data.themeTokens as any)?.mobileHeaderBehavior || pcBehavior) setMobileHeaderBehavior((data as any).mobileHeaderBehavior || (data.themeTokens as any)?.mobileHeaderBehavior || pcBehavior)
const cp = (data as any).cardPreset || (data.themeTokens as any)?.cardPreset || 'standard' const cp = (data as any).cardPreset || (data.themeTokens as any)?.cardPreset || 'standard'
setCardPreset(cp === 'minimal_swatch' ? 'swatch' : cp) 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) .catch(console.error)
.finally(() => setLoading(false)) .finally(() => setLoading(false))
@@ -221,6 +239,12 @@ export default function ThemeSettingsPage() {
headerBehavior, headerBehavior,
mobileHeaderBehavior, mobileHeaderBehavior,
mobileHeaderStyle, mobileHeaderStyle,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
cardPreset, cardPreset,
}, },
cardPreset, cardPreset,
@@ -231,6 +255,12 @@ export default function ThemeSettingsPage() {
headerBehavior, headerBehavior,
mobileHeaderStyle, mobileHeaderStyle,
mobileHeaderBehavior, 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') { } else if (extractedTokens?.mobileHeaderBehavior === 'always_sticky' || extractedTokens?.mobileHeaderBehavior === 'hide_on_scroll') {
setMobileHeaderBehavior(extractedTokens.mobileHeaderBehavior) 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 ────────────────────────────────────────────── // ── Paste JSON Text Handlers ──────────────────────────────────────────────
@@ -450,6 +499,12 @@ export default function ThemeSettingsPage() {
headerBehavior, headerBehavior,
mobileHeaderBehavior, mobileHeaderBehavior,
mobileHeaderStyle, mobileHeaderStyle,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
cardPreset, cardPreset,
}, },
cardPreset, cardPreset,
@@ -460,6 +515,12 @@ export default function ThemeSettingsPage() {
headerBehavior, headerBehavior,
mobileHeaderStyle, mobileHeaderStyle,
mobileHeaderBehavior, mobileHeaderBehavior,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
}), }),
}) })
if (res.ok) { if (res.ok) {
@@ -1262,6 +1323,141 @@ export default function ThemeSettingsPage() {
</button> </button>
</div> </div>
</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> </div>
{/* ── 5. Mobile Header Settings ── */} {/* ── 5. Mobile Header Settings ── */}
@@ -1487,7 +1683,15 @@ export default function ThemeSettingsPage() {
{/* ── Right Column: Live Interactive UI Preview (Sticky) ── */} {/* ── Right Column: Live Interactive UI Preview (Sticky) ── */}
<div className="xl:col-span-5 xl:sticky xl:top-20 space-y-6"> <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>
</div> </div>
+114 -8
View File
@@ -189,6 +189,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
const mobileHeaderBehavior = (settings.themeTokens as any)?.mobileHeaderBehavior ?? headerBehavior; const mobileHeaderBehavior = (settings.themeTokens as any)?.mobileHeaderBehavior ?? headerBehavior;
const mobileHeaderStyle = (settings.themeTokens as any)?.mobileHeaderStyle ?? 'logo_center'; const mobileHeaderStyle = (settings.themeTokens as any)?.mobileHeaderStyle ?? 'logo_center';
const cardPreset = (settings.themeTokens as any)?.cardPreset ?? 'standard'; 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 { return {
...settingsData, ...settingsData,
footerLayout: normalizeFooterLayoutSettings((settingsData as any).footerLayout), footerLayout: normalizeFooterLayoutSettings((settingsData as any).footerLayout),
@@ -197,6 +203,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
mobileHeaderBehavior, mobileHeaderBehavior,
mobileHeaderStyle, mobileHeaderStyle,
cardPreset, cardPreset,
headerShowSearch,
headerShowWishlist,
headerShowCart,
headerShowTrackOrder,
headerShowLanguage,
headerShowAccount,
footerDescription: localizedDescription ?? settingsData.footerDescription, footerDescription: localizedDescription ?? settingsData.footerDescription,
defaultSeoTitle: localizedSeoTitle ?? settingsData.defaultSeoTitle, defaultSeoTitle: localizedSeoTitle ?? settingsData.defaultSeoTitle,
defaultSeoDescription: localizedSeoDescription ?? settingsData.defaultSeoDescription, defaultSeoDescription: localizedSeoDescription ?? settingsData.defaultSeoDescription,
@@ -371,15 +383,33 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
}, { beforeHandle: requireAdmin, body: t.Any() }) }, { beforeHandle: requireAdmin, body: t.Any() })
.put("/", async ({ body, set }: { body: any; set: any }) => { .put("/", async ({ body, set }: { body: any; set: any }) => {
try { 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) { if (footerLayout !== undefined) {
set.status = 400; set.status = 400;
return { error: "Use PATCH /settings/footer-layout to update footer layout" }; 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 ? data.themeTokens
: {}; : {};
const updatedTokens = { ...currentTokens }; const updatedTokens = { ...existingTokens, ...passedTokens };
if (headerBehavior !== undefined) { if (headerBehavior !== undefined) {
updatedTokens.headerBehavior = headerBehavior; updatedTokens.headerBehavior = headerBehavior;
} }
@@ -389,12 +419,29 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
if (mobileHeaderStyle !== undefined) { if (mobileHeaderStyle !== undefined) {
updatedTokens.mobileHeaderStyle = mobileHeaderStyle; 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) { if (cardPreset !== undefined) {
updatedTokens.cardPreset = cardPreset; updatedTokens.cardPreset = cardPreset;
} }
data.themeTokens = updatedTokens; data.themeTokens = updatedTokens;
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
if (!settings) { if (!settings) {
settings = await prisma.storeSettings.create({ data, include: { translations: true } }); settings = await prisma.storeSettings.create({ data, include: { translations: true } });
} else { } else {
@@ -422,6 +469,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
mobileHeaderBehavior: returnedMobileBehavior, mobileHeaderBehavior: returnedMobileBehavior,
mobileHeaderStyle: returnedMobileStyle, mobileHeaderStyle: returnedMobileStyle,
cardPreset: returnedCardPreset, 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) { } catch (err) {
console.error("[Settings] Error:", err); console.error("[Settings] Error:", err);
@@ -456,6 +509,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
headerBehavior: t.Optional(t.String()), headerBehavior: t.Optional(t.String()),
mobileHeaderBehavior: t.Optional(t.String()), mobileHeaderBehavior: t.Optional(t.String()),
mobileHeaderStyle: 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()), cardPreset: t.Optional(t.String()),
defaultSeoTitle: t.Optional(t.Nullable(t.String())), defaultSeoTitle: t.Optional(t.Nullable(t.String())),
defaultSeoDescription: 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 }) => { .post("/", async ({ body, set }: { body: any; set: any }) => {
try { 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) { if (footerLayout !== undefined) {
set.status = 400; set.status = 400;
return { error: "Use PATCH /settings/footer-layout to update footer layout" }; 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 ? data.themeTokens
: {}; : {};
const updatedTokens = { ...currentTokens }; const updatedTokens = { ...existingTokens, ...passedTokens };
if (headerBehavior !== undefined) { if (headerBehavior !== undefined) {
updatedTokens.headerBehavior = headerBehavior; updatedTokens.headerBehavior = headerBehavior;
} }
@@ -485,12 +562,29 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
if (mobileHeaderStyle !== undefined) { if (mobileHeaderStyle !== undefined) {
updatedTokens.mobileHeaderStyle = mobileHeaderStyle; 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) { if (cardPreset !== undefined) {
updatedTokens.cardPreset = cardPreset; updatedTokens.cardPreset = cardPreset;
} }
data.themeTokens = updatedTokens; data.themeTokens = updatedTokens;
let settings = await prisma.storeSettings.findFirst({ include: { translations: true } });
if (!settings) { if (!settings) {
settings = await prisma.storeSettings.create({ data, include: { translations: true } }); settings = await prisma.storeSettings.create({ data, include: { translations: true } });
} else { } else {
@@ -518,6 +612,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
mobileHeaderBehavior: returnedMobileBehavior, mobileHeaderBehavior: returnedMobileBehavior,
mobileHeaderStyle: returnedMobileStyle, mobileHeaderStyle: returnedMobileStyle,
cardPreset: returnedCardPreset, 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) { } catch (err) {
console.error("[Settings] Error:", err); console.error("[Settings] Error:", err);
@@ -552,6 +652,12 @@ export const settingsRoute = new Elysia({ prefix: "/settings" })
headerBehavior: t.Optional(t.String()), headerBehavior: t.Optional(t.String()),
mobileHeaderBehavior: t.Optional(t.String()), mobileHeaderBehavior: t.Optional(t.String()),
mobileHeaderStyle: 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()), cardPreset: t.Optional(t.String()),
defaultSeoTitle: t.Optional(t.Nullable(t.String())), defaultSeoTitle: t.Optional(t.Nullable(t.String())),
defaultSeoDescription: t.Optional(t.Nullable(t.String())), defaultSeoDescription: t.Optional(t.Nullable(t.String())),
+19 -3
View File
@@ -199,9 +199,16 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
const isTwoTier = settings?.headerStyle === 'two_tier' 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 = () => ( const renderActionButtons = () => (
<div className="flex items-center gap-1.5 md:gap-2 shrink-0"> <div className="flex items-center gap-1.5 md:gap-2 shrink-0">
{/* Search */} {/* Search */}
{showSearch && (
<button <button
onClick={() => { setSearchOpen(true); setMobileOpen(false); }} 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" className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
@@ -211,8 +218,10 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg> </svg>
</button> </button>
)}
{/* Wishlist */} {/* Wishlist */}
{showWishlist && (
<Link <Link
to="/wishlist" to="/wishlist"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -226,8 +235,10 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
</span> </span>
)} )}
</Link> </Link>
)}
{/* Cart */} {/* Cart */}
{showCart && (
<button <button
onClick={() => { toggleCart(true); setMobileOpen(false); }} 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" className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5 md:p-2"
@@ -240,9 +251,10 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
</span> </span>
)} )}
</button> </button>
)}
{/* Language switcher (desktop) */} {/* Language switcher (desktop) */}
{isClientReady && isDesktop && ( {showLanguage && isClientReady && isDesktop && (
<div className="relative group flex items-center"> <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"> <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} /> <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) */} {/* Auth / Profile (desktop) */}
{isClientReady && isDesktop && ( {showAccount && isClientReady && isDesktop && (
customer ? ( customer ? (
<div className="relative group flex items-center ml-1"> <div className="relative group flex items-center ml-1">
<Link to="/profile"> <Link to="/profile">
@@ -314,7 +326,7 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
const renderMobileActionButtons = (includeSearch = true) => ( const renderMobileActionButtons = (includeSearch = true) => (
<div className="flex items-center gap-1.5 shrink-0"> <div className="flex items-center gap-1.5 shrink-0">
{includeSearch && ( {includeSearch && showSearch && (
<button <button
onClick={() => { setSearchOpen(true); setMobileOpen(false); }} onClick={() => { setSearchOpen(true); setMobileOpen(false); }}
className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5" className="group rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
@@ -326,6 +338,7 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
</button> </button>
)} )}
{showWishlist && (
<Link <Link
to="/wishlist" to="/wishlist"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -339,7 +352,9 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
</span> </span>
)} )}
</Link> </Link>
)}
{showCart && (
<button <button
onClick={() => { toggleCart(true); setMobileOpen(false); }} onClick={() => { toggleCart(true); setMobileOpen(false); }}
className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5" className="group relative rounded-full p-1.5 text-pop-black/80 transition-all hover:bg-pop-black/5"
@@ -352,6 +367,7 @@ export default function Navbar({ topOffsetClassName = 'top-0', isTransparent = f
</span> </span>
)} )}
</button> </button>
)}
</div> </div>
) )
+48 -1
View File
@@ -265,9 +265,16 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
const isTwoTier = settings?.headerStyle === 'two_tier' 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) => ( const renderMobileActionButtons = (includeSearch = true) => (
<div className="flex items-center gap-1.5 shrink-0"> <div className="flex items-center gap-1.5 shrink-0">
{includeSearch && ( {includeSearch && showSearch && (
<button <button
onClick={() => { onClick={() => {
setSearchOpen(true) setSearchOpen(true)
@@ -280,6 +287,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</button> </button>
)} )}
{showWishlist && (
<Link <Link
href="/wishlist" href="/wishlist"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -293,7 +301,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</Link> </Link>
)}
{showCart && (
<button <button
onClick={() => { onClick={() => {
toggleCart(true) toggleCart(true)
@@ -309,7 +319,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</button> </button>
)}
{showTrackOrder && (
<Link <Link
href="/track" href="/track"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -319,6 +331,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
> >
<Truck className="h-4 w-4 transition-colors" /> <Truck className="h-4 w-4 transition-colors" />
</Link> </Link>
)}
</div> </div>
) )
@@ -451,6 +464,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</div> </div>
<div className="flex items-center justify-end gap-1.5 md:gap-2"> <div className="flex items-center justify-end gap-1.5 md:gap-2">
{showSearch && (
<button <button
onClick={() => { onClick={() => {
setSearchOpen(true) setSearchOpen(true)
@@ -461,7 +475,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
> >
<Search className="h-4 w-4 transition-colors" /> <Search className="h-4 w-4 transition-colors" />
</button> </button>
)}
{showWishlist && (
<Link <Link
href="/wishlist" href="/wishlist"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -475,7 +491,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</Link> </Link>
)}
{showCart && (
<button <button
onClick={() => { onClick={() => {
toggleCart(true) toggleCart(true)
@@ -491,7 +509,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</button> </button>
)}
{showTrackOrder && (
<Link <Link
href="/track" href="/track"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -501,7 +521,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
> >
<Truck className="h-4 w-4 transition-colors" /> <Truck className="h-4 w-4 transition-colors" />
</Link> </Link>
)}
{showLanguage && (
<div className="hidden md:flex group relative items-center"> <div className="hidden md:flex group relative items-center">
<button className="ui-nav-chip"> <button className="ui-nav-chip">
<Globe className="h-3.5 w-3.5" strokeWidth={1.5} /> <Globe className="h-3.5 w-3.5" strokeWidth={1.5} />
@@ -527,7 +549,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</div> </div>
</div> </div>
</div> </div>
)}
{showAccount && (
<div className="hidden md:flex relative items-center"> <div className="hidden md:flex relative items-center">
{customer ? ( {customer ? (
<Link href="/profile" className="ml-1"> <Link href="/profile" className="ml-1">
@@ -555,6 +579,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</button> </button>
)} )}
</div> </div>
)}
</div> </div>
</div> </div>
@@ -577,9 +602,12 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</div> </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'}`}> <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"> <button onClick={() => setSearchOpen(true)} className={iconButtonClass} aria-label="Search">
<Search className="h-4 w-4" /> <Search className="h-4 w-4" />
</button> </button>
)}
{showWishlist && (
<Link href="/wishlist" className={`relative ${iconButtonClass}`} aria-label="Wishlist"> <Link href="/wishlist" className={`relative ${iconButtonClass}`} aria-label="Wishlist">
<Heart className="h-4 w-4" /> <Heart className="h-4 w-4" />
{wishlistCount > 0 && ( {wishlistCount > 0 && (
@@ -588,6 +616,8 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</Link> </Link>
)}
{showCart && (
<button onClick={() => toggleCart(true)} className={`relative ${iconButtonClass}`} aria-label="Cart"> <button onClick={() => toggleCart(true)} className={`relative ${iconButtonClass}`} aria-label="Cart">
<ShoppingBag className="h-4 w-4" /> <ShoppingBag className="h-4 w-4" />
{cartCount > 0 && ( {cartCount > 0 && (
@@ -596,6 +626,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</button> </button>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -622,6 +653,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</div> </div>
<div className="flex shrink-0 items-center gap-1.5 md:gap-2"> <div className="flex shrink-0 items-center gap-1.5 md:gap-2">
{showSearch && (
<button <button
onClick={() => { onClick={() => {
setSearchOpen(true) setSearchOpen(true)
@@ -632,7 +664,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
> >
<Search className="h-4 w-4 transition-colors" /> <Search className="h-4 w-4 transition-colors" />
</button> </button>
)}
{showWishlist && (
<Link <Link
href="/wishlist" href="/wishlist"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -646,7 +680,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</Link> </Link>
)}
{showCart && (
<button <button
onClick={() => { onClick={() => {
toggleCart(true) toggleCart(true)
@@ -662,7 +698,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</span> </span>
)} )}
</button> </button>
)}
{showTrackOrder && (
<Link <Link
href="/track" href="/track"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
@@ -672,9 +710,11 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
> >
<Truck className="h-4 w-4 transition-colors" /> <Truck className="h-4 w-4 transition-colors" />
</Link> </Link>
)}
{canRenderDesktopMenu && ( {canRenderDesktopMenu && (
<> <>
{showLanguage && (
<div className="group relative flex items-center"> <div className="group relative flex items-center">
<button className="ui-nav-chip"> <button className="ui-nav-chip">
<Globe className="h-3.5 w-3.5" strokeWidth={1.5} /> <Globe className="h-3.5 w-3.5" strokeWidth={1.5} />
@@ -700,7 +740,9 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</div> </div>
</div> </div>
</div> </div>
)}
{showAccount && (
<div className="relative flex items-center"> <div className="relative flex items-center">
{customer ? ( {customer ? (
<Link href="/profile" className="ml-1"> <Link href="/profile" className="ml-1">
@@ -728,6 +770,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
</button> </button>
)} )}
</div> </div>
)}
</> </>
)} )}
</div> </div>
@@ -745,6 +788,10 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
onOpenLang={() => setLangSheetOpen(true)} onOpenLang={() => setLangSheetOpen(true)}
onLogin={loginWithLine} onLogin={loginWithLine}
onLogout={logout} onLogout={logout}
showLanguage={showLanguage}
showAccount={showAccount}
showTrackOrder={showTrackOrder}
showCart={showCart}
/> />
)} )}
@@ -20,6 +20,10 @@ interface MobileDrawerProps {
onOpenLang: () => void onOpenLang: () => void
onLogin: () => void onLogin: () => void
onLogout: () => void onLogout: () => void
showLanguage?: boolean
showAccount?: boolean
showTrackOrder?: boolean
showCart?: boolean
} }
export function MobileDrawer({ export function MobileDrawer({
@@ -31,6 +35,10 @@ export function MobileDrawer({
onOpenLang, onOpenLang,
onLogin, onLogin,
onLogout, onLogout,
showLanguage = true,
showAccount = true,
showTrackOrder = true,
showCart = true,
}: MobileDrawerProps) { }: MobileDrawerProps) {
const [stack, setStack] = useState<MenuNode[]>([{ type: 'root', data: items, title: 'Menu' }]) const [stack, setStack] = useState<MenuNode[]>([{ type: 'root', data: items, title: 'Menu' }])
@@ -81,18 +89,20 @@ export function MobileDrawer({
)} )}
</div> </div>
))} ))}
{customerName && ( {showAccount && customerName && (
<div className="border-b border-text-main/10"> <div className="border-b border-text-main/10">
<Link href="/profile" onClick={onClose} className={linkClass}> <Link href="/profile" onClick={onClose} className={linkClass}>
My Profile My Profile
</Link> </Link>
</div> </div>
)} )}
{showTrackOrder && (
<div className="border-b border-text-main/10"> <div className="border-b border-text-main/10">
<Link href="/track" onClick={onClose} className={linkClass}> <Link href="/track" onClick={onClose} className={linkClass}>
{lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'} {lang === 'th' ? 'ติดตามคำสั่งซื้อ' : 'Track Order'}
</Link> </Link>
</div> </div>
)}
</div> </div>
) )
} }
@@ -167,6 +177,7 @@ 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"> <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> <span className="text-sm font-bold uppercase tracking-widest text-text-main">Menu</span>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
{showLanguage && (
<button <button
onClick={onOpenLang} 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" 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"
@@ -174,6 +185,7 @@ export function MobileDrawer({
<Globe className="h-3 w-3" /> <Globe className="h-3 w-3" />
{lang.toUpperCase()} {lang.toUpperCase()}
</button> </button>
)}
<button onClick={onClose} className="rounded-full p-1.5 text-text-main/65 transition hover:bg-surface-page hover:text-brand-primary"> <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" /> <X className="h-5 w-5" />
</button> </button>
@@ -215,6 +227,7 @@ export function MobileDrawer({
</div> </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"> <div className="z-10 shrink-0 space-y-3 border-t border-text-main/10 bg-surface-card px-6 pb-8 pt-6">
{showCart && (
<Link <Link
href="/shop" href="/shop"
onClick={onClose} onClick={onClose}
@@ -222,7 +235,8 @@ export function MobileDrawer({
> >
Shop Now Shop Now
</Link> </Link>
{customerName ? ( )}
{showAccount && (customerName ? (
<button <button
onClick={() => { onClick={() => {
onLogout() onLogout()
@@ -242,7 +256,7 @@ export function MobileDrawer({
> >
Login with LINE Login with LINE
</button> </button>
)} ))}
</div> </div>
</div> </div>
</div> </div>
+6
View File
@@ -417,6 +417,12 @@ export interface StoreSettings {
headerBehavior?: 'always_sticky' | 'hide_on_scroll' | string | null headerBehavior?: 'always_sticky' | 'hide_on_scroll' | string | null
mobileHeaderStyle?: 'logo_center' | 'logo_left' | 'search_bar_below' | string | null mobileHeaderStyle?: 'logo_center' | 'logo_left' | 'search_bar_below' | string | null
mobileHeaderBehavior?: 'always_sticky' | 'hide_on_scroll' | 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 cardPreset?: 'standard' | 'minimal' | 'swatch' | string | null
defaultSeoTitle?: string | null defaultSeoTitle?: string | null
defaultSeoDescription?: string | null defaultSeoDescription?: string | null