+
e.stopPropagation()}
+ style={{
+ background: T.card, border: `1px solid ${T.border}`,
+ borderRadius: 16, width: "100%", maxWidth: 700,
+ maxHeight: "90vh", display: "flex", flexDirection: "column",
+ boxShadow: "0 16px 48px rgba(0,0,0,0.4)",
+ overflow: "hidden",
+ }}
+ >
+ {/* Header */}
+
+
+ Gebruikersbeheer
+
+ {users.length} gebruiker{users.length !== 1 ? "s" : ""}
+
+
+
+
-
+
{loadErr && (
{loadErr}
)}
@@ -342,14 +366,7 @@ export default function GebruikersBeheer({ onClose }) {
)}
-
-
-
-
-
+
+
);
}
diff --git a/frontend/src/components/InstellingenModal.jsx b/frontend/src/components/InstellingenModal.jsx
new file mode 100644
index 0000000..4bdb1c5
--- /dev/null
+++ b/frontend/src/components/InstellingenModal.jsx
@@ -0,0 +1,111 @@
+import { useApp } from "../context/AppContext.jsx";
+import { PURPLE } from "../constants/index.js";
+
+const BACKUP_ICON = (
+
+);
+
+const RESTORE_ICON = (
+
+);
+
+function ActionRow({ icon, title, description, onClick }) {
+ return (
+
+
{icon}
+
+
{title}
+
{description}
+
+
+ );
+}
+
+export default function InstellingenModal({ onClose }) {
+ const { T, darkMode, setDarkMode, doBackup, doRestore } = useApp();
+
+ return (
+
+
e.stopPropagation()}
+ style={{
+ background: T.card, border: `1px solid ${T.border}`,
+ borderRadius: 16, padding: "22px 22px 8px",
+ width: 360, boxShadow: "0 16px 48px rgba(0,0,0,0.4)",
+ color: T.text,
+ }}
+ >
+ {/* Header */}
+
+ Instellingen
+
+
+
+ {/* Dark mode rij */}
+
+
Dark mode
+
setDarkMode((d) => !d)}
+ style={{
+ width: 44, height: 24, borderRadius: 12, cursor: "pointer",
+ background: darkMode ? `linear-gradient(135deg, ${PURPLE}, #a855f7)` : "#334155",
+ position: "relative", transition: "background 0.2s", flexShrink: 0,
+ }}
+ >
+
+
+
+
+
+
+ {/* Acties */}
+
+
{ doBackup(); onClose(); }}
+ />
+
+ { doRestore(); onClose(); }}
+ />
+
+
+
+ );
+}
diff --git a/frontend/src/components/LoginPage.jsx b/frontend/src/components/LoginPage.jsx
index 21c045b..88ee923 100644
--- a/frontend/src/components/LoginPage.jsx
+++ b/frontend/src/components/LoginPage.jsx
@@ -1,20 +1,82 @@
import { useState } from "react";
import { PURPLE, PURPLE_LIGHT, RED } from "../constants/index.js";
-export default function LoginPage({ onLogin }) {
- const [mode, setMode] = useState("login");
- const [naam, setNaam] = useState("");
- const [email, setEmail] = useState("");
- const [wachtwoord, setWachtwoord] = useState("");
- const [showPass, setShowPass] = useState(false);
- const [err, setErr] = useState("");
- const [loading, setLoading] = useState(false);
+const PersonIcon = () => (
+
+);
+const MailIcon = () => (
+
+);
+const LockIcon = () => (
+
+);
- const switchMode = (m) => { setMode(m); setNaam(""); setEmail(""); setWachtwoord(""); setErr(""); };
+function EyeBtn({ show, onToggle, absolute = false }) {
+ const style = absolute
+ ? { position: "absolute", right: 12, top: "50%", transform: "translateY(-50%)" }
+ : { position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)" };
+ return (
+
+ );
+}
+
+function FieldCard({ icon, label, children, extra }) {
+ return (
+
+
{icon}
+
+
{label}
+
+ {children}
+ {extra}
+
+
+
+ );
+}
+
+export default function LoginPage({ onLogin }) {
+ const [mode, setMode] = useState("login");
+ const [naam, setNaam] = useState("");
+ const [email, setEmail] = useState("");
+ const [wachtwoord, setWachtwoord] = useState("");
+ const [herhaalWw, setHerhaalWw] = useState("");
+ const [showPass, setShowPass] = useState(false);
+ const [showPass2, setShowPass2] = useState(false);
+ const [err, setErr] = useState("");
+ const [loading, setLoading] = useState(false);
+
+ const switchMode = (m) => { setMode(m); setNaam(""); setEmail(""); setWachtwoord(""); setHerhaalWw(""); setErr(""); };
const handleSubmit = async () => {
setErr("");
- if (mode === "register" && !naam.trim()) { setErr("Vul je naam in."); return; }
+ if (mode === "register") {
+ if (!naam.trim()) { setErr("Vul je naam in."); return; }
+ if (wachtwoord.length < 8) { setErr("Wachtwoord moet minimaal 8 tekens zijn."); return; }
+ if (!/[A-Z]/.test(wachtwoord)) { setErr("Wachtwoord moet minimaal één hoofdletter bevatten."); return; }
+ if (!/[0-9]/.test(wachtwoord)) { setErr("Wachtwoord moet minimaal één cijfer bevatten."); return; }
+ if (wachtwoord !== herhaalWw) { setErr("Wachtwoorden komen niet overeen."); return; }
+ }
if (!email || !wachtwoord) { setErr("Vul e-mail en wachtwoord in."); return; }
setLoading(true);
try {
@@ -33,11 +95,8 @@ export default function LoginPage({ onLogin }) {
};
const iStyle = {
- width: "100%",
- background: "rgba(255,255,255,0.06)",
- border: "1px solid rgba(255,255,255,0.1)",
- borderRadius: 8, color: "#f1f5f9",
- padding: "11px 14px", fontSize: 13,
+ width: "100%", background: "transparent", border: "none",
+ color: "#f1f5f9", padding: "0", fontSize: 13,
outline: "none", boxSizing: "border-box",
};
@@ -80,7 +139,7 @@ export default function LoginPage({ onLogin }) {
{/* Heading */}
-
+
{mode === "login" ? "Welkom terug!" : "Account aanmaken"}
@@ -91,36 +150,86 @@ export default function LoginPage({ onLogin }) {
+ {/* Tab switcher */}
+
+ {[["login", "Inloggen"], ["register", "Registreren"]].map(([m, label]) => (
+
+ ))}
+
+
{/* Formulier */}
-
- {mode === "register" && (
-
- { setNaam(e.target.value); setErr(""); }}
- onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
- style={iStyle} />
-
+
+ {mode === "register" ? (
+ <>
+ {/* Volledige naam */}
+
} label="Volledige naam">
+
{ setNaam(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={iStyle} />
+
+
+ {/* E-mail */}
+
} label="E-mailadres">
+
{ setEmail(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={iStyle} />
+
+
+ {/* Wachtwoord */}
+
} label="Wachtwoord" extra={
+
setShowPass((s) => !s)} />
+ }>
+ { setWachtwoord(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={{ ...iStyle, paddingRight: 36 }} />
+
+
+ {/* Herhaal wachtwoord */}
+ } label="Herhaal wachtwoord" extra={
+ setShowPass2((s) => !s)} />
+ }>
+ { setHerhaalWw(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={{ ...iStyle, paddingRight: 36 }} />
+
+
+
+ Min. 8 tekens · één hoofdletter · één cijfer
+
+ >
+ ) : (
+ <>
+ } label="E-mailadres">
+ { setEmail(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={iStyle} />
+
+
+ } label="Wachtwoord" extra={
+ setShowPass((s) => !s)} />
+ }>
+ { setWachtwoord(e.target.value); setErr(""); }}
+ onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
+ style={{ ...iStyle, paddingRight: 36 }} />
+
+ >
)}
-
- { setEmail(e.target.value); setErr(""); }}
- onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
- style={iStyle} />
-
-
-
- { setWachtwoord(e.target.value); setErr(""); }}
- onKeyDown={(e) => e.key === "Enter" && handleSubmit()}
- style={{ ...iStyle, paddingRight: 42 }} />
-
-
-
{err && (
@@ -145,20 +254,6 @@ export default function LoginPage({ onLogin }) {
- {/* Switch mode */}
-
- {mode === "login" ? (
-
- Nog geen account?{" "}
-
-
- ) : (
-
- Al een account?{" "}
-
-
- )}
-
{/* Rechts: app mockup β flat, groot, licht zwevend */}
diff --git a/frontend/src/components/NavBar.jsx b/frontend/src/components/NavBar.jsx
index a464c77..4acaef4 100644
--- a/frontend/src/components/NavBar.jsx
+++ b/frontend/src/components/NavBar.jsx
@@ -35,10 +35,9 @@ const GEBRUIKERS_ICON = (
);
-const DATA_ICON = (
-