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 && (
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') && (
)}
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
} />
);
}
function RoleHome({ isOnline }) {
const { user } = useAuth();
return user?.role === 'peer' ? : ;
}
function App() {
return (
);
}
export default App;