import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { useEffect, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Bell, BookOpen, ChartBar, CheckCircle2, Container, Database, FolderKanban, LayoutTemplate, LogOut, Moon, RefreshCw, Search, Settings, Sun, UploadCloud, Users, } from 'lucide-react'; import { getUpgradeStatus, listNotifications, markAllNotificationsRead, markNotificationRead, pullUpgradeImage, } from '@/lib/api-client'; import { signOutAuthSession } from '@/lib/auth-client'; import { useAuthSession } from '@/lib/use-auth-session'; import { useToast } from '@/shared/components'; const navItems = [ { label: 'Projects', href: '/projects', icon: FolderKanban }, { label: 'Templates', href: '/templates', icon: LayoutTemplate }, { label: 'Databases', href: '/databases', icon: Database }, { label: 'Usage', href: '/usage', icon: ChartBar }, { label: 'People', href: '/people', icon: Users }, { label: 'Settings', href: '/settings', icon: Settings }, { label: 'Docs', href: '/docs', icon: BookOpen }, ]; type ThemeMode = 'dark' | 'light'; function getInitialTheme(): ThemeMode { if (typeof window === 'undefined') { return 'dark'; } return localStorage.getItem('containr.theme') === 'light' ? 'light' : 'dark'; } export function PlatformShell() { const location = useLocation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { showToast } = useToast(); const isDemoMode = new URLSearchParams(location.search).get('demo') === '1'; const href = (target: string) => (isDemoMode ? `${target}?demo=1` : target); const [theme, setTheme] = useState(() => getInitialTheme()); const [notificationsOpen, setNotificationsOpen] = useState(false); const sessionQuery = useAuthSession({ enabled: !isDemoMode }); const upgradeQuery = useQuery({ queryKey: ['upgrade-status'], queryFn: getUpgradeStatus, enabled: !isDemoMode, }); const notificationsQuery = useQuery({ queryKey: ['shell-notifications'], queryFn: () => listNotifications(20), enabled: !isDemoMode, refetchInterval: 30_000, }); const markReadMutation = useMutation({ mutationFn: (id: string) => markNotificationRead(id), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['shell-notifications'] }), }); const markAllReadMutation = useMutation({ mutationFn: markAllNotificationsRead, onSuccess: () => queryClient.invalidateQueries({ queryKey: ['shell-notifications'] }), }); useEffect(() => { document.documentElement.dataset.theme = theme; localStorage.setItem('containr.theme', theme); }, [theme]); const pullMutation = useMutation({ mutationFn: pullUpgradeImage, onSuccess: (status) => { queryClient.invalidateQueries({ queryKey: ['upgrade-status'] }); showToast('success', 'Upgrade image pulled', status.digest || status.imageRef || status.message); }, onError: (error) => { showToast('error', 'Upgrade failed', error instanceof Error ? error.message : 'Unable to pull image'); }, }); const notifications = notificationsQuery.data?.notifications ?? []; const unreadCount = notificationsQuery.data?.unread ?? 0; const signOut = async () => { try { await signOutAuthSession(); } finally { queryClient.clear(); navigate('/auth/sign-in', { replace: true }); } }; const isActiveRoute = (itemHref: string) => location.pathname === itemHref || location.pathname.startsWith(`${itemHref}/`); const userName = sessionQuery.data?.user.name ?? 'Account'; const userEmail = sessionQuery.data?.user.email ?? 'Local session'; const initials = userName .split(' ') .map((part) => part[0]) .join('') .slice(0, 2) .toUpperCase() || 'C'; return (
containr
{isDemoMode ? 'demo' : 'nominal'} node local
env:production
{notificationsOpen ? (

Notifications

{unreadCount > 0 ? `${unreadCount} unread` : 'All caught up'}

{unreadCount > 0 ? ( ) : null}
{notifications.length > 0 ? ( notifications.map((item) => ( )) ) : (
No notifications yet
)}
) : null}
Containr
); }