import "./App.css"; import { AppProvider, useApp } from "./context/AppContext.jsx"; import { useEffect, useRef, useState } from "react"; import LoginPage from "./components/LoginPage.jsx"; import NavBar from "./components/NavBar.jsx"; import GebruikersBeheer from "./components/GebruikersBeheer.jsx"; import DashboardTab from "./pages/DashboardTab.jsx"; import EigenVermogenTab from "./pages/EigenVermogenTab.jsx"; import SchuldTab from "./pages/SchuldTab.jsx"; import VoortgangTab from "./pages/VoortgangTab.jsx"; import { RED, PURPLE_LIGHT } from "./constants/index.js"; import { fmt } from "./utils/format.js"; import { version } from "./version.js"; import { useUpdateCheck } from "./hooks/useUpdateCheck.js"; import ProfielPopup from "./components/ProfielPopup.jsx"; import InstellingenModal from "./components/InstellingenModal.jsx"; function initials(naam = "") { const parts = naam.trim().split(/\s+/); if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase(); return naam.slice(0, 2).toUpperCase(); } const PAGES = [DashboardTab, EigenVermogenTab, SchuldTab, VoortgangTab]; function AppInner() { const { loggedIn, login, logout, locked, tab, showUsers, setShowUsers, showInstellingen, setShowInstellingen, loading, T, currentUser, avatar, idleMinutes, totEV } = useApp(); const [profielOpen, setProfielOpen] = useState(false); const [searchOpen, setSearchOpen] = useState(false); const nieuweVersie = useUpdateCheck(); // { latest, naam, notes, url } of null const [bellOpen, setBellOpen] = useState(false); const searchRef = useRef(null); const idleTimer = useRef(null); const IDLE_MS = (idleMinutes ?? 30) * 60 * 1000; const [sessionVerlopen, setSessionVerlopen] = useState(false); useEffect(() => { if (!loggedIn) return; const reset = () => { clearTimeout(idleTimer.current); idleTimer.current = setTimeout(() => { logout(); setSessionVerlopen(true); }, IDLE_MS); }; const events = ["mousemove", "mousedown", "keydown", "touchstart", "scroll"]; events.forEach((e) => window.addEventListener(e, reset, { passive: true })); reset(); return () => { events.forEach((e) => window.removeEventListener(e, reset)); clearTimeout(idleTimer.current); }; }, [loggedIn]); useEffect(() => { const handler = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setSearchOpen(true); setTimeout(() => searchRef.current?.focus(), 50); } if (e.key === "Escape") setSearchOpen(false); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, []); if (!loggedIn) { if (sessionVerlopen) { return (
⏱
Sessie verlopen
Je bent automatisch uitgelogd na 30 minuten inactiviteit. Klik hieronder om opnieuw in te loggen.
); } return ; } if (loading) { return (
Data laden…
); } const ActivePage = PAGES[tab] ?? DashboardTab; return (
{/* Hoofdinhoud */}
{/* Topbalk */}
{/* Notificatiebel */}
{/* Badge */} {nieuweVersie && (
1
)} {/* Popup */} {bellOpen && nieuweVersie && ( <>
setBellOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 190 }} />
Meldingen
Nieuwe versie beschikbaar
v{nieuweVersie.latest} is uitgebracht. Je gebruikt momenteel v{version}.
{/* De release-notes zelf staan hier niet: dat is Markdown uit de changelog, en die kwam als ruwe `### `-koppen in een smalle kolom te staan. Wie de details wil, volgt de link naar de release. */} {nieuweVersie.url && ( Release bekijken → )}
)}
{/* Gebruikersprofiel */}
{profielOpen && setProfielOpen(false)} />}
{/* Vergrendeld banner */} {locked && (
🔒 Vergrendeld — ga naar Gebruikers om te ontgrendelen
)} {/* Modals */} {showUsers && setShowUsers(false)} />} {showInstellingen && setShowInstellingen(false)} />} {/* Actieve pagina */}
); } export default function App() { return ( ); }