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 @@ -
{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{t('login.subtitle', 'Welcome back to the sensory command center.')}
- © 2026 HomAroma & Essential Oils + © {new Date().getFullYear()} {brandName}
Used in header, footer, and email communications.
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] = useStateUsed by the footer and shared site brand.