diff --git a/apps/mcp-server/index.ts b/apps/mcp-server/index.ts index 4314731..a502ccf 100644 --- a/apps/mcp-server/index.ts +++ b/apps/mcp-server/index.ts @@ -189,11 +189,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { }, { name: "update_store_settings_translation", - description: "Add or update translations for store settings.", + description: "Update translated footer and newsletter text for a specific language.", inputSchema: { type: "object", properties: { - lang: { type: "string", description: "Language code" }, + lang: { type: "string" }, footerDescription: { type: "string" }, newsletterTitle: { type: "string" }, newsletterDescription: { type: "string" }, @@ -203,6 +203,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { required: ["lang"] } }, + { + name: "update_theme_tokens", + description: "Update the theme tokens (colors, fonts, shadows) in the store settings.", + inputSchema: { + type: "object", + properties: { + brandPrimary: { type: "string" }, + brandPrimaryHover: { type: "string" }, + brandAccent: { type: "string" }, + brandWarm: { type: "string" }, + brandCool: { type: "string" }, + brandSuccess: { type: "string" }, + brandSoft: { type: "string" }, + surfacePage: { type: "string" }, + surfaceNavRest: { type: "string" }, + surfaceCard: { type: "string" }, + textMain: { type: "string" }, + mutedText: { type: "string" }, + linePrimary: { type: "string" }, + linePrimaryHover: { type: "string" }, + fontBody: { type: "string" }, + fontDisplay: { type: "string" }, + shadowCard: { type: "string" }, + shadowCardHover: { type: "string" } + } + } + }, { name: "update_brand_name", description: "Update the brand name used across website and SPA headers, footers, and brand displays.", @@ -523,6 +550,39 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { const res = await api.patch(`/settings/translations`, payload); return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] }; } + case "update_theme_tokens": { + const tokenParams = params as Record; + + const currentSettingsRes = await api.get("/settings"); + const currentSettings = currentSettingsRes.data || {}; + const currentTokens = currentSettings.themeTokens || {}; + const newTokens = { ...currentTokens }; + + const validKeys = [ + "brandPrimary", "brandPrimaryHover", "brandAccent", "brandWarm", + "brandCool", "brandSuccess", "brandSoft", "surfacePage", + "surfaceNavRest", "surfaceCard", "textMain", "mutedText", + "linePrimary", "linePrimaryHover", "fontBody", "fontDisplay", + "shadowCard", "shadowCardHover" + ]; + + for (const key of validKeys) { + if (tokenParams[key] !== undefined) { + newTokens[key] = tokenParams[key]; + } + } + + const mergedSettings = { ...currentSettings, themeTokens: newTokens }; + + for (const key in mergedSettings) { + if (mergedSettings[key] === null) { + delete mergedSettings[key]; + } + } + + const res = await api.put("/settings", mergedSettings); + return { content: [{ type: "text", text: "Theme tokens updated successfully:\\n" + JSON.stringify(res.data.themeTokens, null, 2) }] }; + } case "update_brand_name": { const { brandName } = params as any; const currentSettingsRes = await api.get("/settings"); diff --git a/apps/spa/src/App.tsx b/apps/spa/src/App.tsx index 1272142..1e171f3 100644 --- a/apps/spa/src/App.tsx +++ b/apps/spa/src/App.tsx @@ -19,6 +19,7 @@ import WishlistPage from './pages/WishlistPage' import CustomPage from './pages/CustomPage' import ProfilePage from './pages/ProfilePage' import CartDrawer from './components/CartDrawer' +import ThemeInjector from './components/ThemeInjector' function App() { const { i18n } = useTranslation(); @@ -39,6 +40,7 @@ function App() { return ( + diff --git a/apps/spa/src/components/CartDrawer.tsx b/apps/spa/src/components/CartDrawer.tsx index e96fc50..ffd53ec 100644 --- a/apps/spa/src/components/CartDrawer.tsx +++ b/apps/spa/src/components/CartDrawer.tsx @@ -68,7 +68,7 @@ export default function CartDrawer() {
{item.productName} @@ -151,7 +151,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: }, []) React.useEffect(() => { - const code = localStorage.getItem('homaroma_active_coupon') + const code = localStorage.getItem('app_active_coupon') setSavedCoupon(code) if (code && subtotal > 0) { fetch(`${API_BASE}/coupons/validate`, { @@ -174,7 +174,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: }, [subtotal, activePromos]) const removeCoupon = () => { - localStorage.removeItem('homaroma_active_coupon') + localStorage.removeItem('app_active_coupon') setSavedCoupon(null) setDiscountAmount(0) setIsFreeShipping(false) @@ -190,7 +190,7 @@ function CartDrawerFooter({ subtotal, onClose, t }: { subtotal: number; onClose: // We must validate with subtotalAfterL2, but since we don't have it defined here yet, // we'll calculate it inline or rely on the effect. Actually, we can just save it to localStorage and let the effect handle it. - localStorage.setItem('homaroma_active_coupon', couponInput.toUpperCase()) + localStorage.setItem('app_active_coupon', couponInput.toUpperCase()) setSavedCoupon(couponInput.toUpperCase()) setCouponInput('') setCouponLoading(false) diff --git a/apps/spa/src/components/DynamicCategories.tsx b/apps/spa/src/components/DynamicCategories.tsx index 91b4505..ccc8f1d 100644 --- a/apps/spa/src/components/DynamicCategories.tsx +++ b/apps/spa/src/components/DynamicCategories.tsx @@ -60,7 +60,7 @@ export default function DynamicCategories({ payload, categories, loading }: Dyna />
- {getLocalizedField(cat.name, localStorage.getItem('homaroma_lang') || 'en')} + {getLocalizedField(cat.name, localStorage.getItem('app_lang') || 'en')} {cat._count && ( diff --git a/apps/spa/src/components/Footer.tsx b/apps/spa/src/components/Footer.tsx index 776dc09..2005cfa 100644 --- a/apps/spa/src/components/Footer.tsx +++ b/apps/spa/src/components/Footer.tsx @@ -24,7 +24,7 @@ export default function Footer({ categories = [] }: FooterProps) { const brandName = settings?.brandName || '' const translateMenuLabel = (item: MenuItemWithTranslations) => { - const lang = localStorage.getItem('homaroma_lang') || 'en' + const lang = localStorage.getItem('app_lang') || 'en' if (lang !== 'en' && item.translations && item.translations.length > 0) { const trans = item.translations.find((translation) => translation.langCode === lang) if (trans && trans.label) { @@ -128,7 +128,7 @@ export default function Footer({ categories = [] }: FooterProps) {
{categories.map((c) => ( - {getLocalizedField(c.name, localStorage.getItem('homaroma_lang') || 'en')} + {getLocalizedField(c.name, localStorage.getItem('app_lang') || 'en')} ))}
diff --git a/apps/spa/src/components/Navbar.tsx b/apps/spa/src/components/Navbar.tsx index 946290f..a9f7a65 100644 --- a/apps/spa/src/components/Navbar.tsx +++ b/apps/spa/src/components/Navbar.tsx @@ -49,7 +49,7 @@ export default function Navbar({ topOffsetClassName = 'top-0' }: NavbarProps) { const changeLanguage = (code: string) => { i18n.changeLanguage(code) - localStorage.setItem('homaroma_lang', code) + localStorage.setItem('app_lang', code) setLangSheetOpen(false) } diff --git a/apps/spa/src/components/ThemeInjector.tsx b/apps/spa/src/components/ThemeInjector.tsx new file mode 100644 index 0000000..0f6554a --- /dev/null +++ b/apps/spa/src/components/ThemeInjector.tsx @@ -0,0 +1,45 @@ +import { useEffect } from 'react' +import { API_BASE } from '@homaroma/types' + +export default function ThemeInjector() { + useEffect(() => { + const lang = localStorage.getItem('app_lang') || 'en' + + fetch(`${API_BASE}/settings?lang=${lang}`) + .then(res => res.ok ? res.json() : null) + .then(settings => { + const tokens = settings?.themeTokens + if (!tokens || typeof tokens !== 'object') return + + const root = document.documentElement + + // Map tokens from the backend (which use brandPrimary etc.) + // to the CSS variables expected by SPA's tailwind.config.js + const mapping: Record = { + '--color-spa-light': tokens.surfacePage, + '--color-spa-sage': tokens.brandSoft, + '--color-spa-stone': tokens.mutedText, + '--color-spa-forest': tokens.brandPrimary, + '--color-spa-gold': tokens.brandAccent, + + '--color-pop-pink': tokens.brandPrimary, + '--color-pop-yellow': tokens.brandAccent, + '--color-pop-orange': tokens.brandWarm, + '--color-pop-cyan': tokens.brandCool, + '--color-pop-bg': tokens.surfacePage, + '--color-pop-black': tokens.textMain, + + '--color-night-900': tokens.surfaceCard, + } + + for (const [key, value] of Object.entries(mapping)) { + if (value && typeof value === 'string' && value.trim() !== '') { + root.style.setProperty(key, value) + } + } + }) + .catch(err => console.error('Failed to inject theme in SPA:', err)) + }, []) + + return null +} diff --git a/apps/spa/src/i18n.ts b/apps/spa/src/i18n.ts index 6aaeb8b..3210ed1 100644 --- a/apps/spa/src/i18n.ts +++ b/apps/spa/src/i18n.ts @@ -9,7 +9,7 @@ import viTranslation from "./locales/vi/translation.json" import koTranslation from "./locales/ko/translation.json" // Retrieve default lang from localStorage if set -const savedLang = localStorage.getItem('homaroma_lang') || 'th' +const savedLang = localStorage.getItem('app_lang') || 'th' i18n .use(initReactI18next) diff --git a/apps/spa/src/pages/CategoryPage.tsx b/apps/spa/src/pages/CategoryPage.tsx index e6586f1..dda8a87 100644 --- a/apps/spa/src/pages/CategoryPage.tsx +++ b/apps/spa/src/pages/CategoryPage.tsx @@ -136,7 +136,7 @@ export default function CategoryPage() { / Shop / - {typeof category?.name === 'string' ? category.name : (category?.name as any)?.[localStorage.getItem('homaroma_lang') || 'en'] || (category?.name as any)?.en || '...'} + {typeof category?.name === 'string' ? category.name : (category?.name as any)?.[localStorage.getItem('app_lang') || 'en'] || (category?.name as any)?.en || '...'}

{loading ? ( @@ -169,7 +169,7 @@ export default function CategoryPage() { : 'bg-white text-spa-stone border-spa-stone/20 hover:border-spa-forest hover:text-spa-forest shadow-sm' }`} > - {typeof cat.name === 'string' ? cat.name : (cat.name as any)?.[localStorage.getItem('homaroma_lang') || 'en'] || (cat.name as any)?.en || ''} + {typeof cat.name === 'string' ? cat.name : (cat.name as any)?.[localStorage.getItem('app_lang') || 'en'] || (cat.name as any)?.en || ''} ))}

diff --git a/apps/spa/src/pages/CheckoutPage.tsx b/apps/spa/src/pages/CheckoutPage.tsx index 39f93ff..efeec04 100644 --- a/apps/spa/src/pages/CheckoutPage.tsx +++ b/apps/spa/src/pages/CheckoutPage.tsx @@ -127,7 +127,7 @@ export default function CheckoutPage() { .catch(console.error) // Check if there's an active coupon saved in localStorage - const savedCoupon = localStorage.getItem('homaroma_active_coupon') + const savedCoupon = localStorage.getItem('app_active_coupon') if (savedCoupon) { setCouponCode(savedCoupon) // We will trigger handleApplyCoupon but we need to wait for items/subtotal to be ready. @@ -174,7 +174,7 @@ export default function CheckoutPage() { // Auto-apply saved coupon if subtotal is ready useEffect(() => { - const savedCoupon = localStorage.getItem('homaroma_active_coupon') + const savedCoupon = localStorage.getItem('app_active_coupon') if (savedCoupon && subtotal > 0 && !appliedCoupon && !couponError && !couponLoading) { setCouponCode(savedCoupon) // trigger fetch to validate @@ -190,7 +190,7 @@ export default function CheckoutPage() { setDiscountAmount(data.discountAmount) setIsFreeShipping(data.isFreeShipping) } else { - localStorage.removeItem('homaroma_active_coupon') // Remove invalid + localStorage.removeItem('app_active_coupon') // Remove invalid } }) .catch(console.error) @@ -254,7 +254,7 @@ export default function CheckoutPage() { setDiscountAmount(0) setIsFreeShipping(false) setCouponError('') - localStorage.removeItem('homaroma_active_coupon') + localStorage.removeItem('app_active_coupon') } const handleSubmit = async (e: React.FormEvent) => { @@ -309,10 +309,10 @@ export default function CheckoutPage() { if (res.ok) { clearCart() - const existingStr = localStorage.getItem('homaroma_local_orders') + const existingStr = localStorage.getItem('app_local_orders') const existing = existingStr ? JSON.parse(existingStr) : [] if (!existing.includes(responseData.id)) { - localStorage.setItem('homaroma_local_orders', JSON.stringify([responseData.id, ...existing])) + localStorage.setItem('app_local_orders', JSON.stringify([responseData.id, ...existing])) } navigate(`/order/${responseData.id}`) diff --git a/apps/spa/src/pages/CouponsPage.tsx b/apps/spa/src/pages/CouponsPage.tsx index 3352621..028ffac 100644 --- a/apps/spa/src/pages/CouponsPage.tsx +++ b/apps/spa/src/pages/CouponsPage.tsx @@ -82,7 +82,7 @@ export default function CouponsPage() { } const handleUseNow = (code: string) => { - localStorage.setItem('homaroma_active_coupon', code) + localStorage.setItem('app_active_coupon', code) navigate('/shop') } diff --git a/apps/spa/src/pages/OrderSuccessPage.tsx b/apps/spa/src/pages/OrderSuccessPage.tsx index a2656bb..d612bea 100644 --- a/apps/spa/src/pages/OrderSuccessPage.tsx +++ b/apps/spa/src/pages/OrderSuccessPage.tsx @@ -264,7 +264,7 @@ export default function OrderSuccessPage() { {order.items.map(item => (
- Product + Product

{item.productName}

diff --git a/apps/spa/src/pages/ProfilePage.tsx b/apps/spa/src/pages/ProfilePage.tsx index b1626f7..a4c29dc 100644 --- a/apps/spa/src/pages/ProfilePage.tsx +++ b/apps/spa/src/pages/ProfilePage.tsx @@ -504,7 +504,7 @@ export default function ProfilePage() { {order.items.map(item => (
- Product + Product

{item.productName}

diff --git a/apps/spa/src/pages/ShopPage.tsx b/apps/spa/src/pages/ShopPage.tsx index 4edef92..23d39b7 100644 --- a/apps/spa/src/pages/ShopPage.tsx +++ b/apps/spa/src/pages/ShopPage.tsx @@ -32,7 +32,7 @@ export default function ShopPage() { const params = new URLSearchParams(location.search) const coupon = params.get('coupon') if (coupon) { - localStorage.setItem('homaroma_active_coupon', coupon) + localStorage.setItem('app_active_coupon', coupon) // Optionally clean URL window.history.replaceState({}, document.title, window.location.pathname) } diff --git a/apps/spa/src/pages/TrackOrderPage.tsx b/apps/spa/src/pages/TrackOrderPage.tsx index 69dffe3..cb1a2e3 100644 --- a/apps/spa/src/pages/TrackOrderPage.tsx +++ b/apps/spa/src/pages/TrackOrderPage.tsx @@ -64,7 +64,7 @@ export default function TrackOrderPage() { .finally(() => setHistoryLoading(false)) } else { // Load local orders if not logged in - const storedStr = localStorage.getItem('homaroma_local_orders') + const storedStr = localStorage.getItem('app_local_orders') if (storedStr) { setLocalOrders(JSON.parse(storedStr)) } @@ -92,7 +92,7 @@ export default function TrackOrderPage() { if (!localOrders.includes(data.orderId)) { const newOrders = [data.orderId, ...localOrders] setLocalOrders(newOrders) - localStorage.setItem('homaroma_local_orders', JSON.stringify(newOrders)) + localStorage.setItem('app_local_orders', JSON.stringify(newOrders)) } navigate(`/order/${data.orderId}`) } else { diff --git a/apps/spa/src/store/authStore.ts b/apps/spa/src/store/authStore.ts index 629f11c..822ac49 100644 --- a/apps/spa/src/store/authStore.ts +++ b/apps/spa/src/store/authStore.ts @@ -31,7 +31,7 @@ export const useAuthStore = create()( logout: () => set({ token: null, customer: null }), }), { - name: "homaroma-auth-storage", + name: "app-auth-storage", partialize: (state) => ({ token: state.token, customer: state.customer }), // don't persist isLiffInitialized } ) diff --git a/apps/spa/src/store/cartStore.ts b/apps/spa/src/store/cartStore.ts index fdb98ef..18b1fe4 100644 --- a/apps/spa/src/store/cartStore.ts +++ b/apps/spa/src/store/cartStore.ts @@ -71,7 +71,7 @@ export const useCartStore = create()( } }), { - name: 'homaroma-cart-storage', + name: 'app-cart-storage', partialize: (state) => ({ items: state.items }), // Only persist items, not UI state } ) diff --git a/apps/spa/src/store/wishlistStore.ts b/apps/spa/src/store/wishlistStore.ts index 7410c3c..7b1c3b1 100644 --- a/apps/spa/src/store/wishlistStore.ts +++ b/apps/spa/src/store/wishlistStore.ts @@ -27,7 +27,7 @@ export const useWishlistStore = create()( } }), { - name: 'homaroma-wishlist', + name: 'app-wishlist', } ) ) diff --git a/apps/spa/tailwind.config.js b/apps/spa/tailwind.config.js index d74c232..583a8df 100644 --- a/apps/spa/tailwind.config.js +++ b/apps/spa/tailwind.config.js @@ -16,28 +16,28 @@ export default { }, colors: { pop: { - pink: "#FF6B6B", - yellow: "#FFF200", - orange: "#FFA07A", - red: "#FF4500", - cyan: "#E0FFFF", - green: "#98FB98", - purple: "#D8BFD8", - bg: "#FFF0F5", - black: "#333333", + pink: "var(--color-pop-pink, #FF6B6B)", + yellow: "var(--color-pop-yellow, #FFF200)", + orange: "var(--color-pop-orange, #FFA07A)", + red: "var(--color-pop-red, #FF4500)", + cyan: "var(--color-pop-cyan, #E0FFFF)", + green: "var(--color-pop-green, #98FB98)", + purple: "var(--color-pop-purple, #D8BFD8)", + bg: "var(--color-pop-bg, #FFF0F5)", + black: "var(--color-pop-black, #333333)", }, night: { - 900: "#111827", + 900: "var(--color-night-900, #111827)", }, cyan: { - glow: "#06b6d4", + glow: "var(--color-cyan-glow, #06b6d4)", }, spa: { - light: "#FDFDF9", // Off-white/Cream - sage: "#EAEFEA", // Soft Sage Green - stone: "#4B4B46", // Dark Stone Gray - forest: "#1F3D2D", // Deep Forest Green - gold: "#D4AF37", // Elegant Gold + light: "var(--color-spa-light, #FDFDF9)", // Off-white/Cream + sage: "var(--color-spa-sage, #EAEFEA)", // Soft Sage Green + stone: "var(--color-spa-stone, #4B4B46)", // Dark Stone Gray + forest: "var(--color-spa-forest, #1F3D2D)", // Deep Forest Green + gold: "var(--color-spa-gold, #D4AF37)", // Elegant Gold } }, boxShadow: { diff --git a/apps/website/src/app/[lang]/[slug]/[...rest]/page.tsx b/apps/website/src/app/[lang]/[slug]/[...rest]/page.tsx index 4033cbd..d5d0939 100644 --- a/apps/website/src/app/[lang]/[slug]/[...rest]/page.tsx +++ b/apps/website/src/app/[lang]/[slug]/[...rest]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from 'next' import { notFound } from 'next/navigation' export const metadata: Metadata = { - title: 'Page Not Found | HomAroma', + title: 'Page Not Found', } export default function NestedLocaleNotFound() { diff --git a/apps/website/src/app/[lang]/[slug]/page.tsx b/apps/website/src/app/[lang]/[slug]/page.tsx index 9a13f43..0c6024e 100644 --- a/apps/website/src/app/[lang]/[slug]/page.tsx +++ b/apps/website/src/app/[lang]/[slug]/page.tsx @@ -34,14 +34,14 @@ function isReservedSlug(slug: string) { export async function generateMetadata({ params }: { params: Promise<{ slug: string, lang: string }> }): Promise { const { slug, lang } = await params - if (isReservedSlug(slug)) return { title: 'Page Not Found | HomAroma' } + if (isReservedSlug(slug)) return { title: 'Page Not Found' } const pageResult = await getCmsPageResult(slug, lang) - if (!pageResult.ok || !pageResult.data.isPublished) return { title: 'Page Not Found | HomAroma' } + if (!pageResult.ok || !pageResult.data.isPublished) return { title: 'Page Not Found' } return generateSeoMetadata({ - title: `${pageResult.data.title} | HomAroma`, - description: `HomAroma - ${pageResult.data.title}`, + title: `${pageResult.data.title}`, + description: `${pageResult.data.title}`, path: `/${slug}`, lang, }) @@ -100,7 +100,7 @@ export default async function CmsCustomPage({ params }: { params: Promise<{ slug ) : (
-

HomAroma

+

{page.title}

หน้านี้ถูกเผยแพร่แล้ว แต่ยังไม่มี section ที่เปิดใช้งานใน Page Builder diff --git a/apps/website/src/app/[lang]/about/page.tsx b/apps/website/src/app/[lang]/about/page.tsx index 6d2c8cb..761c63d 100644 --- a/apps/website/src/app/[lang]/about/page.tsx +++ b/apps/website/src/app/[lang]/about/page.tsx @@ -1,5 +1,5 @@ import Link from '@/components/LocalizedLink' -import { generateSeoMetadata } from '@/lib/seo' +import { API_BASE } from '@/lib/api' const PILLARS = [ { @@ -26,17 +26,12 @@ const STATS = [ { value: '100%', label: 'Natural Extracts' }, ] -export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { +export default async function AboutPage({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; - return generateSeoMetadata({ - title: 'About Us | HomAroma', - description: 'Born from a passion for stillness. HomAroma began in 2019 as a Bangkok studio crafting botanical aroma blends.', - path: '/about', - lang, - }); -} + const res = await fetch(`${API_BASE}/settings?lang=${lang}`, { next: { revalidate: 600 } }); + const settings = res.ok ? await res.json() : {}; + const brandName = settings?.brandName || ''; -export default function AboutPage() { return (

@@ -47,7 +42,7 @@ export default function AboutPage() { Born from a passion for stillness

- HomAroma began in 2019 as a one-person studio in Bangkok, crafting small batches of botanical aroma blends for friends and family. What started as a personal journey toward calm became a philosophy — that our environment shapes how we feel, think, and rest. + {brandName} began in 2019 as a one-person studio in Bangkok, crafting small batches of botanical aroma blends for friends and family. What started as a personal journey toward calm became a philosophy — that our environment shapes how we feel, think, and rest.

@@ -72,7 +67,7 @@ export default function AboutPage() { We believe that scent is the fastest pathway to emotional transformation. A single breath of the right blend can dissolve anxiety, sharpen focus, or unlock creativity. Our mission is to make that transformation accessible — through products that are as beautiful as they are effective.

- Every HomAroma product is designed with intention: the glass bottles are reusable, the packaging is recycled, and the formulas are written to be understood by anyone — not hidden behind chemical jargon. + Every {brandName} product is designed with intention: the glass bottles are reusable, the packaging is recycled, and the formulas are written to be understood by anyone — not hidden behind chemical jargon.

@@ -80,7 +75,7 @@ export default function AboutPage() {
🌿
-

HomAroma Studio, Bangkok

+

{brandName} Studio, Bangkok

diff --git a/apps/website/src/app/[lang]/article/[slug]/page.tsx b/apps/website/src/app/[lang]/article/[slug]/page.tsx index 7339a16..68ac050 100644 --- a/apps/website/src/app/[lang]/article/[slug]/page.tsx +++ b/apps/website/src/app/[lang]/article/[slug]/page.tsx @@ -18,9 +18,9 @@ async function getArticle(slug: string): Promise
{ export async function generateMetadata({ params }: { params: Promise<{ slug: string, lang: string }> }): Promise { const { slug, lang } = await params const article = await getArticle(slug) - if (!article) return { title: 'Article Not Found | HomAroma' } + if (!article) return { title: 'Article Not Found' } return generateSeoMetadata({ - title: `${article.title} | HomAroma Scent Guide`, + title: `${article.title} Scent Guide`, description: article.content.replace(/<[^>]+>/g, '').slice(0, 160), path: `/article/${slug}`, lang, diff --git a/apps/website/src/app/[lang]/category/[id]/page.tsx b/apps/website/src/app/[lang]/category/[id]/page.tsx index bacdb34..b84515f 100644 --- a/apps/website/src/app/[lang]/category/[id]/page.tsx +++ b/apps/website/src/app/[lang]/category/[id]/page.tsx @@ -29,8 +29,8 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin const categoryName = result.ok && result.data.category ? resolveLocalized(result.data.category.name, lang) : '' return generateSeoMetadata({ - title: categoryName ? `${categoryName} | HomAroma` : 'Category | HomAroma', - description: categoryName ? `Shop ${categoryName} products from HomAroma.` : 'Shop HomAroma category products.', + title: categoryName ? `${categoryName}` : 'Category', + description: categoryName ? `Shop ${categoryName} products from .` : 'Shop category products.', path: `/category/${id}`, lang, }) diff --git a/apps/website/src/app/[lang]/checkout/page.tsx b/apps/website/src/app/[lang]/checkout/page.tsx index fa85b8e..3778f5c 100644 --- a/apps/website/src/app/[lang]/checkout/page.tsx +++ b/apps/website/src/app/[lang]/checkout/page.tsx @@ -10,8 +10,8 @@ import { splitCmsLayoutSections } from '@/lib/cms-sections' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'Checkout | HomAroma', - description: 'ตรวจสอบรายการสินค้า กรอกข้อมูลจัดส่ง และสร้างคำสั่งซื้อ HomAroma', + title: 'Checkout', + description: 'ตรวจสอบรายการสินค้า กรอกข้อมูลจัดส่ง และสร้างคำสั่งซื้อ ', path: '/checkout', lang, }); diff --git a/apps/website/src/app/[lang]/contact/layout.tsx b/apps/website/src/app/[lang]/contact/layout.tsx index 8b8d01d..7805127 100644 --- a/apps/website/src/app/[lang]/contact/layout.tsx +++ b/apps/website/src/app/[lang]/contact/layout.tsx @@ -1,8 +1,8 @@ import type { Metadata } from 'next' export const metadata: Metadata = { - title: 'Contact Us | HomAroma', - description: 'Get in touch with HomAroma. We respond to all inquiries within 2 hours.', + title: 'Contact Us', + description: 'Get in touch with . We respond to all inquiries within 2 hours.', } export default function ContactLayout({ children }: { children: React.ReactNode }) { diff --git a/apps/website/src/app/[lang]/contact/page.tsx b/apps/website/src/app/[lang]/contact/page.tsx index 837f852..ca5d91a 100644 --- a/apps/website/src/app/[lang]/contact/page.tsx +++ b/apps/website/src/app/[lang]/contact/page.tsx @@ -1,13 +1,41 @@ "use client" -import { useState } from 'react' +import { useState, useEffect } from 'react' +import { useParams } from 'next/navigation' type FormState = { name: string; email: string; subject: string; message: string } type Status = 'idle' | 'sending' | 'sent' | 'error' const SUBJECTS = ['Product Inquiry', 'Order Support', 'Custom Blending', 'Wholesale', 'Partnership', 'Other'] export default function ContactPage() { + const params = useParams() + const lang = params?.lang as string || 'en' const [form, setForm] = useState({ name: '', email: '', subject: '', message: '' }) const [status, setStatus] = useState('idle') + const [brandName, setBrandName] = useState('') + const [socials, setSocials] = useState({ line: '@homaroma', ig: '@homaroma', email: 'hello@homaroma.shop' }) + + useEffect(() => { + fetch(`/api/settings?lang=${lang}`) + .then(res => res.ok ? res.json() : {}) + .then(data => { + const brand = data.brandName || '' + setBrandName(brand) + const username = brand.toLowerCase().replace(/\s+/g, '') + + let ig = `@${username}` + if (data.instagramUrl) { + const match = data.instagramUrl.match(/instagram\.com\/([^/?]+)/) + if (match) ig = `@${match[1]}` + } + + setSocials({ + line: `@${username}`, + ig, + email: `hello@${username}.shop` + }) + }) + .catch(() => {}) + }, [lang]) const handleChange = (e: React.ChangeEvent) => { setForm(prev => ({ ...prev, [e.target.name]: e.target.value })) @@ -35,9 +63,9 @@ export default function ContactPage() {

Reach Us Directly

{[ - { icon: '📧', label: 'Email', value: 'hello@homaroma.shop', href: 'mailto:hello@homaroma.shop' }, - { icon: '📱', label: 'LINE Official', value: '@homaroma', href: '#' }, - { icon: '📸', label: 'Instagram', value: '@homaroma', href: '#' }, + { icon: '📧', label: 'Email', value: socials.email, href: `mailto:${socials.email}` }, + { icon: '📱', label: 'LINE Official', value: socials.line, href: '#' }, + { icon: '📸', label: 'Instagram', value: socials.ig, href: '#' }, { icon: '📍', label: 'Studio', value: 'Bangkok, Thailand', href: '#' }, ].map(item => ( diff --git a/apps/website/src/app/[lang]/coupons/page.tsx b/apps/website/src/app/[lang]/coupons/page.tsx index 544590d..778a646 100644 --- a/apps/website/src/app/[lang]/coupons/page.tsx +++ b/apps/website/src/app/[lang]/coupons/page.tsx @@ -17,8 +17,8 @@ function formatDiscount(type: string, value: number) { export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'โปรโมชั่นและคูปองส่วนลด | HomAroma', - description: 'รวมรหัสส่วนลดและโปรโมชั่นพิเศษจาก HomAroma สำหรับการสั่งซื้อน้ำหอมและผลิตภัณฑ์อโรม่าที่คุณชื่นชอบ', + title: 'โปรโมชั่นและคูปองส่วนลด', + description: 'รวมรหัสส่วนลดและโปรโมชั่นพิเศษจาก สำหรับการสั่งซื้อน้ำหอมและผลิตภัณฑ์อโรม่าที่คุณชื่นชอบ', path: '/coupons', lang, }); diff --git a/apps/website/src/app/[lang]/faq/layout.tsx b/apps/website/src/app/[lang]/faq/layout.tsx index 144e17d..0b97ca9 100644 --- a/apps/website/src/app/[lang]/faq/layout.tsx +++ b/apps/website/src/app/[lang]/faq/layout.tsx @@ -1,8 +1,8 @@ import type { Metadata } from 'next' export const metadata: Metadata = { - title: 'FAQ | HomAroma', - description: 'Everything you need to know about HomAroma products, shipping, and care.', + title: 'FAQ', + description: 'Everything you need to know about products, shipping, and care.', } export default function FaqLayout({ children }: { children: React.ReactNode }) { diff --git a/apps/website/src/app/[lang]/faq/page.tsx b/apps/website/src/app/[lang]/faq/page.tsx index 28bf429..09b0e5d 100644 --- a/apps/website/src/app/[lang]/faq/page.tsx +++ b/apps/website/src/app/[lang]/faq/page.tsx @@ -1,12 +1,13 @@ "use client" -import { useState } from 'react' +import { useState, useEffect } from 'react' import Link from '@/components/LocalizedLink' +import { useParams } from 'next/navigation' const FAQS = [ { category: 'Products', icon: '🌿', items: [ - { q: 'What are the ingredients in your aroma products?', a: 'All HomAroma products are crafted from 100% natural botanical extracts, pure essential oils, and pharmaceutical-grade carrier bases. We never use synthetic fragrances, parabens, or harmful chemicals. Each product label lists full ingredient transparency.' }, + { q: 'What are the ingredients in your aroma products?', a: 'All __BRAND__ products are crafted from 100% natural botanical extracts, pure essential oils, and pharmaceutical-grade carrier bases. We never use synthetic fragrances, parabens, or harmful chemicals. Each product label lists full ingredient transparency.' }, { q: 'How long does each product last?', a: 'Our Reed Diffusers last 4–8 weeks depending on room size and airflow. Aroma Oils (30ml) last approximately 3–4 months with normal use. Room Sprays (100ml) provide 60–80 uses. Car Diffusers cartridges last up to 45 days.' }, { q: 'Are your products suitable for sensitive skin or allergies?', a: 'We recommend patch testing before topical use. Our Aroma Oils are formulated for diffuser use primarily. If you have known fragrance allergies, consult our scent guide or contact us with your specific concerns.' }, { q: 'What is the difference between Aroma Oil and Oil Ozone?', a: 'Aroma Oils are pure essential oil blends designed for aromatherapy diffusers and therapeutic use. Oil Ozone is our premium line featuring ozone-infused botanical extracts — designed for air purification and deeper space transformation.' }, @@ -38,22 +39,45 @@ const FAQS = [ ] export default function FaqPage() { + const params = useParams() + const lang = params?.lang as string || 'en' + const [brandName, setBrandName] = useState('') const [openItems, setOpenItems] = useState>({}) const [activeCategory, setActiveCategory] = useState('Products') + + useEffect(() => { + fetch(`/api/settings?lang=${lang}`) + .then(res => res.ok ? res.json() : {}) + .then(data => { + if (data.brandName) setBrandName(data.brandName) + }) + .catch(() => {}) + }, [lang]) + const toggle = (key: string) => setOpenItems(prev => ({ ...prev, [key]: !prev[key] })) - const activeFaqs = FAQS.find(f => f.category === activeCategory) + + const processedFaqs = FAQS.map(f => ({ + ...f, + items: f.items.map(i => ({ + ...i, + q: i.q.replace(/__BRAND__/g, brandName), + a: i.a.replace(/__BRAND__/g, brandName) + })) + })) + + const activeFaqs = processedFaqs.find(f => f.category === activeCategory) return (

Support

Frequently Asked Questions

-

Everything you need to know about HomAroma products, shipping, and care.

+

Everything you need to know about {brandName} products, shipping, and care.

- {FAQS.map(({ category, icon }) => ( + {processedFaqs.map(({ category, icon }) => ( {isOpen && (
-
-

{item.a}

+
+

{item.a}

)}
diff --git a/apps/website/src/app/[lang]/layout.tsx b/apps/website/src/app/[lang]/layout.tsx index 49a4a10..83948b2 100644 --- a/apps/website/src/app/[lang]/layout.tsx +++ b/apps/website/src/app/[lang]/layout.tsx @@ -25,16 +25,28 @@ const kanit = Kanit({ variable: '--font-kanit', }); -export const metadata: Metadata = { - title: 'HomAroma', - description: 'Pure Origins, Artisan Craft, Zero Compromise', - metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'), -}; +export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { + const { lang } = await params; + const settings = await getStoreSettings(lang); + const brandName = settings?.brandName || ''; + + return { + title: { + template: `%s | ${brandName}`, + default: brandName, + }, + description: 'Pure Origins, Artisan Craft, Zero Compromise', + metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'), + }; +} + +// Cache configuration: 0 for dev (no cache), strict numbers for production +const getCacheTime = (prodSeconds: number) => process.env.NODE_ENV === 'development' ? 0 : prodSeconds; // Fetch categories for Navbar and Footer on the server async function getCategories(lang: string) { try { - const res = await fetch(`${API_BASE}/categories?lang=${lang}`, { next: { revalidate: 3600 } }); + const res = await fetch(`${API_BASE}/categories?lang=${lang}`, { next: { revalidate: getCacheTime(3600) } }); if (!res.ok) return []; return await res.json(); } catch (error) { @@ -46,7 +58,7 @@ async function getCategories(lang: string) { // Fetch mega menu on the server — eliminates client-side flash async function getMegaMenu(lang: string): Promise { try { - const res = await fetch(`${API_BASE}/menus/main?lang=${lang}`, { next: { revalidate: 300 } }); + const res = await fetch(`${API_BASE}/menus/main?lang=${lang}`, { next: { revalidate: getCacheTime(300) } }); if (!res.ok) return []; const data = await res.json() as { payload?: MegaMenuPayload[] }; return Array.isArray(data.payload) ? data.payload : []; @@ -60,7 +72,7 @@ async function getMegaMenu(lang: string): Promise { async function getFooterMenus(lang: string): Promise<{ exploreMenu: MenuItem[]; supportMenu: MenuItem[] }> { const fetchMenu = async (slug: string): Promise => { try { - const res = await fetch(`${API_BASE}/menus/${slug}?lang=${lang}`, { next: { revalidate: 300 } }); + const res = await fetch(`${API_BASE}/menus/${slug}?lang=${lang}`, { next: { revalidate: getCacheTime(300) } }); if (!res.ok) return []; const data = await res.json() as { items?: MenuItem[] }; return Array.isArray(data.items) @@ -82,7 +94,7 @@ async function getFooterMenus(lang: string): Promise<{ exploreMenu: MenuItem[]; // Fetch store settings (social links, footer texts) on the server async function getStoreSettings(lang: string): Promise { try { - const res = await fetch(`${API_BASE}/settings?lang=${lang}`, { next: { revalidate: 600 } }); + const res = await fetch(`${API_BASE}/settings?lang=${lang}`, { next: { revalidate: getCacheTime(600) } }); if (!res.ok) return null; return await res.json() as StoreSettings; } catch (error) { diff --git a/apps/website/src/app/[lang]/order/[id]/page.tsx b/apps/website/src/app/[lang]/order/[id]/page.tsx index c3842fd..418b340 100644 --- a/apps/website/src/app/[lang]/order/[id]/page.tsx +++ b/apps/website/src/app/[lang]/order/[id]/page.tsx @@ -33,11 +33,11 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin const { id, lang } = await params const result = await getOrderResult(id) - if (!result.ok) return { title: 'Order Not Found | HomAroma' } + if (!result.ok) return { title: 'Order Not Found' } return generateSeoMetadata({ - title: `Order ${orderLabel(result.data.id)} | HomAroma`, - description: `ตรวจสอบสถานะคำสั่งซื้อ ${orderLabel(result.data.id)} จาก HomAroma`, + title: `Order ${orderLabel(result.data.id)}`, + description: `ตรวจสอบสถานะคำสั่งซื้อ ${orderLabel(result.data.id)} จาก `, path: `/order/${id}`, lang, }) @@ -129,7 +129,7 @@ export default async function OrderStatusPage({ params }: { params: Promise<{ id
{item.productName} { const page = await getCmsPage('home', lang) return generateSeoMetadata({ - title: page?.title ? `${page.title} | HomAroma` : 'HomAroma', - description: 'Premium aromatherapy, home fragrance, and botanical scent products from HomAroma.', + title: page?.title ? `${page.title}` : undefined, + description: 'Premium aromatherapy, home fragrance, and botanical scent products from .', path: '', lang, }) @@ -48,7 +48,7 @@ export default async function HomePage({ params }: Props) { ) : (

- HomAroma +

Premium scents for calmer spaces diff --git a/apps/website/src/app/[lang]/products/[id]/page.tsx b/apps/website/src/app/[lang]/products/[id]/page.tsx index b1159e4..f45f696 100644 --- a/apps/website/src/app/[lang]/products/[id]/page.tsx +++ b/apps/website/src/app/[lang]/products/[id]/page.tsx @@ -21,15 +21,15 @@ export async function generateMetadata({ params }: { params: Promise<{ id: strin const { id, lang } = await params const product = await getProduct(id, lang) - if (!product) return { title: 'Product Not Found | HomAroma' } + if (!product) return { title: 'Product Not Found' } const productName = resolveLocalized(product.name, lang) || product.scent.name const categoryName = resolveCategoryName(product.category, lang) const description = resolveLocalized(product.description, lang) return generateSeoMetadata({ - title: `${productName} ${categoryName ? `| ${categoryName}` : ''} | HomAroma`, - description: description || `Premium ${categoryName} from HomAroma.`, + title: `${productName} ${categoryName ? `| ${categoryName}` : ''}`, + description: description || `Premium ${categoryName} from .`, path: `/products/${id}`, lang, openGraph: { diff --git a/apps/website/src/app/[lang]/profile/page.tsx b/apps/website/src/app/[lang]/profile/page.tsx index ab4b9fd..e5b4e52 100644 --- a/apps/website/src/app/[lang]/profile/page.tsx +++ b/apps/website/src/app/[lang]/profile/page.tsx @@ -10,8 +10,8 @@ import { splitCmsLayoutSections } from '@/lib/cms-sections' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'โปรไฟล์สมาชิก | HomAroma', - description: 'จัดการโปรไฟล์สมาชิก HomAroma ประวัติคำสั่งซื้อ คะแนนสะสม และสมุดที่อยู่จัดส่ง', + title: 'โปรไฟล์สมาชิก', + description: 'จัดการโปรไฟล์สมาชิก ประวัติคำสั่งซื้อ คะแนนสะสม และสมุดที่อยู่จัดส่ง', path: '/profile', lang, }); diff --git a/apps/website/src/app/[lang]/scent-guide/page.tsx b/apps/website/src/app/[lang]/scent-guide/page.tsx index 09a225a..2c36d29 100644 --- a/apps/website/src/app/[lang]/scent-guide/page.tsx +++ b/apps/website/src/app/[lang]/scent-guide/page.tsx @@ -9,7 +9,7 @@ import { splitCmsLayoutSections } from '@/lib/cms-sections' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'Scent Guide | HomAroma', + title: 'Scent Guide', description: 'Explore our collection of aroma articles, guides, and sensory knowledge to find your perfect scent.', path: '/scent-guide', lang, diff --git a/apps/website/src/app/[lang]/search/layout.tsx b/apps/website/src/app/[lang]/search/layout.tsx index 63b804a..80c36e2 100644 --- a/apps/website/src/app/[lang]/search/layout.tsx +++ b/apps/website/src/app/[lang]/search/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next' export const metadata: Metadata = { - title: 'Search Products | HomAroma', + title: 'Search Products', description: 'Search our collection of premium aroma products by scent or product name.', } diff --git a/apps/website/src/app/[lang]/search/page.tsx b/apps/website/src/app/[lang]/search/page.tsx index f5a911c..16b4d2d 100644 --- a/apps/website/src/app/[lang]/search/page.tsx +++ b/apps/website/src/app/[lang]/search/page.tsx @@ -9,8 +9,8 @@ import { resolveLocalized } from '@/lib/localization' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'Search Products | HomAroma', - description: 'Search HomAroma products by scent, category, or keyword.', + title: 'Search Products', + description: 'Search products by scent, category, or keyword.', path: '/search', lang, }); diff --git a/apps/website/src/app/[lang]/shop/layout.tsx b/apps/website/src/app/[lang]/shop/layout.tsx index e6d47ec..f5e4c30 100644 --- a/apps/website/src/app/[lang]/shop/layout.tsx +++ b/apps/website/src/app/[lang]/shop/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next' export const metadata: Metadata = { - title: 'Shop All Products | HomAroma', + title: 'Shop All Products', description: 'Browse our full range of premium aroma products — crafted to elevate every space.', } diff --git a/apps/website/src/app/[lang]/shop/page.tsx b/apps/website/src/app/[lang]/shop/page.tsx index 5c71ebb..8eaa162 100644 --- a/apps/website/src/app/[lang]/shop/page.tsx +++ b/apps/website/src/app/[lang]/shop/page.tsx @@ -11,8 +11,8 @@ import { resolveLocalized } from '@/lib/localization' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'Shop Aroma Products | HomAroma', - description: 'Browse HomAroma aroma oils, reed diffusers, room sprays, and premium scent products.', + title: 'Shop Aroma Products', + description: 'Browse aroma oils, reed diffusers, room sprays, and premium scent products.', path: '/shop', lang, }); diff --git a/apps/website/src/app/[lang]/track/page.tsx b/apps/website/src/app/[lang]/track/page.tsx index 498d05a..3b02d29 100644 --- a/apps/website/src/app/[lang]/track/page.tsx +++ b/apps/website/src/app/[lang]/track/page.tsx @@ -10,8 +10,8 @@ import { splitCmsLayoutSections } from '@/lib/cms-sections' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'ติดตามคำสั่งซื้อ | HomAroma', - description: 'ตรวจสอบสถานะคำสั่งซื้อ HomAroma ด้วยหมายเลขคำสั่งซื้อและเบอร์โทรศัพท์ที่ใช้สั่งซื้อ', + title: 'ติดตามคำสั่งซื้อ', + description: 'ตรวจสอบสถานะคำสั่งซื้อ ด้วยหมายเลขคำสั่งซื้อและเบอร์โทรศัพท์ที่ใช้สั่งซื้อ', path: '/track', lang, }); diff --git a/apps/website/src/app/[lang]/wishlist/page.tsx b/apps/website/src/app/[lang]/wishlist/page.tsx index d2c8c02..d5f5d2b 100644 --- a/apps/website/src/app/[lang]/wishlist/page.tsx +++ b/apps/website/src/app/[lang]/wishlist/page.tsx @@ -10,8 +10,8 @@ import { splitCmsLayoutSections } from '@/lib/cms-sections' export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }) { const { lang } = await params; return generateSeoMetadata({ - title: 'Wishlist | HomAroma', - description: 'รายการสินค้าที่คุณบันทึกไว้ใน Wishlist ของ HomAroma', + title: 'Wishlist', + description: 'รายการสินค้าที่คุณบันทึกไว้ใน Wishlist ของ ', path: '/wishlist', lang, }); diff --git a/apps/website/src/components/CartDrawer.tsx b/apps/website/src/components/CartDrawer.tsx index 59d766d..d9fc083 100644 --- a/apps/website/src/components/CartDrawer.tsx +++ b/apps/website/src/components/CartDrawer.tsx @@ -44,7 +44,7 @@ export default function CartDrawer() {
{item.productName} typeof id === 'number') : [] if (!orderIds.includes(orderId)) { - window.localStorage.setItem('homaroma_local_orders', JSON.stringify([orderId, ...orderIds])) + window.localStorage.setItem('app_local_orders', JSON.stringify([orderId, ...orderIds])) } } catch (error) { console.warn('[website-checkout] Failed to persist local order history', error) @@ -193,7 +193,7 @@ export default function CheckoutClient() { useEffect(() => { queueMicrotask(() => { - const savedCoupon = window.localStorage.getItem('homaroma_active_coupon') + const savedCoupon = window.localStorage.getItem('app_active_coupon') if (savedCoupon) setCouponCode(savedCoupon) }) }, []) @@ -242,7 +242,7 @@ export default function CheckoutClient() { useEffect(() => { if (!couponCode || appliedCoupon || subtotalAfterL2 <= 0) return - const savedCoupon = window.localStorage.getItem('homaroma_active_coupon') + const savedCoupon = window.localStorage.getItem('app_active_coupon') if (!savedCoupon || savedCoupon !== couponCode) return const applySavedCoupon = async () => { @@ -258,10 +258,10 @@ export default function CheckoutClient() { setDiscountAmount(data.discountAmount ?? 0) setIsFreeShipping(Boolean(data.isFreeShipping)) } else { - window.localStorage.removeItem('homaroma_active_coupon') + window.localStorage.removeItem('app_active_coupon') } } catch { - window.localStorage.removeItem('homaroma_active_coupon') + window.localStorage.removeItem('app_active_coupon') } } @@ -289,7 +289,7 @@ export default function CheckoutClient() { setAppliedCoupon(data.coupon.code) setDiscountAmount(data.discountAmount ?? 0) setIsFreeShipping(Boolean(data.isFreeShipping)) - window.localStorage.setItem('homaroma_active_coupon', data.coupon.code) + window.localStorage.setItem('app_active_coupon', data.coupon.code) } catch (error) { setAppliedCoupon(null) setDiscountAmount(0) @@ -306,7 +306,7 @@ export default function CheckoutClient() { setDiscountAmount(0) setIsFreeShipping(false) setCouponError(null) - window.localStorage.removeItem('homaroma_active_coupon') + window.localStorage.removeItem('app_active_coupon') } const selectAddress = (address: CustomerAddress | 'new') => { @@ -611,7 +611,7 @@ export default function CheckoutClient() {
{item.productName} { i18n.changeLanguage(code) - localStorage.setItem('homaroma_lang', code) + localStorage.setItem('app_lang', code) setLanguageCookie(code) const currentPathWithoutLang = getPathWithoutLocale(pathname) diff --git a/apps/website/src/components/ProfileClient.tsx b/apps/website/src/components/ProfileClient.tsx index 86c4551..193b19d 100644 --- a/apps/website/src/components/ProfileClient.tsx +++ b/apps/website/src/components/ProfileClient.tsx @@ -327,7 +327,7 @@ export default function ProfileClient() {
{displayCustomer.displayName
-

{displayCustomer.displayName || 'HomAroma Member'}

+

{displayCustomer.displayName || 'Member'}

{displayCustomer.phone && {displayCustomer.phone}} {displayCustomer.email && {displayCustomer.email}} @@ -379,7 +379,7 @@ export default function ProfileClient() { {activeTab === 'orders' && (
{orders.length === 0 ? ( - + ) : ( orders.map((order) => (
@@ -404,7 +404,7 @@ export default function ProfileClient() { {order.items.map((item) => (
- {item.productName} + {item.productName}

{item.productName}

diff --git a/apps/website/src/components/ShopCouponBridge.tsx b/apps/website/src/components/ShopCouponBridge.tsx index a438215..b838ec2 100644 --- a/apps/website/src/components/ShopCouponBridge.tsx +++ b/apps/website/src/components/ShopCouponBridge.tsx @@ -16,7 +16,7 @@ export default function ShopCouponBridge({ coupon }: ShopCouponBridgeProps) { useEffect(() => { if (!coupon) return - localStorage.setItem('homaroma_active_coupon', coupon) + localStorage.setItem('app_active_coupon', coupon) router.replace(getPathWithLocale('/shop', lang), { scroll: false }) }, [coupon, router, lang]) diff --git a/apps/website/src/components/TrackOrderClient.tsx b/apps/website/src/components/TrackOrderClient.tsx index b967239..75618bb 100644 --- a/apps/website/src/components/TrackOrderClient.tsx +++ b/apps/website/src/components/TrackOrderClient.tsx @@ -41,14 +41,14 @@ export default function TrackOrderClient() { const [error, setError] = useState(null) useEffect(() => { - const storedOrders = parseStoredOrders(localStorage.getItem('homaroma_local_orders')) + const storedOrders = parseStoredOrders(localStorage.getItem('app_local_orders')) queueMicrotask(() => setLocalOrders(storedOrders)) }, []) const saveLocalOrder = (id: number) => { setLocalOrders((current) => { const next = [id, ...current.filter((item) => item !== id)].slice(0, 12) - localStorage.setItem('homaroma_local_orders', JSON.stringify(next)) + localStorage.setItem('app_local_orders', JSON.stringify(next)) return next }) } diff --git a/apps/website/src/components/dynamic/DynamicPageRenderer.tsx b/apps/website/src/components/dynamic/DynamicPageRenderer.tsx index d096187..9f92d2b 100644 --- a/apps/website/src/components/dynamic/DynamicPageRenderer.tsx +++ b/apps/website/src/components/dynamic/DynamicPageRenderer.tsx @@ -115,7 +115,7 @@ function HeroSection({ payload, lang }: { payload: Record, lang
{title, lang

)}

- {renderAccentTitle(title || 'HomAroma')} + {renderAccentTitle(title || '')}

{description && (

diff --git a/apps/website/src/proxy.ts b/apps/website/src/proxy.ts index 21aa085..279a3df 100644 --- a/apps/website/src/proxy.ts +++ b/apps/website/src/proxy.ts @@ -35,7 +35,7 @@ export function proxy(request: NextRequest) { // Determine the preferred locale // 1. Check cookie - const cookieLocale = request.cookies.get('homaroma_lang')?.value; + const cookieLocale = request.cookies.get('app_lang')?.value; let targetLocale = DEFAULT_LOCALE; if (cookieLocale && (SUPPORTED_LOCALES as readonly string[]).includes(cookieLocale)) {