From 56ce9717aaed5fff2e780d506235975e4776e723 Mon Sep 17 00:00:00 2001
From: belviskhoremk
Date: Fri, 3 Apr 2026 09:15:25 +0000
Subject: [PATCH] Updates Apr3: new pages, components, and widespread UI/API
improvements
Co-Authored-By: Claude Sonnet 4.6
---
src/App.tsx | 93 +-
src/components/AdminLayout.tsx | 139 ++
src/components/ErrorBoundary.tsx | 45 +
src/components/Layout.tsx | 207 ++-
src/components/Skeletons.tsx | 65 +
src/components/ui.tsx | 269 ++-
src/contexts/ToastContext.tsx | 92 +
src/index.css | 142 +-
src/lib/apiError.ts | 13 +
src/main.tsx | 10 +-
src/pages/AnalyticsPage.tsx | 432 +++--
src/pages/AppointmentsPage.tsx | 426 +++++
src/pages/AuthPages.tsx | 427 +++--
src/pages/CampaignsPage.tsx | 383 +++++
src/pages/ChatbotBuilderPage.tsx | 1819 ++++++++++++--------
src/pages/DashboardPage.tsx | 506 +++---
src/pages/ForgotPasswordPage.tsx | 126 +-
src/pages/InboxPage.tsx | 446 +++--
src/pages/LandingPage.tsx | 810 ++++++---
src/pages/LeadsPage.tsx | 361 +++-
src/pages/MarketplacePage.tsx | 441 +++--
src/pages/PricingPage.tsx | 341 ++--
src/pages/PublicBookingPage.tsx | 413 +++++
src/pages/ResetPasswordPage.tsx | 149 +-
src/pages/SettingsPage.tsx | 45 +-
src/pages/admin/AdminChatbotsPage.tsx | 130 ++
src/pages/admin/AdminConversationsPage.tsx | 88 +
src/pages/admin/AdminDashboardPage.tsx | 116 ++
src/pages/admin/AdminSystemPage.tsx | 106 ++
src/pages/admin/AdminUsersPage.tsx | 219 +++
src/services/api.ts | 100 +-
src/store/themeStore.ts | 32 +
src/types/index.ts | 109 +-
tailwind.config.ts | 1 +
34 files changed, 6810 insertions(+), 2291 deletions(-)
create mode 100644 src/components/AdminLayout.tsx
create mode 100644 src/components/ErrorBoundary.tsx
create mode 100644 src/components/Skeletons.tsx
create mode 100644 src/contexts/ToastContext.tsx
create mode 100644 src/lib/apiError.ts
create mode 100644 src/pages/AppointmentsPage.tsx
create mode 100644 src/pages/CampaignsPage.tsx
create mode 100644 src/pages/PublicBookingPage.tsx
create mode 100644 src/pages/admin/AdminChatbotsPage.tsx
create mode 100644 src/pages/admin/AdminConversationsPage.tsx
create mode 100644 src/pages/admin/AdminDashboardPage.tsx
create mode 100644 src/pages/admin/AdminSystemPage.tsx
create mode 100644 src/pages/admin/AdminUsersPage.tsx
create mode 100644 src/store/themeStore.ts
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}
+
+
+