diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx
index 3ca3d7c..243488e 100644
--- a/apps/admin/src/App.tsx
+++ b/apps/admin/src/App.tsx
@@ -1,4 +1,4 @@
-import { BrowserRouter, Routes, Route } from 'react-router-dom'
+import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
import AdminLayout from './components/AdminLayout'
import DashboardPage from './pages/DashboardPage'
import ProductsPage from './pages/ProductsPage'
@@ -17,7 +17,9 @@ import PaymentSettingsPage from './pages/PaymentSettingsPage'
import ThemeSettingsPage from './pages/ThemeSettingsPage'
import ContentSettingsPage from './pages/ContentSettingsPage'
import SystemMaintenancePage from './pages/SystemMaintenancePage'
-import MenuManagerPage from './pages/MenuManagerPage'
+import MainNavigationPage from './pages/menu-manager/MainNavigationPage'
+import FooterExplorePage from './pages/menu-manager/FooterExplorePage'
+import FooterSupportPage from './pages/menu-manager/FooterSupportPage'
import NewsletterSubscribersPage from './pages/NewsletterSubscribersPage'
import ArticlesPage from './pages/ArticlesPage'
import ArticleEditPage from './pages/ArticleEditPage'
@@ -70,7 +72,10 @@ function App() {
} />
} />
} />
- } />
+ } />
+ } />
+ } />
+ } />
} />
} />
} />
diff --git a/apps/admin/src/components/Sidebar.tsx b/apps/admin/src/components/Sidebar.tsx
index 1bd83a5..a326c9b 100644
--- a/apps/admin/src/components/Sidebar.tsx
+++ b/apps/admin/src/components/Sidebar.tsx
@@ -53,11 +53,16 @@ const MAIN_MENU = [
{ icon: BarChart3, label: 'Lazada Analytics', to: '/lazada-analytics' },
{ icon: Layers, label: 'Lazada ทุกร้าน', to: '/lazada-overview' },
{ icon: Globe2, label: 'All Analytics', to: '/global-analytics' },
- { icon: Layers, label: 'Menu Manager', to: '/menu-manager' },
{ icon: Users, label: 'Subscribers', to: '/subscribers' },
{ icon: MessageSquare, label: 'Contact Messages', to: '/contacts' },
]
+const MENU_MANAGER_MENU = [
+ { icon: Layers, label: 'Main Navigation', to: '/menu-manager/main-navigation' },
+ { icon: FileText, label: 'Footer Explore', to: '/menu-manager/footer-explore' },
+ { icon: MessageSquare, label: 'Footer Support', to: '/menu-manager/footer-support' },
+]
+
const SETTINGS_MENU = [
{ icon: Store, label: 'Store Settings', to: '/settings' },
{ icon: CreditCard, label: 'Payment & Shipping',to: '/settings/payments' },
@@ -69,7 +74,9 @@ const SETTINGS_MENU = [
export default function Sidebar() {
const navigate = useNavigate()
const location = useLocation()
+ const isInMenuManager = location.pathname.startsWith('/menu-manager')
const isInSettings = location.pathname.startsWith('/settings')
+ const [menuManagerOpen, setMenuManagerOpen] = useState(isInMenuManager)
const [settingsOpen, setSettingsOpen] = useState(isInSettings)
const handleLogout = () => {
@@ -104,6 +111,35 @@ export default function Sidebar() {
))}
+ {/* Menu Manager Group */}
+
+
+
+ {menuManagerOpen && (
+
+ {MENU_MANAGER_MENU.map((item) => (
+
+ `sidebar-link text-sm ${isActive ? 'sidebar-link-active' : 'sidebar-link-inactive'}`
+ }
+ >
+
+ {item.label}
+
+ ))}
+
+ )}
+
+
{/* Settings Group */}
+ )
+}
diff --git a/apps/admin/src/pages/menu-manager/FooterSupportPage.tsx b/apps/admin/src/pages/menu-manager/FooterSupportPage.tsx
new file mode 100644
index 0000000..edc5c8c
--- /dev/null
+++ b/apps/admin/src/pages/menu-manager/FooterSupportPage.tsx
@@ -0,0 +1,11 @@
+import MenuEditorPage from './MenuEditorPage'
+
+export default function FooterSupportPage() {
+ return (
+
+ )
+}
diff --git a/apps/admin/src/pages/menu-manager/MainNavigationPage.tsx b/apps/admin/src/pages/menu-manager/MainNavigationPage.tsx
new file mode 100644
index 0000000..bb57f99
--- /dev/null
+++ b/apps/admin/src/pages/menu-manager/MainNavigationPage.tsx
@@ -0,0 +1,11 @@
+import MenuEditorPage from './MenuEditorPage'
+
+export default function MainNavigationPage() {
+ return (
+
+ )
+}
diff --git a/apps/admin/src/pages/MenuManagerPage.tsx b/apps/admin/src/pages/menu-manager/MenuEditorPage.tsx
similarity index 60%
rename from apps/admin/src/pages/MenuManagerPage.tsx
rename to apps/admin/src/pages/menu-manager/MenuEditorPage.tsx
index 6bd2ccb..ad72d5b 100644
--- a/apps/admin/src/pages/MenuManagerPage.tsx
+++ b/apps/admin/src/pages/menu-manager/MenuEditorPage.tsx
@@ -1,8 +1,205 @@
import { useState, useEffect, useRef } from 'react'
import { API_BASE, type Menu } from '@homaroma/types'
-import { Save, Loader2, Plus, Trash2, GripVertical, Copy, Check, Download, Upload } from 'lucide-react'
+import { Save, Loader2, Plus, Trash2, GripVertical, Copy, Check, Download, Upload, Star, Pencil } from 'lucide-react'
-const TreeNodeEditor = ({ node, type, depth, onChange, onRemove }: any) => {
+const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
+
+type MenuEditorPageProps = {
+ menuName: 'main' | 'footer-explore' | 'footer-support';
+ title: string;
+ description: string;
+}
+
+const FeaturedCardEditorFields = ({ featured, onChange }: any) => {
+ const [activeTab, setActiveTab] = useState<'content' | 'media'>('content');
+ const [activeLang, setActiveLang] = useState('th');
+
+ const updateFeatured = (field: string, value: any) => {
+ onChange({
+ ...featured,
+ [field]: value
+ });
+ };
+
+ const updateFeaturedTranslation = (field: string, lang: string, value: string) => {
+ const current = featured[field] || {};
+ updateFeatured(field, { ...current, [lang]: value });
+ };
+
+ const contentFields = [
+ ['sectionTitle', 'Section title', 'Featured'],
+ ['badge', 'Badge', 'Recommended'],
+ ['title', 'Card title', 'Product name'],
+ ['subtitle', 'Card subtitle', 'Short description'],
+ ['ctaLabel', 'CTA label', 'View details'],
+ ];
+
+ return (
+
+
+ {[
+ ['content', 'Content'],
+ ['media', 'Media & Link'],
+ ].map(([tab, label]) => (
+
+ ))}
+
+
+ {activeTab === 'content' ? (
+
+
+ {languages.map(lang => (
+
+ ))}
+
+
+ {contentFields.map(([field, label, placeholder]) => {
+ const value = featured[field]?.[activeLang] || '';
+ const isLongText = field === 'subtitle';
+
+ return (
+
+
+ {isLongText ? (
+
+ );
+ })}
+
+ ) : (
+
+
+
+
Show featured card
+
Disable this when this root menu should only show link columns.
+
+
+
+
+
+
+ {featured.imageUrl ? (
+

+ ) : (
+
No featured image
+ )}
+
+
+
+
+ )}
+
+ );
+}
+
+const FeaturedCardSidePanel = ({ node, onChange }: any) => {
+ if (!node) {
+ return (
+
+
+
+
+
+
Featured Card
+
Add a root menu item first, then select it to edit its featured card.
+
+
+ );
+ }
+
+ const rootLabel = node.label?.en || node.label?.th || 'Selected root menu';
+ const featured = node.featured || {};
+
+ return (
+
+
+
+
+
+
+
+
Featured Card
+
Editing: {rootLabel}
+
+
+
+
+
onChange({ ...node, featured: newFeatured })}
+ />
+
+ );
+}
+
+const TreeNodeEditor = ({ node, type, depth, onChange, onRemove, onEditFeatured, isFeaturedSelected }: any) => {
const childrenKey = type === 'root' ? 'columns' : 'links';
const labelKey = type === 'column' ? 'title' : 'label';
const hasUrl = type !== 'column';
@@ -31,9 +228,10 @@ const TreeNodeEditor = ({ node, type, depth, onChange, onRemove }: any) => {
onChange({ ...node, [childrenKey]: children.filter((_: any, i: number) => i !== idx) });
};
- const languages = ['th', 'en', 'ja', 'zh', 'vi', 'ko'];
const nodeLabel = node[labelKey] || {};
const imageValue: string = node[imageFieldKey] || '';
+ const featured = node.featured || {};
+ const featuredTitle = featured.title?.en || featured.title?.th || featured.sectionTitle?.en || featured.sectionTitle?.th || 'Not configured';
return (
0 ? 'ml-6 border-l-cyan-500/30' : ''}`}>
@@ -76,6 +274,34 @@ const TreeNodeEditor = ({ node, type, depth, onChange, onRemove }: any) => {
/>
+
+ {type === 'root' && (
+
+
+
+
+
+
+
+
Featured card
+
+ {featured.enabled === false ? 'Disabled' : featuredTitle}
+
+
+
+
+
+
+ )}
{hasUrl && (
@@ -97,7 +323,7 @@ const TreeNodeEditor = ({ node, type, depth, onChange, onRemove }: any) => {
-
+
{children.length > 0 && (
{children.map((child: any, i: number) => (
@@ -117,15 +343,13 @@ const TreeNodeEditor = ({ node, type, depth, onChange, onRemove }: any) => {
}
-export default function MenuManagerPage() {
+export default function MenuEditorPage({ menuName, title, description }: MenuEditorPageProps) {
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [menus, setMenus] = useState