import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; import { useState, useEffect, useCallback } from 'react'; import { Home, Users, Network, Shield, Mail, Calendar as CalendarIcon, FolderOpen, Activity, Wifi, Server, Key, Package2, Settings as SettingsIcon, Link2, RefreshCw, AlertTriangle, User, } from 'lucide-react'; import { healthAPI, cellAPI } from './services/api'; import { ConfigProvider } from './contexts/ConfigContext'; import { DraftConfigProvider, useDraftConfig } from './contexts/DraftConfigContext'; import { AuthProvider, useAuth } from './contexts/AuthContext'; import PrivateRoute from './components/PrivateRoute'; import Sidebar from './components/Sidebar'; import Dashboard from './pages/Dashboard'; import Peers from './pages/Peers'; import NetworkServices from './pages/NetworkServices'; import WireGuard from './pages/WireGuard'; import Email from './pages/Email'; import Calendar from './pages/Calendar'; import Files from './pages/Files'; import Routing from './pages/Routing'; import Logs from './pages/Logs'; import Settings from './pages/Settings'; import Vault from './pages/Vault'; import ContainerDashboard from './components/ContainerDashboard'; import CellNetwork from './pages/CellNetwork'; import Login from './pages/Login'; import AccountSettings from './pages/AccountSettings'; import PeerDashboard from './pages/PeerDashboard'; import MyServices from './pages/MyServices'; function PendingRestartBanner({ pending, onApply, onCancel }) { const [confirming, setConfirming] = useState(false); const [applying, setApplying] = useState(false); const [cancelling, setCancelling] = useState(false); const handleApply = async () => { setApplying(true); setConfirming(false); try { await onApply(); } finally { setApplying(false); } }; const handleCancel = async () => { setCancelling(true); try { await onCancel(); } finally { setCancelling(false); } }; return ( <>

Configuration changes pending — containers need restart

{pending.changes?.length > 0 && (
    {pending.changes.map((c, i) =>
  • {c}
  • )}
)}
{confirming && (

Restart containers?

All containers will be restarted to apply the new configuration. The UI will be briefly unavailable during the restart.

)} ); } // AppCore is the real application — it consumes DraftConfigContext and must // be rendered inside DraftConfigProvider (see App below). function AppCore() { const [isOnline, setIsOnline] = useState(false); const [isLoading, setIsLoading] = useState(true); const [pending, setPending] = useState({ needs_restart: false, changes: [] }); const checkHealth = useCallback(async () => { try { await healthAPI.check(); setIsOnline(true); } catch { setIsOnline(false); } finally { setIsLoading(false); } }, []); const checkPending = useCallback(async () => { try { const res = await cellAPI.getPending(); setPending(res.data); } catch { // ignore — not critical } }, []); useEffect(() => { checkHealth(); checkPending(); const healthInterval = setInterval(checkHealth, 5000); const pendingInterval = setInterval(checkPending, 5000); return () => { clearInterval(healthInterval); clearInterval(pendingInterval); }; }, [checkHealth, checkPending]); const [applyStatus, setApplyStatus] = useState(null); // null | 'saving' | 'restarting' | 'done' | 'timeout' | 'error' const [applyError, setApplyError] = useState(''); const { flushAll, hasDirty } = useDraftConfig(); const handleApply = useCallback(async () => { setApplyError(''); if (hasDirty()) { setApplyStatus('saving'); try { await flushAll(); } catch { // flush errors are shown via Settings toasts; continue with apply } } try { await cellAPI.applyPending(); } catch (err) { setApplyStatus('error'); setApplyError(err?.response?.data?.error || 'Apply request failed'); setTimeout(() => setApplyStatus(null), 6000); return; } setPending({ needs_restart: false, changes: [] }); setApplyStatus('restarting'); // Poll health until API responds again (max 45 s; it may briefly drop if cell-api restarts) const deadline = Date.now() + 45000; while (Date.now() < deadline) { await new Promise(r => setTimeout(r, 2000)); try { await healthAPI.check(); setIsOnline(true); setApplyStatus('done'); setTimeout(() => setApplyStatus(null), 4000); return; } catch { setIsOnline(false); } } setApplyStatus('timeout'); setApplyError('Containers may still be starting — check docker logs if services are unavailable'); setTimeout(() => setApplyStatus(null), 8000); }, [flushAll, hasDirty]); const handleCancel = useCallback(async () => { await cellAPI.cancelPending(); setPending({ needs_restart: false, changes: [] }); window.dispatchEvent(new CustomEvent('pic-config-discarded')); }, []); const adminNavigation = [ { name: 'Dashboard', href: '/', icon: Home }, { name: 'Peers', href: '/peers', icon: Users }, { name: 'Network Services', href: '/network', icon: Network }, { name: 'WireGuard', href: '/wireguard', icon: Shield }, { name: 'Email', href: '/email', icon: Mail }, { name: 'Calendar', href: '/calendar', icon: CalendarIcon }, { name: 'Files', href: '/files', icon: FolderOpen }, { name: 'Routing', href: '/routing', icon: Wifi }, { name: 'Vault', href: '/vault', icon: Key }, { name: 'Containers', href: '/containers', icon: Package2 }, { name: 'Cell Network', href: '/cell-network', icon: Link2 }, { name: 'Logs', href: '/logs', icon: Activity }, { name: 'Settings', href: '/settings', icon: SettingsIcon }, { name: 'Account', href: '/account', icon: User }, ]; const peerNavigation = [ { name: 'Dashboard', href: '/', icon: Home }, { name: 'My Services', href: '/my-services', icon: FolderOpen }, { name: 'Account', href: '/account', icon: User }, ]; const { user } = useAuth(); const navigation = user?.role === 'peer' ? peerNavigation : adminNavigation; if (isLoading) { return (

Connecting to Personal Internet Cell...

); } return ( } />
{!isOnline && (

Backend Unavailable

Unable to connect to the Personal Internet Cell backend. Please ensure the API server is running on port 3000.

)} {isOnline && pending.needs_restart && !applyStatus && ( )} {applyStatus === 'saving' && (
Saving settings…
)} {applyStatus === 'restarting' && (
Restarting containers — please wait…
)} {applyStatus === 'done' && (
Containers restarted successfully
)} {(applyStatus === 'timeout' || applyStatus === 'error') && (
{applyError}
)} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />
} />
); } function RoleHome({ isOnline }) { const { user } = useAuth(); return user?.role === 'peer' ? : ; } function App() { return ( ); } export default App;