import { Component, type ReactNode, type ErrorInfo } from 'react'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { Layout } from './components/Layout'; import { PrintersPage } from './pages/PrintersPage'; import { ArchivesPage } from './pages/ArchivesPage'; import { QueuePage } from './pages/QueuePage'; import { StatsPage } from './pages/StatsPage'; import { SettingsPage } from './pages/SettingsPage'; import { ProfilesPage } from './pages/ProfilesPage'; import { MaintenancePage } from './pages/MaintenancePage'; import { ProjectsPage } from './pages/ProjectsPage'; import { ProjectDetailPage } from './pages/ProjectDetailPage'; import { FileManagerPage } from './pages/FileManagerPage'; import { LibraryTrashPage } from './pages/LibraryTrashPage'; import { CameraPage } from './pages/CameraPage'; import { StreamOverlayPage } from './pages/StreamOverlayPage'; import { ExternalLinkPage } from './pages/ExternalLinkPage'; import { GroupEditPage } from './pages/GroupEditPage'; import InventoryPage from './pages/InventoryPage'; import { MakerworldPage } from './pages/MakerworldPage'; import { SystemInfoPage } from './pages/SystemInfoPage'; import { LoginPage } from './pages/LoginPage'; import { SetupPage } from './pages/SetupPage'; import { NotificationsPage } from './pages/NotificationsPage'; import { GCodeViewerPage } from './pages/GCodeViewerPage'; import { useWebSocket } from './hooks/useWebSocket'; import { useStreamTokenSync } from './hooks/useCameraStreamToken'; import { ThemeProvider } from './contexts/ThemeContext'; import { ToastProvider } from './contexts/ToastContext'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import { ColorCatalogProvider } from './contexts/ColorCatalogContext'; import { SpoolBuddyLayout } from './components/spoolbuddy/SpoolBuddyLayout'; import { SpoolBuddyDashboard } from './pages/spoolbuddy/SpoolBuddyDashboard'; import { SpoolBuddyAmsPage } from './pages/spoolbuddy/SpoolBuddyAmsPage'; import { SpoolBuddySettingsPage } from './pages/spoolbuddy/SpoolBuddySettingsPage'; import { SpoolBuddyCalibrationPage } from './pages/spoolbuddy/SpoolBuddyCalibrationPage'; import { SpoolBuddyWriteTagPage } from './pages/spoolbuddy/SpoolBuddyWriteTagPage'; import { SpoolBuddyInventoryPage } from './pages/spoolbuddy/SpoolBuddyInventoryPage'; class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null; errorInfo: ErrorInfo | null }> { state = { error: null as Error | null, errorInfo: null as ErrorInfo | null }; static getDerivedStateFromError(error: Error) { return { error }; } componentDidCatch(error: Error, errorInfo: ErrorInfo) { this.setState({ errorInfo }); console.error('React crash:', error, errorInfo); } render() { if (this.state.error) { return (

UI Crash

{this.state.error.message}
            {this.state.error.stack}
          
); } return this.props.children; } } const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60, retry: 1, }, }, }); function StreamTokenSync() { useStreamTokenSync(); return null; } function WebSocketProvider({ children }: { children: React.ReactNode }) { useWebSocket(); return <>{children}; } function ProtectedRoute({ children }: { children: React.ReactNode }) { const { authEnabled, loading, user } = useAuth(); if (loading) { return
Loading...
; } if (authEnabled && !user) { return ; } return <>{children}; } function PermissionRoute({ permission, children }: { permission: string; children: React.ReactNode }) { // Permission-gated route: any user with the given permission can enter, not // just admins. Individual components below this guard apply their own // per-action permission checks. Used for pages where delegation is supported // (e.g. settings:read grants read-only access to Settings; specific tabs // require their own permissions like users:read, groups:update, etc.). const { authEnabled, loading, user, hasPermission } = useAuth(); if (loading) { return
Loading...
; } // Auth disabled → open access (backward compatibility) if (!authEnabled) { return <>{children}; } if (!user) { return ; } if (!hasPermission(permission as Parameters[0])) { return ; } return <>{children}; } function SetupRoute({ children }: { children: React.ReactNode }) { const { authEnabled, loading } = useAuth(); if (loading) { return
Loading...
; } // If auth is already enabled, redirect to login // Otherwise, allow access to setup page (even if setup was completed before) // This allows users to enable auth later if they skipped it during initial setup if (authEnabled) { return ; } return <>{children}; } function App() { return ( {/* Setup page - only accessible if auth not enabled */} } /> {/* Login page */} } /> {/* Camera page - standalone, no layout, no WebSocket (doesn't need real-time updates) */} } /> {/* Stream overlay page - standalone for OBS/streaming embeds, no auth required */} } /> {/* SpoolBuddy kiosk UI */} }> } /> } /> } /> } /> } /> } /> {/* Main app with WebSocket for real-time updates */} }> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> ); } export default App;