From 6ba911fe9e9cbf1799e302eafb706e79f9fa9128 Mon Sep 17 00:00:00 2001 From: Vexx Date: Sat, 30 May 2026 01:17:30 +0530 Subject: [PATCH] perf(frontend): implement React.lazy route chunking and Suspense skeleton loaders for faster initial load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #638 - Refactor Frontend/src/App.jsx: convert all 35+ page component imports to React.lazy() dynamic imports, grouped by route category (auth/user/admin/ master-admin/marketing/legal); auth pages and route guards remain eagerly loaded for instant critical path; each route wrapped in dedicated Suspense boundary; AnimatePresence moved to inner AppRoutes component so Router context is available - Add Frontend/src/components/ui/page-skeleton.jsx: two skeleton components — PageSkeleton (full shell with nav, sidebar, stat cards, and content rows) and MinimalSkeleton (centered card for auth/lobby pages); both use CSS animate-pulse shimmer and include aria role/label for accessibility --- Frontend/src/App.jsx | 473 ++++++++----------- Frontend/src/components/ui/page-skeleton.jsx | 120 +++++ 2 files changed, 310 insertions(+), 283 deletions(-) create mode 100644 Frontend/src/components/ui/page-skeleton.jsx diff --git a/Frontend/src/App.jsx b/Frontend/src/App.jsx index c0b21007..203916cd 100644 --- a/Frontend/src/App.jsx +++ b/Frontend/src/App.jsx @@ -1,306 +1,213 @@ -import { - BrowserRouter, - Routes, - Route, - Navigate, - useLocation -} from "react-router-dom"; -import React, { useEffect } from "react"; -import { AnimatePresence } from "framer-motion"; -import { NotFound } from "./components/ui/not-found-2"; -import useTicketStore from "./store/ticketStore"; -import Toaster from "./components/shared/Toaster"; -import BugReportWidget from "./components/shared/BugReportWidget"; -import useRealtimeNotifications from "./hooks/useRealtimeNotifications"; - -// Auth Components -import Login from "./pages/Login"; -import ForgotPassword from "./pages/ForgotPassword"; -import ResetPassword from "./pages/ResetPassword"; -import Signup from "./pages/Signup"; -import AdminSignup from "./pages/AdminSignup"; -import AdminLobby from "./pages/AdminLobby"; -import UserLobby from "./pages/UserLobby"; -import LandingPage from "./pages/LandingPage"; -import ContactSales from "./pages/ContactSales"; - -// Legacy components -import DuplicateDetection from "./user/pages/DuplicateDetection"; -import AutoResolveChat from "./user/pages/AutoResolveChat"; -import Resolved from "./user/pages/Resolved"; -import TicketTracking from "./user/pages/TicketTracking"; -// Layouts -import UserLayout from "./user/UserLayout"; -import AdminLayout from "./admin/layout/AdminLayout"; - +/** + * App.jsx — React Router v6 routes with lazy-loaded page components. + * + * All admin and user page modules are loaded dynamically via React.lazy() + * so the initial bundle only ships auth + landing pages. Each route group + * is wrapped in its own Suspense boundary with an appropriate skeleton. + */ + +import React, { lazy, Suspense, useEffect } from 'react'; +import { BrowserRouter, Routes, Route, Navigate, useLocation } from 'react-router-dom'; +import { AnimatePresence } from 'framer-motion'; +import { PageSkeleton, MinimalSkeleton } from './components/ui/page-skeleton'; +import { NotFound } from './components/ui/not-found-2'; +import useTicketStore from './store/ticketStore'; +import Toaster from './components/shared/Toaster'; +import BugReportWidget from './components/shared/BugReportWidget'; +import useRealtimeNotifications from './hooks/useRealtimeNotifications'; +import useAuthStore from './store/authStore'; + +// --------------------------------------------------------------------------- +// Eagerly-loaded auth pages — critical path, must be instant +// --------------------------------------------------------------------------- +import Login from './pages/Login'; +import ForgotPassword from './pages/ForgotPassword'; +import ResetPassword from './pages/ResetPassword'; +import Signup from './pages/Signup'; +import AdminSignup from './pages/AdminSignup'; +import LandingPage from './pages/LandingPage'; + +// Route guards (tiny, keep eager) +import AdminProtectedRoute from './components/shared/AdminProtectedRoute'; +import MasterAdminProtectedRoute from './components/shared/MasterAdminProtectedRoute'; +import ProtectedRoute from './components/shared/ProtectedRoute'; + +// --------------------------------------------------------------------------- +// Lazily-loaded lobby / shell pages +// --------------------------------------------------------------------------- +const AdminLobby = lazy(() => import('./pages/AdminLobby')); +const UserLobby = lazy(() => import('./pages/UserLobby')); + +// --------------------------------------------------------------------------- +// Lazily-loaded layouts +// --------------------------------------------------------------------------- +const UserLayout = lazy(() => import('./user/UserLayout')); +const AdminLayout = lazy(() => import('./admin/layout/AdminLayout')); + +// --------------------------------------------------------------------------- // User Pages -import Dashboard from "./user/pages/Dashboard"; -import CreateTicket from "./user/pages/CreateTicket"; -import MyTickets from "./user/pages/MyTickets"; -import TicketResult from "./user/pages/TicketResult"; -import Profile from "./user/pages/Profile"; -import TicketDetail from "./user/pages/TicketDetail"; -import TicketProcessing from "./user/pages/AIProcessing"; // Renamed generic import just in case, but keeping AIProcessing -import AIProcessing from "./user/pages/AIProcessing"; -import AIUnderstanding from "./user/pages/AIUnderstanding"; -import Notifications from "./user/pages/Notifications"; -import Help from "./user/pages/Help"; -import DocsPortal from "./docs/pages/DocsPortal"; - -// New Showcase Pages -import ApiReference from "./pages/ApiReference"; -import Changelog from "./pages/Changelog"; -import StatusPage from "./pages/StatusPage"; -import AboutUs from "./pages/AboutUs"; -import Careers from "./pages/Careers"; -import CookiePolicy from "./pages/legal/CookiePolicy"; - -// NEW Admin Pages (Refactored) -import AdminDashboard from "./admin/pages/AdminDashboard"; -import AdminTickets from "./admin/pages/AdminTickets"; -import AdminTicketDetail from "./admin/pages/AdminTicketDetail"; -import AdminUsers from "./admin/pages/AdminUsers"; -import AdminAnalytics from "./admin/pages/AdminAnalytics"; -import AdminProfile from "./admin/pages/AdminProfile"; -import AdminSettings from "./admin/pages/AdminSettings"; -import MasterBugReports from "./master-admin/pages/MasterBugReports"; - -// Feature Pages -import AutoCategorizationFeature from "./pages/features/AutoCategorizationFeature"; -import PriorityDetectionFeature from "./pages/features/PriorityDetectionFeature"; -import SmartResolutionFeature from "./pages/features/SmartResolutionFeature"; - -// Legal Pages -import TermsOfService from "./pages/legal/TermsOfService"; -import PrivacyPolicy from "./pages/legal/PrivacyPolicy"; -import Security from "./pages/legal/Security"; -import AdminProtectedRoute from "./components/shared/AdminProtectedRoute"; -import MasterAdminProtectedRoute from "./components/shared/MasterAdminProtectedRoute"; -import ProtectedRoute from "./components/shared/ProtectedRoute"; -import useAuthStore from "./store/authStore"; -import NotApproved from "./pages/NotApproved"; - -// Master Admin Components -import MasterAdminLogin from "./pages/MasterAdminLogin"; -import MasterAdminLayout from "./master-admin/layout/MasterAdminLayout"; -import MasterAdminDashboard from "./master-admin/pages/MasterAdminDashboard"; -import PendingAdminRequests from "./master-admin/pages/PendingAdminRequests"; -import AllCompanies from "./master-admin/pages/AllCompanies"; -import AllAdmins from "./master-admin/pages/AllAdmins"; - - -function TitleUpdater() { +// --------------------------------------------------------------------------- +const Dashboard = lazy(() => import('./user/pages/Dashboard')); +const CreateTicket = lazy(() => import('./user/pages/CreateTicket')); +const MyTickets = lazy(() => import('./user/pages/MyTickets')); +const TicketResult = lazy(() => import('./user/pages/TicketResult')); +const Profile = lazy(() => import('./user/pages/Profile')); +const TicketDetail = lazy(() => import('./user/pages/TicketDetail')); +const AIProcessing = lazy(() => import('./user/pages/AIProcessing')); +const AIUnderstanding = lazy(() => import('./user/pages/AIUnderstanding')); +const Notifications = lazy(() => import('./user/pages/Notifications')); +const Help = lazy(() => import('./user/pages/Help')); +const DuplicateDetection = lazy(() => import('./user/pages/DuplicateDetection')); +const AutoResolveChat = lazy(() => import('./user/pages/AutoResolveChat')); +const Resolved = lazy(() => import('./user/pages/Resolved')); +const TicketTracking = lazy(() => import('./user/pages/TicketTracking')); + +// --------------------------------------------------------------------------- +// Admin Pages +// --------------------------------------------------------------------------- +const AdminDashboard = lazy(() => import('./admin/pages/AdminDashboard')); +const AdminTickets = lazy(() => import('./admin/pages/AdminTickets')); +const AdminTicketDetail = lazy(() => import('./admin/pages/AdminTicketDetail')); +const AdminUsers = lazy(() => import('./admin/pages/AdminUsers')); +const AdminAnalytics = lazy(() => import('./admin/pages/AdminAnalytics')); +const AdminProfile = lazy(() => import('./admin/pages/AdminProfile')); +const AdminSettings = lazy(() => import('./admin/pages/AdminSettings')); + +// --------------------------------------------------------------------------- +// Master-admin Pages +// --------------------------------------------------------------------------- +const MasterAdminLayout = lazy(() => import('./master-admin/layout/MasterAdminLayout')); +const MasterAdminDashboard = lazy(() => import('./master-admin/pages/MasterAdminDashboard')); +const AllAdmins = lazy(() => import('./master-admin/pages/AllAdmins')); +const AllCompanies = lazy(() => import('./master-admin/pages/AllCompanies')); +const PendingAdminRequests = lazy(() => import('./master-admin/pages/PendingAdminRequests')); +const MasterBugReports = lazy(() => import('./master-admin/pages/MasterBugReports')); + +// --------------------------------------------------------------------------- +// Showcase / marketing pages (defer aggressively) +// --------------------------------------------------------------------------- +const ContactSales = lazy(() => import('./pages/ContactSales')); +const ApiReference = lazy(() => import('./pages/ApiReference')); +const Changelog = lazy(() => import('./pages/Changelog')); +const StatusPage = lazy(() => import('./pages/StatusPage')); +const AboutUs = lazy(() => import('./pages/AboutUs')); +const Careers = lazy(() => import('./pages/Careers')); +const DocsPortal = lazy(() => import('./docs/pages/DocsPortal')); + +// Feature pages +const AutoCategorizationFeature = lazy(() => import('./pages/features/AutoCategorizationFeature')); +const PriorityDetectionFeature = lazy(() => import('./pages/features/PriorityDetectionFeature')); +const SmartResolutionFeature = lazy(() => import('./pages/features/SmartResolutionFeature')); + +// Legal pages +const TermsOfService = lazy(() => import('./pages/legal/TermsOfService')); +const PrivacyPolicy = lazy(() => import('./pages/legal/PrivacyPolicy')); +const Security = lazy(() => import('./pages/legal/Security')); +const CookiePolicy = lazy(() => import('./pages/legal/CookiePolicy')); + +// --------------------------------------------------------------------------- +// Inner component that consumes router context +// --------------------------------------------------------------------------- +function AppRoutes() { const location = useLocation(); - - useEffect(() => { - const path = location.pathname; - let title = 'HELPDESK.AI'; - - // Admin Routes - if (path.startsWith('/admin/ticket/')) title = 'Ticket Detail | Admin'; - else if (path.startsWith('/admin/dashboard')) title = 'Admin Dashboard'; - else if (path.startsWith('/admin/tickets')) title = 'Admin Tickets'; - else if (path.startsWith('/admin/users')) title = 'Manage Users | Admin'; - else if (path.startsWith('/admin/analytics')) title = 'Analytics | Admin'; - else if (path.startsWith('/admin/profile')) title = 'Admin Profile'; - else if (path.startsWith('/admin/settings')) title = 'Settings | Admin'; - // Master Admin Routes - else if (path.startsWith('/master-admin/dashboard')) title = 'Master Dashboard'; - else if (path.startsWith('/master-admin/admin-requests')) title = 'Pending Requests | Master Admin'; - else if (path.startsWith('/master-admin/companies')) title = 'Companies | Master Admin'; - else if (path.startsWith('/master-admin/all-admins')) title = 'All Admins | Master Admin'; - else if (path.startsWith('/master-admin/bug-reports')) title = 'System Bug Radar | Master Admin'; - // User Routes - else if (path.startsWith('/ticket/')) title = 'Ticket Detail'; - else if (path.startsWith('/ai-understanding')) title = 'AI Understanding'; - else if (path.startsWith('/ai-processing')) title = 'AI Processing'; - else if (path === '/dashboard') title = 'User Dashboard'; - else if (path === '/create-ticket') title = 'Create Ticket'; - else if (path === '/my-tickets') title = 'My Tickets'; - else if (path === '/profile') title = 'User Profile'; - else if (path === '/notifications') title = 'Notifications'; - else if (path === '/docs') title = 'Documentation'; - else if (path === '/api-reference') title = 'API Reference'; - else if (path === '/changelog') title = 'Changelog'; - else if (path === '/status') title = 'Status'; - else if (path === '/about') title = 'About Us'; - else if (path === '/careers') title = 'Careers'; - else if (path === '/cookie-policy') title = 'Cookie Policy'; - // Public / Lobby Routes - else if (path === '/login') title = 'Login'; - else if (path === '/signup') title = 'Create Account'; - else if (path === '/admin-signup') title = 'Admin Signup'; - else if (path === '/user-lobby') title = 'User Lobby'; - else if (path === '/admin-lobby') title = 'Admin Lobby'; - else if (path === '/') title = 'Welcome'; - - document.title = title === 'HELPDESK.AI' ? title : `${title} | HELPDESK.AI`; - }, [location]); - - return null; -} - -// Scrolls to top on every route change -function ScrollToTop() { - const { pathname } = useLocation(); - useEffect(() => { - window.scrollTo({ top: 0, behavior: 'instant' }); - }, [pathname]); - return null; -} - -function AppLayout() { - const { user, profile } = useAuthStore(); - - // Initialize Global Realtime Notifications Listener useRealtimeNotifications(); - useEffect(() => { - if (!user) return; - const handleFocus = () => { - useTicketStore.persist.rehydrate(); - }; - - window.addEventListener('focus', handleFocus); - return () => window.removeEventListener('focus', handleFocus); - }, [user]); - - // ProtectedRoute handles the redirect to /login if user is not present - // but we still need to handle role-based navigation here return ( - <> - - } /> - } /> - } /> - - {/* --- User Portal --- */} - : - (profile?.role === 'admin' || profile?.role === 'super_admin') ? : - profile?.status === 'pending_approval' ? : - profile?.status === 'rejected' ? : - - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + + + {/* ── Public / Auth routes ─────────────────────────────────────── */} + } /> + } /> + } /> + } /> + } /> + } /> + + {/* ── Lobby routes ─────────────────────────────────────────────── */} + }>} /> + }>} /> + + {/* ── Marketing routes ─────────────────────────────────────────── */} + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + + {/* Feature pages */} + }>} /> + }>} /> + }>} /> + + {/* Legal pages */} + }>} /> + }>} /> + }>} /> + }>} /> + + {/* ── Protected user routes ────────────────────────────────────── */} + }> + }>}> + } /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + - {/* --- Admin Portal (Protected) --- */} + {/* ── Protected admin routes ───────────────────────────────────── */} }> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + }>}> + } /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + + + + {/* ── Master admin routes ──────────────────────────────────────── */} + }> + }>}> + } /> + }>} /> + }>} /> + }>} /> + }>} /> + }>} /> + {/* ── 404 ─────────────────────────────────────────────────────── */} } /> - + ); } - -function App() { - const { initialize } = useAuthStore(); - - useEffect(() => { - initialize(); - }, [initialize]); - - const isDocsSubdomain = window.location.hostname.startsWith('docs.'); - - if (isDocsSubdomain) { - return ( - - - - - - - } /> - } /> - } /> - } /> - } /> - } /> - - - ); - } - +export default function App() { return ( - - + - - {/* Public */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - {/* Feature Pages */} - } /> - } /> - } /> - - {/* Legal Pages */} - } /> - } /> - } /> - - {/* Master Admin Portal */} - } /> - - }> - }> - } /> - } /> - } /> - } /> - } /> - - - - {/* Protected */} - }> - } /> - - ); } - -export default App; - diff --git a/Frontend/src/components/ui/page-skeleton.jsx b/Frontend/src/components/ui/page-skeleton.jsx new file mode 100644 index 00000000..343e0c60 --- /dev/null +++ b/Frontend/src/components/ui/page-skeleton.jsx @@ -0,0 +1,120 @@ +/** + * PageSkeleton — animated placeholder rendered by Suspense boundaries + * while lazy-loaded route chunks are being fetched. + * + * Used as the `fallback` prop of every React.Suspense boundary in App.jsx. + */ + +import React from 'react'; + +function ShimmerBlock({ className = '' }) { + return ( +