This commit is contained in:
Your Name committed 2026-06-25 19:57:14 -04:00
1 parent 46d504e483
commit 4950661abf
15 files changed
+151 -123

No files matched your search

+1 -1
View File
@@ -10,7 +10,7 @@
--color-brand-soft: #D8BFD8;
--color-surface-page: #FFF0F5;
--color-surface-nav-rest: #F9F8F5;
--color-surface-card: var(--color-surface-card);
--color-surface-card: #FFFFFF;
--color-text-main: #333333;
--color-muted-text: #666666;
--color-line-primary: #00B900;
+1 -32
View File
@@ -39,36 +39,7 @@ function setLanguageCookie(code: string) {
}
}
function createFallbackMenu(categories: Category[]): MegaMenuPayload[] {
const categoryLinks = categories.map((category) => {
// category.name is a multilingual Record<lang,string> when fetched with lang=all
const label: Record<string, string> =
typeof category.name === 'string'
? { th: category.name, en: category.name }
: (category.name as Record<string, string>)
return { label, url: `/category/${category.id}` }
})
return [
{ id: 'home', label: { th: 'หน้าหลัก', en: 'Home' }, url: '/' },
{
id: 'shop',
label: { th: 'ร้านค้า', en: 'Shop' },
url: '/shop',
columns: categoryLinks.length
? [
{
title: { th: 'คอลเลกชัน', en: 'Collections' },
links: categoryLinks,
},
]
: undefined,
},
{ id: 'promotions', label: { th: 'โปรโมชัน', en: 'Promotions' }, url: '/coupons' },
{ id: 'scent-guide', label: { th: 'คลาสแนะนำกลิ่น', en: 'Scent Guide' }, url: '/scent-guide' },
{ id: 'about', label: { th: 'เกี่ยวกับเรา', en: 'About' }, url: '/about' },
]
}
export default function Navbar({ categories = [], initialMenu = [], settings = null }: NavbarProps) {
const [searchOpen, setSearchOpen] = useState(false)
@@ -90,9 +61,7 @@ export default function Navbar({ categories = [], initialMenu = [], settings = n
const { t, i18n } = useTranslation()
const lang = useLang()
const fallbackMenu = useMemo(() => createFallbackMenu(categories), [categories])
// Use server menu if available, fall back to categories-based menu
const menuItems = megaMenu.length > 0 ? megaMenu : fallbackMenu
const menuItems = megaMenu
const changeLanguage = (code: string) => {
i18n.changeLanguage(code)
+50 -36
View File
@@ -3,7 +3,7 @@
import { useState } from 'react'
import Image from 'next/image'
import Link from './LocalizedLink'
import { ShoppingBag } from 'lucide-react'
import { ShoppingBag, Layers } from 'lucide-react'
import type { Product } from '@homaroma/types'
import { getCategoryImage, formatPrice } from '@homaroma/types'
import { resolveCategoryName, resolveLocalized } from '../lib/localization'
@@ -33,7 +33,26 @@ export default function ProductCard({ product }: ProductCardProps) {
vi: 'Thêm vào giỏ',
ko: '장바구니에 담기'
}
const optionsLabels: Record<string, string> = {
th: 'ตัวเลือก',
en: 'Options',
ja: 'オプション',
zh: '选项',
vi: 'Tùy chọn',
ko: '옵션'
}
const selectOptionsLabels: Record<string, string> = {
th: 'เลือกตัวเลือก',
en: 'Select Options',
ja: 'オプションを選択',
zh: '选择选项',
vi: 'Chọn tùy chọn',
ko: '옵션 선택'
}
const addToCartText = addToCartLabels[lang] || addToCartLabels.en
const optionsText = optionsLabels[lang] || optionsLabels.en
const selectOptionsText = selectOptionsLabels[lang] || selectOptionsLabels.en
return (
<div className="ui-card ui-card-hover group flex h-full cursor-pointer flex-col overflow-hidden p-2">
@@ -69,56 +88,51 @@ export default function ProductCard({ product }: ProductCardProps) {
)}
</div>
{product.variants.length > 0 && (
<div className="mb-1 mt-2 flex flex-wrap gap-1.5">
{product.variants.map((variant) => {
const isSelected = selectedVariant?.id === variant.id
return (
<button
key={variant.id}
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
setSelectedVariant(variant)
}}
className={`rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-wider transition-all cursor-pointer ${
isSelected
? 'border-brand-primary bg-brand-primary text-surface-card shadow-sm'
: 'border-line-primary/20 bg-surface-card text-text-main hover:border-brand-primary/50'
}`}
>
{[variant.option1, variant.option2, variant.option3].filter(Boolean).join(' / ')}
</button>
)
})}
{product.variants && product.variants.length > 1 && (
<div className="mb-1 mt-2 flex items-center">
<span className="inline-flex items-center gap-1 rounded-md bg-surface-page px-2 py-1 text-[10px] font-semibold text-muted-text border border-line-primary/20 shadow-sm transition-colors group-hover:bg-surface-page/80">
<Layers className="h-3 w-3 opacity-70" />
{product.variants.length} {optionsText}
</span>
</div>
)}
<div className="mt-auto flex flex-col gap-2 pt-3">
<div className="flex items-center justify-between">
<span className="text-sm font-black text-brand-primary md:text-lg">
{formatPrice(displayPrice)}
{formatPrice(selectedVariant?.price ?? product.variants?.[0]?.price ?? product.price)}
</span>
</div>
{selectedVariant && (
{product.variants && product.variants.length > 1 ? (
<Link href={`/products/${product.id}`} className="block w-full">
<button
type="button"
className="ui-button-primary w-full gap-1.5 shadow-sm"
>
{selectOptionsText}
</button>
</Link>
) : (
<button
type="button"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
addToCart({
productId: product.id,
variantId: selectedVariant.id,
quantity: 1,
price: selectedVariant.price || 0,
productName: productName,
variantName: [selectedVariant.option1, selectedVariant.option2, selectedVariant.option3].filter(Boolean).join(' / '),
productImage: imageUrl
})
const targetVariant = selectedVariant || product.variants[0];
if (targetVariant) {
addToCart({
productId: product.id,
variantId: targetVariant.id,
quantity: 1,
price: targetVariant.price || product.price || 0,
productName: productName,
variantName: [targetVariant.option1, targetVariant.option2, targetVariant.option3].filter(Boolean).join(' / '),
productImage: imageUrl
})
}
}}
className="ui-button-primary w-full gap-1.5 rounded-xl py-2.5 shadow-sm"
className="ui-button-primary w-full gap-1.5 shadow-sm"
>
<ShoppingBag className="h-3.5 w-3.5" />
{addToCartText}