diff --git a/apps/admin/index.html b/apps/admin/index.html index dbb7acd..3b9bd71 100644 --- a/apps/admin/index.html +++ b/apps/admin/index.html @@ -4,7 +4,7 @@ - HomAroma Admin + Admin Control Center diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index cd3a1b3..0a73177 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -71,13 +71,15 @@ import MarketplaceProductsPage from './pages/tools/MarketplaceProductsPage' import MarketplaceCrmPage from './pages/crm/MarketplaceCrmPage' import MarketplaceCustomerDetailPage from './pages/crm/MarketplaceCustomerDetailPage' import PosPage from './pages/PosPage' +import { StoreSettingsProvider } from './contexts/StoreSettingsContext' function App() { return ( - - - {/* Public Route */} - } /> + + + + {/* Public Route */} + } /> {/* Guarded Admin Routes */} }> @@ -171,6 +173,7 @@ function App() { } /> + ) } diff --git a/apps/admin/src/components/AdminLayout.tsx b/apps/admin/src/components/AdminLayout.tsx index 3e273b7..21270b0 100644 --- a/apps/admin/src/components/AdminLayout.tsx +++ b/apps/admin/src/components/AdminLayout.tsx @@ -3,9 +3,11 @@ import { Outlet, Link } from 'react-router-dom' import { Store } from 'lucide-react' import Sidebar from './Sidebar' import LanguageSelector from './LanguageSelector' +import { useStoreSettings } from '../contexts/StoreSettingsContext' export default function AdminLayout() { const [isCollapsed, setIsCollapsed] = useState(false) + const { brandName } = useStoreSettings() return (
@@ -16,7 +18,7 @@ export default function AdminLayout() {
- HomAroma Control Center + {brandName} Control Center
diff --git a/apps/admin/src/components/ProtectedRoute.tsx b/apps/admin/src/components/ProtectedRoute.tsx index 600b80d..dfbe6e3 100644 --- a/apps/admin/src/components/ProtectedRoute.tsx +++ b/apps/admin/src/components/ProtectedRoute.tsx @@ -1,7 +1,8 @@ import { Navigate, Outlet } from 'react-router-dom' +import { getAdminToken } from '../utils/auth' export default function ProtectedRoute() { - const token = localStorage.getItem('homaroma_admin_token') + const token = getAdminToken() if (!token) { // If no token, redirect to login page diff --git a/apps/admin/src/components/Sidebar.tsx b/apps/admin/src/components/Sidebar.tsx index 4ed9e12..cf5fe35 100644 --- a/apps/admin/src/components/Sidebar.tsx +++ b/apps/admin/src/components/Sidebar.tsx @@ -38,6 +38,8 @@ import { Map, Target, } from 'lucide-react' +import { useStoreSettings } from '../contexts/StoreSettingsContext' +import { clearAdminAuth } from '../utils/auth' interface SidebarProps { isCollapsed: boolean @@ -45,6 +47,7 @@ interface SidebarProps { } export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { + const { brandName } = useStoreSettings() const { t } = useTranslation() const navigate = useNavigate() const location = useLocation() @@ -174,8 +177,7 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) { ] const handleLogout = () => { - localStorage.removeItem('homaroma_admin_token') - localStorage.removeItem('homaroma_admin_user') + clearAdminAuth() navigate('/login') } @@ -194,7 +196,7 @@ export default function Sidebar({ isCollapsed, onToggle }: SidebarProps) {
{!isCollapsed && (
- HomAroma Admin + {brandName} Admin
)}
diff --git a/apps/admin/src/pages/DashboardPage.tsx b/apps/admin/src/pages/DashboardPage.tsx index 73cbeb9..18e85b8 100644 --- a/apps/admin/src/pages/DashboardPage.tsx +++ b/apps/admin/src/pages/DashboardPage.tsx @@ -2,8 +2,10 @@ import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Package, Tag, Layers, TrendingUp } from 'lucide-react' import { API_BASE, type Category, type Product } from '@homaroma/types' +import { useStoreSettings } from '../contexts/StoreSettingsContext' export default function DashboardPage() { + const { brandName } = useStoreSettings() const { t } = useTranslation() const [stats, setStats] = useState({ products: 0, @@ -74,7 +76,7 @@ export default function DashboardPage() {

{t('dashboard.title', 'System Overview')}

-

{t('dashboard.subtitle', 'Real-time stats from the HomAroma platform.')}

+

{t('dashboard.subtitle', `Real-time stats from the ${brandName} platform.`)}

Last updated

diff --git a/apps/admin/src/pages/LoginPage.tsx b/apps/admin/src/pages/LoginPage.tsx index 8660419..05337a6 100644 --- a/apps/admin/src/pages/LoginPage.tsx +++ b/apps/admin/src/pages/LoginPage.tsx @@ -4,8 +4,11 @@ import { useTranslation } from 'react-i18next' import { Lock, User, AlertCircle, ArrowRight, Loader2 } from 'lucide-react' import { API_BASE } from '@homaroma/types' import LanguageSelector from '../components/LanguageSelector' +import { useStoreSettings } from '../contexts/StoreSettingsContext' +import { setAdminToken, setAdminUser } from '../utils/auth' export default function LoginPage() { + const { brandName } = useStoreSettings() const { t } = useTranslation() const [username, setUsername] = useState('') const [password, setPassword] = useState('') @@ -32,8 +35,8 @@ export default function LoginPage() { } // Store token - localStorage.setItem('homaroma_admin_token', data.token) - localStorage.setItem('homaroma_admin_user', JSON.stringify(data.user)) + setAdminToken(data.token) + setAdminUser(data.user) // Redirect to dashboard navigate('/') @@ -61,7 +64,7 @@ export default function LoginPage() { Authorized Access Only

- HomAroma {t('login.title', 'Admin')} + {brandName} {t('login.title', 'Admin')}

{t('login.subtitle', 'Welcome back to the sensory command center.')}

@@ -127,7 +130,7 @@ export default function LoginPage() {

- © 2026 HomAroma & Essential Oils + © {new Date().getFullYear()} {brandName}

diff --git a/apps/admin/src/pages/PageBuilderPage.tsx b/apps/admin/src/pages/PageBuilderPage.tsx index c8ddd47..95e98a0 100644 --- a/apps/admin/src/pages/PageBuilderPage.tsx +++ b/apps/admin/src/pages/PageBuilderPage.tsx @@ -23,6 +23,8 @@ import { import { API_BASE, type Page, type PageSection, type Category } from '@homaroma/types'; import { getAdminDisplayString } from '../utils/localization'; import MediaLibraryModal from '../components/MediaLibraryModal'; +import { useStoreSettings } from '../contexts/StoreSettingsContext'; +import { getAuthHeaders } from '../utils/auth'; import HeroForm from '../components/page-builder/HeroForm'; import AnnouncementBarForm from '../components/page-builder/AnnouncementBarForm'; @@ -83,6 +85,7 @@ interface Brand { } export default function PageBuilderPage() { + const { brandName } = useStoreSettings(); const { t } = useTranslation(); const navigate = useNavigate(); const frontendBase = ( @@ -90,10 +93,7 @@ export default function PageBuilderPage() { ? window.location.origin : (import.meta.env.VITE_FRONTEND_URL || window.location.origin) ).replace(/\/$/, ''); - const authHeaders = () => ({ - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('homaroma_admin_token')}`, - }); + const authHeaders = () => getAuthHeaders(); const [page, setPage] = useState(null); const [sections, setSections] = useState([]); const [categories, setCategories] = useState([]); @@ -606,7 +606,7 @@ export default function PageBuilderPage() { case 'EDITORIAL_TILE': defaultPayload = { title: 'Crafted with [Sensory Passion]', - description: 'Every formulation at HomAroma is a tribute to pure botanical essences, hand-blended to capture nature’s quiet intelligence.', + description: `Every formulation at ${brandName} is a tribute to pure botanical essences, hand-blended to capture nature’s quiet intelligence.`, imageUrl: '/images/oil_ozone.png', imagePosition: 'left', buttonText: 'Read Our Story', @@ -791,7 +791,7 @@ export default function PageBuilderPage() { defaultPayload = { title: 'Scent Guide [Article]', subtitle: 'Discover the story behind each fragrance.', - author: 'HomAroma', + author: brandName || 'Author', category: 'Story', publishDate: '2026-01-01', imageUrl: '/images/hero.png', @@ -860,7 +860,7 @@ export default function PageBuilderPage() { case 'QUOTE_CALLOUT': defaultPayload = { quote: 'Fragrance is the invisible, unforgettable accessory.', - author: 'HomAroma', + author: brandName || 'Author', role: 'Founder', }; break; diff --git a/apps/admin/src/pages/PosPage.tsx b/apps/admin/src/pages/PosPage.tsx index c002df2..ded09a6 100644 --- a/apps/admin/src/pages/PosPage.tsx +++ b/apps/admin/src/pages/PosPage.tsx @@ -26,6 +26,8 @@ import { } from 'lucide-react' import { API_BASE, formatPrice, type Product, type Category, type Order } from '@homaroma/types' import { getAdminDisplayString } from '../utils/localization' +import { useStoreSettings } from '../contexts/StoreSettingsContext' +import { getAdminUser } from '../utils/auth' interface PosCartItem { productId: number @@ -88,9 +90,11 @@ export default function PosPage() { const [completedOrder, setCompletedOrder] = useState(null) const [receiptModalOpen, setReceiptModalOpen] = useState(false) + const { brandName: contextBrandName, settings: contextSettings } = useStoreSettings() + // Store Brand Settings - const [storeName, setStoreName] = useState('HomAroma Store') - const [pointsEarnRate, setPointsEarnRate] = useState(1.0) + const [storeName, setStoreName] = useState(contextBrandName ? `${contextBrandName} Store` : 'Store') + const [pointsEarnRate, setPointsEarnRate] = useState(contextSettings?.pointsEarnRate ? Number(contextSettings.pointsEarnRate) : 1.0) // Fullscreen State const [isFullscreen, setIsFullscreen] = useState(false) @@ -102,17 +106,17 @@ export default function PosPage() { useEffect(() => { // Current cashier try { - const userStr = localStorage.getItem('homaroma_admin_user') - if (userStr) setCashier(JSON.parse(userStr)) + const user = getAdminUser() + if (user) setCashier(user) } catch (e) { console.error(e) } - // Load store settings + // Load store settings fallback/sync fetch(`${API_BASE}/settings`) .then(res => res.json()) .then(data => { - if (data.brandName) setStoreName(data.brandName) + if (data.brandName) setStoreName(`${data.brandName} Store`) if (data.pointsEarnRate) setPointsEarnRate(Number(data.pointsEarnRate)) }) .catch(console.error) diff --git a/apps/admin/src/pages/StoreSettingsPage.tsx b/apps/admin/src/pages/StoreSettingsPage.tsx index b4a3a7c..237389c 100644 --- a/apps/admin/src/pages/StoreSettingsPage.tsx +++ b/apps/admin/src/pages/StoreSettingsPage.tsx @@ -1,8 +1,10 @@ import { useState, useEffect } from 'react' import { API_BASE, type StoreSettings } from '@homaroma/types' import { Save, Loader2 } from 'lucide-react' +import { useStoreSettings } from '../contexts/StoreSettingsContext' export default function StoreSettingsPage() { + const { updateSettingsState } = useStoreSettings() const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) @@ -17,7 +19,7 @@ export default function StoreSettingsPage() { fetch(`${API_BASE}/settings`) .then(r => r.json()) .then((data: StoreSettings) => { - setBrandName(data.brandName || 'HomAroma') + setBrandName(data.brandName || '') setLogoUrl(data.logoUrl || '') setThemePreset(data.themePreset || 'default') setFacebookUrl(data.facebookUrl || '') @@ -37,7 +39,10 @@ export default function StoreSettingsPage() { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ brandName, logoUrl, themePreset, facebookUrl, instagramUrl, tiktokUrl }), }) - if (res.ok) alert('Saved successfully!') + if (res.ok) { + updateSettingsState({ brandName, logoUrl, themePreset, facebookUrl, instagramUrl, tiktokUrl }) + alert('Saved successfully!') + } } catch { alert('Failed to save') } finally { setSaving(false) } } @@ -62,7 +67,7 @@ export default function StoreSettingsPage() {
setBrandName(e.target.value)} - className="admin-input w-full max-w-sm" placeholder="HomAroma" /> + className="admin-input w-full max-w-sm" placeholder="Enter Brand Name" />

Used in header, footer, and email communications.

diff --git a/apps/admin/src/pages/SystemSettingsPage.tsx b/apps/admin/src/pages/SystemSettingsPage.tsx index e0c4931..ca7e24f 100644 --- a/apps/admin/src/pages/SystemSettingsPage.tsx +++ b/apps/admin/src/pages/SystemSettingsPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from 'react' import { API_BASE, type StoreSettings } from '@homaroma/types' import { Save, Loader2, Landmark, Truck, DatabaseZap, CheckCircle2, AlertCircle, Sparkles } from 'lucide-react' +import { useStoreSettings } from '../contexts/StoreSettingsContext' const defaultThemeTokens = { brandPrimary: '#FF6B6B', @@ -113,11 +114,13 @@ export default function SystemSettingsPage() { })) } + const { updateSettingsState } = useStoreSettings() + useEffect(() => { fetch(`${API_BASE}/settings`) .then(r => r.json()) .then((data: StoreSettings) => { - setBrandName(data.brandName || 'HomAroma') + setBrandName(data.brandName || '') setLogoUrl(data.logoUrl || '') setThemePreset(data.themePreset || 'default') setShippingFee(data.shippingFee.toString()) @@ -198,7 +201,33 @@ export default function SystemSettingsPage() { scrollRevealDuration: parseInt(scrollRevealDuration) || 700, }) }) - if (res.ok) alert('Settings saved successfully!') + if (res.ok) { + updateSettingsState({ + brandName, + logoUrl, + themePreset, + themeTokens, + shippingFee: parseFloat(shippingFee) || 0, + bankName, + bankAccountName, + bankAccountNumber, + pointsEarnRate: parseFloat(pointsEarnRate) || 1, + footerDescription, + facebookUrl, + instagramUrl, + tiktokUrl, + newsletterTitle, + newsletterDescription, + newsletterPlaceholder, + newsletterButtonText, + footerSupportHeading, + footerCopyrightText, + scrollRevealEnabled, + scrollRevealStyle, + scrollRevealDuration: parseInt(scrollRevealDuration) || 700, + }) + alert('Settings saved successfully!') + } } catch (err) { console.error(err) alert('Failed to save settings') @@ -339,7 +368,7 @@ export default function SystemSettingsPage() { value={brandName} onChange={e => setBrandName(e.target.value)} className="admin-input w-full max-w-sm" - placeholder="HomAroma" + placeholder="Enter Brand Name" />

This name is used by the website and spa header/footer as the brand display name.

@@ -436,7 +465,7 @@ export default function SystemSettingsPage() { value={footerCopyrightText} onChange={e => setFooterCopyrightText(e.target.value)} className="admin-input w-full" - placeholder="© 2026 HomAroma. All rights reserved." + placeholder={`© ${new Date().getFullYear()} ${brandName || 'Store'}. All rights reserved.`} /> diff --git a/apps/admin/src/pages/analytics/RepeatCustomersActionPlanPage.tsx b/apps/admin/src/pages/analytics/RepeatCustomersActionPlanPage.tsx index 145b7cf..3534c4e 100644 --- a/apps/admin/src/pages/analytics/RepeatCustomersActionPlanPage.tsx +++ b/apps/admin/src/pages/analytics/RepeatCustomersActionPlanPage.tsx @@ -23,6 +23,8 @@ import { useTranslation } from 'react-i18next'; import { API_BASE } from '@homaroma/types'; import { fmtBaht, fmtNum } from '../../utils/thaiGeoUtils'; import RepeatCustomersNavTabs from '../../components/analytics/RepeatCustomersNavTabs'; +import { useStoreSettings } from '../../contexts/StoreSettingsContext'; +import { getAuthHeaders } from '../../utils/auth'; type ChannelType = 'all' | 'tiktok' | 'shopee' | 'lazada'; type PriorityFilter = 'all' | 'due_soon' | 'overdue' | 'vip' | 'healthy'; @@ -45,6 +47,7 @@ interface ActionPlanCustomer { } export default function RepeatCustomersActionPlanPage() { + const { brandName } = useStoreSettings(); const { t } = useTranslation(); const navigate = useNavigate(); @@ -97,10 +100,7 @@ export default function RepeatCustomersActionPlanPage() { fetch(`${API_BASE}/crm/check-imported`, { method: 'POST', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('homaroma_admin_token')}`, - }, + headers: getAuthHeaders(), body: JSON.stringify({ keys }), }) .then((res) => res.json()) @@ -385,22 +385,22 @@ export default function RepeatCustomersActionPlanPage() { // Generate Message Template for Copying const getCustomerMessageTemplate = (cust: ActionPlanCustomer) => { const action = getActionRecommendation(cust); - const prodName = cust.lastOrderProduct || 'น้ำหอมปรับอากาศ/น้ำมันหอมระเหย HomAroma'; + const prodName = cust.lastOrderProduct || `สินค้า ${brandName}`; const cleanName = cust.displayName.replace(/\s*\(\d+\)$/, ''); if (action.strategyKey === 'vip') { - return `สวัสดีครับคุณ ${cleanName} ✨ ขอบพระคุณที่ไว้วางใจ HomAroma เสมอมาครับ\n\nวันนี้ทางร้านขอมอบสิทธิ์ VIP สุดพิเศษสำหรับคุณโดยเฉพาะ! รับส่วนลด 15% หรือสิทธิ์รับ Tester กลิ่นใหม่ล่าสุดฟรี เมื่อสั่งซื้อเติม ${prodName} รอบถัดไปนะครับ 🌿\n\nแจ้งโค้ด: [${action.promoCode}] กับแอดมินได้เลยครับ 😊`; + return `สวัสดีครับคุณ ${cleanName} ✨ ขอบพระคุณที่ไว้วางใจ ${brandName} เสมอมาครับ\n\nวันนี้ทางร้านขอมอบสิทธิ์ VIP สุดพิเศษสำหรับคุณโดยเฉพาะ! รับส่วนลด 15% หรือสิทธิ์รับ Tester กลิ่นใหม่ล่าสุดฟรี เมื่อสั่งซื้อเติม ${prodName} รอบถัดไปนะครับ 🌿\n\nแจ้งโค้ด: [${action.promoCode}] กับแอดมินได้เลยครับ 😊`; } if (action.strategyKey === 'due_soon') { - return `สวัสดีครับคุณ ${cleanName} 🌿 จาก HomAroma นะครับ\n\n${prodName} ที่สั่งซื้อไปก่อนหน้านี้ใกล้หมดหรือยังครับ? ตอนนี้ทางร้านมีโค้ดลดพิเศษสำหรับลูกค้าประจำ ลดเพิ่ม 10% [${action.promoCode}] หรือรับสิทธิ์ส่งฟรีทันทีครับ ✨\n\nสนใจเติมสินค้ากลิ่นเดิมหรือลองกลิ่นใหม่ ทักข้อความหาเราได้เลยนะครับ!`; + return `สวัสดีครับคุณ ${cleanName} 🌿 จาก ${brandName} นะครับ\n\n${prodName} ที่สั่งซื้อไปก่อนหน้านี้ใกล้หมดหรือยังครับ? ตอนนี้ทางร้านมีโค้ดลดพิเศษสำหรับลูกค้าประจำ ลดเพิ่ม 10% [${action.promoCode}] หรือรับสิทธิ์ส่งฟรีทันทีครับ ✨\n\nสนใจเติมสินค้ากลิ่นเดิมหรือลองกลิ่นใหม่ ทักข้อความหาเราได้เลยนะครับ!`; } if (action.strategyKey === 'overdue') { - return `สวัสดีครับคุณ ${cleanName} 🌸 HomAroma คิดถึงคุณลูกค้านะครับ!\n\nวันนี้เรามีโปรโมชัน Welcome Back สุดคุ้ม ลดพิเศษ 20% ทันที [${action.promoCode}] สำหรับการสั่งซื้อครั้งถัดไป ไม่ว่าจะเป็น ${prodName} หรือกลิ่นใหม่ยอดฮิต 🌿\n\nโค้ดมีจำนวนจำกัด ทักแชทสั่งซื้อได้เลยนะครับ 😊`; + return `สวัสดีครับคุณ ${cleanName} 🌸 ${brandName} คิดถึงคุณลูกค้านะครับ!\n\nวันนี้เรามีโปรโมชัน Welcome Back สุดคุ้ม ลดพิเศษ 20% ทันที [${action.promoCode}] สำหรับการสั่งซื้อครั้งถัดไป ไม่ว่าจะเป็น ${prodName} หรือกลิ่นใหม่ยอดฮิต 🌿\n\nโค้ดมีจำนวนจำกัด ทักแชทสั่งซื้อได้เลยนะครับ 😊`; } - return `สวัสดีครับคุณ ${cleanName} 🌿 HomAroma ขอขอบพระคุณสำหรับคำสั่งซื้อที่ผ่านมานะครับ หากคุณลูกค้าต้องการคำแนะนำกลิ่นหอมเพิ่มเติมหรือการใช้งานเครื่องพ่น ทักสอบถามเราได้ตลอดเลยนะครับ ✨`; + return `สวัสดีครับคุณ ${cleanName} 🌿 ${brandName} ขอขอบพระคุณสำหรับคำสั่งซื้อที่ผ่านมานะครับ หากคุณลูกค้าต้องการคำแนะนำกลิ่นหอมเพิ่มเติมหรือการใช้งานเครื่องพ่น ทักสอบถามเราได้ตลอดเลยนะครับ ✨`; }; const handleCopyTemplate = (cust: ActionPlanCustomer) => { diff --git a/apps/admin/src/pages/crm/MarketplaceCustomerDetailPage.tsx b/apps/admin/src/pages/crm/MarketplaceCustomerDetailPage.tsx index ed56fcc..1c8f179 100644 --- a/apps/admin/src/pages/crm/MarketplaceCustomerDetailPage.tsx +++ b/apps/admin/src/pages/crm/MarketplaceCustomerDetailPage.tsx @@ -15,6 +15,7 @@ import { } from 'lucide-react'; import { API_BASE } from '@homaroma/types'; import { fmtBaht } from '../../utils/thaiGeoUtils'; +import { useStoreSettings } from '../../contexts/StoreSettingsContext'; interface OrderItem { name: string; @@ -37,6 +38,7 @@ interface OrderHistory { } export default function MarketplaceCustomerDetailPage() { + const { brandName } = useStoreSettings(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); @@ -155,16 +157,17 @@ export default function MarketplaceCustomerDetailPage() { const handleCopyChatTemplate = () => { if (!customer) return; const cleanName = customer.displayName.replace(/\s*\(\d+\)$/, ''); - const promo = promoCodeSent || 'HOMAROMA'; - const lastProd = orders?.[0]?.items?.[0]?.name || 'น้ำหอมปรับอากาศ/น้ำมันหอมระเหย HomAroma'; + const defaultCode = (brandName || 'VIP').replace(/[^a-zA-Z0-9]/g, '').toUpperCase(); + const promo = promoCodeSent || defaultCode; + const lastProd = orders?.[0]?.items?.[0]?.name || `สินค้า ${brandName}`; let msg = ''; if (strategyType === 'vip' || strategyType === 'vip_reward') { - msg = `สวัสดีครับคุณ ${cleanName} ✨ ขอบพระคุณที่ไว้วางใจ HomAroma เสมอมาครับ\n\nทางร้านขอมอบสิทธิ์ VIP สุดพิเศษสำหรับคุณ รับส่วนลด 15% หรือรับ Tester กลิ่นใหม่ล่าสุดฟรี เมื่อสั่งซื้อ ${lastProd} นะครับ 🌿\n\nแจ้งโค้ด: [${promo}] กับแอดมินได้เลยครับ 😊`; + msg = `สวัสดีครับคุณ ${cleanName} ✨ ขอบพระคุณที่ไว้วางใจ ${brandName} เสมอมาครับ\n\nทางร้านขอมอบสิทธิ์ VIP สุดพิเศษสำหรับคุณ รับส่วนลด 15% หรือรับ Tester กลิ่นใหม่ล่าสุดฟรี เมื่อสั่งซื้อ ${lastProd} นะครับ 🌿\n\nแจ้งโค้ด: [${promo}] กับแอดมินได้เลยครับ 😊`; } else if (strategyType === 'win_back' || strategyType === 'overdue') { - msg = `สวัสดีครับคุณ ${cleanName} 🌸 HomAroma คิดถึงคุณลูกค้านะครับ!\n\nวันนี้เรามีโปรโมชัน Welcome Back ลดพิเศษ 20% [${promo}] สำหรับการสั่งซื้อครั้งถัดไป ไม่ว่าจะเป็น ${lastProd} หรือกลิ่นใหม่ยอดฮิต 🌿 โค้ดมีจำนวนจำกัด ทักแชทสั่งซื้อได้เลยนะครับ 😊`; + msg = `สวัสดีครับคุณ ${cleanName} 🌸 ${brandName} คิดถึงคุณลูกค้านะครับ!\n\nวันนี้เรามีโปรโมชัน Welcome Back ลดพิเศษ 20% [${promo}] สำหรับการสั่งซื้อครั้งถัดไป ไม่ว่าจะเป็น ${lastProd} หรือกลิ่นใหม่ยอดฮิต 🌿 โค้ดมีจำนวนจำกัด ทักแชทสั่งซื้อได้เลยนะครับ 😊`; } else { - msg = `สวัสดีครับคุณ ${cleanName} 🌿 จาก HomAroma นะครับ\n\n${lastProd} ที่สั่งซื้อไปก่อนหน้านี้ใกล้หมดหรือยังครับ? ตอนนี้ทางร้านมีโค้ดลดพิเศษสำหรับลูกค้าประจำ [${promo}] หรือรับสิทธิ์ส่งฟรีทันทีครับ ✨ สนใจเติมสินค้าทักหาเราได้เลยนะครับ!`; + msg = `สวัสดีครับคุณ ${cleanName} 🌿 จาก ${brandName} นะครับ\n\n${lastProd} ที่สั่งซื้อไปก่อนหน้านี้ใกล้หมดหรือยังครับ? ตอนนี้ทางร้านมีโค้ดลดพิเศษสำหรับลูกค้าประจำ [${promo}] หรือรับสิทธิ์ส่งฟรีทันทีครับ ✨ สนใจเติมสินค้าทักหาเราได้เลยนะครับ!`; } navigator.clipboard.writeText(msg); diff --git a/apps/admin/src/pages/menu-manager/FooterBrandPage.tsx b/apps/admin/src/pages/menu-manager/FooterBrandPage.tsx index e7430fd..860c76c 100644 --- a/apps/admin/src/pages/menu-manager/FooterBrandPage.tsx +++ b/apps/admin/src/pages/menu-manager/FooterBrandPage.tsx @@ -2,10 +2,13 @@ import { useEffect, useState } from 'react' import type { FormEvent } from 'react' import { API_BASE, type FooterBrandResponse } from '@homaroma/types' import { AlertCircle, CheckCircle2, Image as ImageIcon, Link as LinkIcon, Loader2, Save } from 'lucide-react' +import { useStoreSettings } from '../../contexts/StoreSettingsContext' +import { getAuthHeaders } from '../../utils/auth' type SaveState = { type: 'success' | 'error'; message: string } | null export default function FooterBrandPage() { + const { updateSettingsState } = useStoreSettings() const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [saveState, setSaveState] = useState(null) @@ -51,13 +54,13 @@ export default function FooterBrandPage() { try { const response = await fetch(`${API_BASE}/settings/footer-brand`, { method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('homaroma_admin_token') || ''}`, - }, + headers: getAuthHeaders(), body: JSON.stringify({ brandName, logoUrl: logoUrl || null, description, facebookUrl: facebookUrl || null, instagramUrl: instagramUrl || null, tiktokUrl: tiktokUrl || null, expectedVersion: version }), }) const result = await response.json() as FooterBrandResponse & { error?: string; code?: string } + if (response.ok && result.brand) { + updateSettingsState({ brandName, logoUrl }) + } if (!response.ok) { if (response.status === 409 && result.code === 'VERSION_CONFLICT') { throw new Error('Footer Brand changed in another session. Reload the page before saving again.') @@ -97,7 +100,7 @@ export default function FooterBrandPage() {

Brand identity

Used by the footer and shared site brand.

-
setBrandName(event.target.value)} className="admin-input w-full" placeholder="HomAroma" />
+
setBrandName(event.target.value)} className="admin-input w-full" placeholder="Enter Brand Name" />
setLogoUrl(event.target.value)} className="admin-input w-full" placeholder="https://example.com/logo.png" />{logoUrl &&
Footer logo preview
}
{(['th', 'en'] as const).map(language => )}