import { lazy, Suspense, useEffect } from 'react'; import { getConfig } from './config/env'; import { AppShell } from './components/AppShell'; import { SikshyaDialogProvider } from './components/shared/SikshyaDialogContext'; import { ShellStateProvider, useShellState } from './context/ShellStateContext'; import { AdminRoutingProvider, parseAdminRoute, useAdminRouting } from './lib/adminRouting'; import { applyAdminBrandThemeToRoot, clearAdminBrandThemeFromRoot } from './lib/adminBrandTokens'; import { term } from './lib/terminology'; import type { NavItem } from './types'; const ActivityLogPage = lazy(() => import('./pages/ActivityLogPage').then((m) => ({ default: m.ActivityLogPage })) ); const AddonsPage = lazy(() => import('./pages/AddonsPage').then((m) => ({ default: m.AddonsPage }))); const BundlesPage = lazy(() => import('./pages/BundlesPage').then((m) => ({ default: m.BundlesPage }))); const CalendarPage = lazy(() => import('./pages/CalendarPage').then((m) => ({ default: m.CalendarPage }))); const ContentDripPage = lazy(() => import('./pages/ContentDripPage').then((m) => ({ default: m.ContentDripPage })) ); const ContentPostEditorPage = lazy(() => import('./pages/ContentPostEditorPage').then((m) => ({ default: m.ContentPostEditorPage })) ); const CouponsPage = lazy(() => import('./pages/CouponsPage').then((m) => ({ default: m.CouponsPage }))); const DiscussionsPage = lazy(() => import('./pages/DiscussionsPage').then((m) => ({ default: m.DiscussionsPage })) ); const CourseBuilderPage = lazy(() => import('./pages/CourseBuilderPage').then((m) => ({ default: m.CourseBuilderPage })) ); const CourseCategoriesPage = lazy(() => import('./pages/CourseCategoriesPage').then((m) => ({ default: m.CourseCategoriesPage })) ); const CourseTeamPage = lazy(() => import('./pages/CourseTeamPage').then((m) => ({ default: m.CourseTeamPage })) ); const CoursesPage = lazy(() => import('./pages/CoursesPage').then((m) => ({ default: m.CoursesPage }))); const DashboardPage = lazy(() => import('./pages/DashboardPage').then((m) => ({ default: m.DashboardPage })) ); const EmailMarketingPage = lazy(() => import('./pages/EmailMarketingPage').then((m) => ({ default: m.EmailMarketingPage })) ); const EmailPage = lazy(() => import('./pages/EmailPage').then((m) => ({ default: m.EmailPage }))); const EmailTemplateEditPage = lazy(() => import('./pages/EmailTemplateEditPage').then((m) => ({ default: m.EmailTemplateEditPage })) ); const EnrollmentsPage = lazy(() => import('./pages/EnrollmentsPage').then((m) => ({ default: m.EnrollmentsPage })) ); const GenericPlaceholderPage = lazy(() => import('./pages/GenericPlaceholderPage').then((m) => ({ default: m.GenericPlaceholderPage })) ); const GradebookPage = lazy(() => import('./pages/GradebookPage').then((m) => ({ default: m.GradebookPage })) ); const GradingPage = lazy(() => import('./pages/GradingPage').then((m) => ({ default: m.GradingPage }))); const InstructorApplicationsPage = lazy(() => import('./pages/InstructorApplicationsPage').then((m) => ({ default: m.InstructorApplicationsPage })) ); const IntegrationsPage = lazy(() => import('./pages/IntegrationsPage').then((m) => ({ default: m.IntegrationsPage })) ); const IssuedCertificatesPage = lazy(() => import('./pages/IssuedCertificatesPage').then((m) => ({ default: m.IssuedCertificatesPage })) ); const LicensePage = lazy(() => import('./pages/LicensePage').then((m) => ({ default: m.LicensePage }))); const MarketplacePage = lazy(() => import('./pages/MarketplacePage').then((m) => ({ default: m.MarketplacePage })) ); const OrdersPage = lazy(() => import('./pages/OrdersPage').then((m) => ({ default: m.OrdersPage }))); const OrderDetailsPage = lazy(() => import('./pages/OrderDetailsPage').then((m) => ({ default: m.OrderDetailsPage })) ); const PaymentsPage = lazy(() => import('./pages/PaymentsPage').then((m) => ({ default: m.PaymentsPage }))); const PaymentDetailsPage = lazy(() => import('./pages/PaymentDetailsPage').then((m) => ({ default: m.PaymentDetailsPage })) ); const PrerequisitesPage = lazy(() => import('./pages/PrerequisitesPage').then((m) => ({ default: m.PrerequisitesPage })) ); const ReviewsPage = lazy(() => import('./pages/ReviewsPage').then((m) => ({ default: m.ReviewsPage }))); const ReviewDetailPage = lazy(() => import('./pages/ReviewDetailPage').then((m) => ({ default: m.ReviewDetailPage })) ); const SettingsPage = lazy(() => import('./pages/SettingsPage').then((m) => ({ default: m.SettingsPage }))); const SocialLoginPage = lazy(() => import('./pages/SocialLoginPage').then((m) => ({ default: m.SocialLoginPage })) ); const SubscriptionsProPage = lazy(() => import('./pages/SubscriptionsProPage').then((m) => ({ default: m.SubscriptionsProPage })) ); const WhiteLabelPage = lazy(() => import('./pages/WhiteLabelPage').then((m) => ({ default: m.WhiteLabelPage })) ); const WpEntityListPage = lazy(() => import('./pages/WpEntityListPage').then((m) => ({ default: m.WpEntityListPage })) ); const WpUserListPage = lazy(() => import('./pages/WpUserListPage').then((m) => ({ default: m.WpUserListPage })) ); const hubPages = () => import('./pages/hubs/HubPages'); const BrandingHubPage = lazy(() => hubPages().then((m) => ({ default: m.BrandingHubPage }))); const CertificatesHubPage = lazy(() => hubPages().then((m) => ({ default: m.CertificatesHubPage }))); const ContentLibraryHubPage = lazy(() => hubPages().then((m) => ({ default: m.ContentLibraryHubPage }))); const EmailHubPage = lazy(() => hubPages().then((m) => ({ default: m.EmailHubPage }))); const IntegrationsHubPage = lazy(() => hubPages().then((m) => ({ default: m.IntegrationsHubPage }))); const LearningRulesHubPage = lazy(() => hubPages().then((m) => ({ default: m.LearningRulesHubPage }))); const PeopleHubPage = lazy(() => hubPages().then((m) => ({ default: m.PeopleHubPage }))); const ReportsHubPage = lazy(() => hubPages().then((m) => ({ default: m.ReportsHubPage }))); const SalesHubPage = lazy(() => hubPages().then((m) => ({ default: m.SalesHubPage }))); const ToolsHubPage = lazy(() => hubPages().then((m) => ({ default: m.ToolsHubPage }))); /** Same splash as PHP `ReactAdminView` boot markup — one loader for pre-React + lazy chunks. */ function AdminRouteFallback() { return (
Loading Sikshya…
); } function prefetchAdminChunks(): void { // Reduce sidebar "flicker" by preloading common route chunks shortly after first paint. // This avoids Suspense fallback flashes on each view change in slower environments. const w = window as unknown as { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => void }; const schedule = (cb: () => void) => { if (typeof w.requestIdleCallback === 'function') { w.requestIdleCallback(cb, { timeout: 1200 }); } else { setTimeout(cb, 350); } }; schedule(() => { void import('./pages/DashboardPage'); void import('./pages/CoursesPage'); void import('./pages/SettingsPage'); void import('./pages/OrdersPage'); void import('./pages/OrderDetailsPage'); void import('./pages/PaymentsPage'); void import('./pages/PaymentDetailsPage'); void import('./pages/EnrollmentsPage'); void import('./pages/AddonsPage'); void import('./pages/EmailPage'); }); } function RoutedApp() { const baseConfig = getConfig(); const { route } = useAdminRouting(); const { navigation } = useShellState(); const pageKey = typeof route.page === 'string' && route.page.trim() !== '' ? route.page.trim() : 'dashboard'; const config = { ...baseConfig, page: pageKey, query: route.query ?? {}, navigation }; const page = config.page; const q = (config.query || {}) as Record; const isCertificateBuilder = page === 'edit-content' && String(q.post_type || '').trim() === 'sikshya_certificate'; const platformName = config.branding?.pluginName?.trim() || 'Sikshya'; useEffect(() => { const root = document.getElementById('sikshya-admin-root'); if (!root) { return; } const b = config.branding; if (!b?.topbarBg && !b?.sidebarBg) { clearAdminBrandThemeFromRoot(root); return; } applyAdminBrandThemeToRoot(root, b); return () => { clearAdminBrandThemeFromRoot(root); }; }, [config.branding?.topbarBg, config.branding?.sidebarBg]); const T = { course: term(config, 'course'), courses: term(config, 'courses'), lesson: term(config, 'lesson'), lessons: term(config, 'lessons'), quiz: term(config, 'quiz'), quizzes: term(config, 'quizzes'), assignment: term(config, 'assignment'), assignments: term(config, 'assignments'), chapter: term(config, 'chapter'), chapters: term(config, 'chapters'), student: term(config, 'student'), students: term(config, 'students'), instructor: term(config, 'instructor'), instructors: term(config, 'instructors'), enrollment: term(config, 'enrollment'), enrollments: term(config, 'enrollments'), }; useEffect(() => { prefetchAdminChunks(); }, []); const routes = (() => { switch (page) { case 'dashboard': return ; case 'courses': return ; case 'add-course': return ; case 'bundle-builder': return ; case 'edit-content': return ; case 'lessons': case 'add-lesson': return ( ); case 'quizzes': return ( ); case 'assignments': return ( ); case 'questions': return ( ); case 'chapters': return ( ); case 'certificates': return ( ); case 'issued-certificates': return ; case 'orders': return ; case 'order': return ; case 'coupons': return ; case 'reviews': return ; case 'review': return ; case 'discussions': return ; case 'gradebook': return ; case 'assignment-submissions': return ( ); case 'grading': return ; case 'activity-log': return ; case 'content-drip': return ; case 'subscriptions': return ; case 'course-team': return ; case 'marketplace': return ; case 'bundles': return ; case 'prerequisites': return ; case 'social-login': return ; case 'white-label': return ; case 'crm-automation': return ( ); case 'calendar': return ; /* ---- Tabbed hubs (new sidebar entries that fan out to existing pages). ---- */ case 'content-library': return ; case 'people': return ; case 'certificates-hub': return ; case 'sales': return ; case 'email-hub': case 'email-templates': return ; case 'branding': return ; case 'integrations-hub': return ; case 'learning-rules': return ; case 'course-categories': return ( ); case 'students': return ( ); case 'instructors': return ( ); case 'instructor-applications': return ( ); case 'enrollments': return ; case 'reports': return ; case 'payments': return ; case 'payment': return ; case 'settings': return ; case 'email': return ; case 'email-template-edit': return ; case 'tools': return ; case 'addons': return ; case 'integrations': return ; case 'email-marketing': return ; case 'license': return ; default: return ( ); } })(); const navTitle = (() => { const items = config.navigation as NavItem[]; const walk = (rows: NavItem[] | undefined): string | null => { if (!Array.isArray(rows)) return null; for (const r of rows) { if ((r as any)?.id === page && typeof (r as any)?.label === 'string' && (r as any).label.trim() !== '') { return String((r as any).label).trim(); } const kids = (r as any)?.children as NavItem[] | undefined; const hit = walk(kids); if (hit) return hit; } return null; }; return walk(items); })(); const shellTitle = (() => { if (navTitle) return navTitle; if (page === 'dashboard') return 'Dashboard'; if (page === 'settings') return 'Settings'; if (page === 'courses') return T.courses; if (page === 'add-course') return `${T.course} builder`; if (page === 'bundle-builder') return 'Bundle builder'; if (page === 'edit-content') { const postType = String(q.post_type || '').trim(); const id = Number(q.post_id || q.id || 0) || 0; const label = postType === 'sikshya_certificate' ? 'Certificate' : postType === 'sik_lesson' ? T.lesson : postType === 'sik_quiz' ? T.quiz : postType === 'sik_assignment' ? T.assignment : postType === 'sik_course' ? T.course : postType ? postType : 'Content'; return id > 0 ? `Edit ${label}` : `New ${label}`; } return platformName; })(); // Certificate builder is a full-screen workspace with its own header/actions. // Do not wrap it in the admin shell (no sidebar, no top bar). // No Suspense fallback: PHP skips the Sikshya boot loader for this route; hosts often show their own loader first. if (isCertificateBuilder) { return {routes}; } return ( }>{routes} ); } export default function App() { const baseConfig = getConfig(); // Ensure the initial render respects the URL (deep links / reload). // This also protects us if PHP-provided config.page lags behind the URL. const initial = parseAdminRoute(baseConfig); const normalizedBase = { ...baseConfig, page: initial.page, query: initial.query }; return ( ); }