Prefetches all main page queries (dashboard, upcoming, todos, reminders, projects, people, locations) in parallel when the app unlocks, so the TanStack Query cache is warm before the user navigates to each tab. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
82 lines
2.7 KiB
TypeScript
82 lines
2.7 KiB
TypeScript
import { useState } from 'react';
|
|
import { Outlet } from 'react-router-dom';
|
|
import { Menu } from 'lucide-react';
|
|
import { useTheme } from '@/hooks/useTheme';
|
|
import { usePrefetch } from '@/hooks/usePrefetch';
|
|
import { AlertsProvider } from '@/hooks/useAlerts';
|
|
import { LockProvider, useLock } from '@/hooks/useLock';
|
|
import { NotificationProvider } from '@/hooks/useNotifications';
|
|
import { Button } from '@/components/ui/button';
|
|
import Sidebar from './Sidebar';
|
|
import AppAmbientBackground from './AppAmbientBackground';
|
|
import LockOverlay from './LockOverlay';
|
|
import NotificationToaster from '@/components/notifications/NotificationToaster';
|
|
|
|
function AppContent({ mobileOpen, setMobileOpen }: {
|
|
mobileOpen: boolean;
|
|
setMobileOpen: (v: boolean) => void;
|
|
}) {
|
|
const { isLocked, isLockResolved } = useLock();
|
|
usePrefetch(isLockResolved && !isLocked);
|
|
const [collapsed, setCollapsed] = useState(() => {
|
|
try { return JSON.parse(localStorage.getItem('umbra-sidebar-collapsed') || 'false'); }
|
|
catch { return false; }
|
|
});
|
|
|
|
// Don't render any content until we know the lock state
|
|
if (!isLockResolved || isLocked) {
|
|
return (
|
|
<>
|
|
<div className="h-dvh bg-background" />
|
|
{isLockResolved && <LockOverlay />}
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="flex h-dvh overflow-hidden bg-background">
|
|
<Sidebar
|
|
collapsed={collapsed}
|
|
onToggle={() => {
|
|
const next = !collapsed;
|
|
setCollapsed(next);
|
|
localStorage.setItem('umbra-sidebar-collapsed', JSON.stringify(next));
|
|
}}
|
|
mobileOpen={mobileOpen}
|
|
onMobileClose={() => setMobileOpen(false)}
|
|
/>
|
|
<div className="flex-1 flex flex-col overflow-hidden relative ambient-glass">
|
|
<AppAmbientBackground />
|
|
{/* Mobile header */}
|
|
<div className="relative z-10 flex md:hidden items-center h-14 border-b bg-card px-4">
|
|
<Button variant="ghost" size="icon" onClick={() => setMobileOpen(true)}>
|
|
<Menu className="h-5 w-5" />
|
|
</Button>
|
|
<h1 className="text-lg font-bold text-accent ml-3">UMBRA</h1>
|
|
</div>
|
|
<main className="relative z-10 flex-1 overflow-y-auto mobile-scale">
|
|
<Outlet />
|
|
</main>
|
|
</div>
|
|
</div>
|
|
<NotificationToaster />
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function AppLayout() {
|
|
useTheme();
|
|
const [mobileOpen, setMobileOpen] = useState(false);
|
|
|
|
return (
|
|
<LockProvider>
|
|
<AlertsProvider>
|
|
<NotificationProvider>
|
|
<AppContent mobileOpen={mobileOpen} setMobileOpen={setMobileOpen} />
|
|
</NotificationProvider>
|
|
</AlertsProvider>
|
|
</LockProvider>
|
|
);
|
|
}
|