import { createBrowserRouter, Navigate } from 'react-router-dom' import { AssistantSessionRedirect } from '@/components/routing/AssistantSessionRedirect' import * as Sentry from '@sentry/react' import { Suspense } from 'react' import { AppLayout, ProtectedRoute } from '@/components/layout' import { RouteError } from '@/components/common/RouteError' import { ErrorBoundary } from '@/components/common/ErrorBoundary' import { PageLoader } from '@/components/common/PageLoader' import { lazyWithRetry } from '@/lib/lazyWithRetry' import { useAuthStore } from '@/store/authStore' import { L1RouteGuard } from '@/components/layout/L1RouteGuard' const sentryCreateBrowserRouter = Sentry.wrapCreateBrowserRouterV7(createBrowserRouter) import { LoginPage, RegisterPage, } from '@/pages' // Public pages const LandingPage = lazyWithRetry(() => import('@/pages/LandingPage')) const PublicTemplatesPage = lazyWithRetry(() => import('@/pages/PublicTemplatesPage')) const SharedSessionPage = lazyWithRetry(() => import('@/pages/SharedSessionPage')) const SurveyPage = lazyWithRetry(() => import('@/pages/SurveyPage')) const SurveyThankYouPage = lazyWithRetry(() => import('@/pages/SurveyThankYouPage')) const PrivacyPage = lazyWithRetry(() => import('@/pages/PrivacyPage')) const TermsPage = lazyWithRetry(() => import('@/pages/TermsPage')) const PricingPage = lazyWithRetry(() => import('@/pages/PricingPage')) const ContactSalesPage = lazyWithRetry(() => import('@/pages/ContactSalesPage')) const ContactPage = lazyWithRetry(() => import('@/pages/ContactPage')) const PoliciesPage = lazyWithRetry(() => import('@/pages/PoliciesPage')) const PromotionsPage = lazyWithRetry(() => import('@/pages/PromotionsPage')) // Standalone auth pages const VerifyEmailPage = lazyWithRetry(() => import('@/pages/VerifyEmailPage')) const OAuthCallbackPage = lazyWithRetry(() => import('@/pages/OAuthCallbackPage')) const AcceptInvitePage = lazyWithRetry(() => import('@/pages/AcceptInvitePage')) const ChangePasswordPage = lazyWithRetry(() => import('@/pages/ChangePasswordPage')) const ForgotPasswordPage = lazyWithRetry(() => import('@/pages/ForgotPasswordPage')) const ResetPasswordPage = lazyWithRetry(() => import('@/pages/ResetPasswordPage')) // Lazy load heavy pages for code splitting const QuickStartPage = lazyWithRetry(() => import('@/pages/QuickStartPage')) const TreeLibraryPage = lazyWithRetry(() => import('@/pages/TreeLibraryPage')) const MyTreesPage = lazyWithRetry(() => import('@/pages/MyTreesPage')) const TreeNavigationPage = lazyWithRetry(() => import('@/pages/TreeNavigationPage')) const TreeEditorPage = lazyWithRetry(() => import('@/pages/TreeEditorPage')) const ProceduralEditorPage = lazyWithRetry(() => import('@/pages/ProceduralEditorPage')) const ProceduralNavigationPage = lazyWithRetry(() => import('@/pages/ProceduralNavigationPage')) const MaintenanceFlowDetailPage = lazyWithRetry(() => import('@/pages/MaintenanceFlowDetailPage')) const BatchStatusPage = lazyWithRetry(() => import('@/pages/BatchStatusPage')) const SessionHistoryPage = lazyWithRetry(() => import('@/pages/SessionHistoryPage')) const SessionDetailPage = lazyWithRetry(() => import('@/pages/SessionDetailPage')) const MySharesPage = lazyWithRetry(() => import('@/pages/MySharesPage')) const TeamAnalyticsPage = lazyWithRetry(() => import('@/pages/TeamAnalyticsPage')) const MyAnalyticsPage = lazyWithRetry(() => import('@/pages/MyAnalyticsPage')) const FeedbackPage = lazyWithRetry(() => import('@/pages/FeedbackPage')) const StepLibraryPage = lazyWithRetry(() => import('@/pages/StepLibraryPage')) const ScriptLibraryPage = lazyWithRetry(() => import('@/pages/ScriptLibraryPage')) const ScriptManagePage = lazyWithRetry(() => import('@/pages/ScriptManagePage')) const AssistantChatPage = lazyWithRetry(() => import('@/pages/AssistantChatPage')) const FlowAssistPage = lazyWithRetry(() => import('@/pages/FlowAssistPage')) // FlowPilotSessionPage (the old guided-mode surface) was removed from active // routing in Phase 1 of the FlowPilot migration. The file is retained on disk // for reference but not mounted — the unified chat-primary surface now serves // /pilot. Delete the file when nothing in the tree references it anymore. const EscalationQueuePage = lazyWithRetry(() => import('@/pages/EscalationQueuePage')) const ReviewQueuePage = lazyWithRetry(() => import('@/pages/ReviewQueuePage')) const FlowPilotAnalyticsPage = lazyWithRetry(() => import('@/pages/FlowPilotAnalyticsPage')) const ScriptBuilderPage = lazyWithRetry(() => import('@/pages/ScriptBuilderPage')) const KBAcceleratorPage = lazyWithRetry(() => import('@/pages/KBAcceleratorPage')) const SessionQueuePage = lazyWithRetry(() => import('@/pages/SessionQueuePage')) const TicketsPage = lazyWithRetry(() => import('@/pages/TicketsPage')) const DevBranchingPage = lazyWithRetry(() => import('@/pages/DevBranchingPage')) const GuidesHubPage = lazyWithRetry(() => import('@/pages/GuidesHubPage')) const GuideDetailPage = lazyWithRetry(() => import('@/pages/GuideDetailPage')) const AccountSettingsPage = lazyWithRetry(() => import('@/pages/AccountSettingsPage')) // Welcome wizard (Phase 2) const WelcomeRouter = lazyWithRetry(() => import('@/pages/welcome/WelcomeRouter')) const WelcomeStep1 = lazyWithRetry(() => import('@/pages/welcome/WelcomeStep1')) const WelcomeStep2 = lazyWithRetry(() => import('@/pages/welcome/WelcomeStep2')) const WelcomeStep3 = lazyWithRetry(() => import('@/pages/welcome/WelcomeStep3')) const NetworkDiagramsPage = lazyWithRetry(() => import('@/pages/NetworkDiagrams')) const DiagramEditorPage = lazyWithRetry(() => import('@/pages/NetworkDiagrams/DiagramEditor')) // Admin pages const AdminLayout = lazyWithRetry(() => import('@/components/admin/AdminLayout')) const AdminDashboardPage = lazyWithRetry(() => import('@/pages/admin/DashboardPage')) const AdminAccountsPage = lazyWithRetry(() => import('@/pages/admin/AccountsPage')) const AdminAccountDetailPage = lazyWithRetry(() => import('@/pages/admin/AccountDetailPage')) const AdminUserDetailPage = lazyWithRetry(() => import('@/pages/admin/UserDetailPage')) const AdminInviteCodesPage = lazyWithRetry(() => import('@/pages/admin/InviteCodesPage')) const AdminAuditLogsPage = lazyWithRetry(() => import('@/pages/admin/AuditLogsPage')) const AdminPlanLimitsPage = lazyWithRetry(() => import('@/pages/admin/PlanLimitsPage')) const AdminFeatureFlagsPage = lazyWithRetry(() => import('@/pages/admin/FeatureFlagsPage')) const AdminSettingsPage = lazyWithRetry(() => import('@/pages/admin/SettingsPage')) const AdminGlobalCategoriesPage = lazyWithRetry(() => import('@/pages/admin/GlobalCategoriesPage')) const AdminSurveyInvitesPage = lazyWithRetry(() => import('@/pages/admin/SurveyInvitesPage')) const AdminSurveyResponsesPage = lazyWithRetry(() => import('@/pages/admin/SurveyResponsesPage')) const AdminGalleryManagementPage = lazyWithRetry(() => import('@/pages/admin/GalleryManagementPage')) // L1 workspace pages const L1Dashboard = lazyWithRetry(() => import('@/pages/l1/L1Dashboard')) const L1WalkPage = lazyWithRetry(() => import('@/pages/l1/L1WalkPage')) const L1DraftsPage = lazyWithRetry(() => import('@/pages/l1/L1DraftsPage')) const L1TicketsPage = lazyWithRetry(() => import('@/pages/l1/L1TicketsPage')) // Account pages const AccountLayout = lazyWithRetry(() => import('@/components/account/AccountLayout')) const ProfileSettingsPage = lazyWithRetry(() => import('@/pages/account/ProfileSettingsPage')) const TeamCategoriesPage = lazyWithRetry(() => import('@/pages/account/TeamCategoriesPage')) const TargetListsPage = lazyWithRetry(() => import('@/pages/account/TargetListsPage')) const ChatRetentionSettingsPage = lazyWithRetry(() => import('@/pages/account/ChatRetentionSettingsPage')) const AccountSecuritySettingsPage = lazyWithRetry(() => import('@/pages/account/AccountSecuritySettingsPage')) const IntegrationsPage = lazyWithRetry(() => import('@/pages/account/IntegrationsPage')) const BrandingSettingsPage = lazyWithRetry(() => import('@/pages/account/BrandingSettingsPage')) const BillingPage = lazyWithRetry(() => import('@/pages/account/BillingPage')) const SelectPlanPage = lazyWithRetry(() => import('@/pages/account/SelectPlanPage')) /** Wraps a lazy-loaded page with Suspense + ErrorBoundary */ function page(Component: React.LazyExoticComponent) { return ( }> ) } /** * Public `/` wrapper — sends authenticated users to /home before LandingPage * mounts, so they never see marketing-frame flicker. */ // eslint-disable-next-line react-refresh/only-export-components -- router.tsx exports a router instance, not a component function PublicLanding() { const isAuthed = useAuthStore((s) => s.isAuthenticated) if (isAuthed) return return page(LandingPage) } export const router = sentryCreateBrowserRouter([ { path: '/', element: , errorElement: , }, // Stale-bookmark redirect — keep one release, delete in a follow-up. { path: '/landing', element: , errorElement: , }, { path: '/templates', element: page(PublicTemplatesPage), errorElement: , }, { path: '/privacy', element: page(PrivacyPage), errorElement: , }, { path: '/terms', element: page(TermsPage), errorElement: , }, { path: '/pricing', element: page(PricingPage), errorElement: , }, { path: '/contact-sales', element: page(ContactSalesPage), errorElement: , }, { path: '/contact', element: page(ContactPage), errorElement: , }, { path: '/policies', element: page(PoliciesPage), errorElement: , }, { path: '/promotions', element: page(PromotionsPage), errorElement: , }, { path: '/login', element: , errorElement: , }, { path: '/register', element: , errorElement: , }, { path: '/forgot-password', element: page(ForgotPasswordPage), errorElement: , }, { path: '/reset-password', element: page(ResetPasswordPage), errorElement: , }, { path: '/verify-email', element: page(VerifyEmailPage), errorElement: , }, { path: '/accept-invite', element: page(AcceptInvitePage), errorElement: , }, { path: '/auth/google/callback', element: page(OAuthCallbackPage), errorElement: , }, { path: '/auth/microsoft/callback', element: page(OAuthCallbackPage), errorElement: , }, { path: '/survey', element: page(SurveyPage), errorElement: , }, { path: '/survey/thank-you', element: page(SurveyThankYouPage), errorElement: , }, { path: '/share/:shareToken', element: page(SharedSessionPage), errorElement: , }, { path: '/change-password', element: ( {page(ChangePasswordPage)} ), errorElement: , }, { element: ( ), errorElement: , children: [ { path: '/home', element: page(QuickStartPage) }, { path: '/trees', element: page(TreeLibraryPage) }, { path: '/my-trees', element: page(MyTreesPage) }, { path: '/trees/new', element: page(TreeEditorPage) }, { path: '/trees/:id/edit', element: page(TreeEditorPage) }, { path: '/flows/new', element: page(ProceduralEditorPage) }, { path: '/flows/:id/edit', element: page(ProceduralEditorPage) }, { path: '/flows/:id/navigate', element: page(ProceduralNavigationPage) }, { path: '/flows/:id/maintenance', element: page(MaintenanceFlowDetailPage) }, { path: '/flows/:id/batches/:batchId', element: page(BatchStatusPage) }, { path: '/trees/:id/navigate', element: page(TreeNavigationPage) }, { path: '/sessions', element: page(SessionHistoryPage) }, { path: '/sessions/:id', element: page(SessionDetailPage) }, { path: '/tickets', element: page(TicketsPage) }, { path: '/shares', element: page(MySharesPage) }, { path: '/analytics', element: page(TeamAnalyticsPage) }, { path: '/analytics/me', element: page(MyAnalyticsPage) }, { path: '/feedback', element: page(FeedbackPage) }, { path: '/step-library', element: page(StepLibraryPage) }, { path: '/scripts', element: page(ScriptLibraryPage) }, { path: '/scripts/manage', element: page(ScriptManagePage) }, { path: '/script-builder', element: page(ScriptBuilderPage) }, { path: '/network-diagrams', element: page(NetworkDiagramsPage) }, { path: '/network-diagrams/new', element: page(DiagramEditorPage) }, { path: '/network-diagrams/:id', element: page(DiagramEditorPage) }, { path: '/kb-accelerator', element: page(KBAcceleratorPage) }, // Phase 1 — FlowPilot migration. The unified chat-primary surface lives at // /pilot; /assistant permanently redirects. FlowPilotSessionPage (old // guided surface) is no longer mounted. { path: '/pilot', element: page(AssistantChatPage) }, { path: '/pilot/:sessionId', element: page(AssistantChatPage) }, { path: '/assistant', element: }, { path: '/assistant/:sessionId', element: }, { path: '/flow-assist', element: page(FlowAssistPage) }, { path: '/escalations', element: page(EscalationQueuePage) }, { path: '/queue', element: page(SessionQueuePage) }, { path: '/review-queue', element: page(ReviewQueuePage) }, { path: '/analytics/flowpilot', element: page(FlowPilotAnalyticsPage) }, { path: '/dev/branching', element: page(DevBranchingPage) }, { path: '/guides', element: page(GuidesHubPage) }, { path: '/guides/:slug', element: page(GuideDetailPage) }, // Welcome wizard (Phase 2). Mounted inside AppLayout so the email- // verification banner persists above each step. { path: '/welcome', element: page(WelcomeRouter) }, { path: '/welcome/step-1', element: page(WelcomeStep1) }, { path: '/welcome/step-2', element: page(WelcomeStep2) }, { path: '/welcome/step-3', element: page(WelcomeStep3) }, // L1 workspace routes — gated by canUseL1Surface { path: '/l1', element: {page(L1Dashboard)} }, { path: '/l1/walk/:sessionId', element: {page(L1WalkPage)} }, { path: '/l1/drafts', element: {page(L1DraftsPage)} }, { path: '/l1/tickets', element: {page(L1TicketsPage)} }, // Admin routes { path: '/admin', element: ( }> ), children: [ { index: true, element: page(AdminDashboardPage) }, { path: 'accounts', element: page(AdminAccountsPage) }, { path: 'accounts/:accountId', element: page(AdminAccountDetailPage) }, { path: 'users', element: page(AdminAccountsPage) }, { path: 'users/:userId', element: page(AdminUserDetailPage) }, { path: 'invite-codes', element: page(AdminInviteCodesPage) }, { path: 'audit-logs', element: page(AdminAuditLogsPage) }, { path: 'plan-limits', element: page(AdminPlanLimitsPage) }, { path: 'feature-flags', element: page(AdminFeatureFlagsPage) }, { path: 'settings', element: page(AdminSettingsPage) }, { path: 'categories', element: page(AdminGlobalCategoriesPage) }, { path: 'survey-invites', element: page(AdminSurveyInvitesPage) }, { path: 'survey-responses', element: page(AdminSurveyResponsesPage) }, { path: 'gallery', element: page(AdminGalleryManagementPage) }, ], }, // Account routes { path: '/account', element: ( }> ), children: [ { index: true, element: page(AccountSettingsPage) }, { path: 'profile', element: page(ProfileSettingsPage) }, { path: 'categories', element: ( {page(TeamCategoriesPage)} ), }, { path: 'chat-retention', element: ( {page(ChatRetentionSettingsPage)} ), }, { path: 'security', element: ( {page(AccountSecuritySettingsPage)} ), }, { path: 'target-lists', element: page(TargetListsPage) }, { path: 'integrations', element: ( {page(IntegrationsPage)} ), }, { path: 'branding', element: ( {page(BrandingSettingsPage)} ), }, { path: 'billing', element: page(BillingPage) }, { path: 'billing/select-plan', element: page(SelectPlanPage) }, ], }, ], }, ]) export default router