import { Check, Grid2X2, LayoutDashboard, Settings as SettingsIcon, } from "lucide-react"; import { useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react"; import type { DashboardPayload } from "../shared/contracts"; import { api } from "./api"; import { persistSortOrder, type SortableEntity } from "./persist-sort-order"; import { Dashboard, DashboardSkeleton } from "./components/Dashboard"; import { Settings, type SettingsSection } from "./components/Settings"; import { Brand } from "./components/Brand"; type Page = "dashboard" | "settings"; export function App() { const [page, setPage] = useState("dashboard"); const [settingsSection, setSettingsSection] = useState("apps"); const [data, setData] = useState(null); const [error, setError] = useState(null); const [syncing, setSyncing] = useState(false); const [toast, setToast] = useState(null); const toastTimerRef = useRef(undefined); const syncInFlightRef = useRef | null>(null); const theme = data?.preferences.theme; const accent = data?.preferences.accent; const density = data?.preferences.density; const contentWidth = data?.preferences.contentWidth; const backgroundStyle = data?.preferences.backgroundStyle; const reducedMotion = data?.preferences.reducedMotion; const pollingIntervalMs = data ? Math.min( data.preferences.pollingIntervalMs, ...data.apps .filter((app) => app.widgetEnabled) .map((app) => app.widgetRefreshIntervalMs), ) : undefined; const load = useCallback(async () => { try { const payload = await api.dashboard(); setData(payload); setError(null); } catch (loadError) { setError( loadError instanceof Error ? loadError.message : "DockDeck is niet bereikbaar.", ); } }, []); const sync = useCallback(async () => { if (syncInFlightRef.current) return syncInFlightRef.current; const request = (async () => { setSyncing(true); try { await api.sync(); await load(); } catch (syncError) { setError( syncError instanceof Error ? syncError.message : "De actuele status kon niet worden vernieuwd.", ); } finally { setSyncing(false); syncInFlightRef.current = null; } })(); syncInFlightRef.current = request; return request; }, [load]); useEffect(() => { void load().then(sync); }, [load, sync]); useLayoutEffect(() => { if (!theme || !accent || !density || !contentWidth || !backgroundStyle) return; const root = document.documentElement; root.dataset.theme = theme; root.dataset.accent = accent; root.dataset.density = density; root.dataset.contentWidth = contentWidth; root.dataset.background = backgroundStyle; root.dataset.reducedMotion = String(Boolean(reducedMotion)); }, [accent, backgroundStyle, contentWidth, density, reducedMotion, theme]); useEffect(() => { if (!pollingIntervalMs) return; const refreshWhenVisible = () => { if (document.visibilityState === "visible") void sync(); }; const timer = window.setInterval(refreshWhenVisible, pollingIntervalMs); document.addEventListener("visibilitychange", refreshWhenVisible); window.addEventListener("online", refreshWhenVisible); return () => { window.clearInterval(timer); document.removeEventListener("visibilitychange", refreshWhenVisible); window.removeEventListener("online", refreshWhenVisible); }; }, [pollingIntervalMs, sync]); const notify = (message: string) => { window.clearTimeout(toastTimerRef.current); setToast(message); toastTimerRef.current = window.setTimeout(() => setToast(null), 2400); }; const reorderDashboardItems = async ( entity: SortableEntity, items: readonly { id: string }[], ) => { try { await persistSortOrder(entity, items); await load(); notify( entity === "app" ? "Appvolgorde bijgewerkt" : entity === "category" ? "Categorievolgorde bijgewerkt" : "Favorietenvolgorde bijgewerkt", ); return true; } catch (reorderError) { await load(); notify( reorderError instanceof Error ? reorderError.message : "De nieuwe volgorde kon niet worden opgeslagen.", ); return false; } }; useEffect( () => () => { window.clearTimeout(toastTimerRef.current); }, [], ); if (!data && !error) return ; if (!data && error) return (

VERBINDING VERBROKEN

DockDeck is even niet bereikbaar.

{error}

); if (!data) return null; return (
Naar inhoud {page === "dashboard" ? ( { setSettingsSection(section); setPage("settings"); }} /> ) : ( setPage("dashboard")} syncing={syncing} onSync={sync} onChanged={load} notify={notify} /> )} {toast && (
{toast}
)}
); }