diff --git a/src/App.tsx b/src/App.tsx
index 3334a6a..97fa625 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,7 +2,9 @@ import React, { lazy, Suspense } from 'react'
import { Routes, Route, Navigate } from 'react-router-dom'
import { useAuthStore } from '@/store/authStore'
import { AppLayout } from '@/components/Layout'
+import { AdminLayout } from '@/components/AdminLayout'
import { PublicLayout } from '@/components/PublicLayout'
+import { ErrorBoundary } from '@/components/ErrorBoundary'
import { Spinner } from '@/components/ui'
import './App.css'
@@ -22,6 +24,16 @@ const AnalyticsPage = lazy(() => import('@/pages/AnalyticsPage').then(m => ({ de
const PublicChatPage = lazy(() => import('@/pages/PublicChatPage').then(m => ({ default: m.PublicChatPage })))
const InboxPage = lazy(() => import('@/pages/InboxPage').then(m => ({ default: m.InboxPage })))
const LeadsPage = lazy(() => import('@/pages/LeadsPage').then(m => ({ default: m.LeadsPage })))
+const AppointmentsPage = lazy(() => import('@/pages/AppointmentsPage').then(m => ({ default: m.AppointmentsPage })))
+const CampaignsPage = lazy(() => import('@/pages/CampaignsPage').then(m => ({ default: m.CampaignsPage })))
+const PublicBookingPage = lazy(() => import('@/pages/PublicBookingPage').then(m => ({ default: m.PublicBookingPage })))
+
+// Admin pages
+const AdminDashboardPage = lazy(() => import('@/pages/admin/AdminDashboardPage').then(m => ({ default: m.AdminDashboardPage })))
+const AdminUsersPage = lazy(() => import('@/pages/admin/AdminUsersPage').then(m => ({ default: m.AdminUsersPage })))
+const AdminChatbotsPage = lazy(() => import('@/pages/admin/AdminChatbotsPage').then(m => ({ default: m.AdminChatbotsPage })))
+const AdminConversationsPage = lazy(() => import('@/pages/admin/AdminConversationsPage').then(m => ({ default: m.AdminConversationsPage })))
+const AdminSystemPage = lazy(() => import('@/pages/admin/AdminSystemPage').then(m => ({ default: m.AdminSystemPage })))
const PageLoader = () => (
@@ -35,6 +47,13 @@ const PrivateRoute: React.FC<{ children: React.ReactNode }> = ({ children }) =>
return
{children}
}
+const AdminRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const { isAuthenticated, user } = useAuthStore()
+ if (!isAuthenticated) return
+ if (!user?.is_admin) return
+ return
{children}
+}
+
const PublicOnlyRoute: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const { isAuthenticated } = useAuthStore()
if (isAuthenticated) return
@@ -50,38 +69,52 @@ const SmartPublicRoute: React.FC<{ children: React.ReactNode }> = ({ children })
}
export const App: React.FC = () => (
-
}>
-
- {/* Public - Landing has its own nav */}
- } />
+
+ }>
+
+ {/* Public - Landing has its own nav */}
+ } />
- {/* Public pages - wrapped in SmartPublicRoute for proper nav */}
- } />
- } />
- } />
+ {/* Public pages - wrapped in SmartPublicRoute for proper nav */}
+ } />
+ } />
+ } />
- {/* Public chat - no auth, no layout */}
- } />
+ {/* Public chat - no auth, no layout */}
+ } />
- {/* Auth */}
- } />
- } />
- } />
- } />
+ {/* Public booking - no auth, no layout */}
+ } />
- {/* Protected */}
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
+ {/* Auth */}
+ } />
+ } />
+ } />
+ } />
- {/* Fallback */}
- } />
-
-
-)
\ No newline at end of file
+ {/* Protected */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Admin */}
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Fallback */}
+ } />
+
+
+
+)
diff --git a/src/components/AdminLayout.tsx b/src/components/AdminLayout.tsx
new file mode 100644
index 0000000..0920670
--- /dev/null
+++ b/src/components/AdminLayout.tsx
@@ -0,0 +1,139 @@
+import React, { useState } from 'react'
+import { Link, useLocation, useNavigate } from 'react-router-dom'
+import { cn } from '@/lib/utils'
+import { useAuthStore } from '@/store/authStore'
+import { authAPI } from '@/services/api'
+import {
+ LayoutDashboard, Users, Bot, MessageSquare, Activity,
+ LogOut, Menu, Shield, X, ChevronRight,
+} from 'lucide-react'
+
+const NAV_ITEMS = [
+ { label: 'Overview', href: '/admin', icon: LayoutDashboard, exact: true },
+ { label: 'Users', href: '/admin/users', icon: Users },
+ { label: 'Chatbots', href: '/admin/chatbots', icon: Bot },
+ { label: 'Conversations', href: '/admin/conversations', icon: MessageSquare },
+ { label: 'System Health', href: '/admin/system', icon: Activity },
+]
+
+export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ const { user, logout } = useAuthStore()
+ const location = useLocation()
+ const navigate = useNavigate()
+ const [sidebarOpen, setSidebarOpen] = useState(false)
+
+ const handleLogout = async () => {
+ try { await authAPI.logout() } catch { /* ignore */ }
+ logout()
+ navigate('/login')
+ }
+
+ const isActive = (item: typeof NAV_ITEMS[0]) =>
+ item.exact ? location.pathname === item.href : location.pathname.startsWith(item.href)
+
+ return (
+
+ {/* Mobile backdrop */}
+ {sidebarOpen && (
+
setSidebarOpen(false)}
+ />
+ )}
+
+ {/* Sidebar */}
+
+
+ {/* Main */}
+
+ {/* Mobile header */}
+
+
+
+
+ Admin Panel
+
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx
new file mode 100644
index 0000000..493da31
--- /dev/null
+++ b/src/components/ErrorBoundary.tsx
@@ -0,0 +1,45 @@
+import React from 'react'
+
+interface Props {
+ children: React.ReactNode
+}
+
+interface State {
+ hasError: boolean
+ error?: Error
+}
+
+export class ErrorBoundary extends React.Component
{
+ state: State = { hasError: false }
+
+ static getDerivedStateFromError(error: Error): State {
+ return { hasError: true, error }
+ }
+
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
+ console.error('[ErrorBoundary]', error, info)
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return (
+
+
+ ⚠️
+
+
Something went wrong
+
+ {this.state.error?.message ?? 'An unexpected error occurred.'}
+
+
+
+ )
+ }
+ return this.props.children
+ }
+}
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 537fb7b..07357b4 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -6,13 +6,16 @@ import { authAPI } from '@/services/api'
import { getPlanColor } from '@/lib/utils'
import {
LayoutDashboard, ShoppingBag, Settings,
- LogOut, Menu, Sparkles, BarChart3, Mail, Users
+ LogOut, Menu, Sparkles, BarChart3, Mail, Users,
+ Shield, X, CalendarDays, Megaphone,
} from 'lucide-react'
const NAV_ITEMS = [
{ label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
{ label: 'Inbox', href: '/inbox', icon: Mail },
{ label: 'Leads', href: '/leads', icon: Users },
+ { label: 'Appointments', href: '/appointments', icon: CalendarDays },
+ { label: 'Campaigns', href: '/campaigns', icon: Megaphone },
{ label: 'Analytics', href: '/analytics', icon: BarChart3 },
{ label: 'Marketplace', href: '/marketplace', icon: ShoppingBag },
{ label: 'Settings', href: '/settings', icon: Settings },
@@ -25,101 +28,131 @@ export const AppLayout: React.FC<{ children: React.ReactNode }> = ({ children })
const [sidebarOpen, setSidebarOpen] = useState(false)
const handleLogout = async () => {
- try { await authAPI.logout() } catch { /* intentionally ignored */ }
+ try { await authAPI.logout() } catch { /* ignore */ }
logout()
navigate('/login')
}
- return (
-
- {/* Mobile backdrop */}
- {sidebarOpen && (
-
setSidebarOpen(false)} />
- )}
+ const initial = user?.email?.charAt(0).toUpperCase() || '?'
- {/* Sidebar */}
-
+ {children}
+
+
+