Kyle Pope 379cc74387 Add data prefetching to eliminate skeleton flash on tab switch
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>
2026-03-12 22:19:08 +08:00

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>
);
}