2026-09-04 09:19:14 +02:00
|
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
|
|
|
|
import { RED, GREEN } from "../constants/index.js";
|
|
|
|
|
|
|
|
|
|
|
|
import { useApp } from "../context/AppContext.jsx";
|
|
|
|
|
|
|
|
|
|
|
|
const getToken = () => localStorage.getItem("vf_token");
|
|
|
|
|
|
const authHdrs = () => ({ "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` });
|
|
|
|
|
|
|
|
|
|
|
|
async function apiFetch(path, options = {}) {
|
|
|
|
|
|
const res = await fetch(`/api${path}`, { ...options, headers: { ...authHdrs(), ...(options.headers || {}) } });
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
const data = await res.json().catch(() => ({}));
|
|
|
|
|
|
throw new Error(data.error || `HTTP ${res.status}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
return res.json();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function fmtDatum(iso) {
|
|
|
|
|
|
if (!iso) return "—";
|
|
|
|
|
|
const d = new Date(iso);
|
|
|
|
|
|
return `${d.getDate()}-${d.getMonth() + 1}-${d.getFullYear()}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function RolSelect({ value, onChange, opties, T, darkMode }) {
|
|
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
|
|
const ref = useRef(null);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handler = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
|
|
|
|
|
|
document.addEventListener("mousedown", handler);
|
|
|
|
|
|
return () => document.removeEventListener("mousedown", handler);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div ref={ref} style={{ position: "relative", width: "100%" }}>
|
|
|
|
|
|
<button type="button" onClick={() => setOpen((o) => !o)} style={{
|
|
|
|
|
|
width: "100%", background: T.inputBg, border: `1px solid ${T.inputBorder}`,
|
|
|
|
|
|
borderRadius: 8, color: T.text, padding: "8px 10px", fontSize: 13,
|
|
|
|
|
|
cursor: "pointer", display: "flex", justifyContent: "space-between", alignItems: "center",
|
|
|
|
|
|
boxSizing: "border-box",
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<span>{value}</span>
|
|
|
|
|
|
<span style={{ fontSize: 10, color: T.muted }}>▼</span>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{open && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
position: "absolute", top: "calc(100% + 4px)", left: 0, right: 0, zIndex: 999,
|
|
|
|
|
|
background: T.card, border: `1px solid ${T.inputBorder}`,
|
|
|
|
|
|
borderRadius: 8, overflow: "hidden",
|
|
|
|
|
|
boxShadow: darkMode ? "0 8px 24px rgba(0,0,0,0.5)" : "0 8px 24px rgba(0,0,0,0.15)",
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{opties.map((o) => (
|
|
|
|
|
|
<div key={o} onClick={() => { onChange(o); setOpen(false); }} style={{
|
|
|
|
|
|
padding: "9px 12px", fontSize: 13, cursor: "pointer",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
color: o === value ? T.accentTekst : T.text,
|
|
|
|
|
|
background: o === value ? T.inputBg : "transparent",
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}
|
|
|
|
|
|
onMouseEnter={(e) => e.currentTarget.style.background = "rgba(14,221,212,0.10)"}
|
2026-09-15 14:02:17 +02:00
|
|
|
|
onMouseLeave={(e) => e.currentTarget.style.background = o === value ? T.inputBg : "transparent"}
|
2026-09-04 09:19:14 +02:00
|
|
|
|
>
|
|
|
|
|
|
{o}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function GebruikersBeheer({ onClose }) {
|
|
|
|
|
|
const { currentUser, darkMode, T } = useApp();
|
|
|
|
|
|
|
|
|
|
|
|
const [users, setUsers] = useState([]);
|
|
|
|
|
|
const [loadErr, setLoadErr] = useState("");
|
|
|
|
|
|
const [showAdd, setShowAdd] = useState(false);
|
|
|
|
|
|
const [editUser, setEditUser] = useState(null); // user object dat bewerkt wordt
|
|
|
|
|
|
const [nieuwNaam, setNieuwNaam] = useState("");
|
|
|
|
|
|
const [nieuwEmail, setNieuwEmail] = useState("");
|
|
|
|
|
|
const [nieuwWw, setNieuwWw] = useState("");
|
|
|
|
|
|
const [nieuwRol, setNieuwRol] = useState("Viewer");
|
|
|
|
|
|
const [editNaam, setEditNaam] = useState("");
|
|
|
|
|
|
const [editEmail, setEditEmail] = useState("");
|
|
|
|
|
|
const [editWw, setEditWw] = useState("");
|
|
|
|
|
|
const [err, setErr] = useState("");
|
|
|
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
const rollen = ["Admin", "Bewerker", "Viewer"];
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
apiFetch("/users")
|
|
|
|
|
|
.then(setUsers)
|
|
|
|
|
|
.catch((e) => setLoadErr(e.message));
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const is = {
|
|
|
|
|
|
background: T.inputBg, border: `1px solid ${T.inputBorder}`,
|
|
|
|
|
|
borderRadius: 8, color: T.text, padding: "8px 10px",
|
|
|
|
|
|
fontSize: 13, outline: "none", boxSizing: "border-box", width: "100%",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const addUser = async () => {
|
|
|
|
|
|
if (!nieuwNaam.trim() || !nieuwEmail.trim() || !nieuwWw.trim()) {
|
|
|
|
|
|
setErr("Naam, e-mail en wachtwoord zijn verplicht.");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const u = await apiFetch("/users", {
|
|
|
|
|
|
method: "POST",
|
|
|
|
|
|
body: JSON.stringify({ naam: nieuwNaam, email: nieuwEmail, wachtwoord: nieuwWw, rol: nieuwRol }),
|
|
|
|
|
|
});
|
|
|
|
|
|
setUsers((prev) => [...prev, u]);
|
|
|
|
|
|
setNieuwNaam(""); setNieuwEmail(""); setNieuwWw(""); setErr("");
|
|
|
|
|
|
setShowAdd(false);
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
setErr(e.message);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openEdit = (u) => {
|
|
|
|
|
|
setEditUser(u);
|
|
|
|
|
|
setEditNaam(u.naam);
|
|
|
|
|
|
setEditEmail(u.email);
|
|
|
|
|
|
setEditWw("");
|
|
|
|
|
|
setErr("");
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const saveEdit = async () => {
|
|
|
|
|
|
if (!editNaam.trim() || !editEmail.trim()) { setErr("Naam en e-mail zijn verplicht."); return; }
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const patch = { naam: editNaam.trim(), email: editEmail.trim() };
|
|
|
|
|
|
if (editWw) patch.wachtwoord = editWw;
|
|
|
|
|
|
const updated = await apiFetch(`/users/${editUser.id}`, {
|
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
|
body: JSON.stringify(patch),
|
|
|
|
|
|
});
|
|
|
|
|
|
setUsers((prev) => prev.map((x) => x.id === editUser.id ? { ...x, ...updated } : x));
|
|
|
|
|
|
setEditUser(null); setErr("");
|
|
|
|
|
|
} catch (e) { setErr(e.message); }
|
|
|
|
|
|
finally { setSaving(false); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const toggleActief = async (u) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const updated = await apiFetch(`/users/${u.id}`, {
|
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
|
body: JSON.stringify({ actief: !u.actief }),
|
|
|
|
|
|
});
|
|
|
|
|
|
setUsers((prev) => prev.map((x) => x.id === u.id ? { ...x, ...updated } : x));
|
|
|
|
|
|
} catch (e) { setErr(e.message); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const removeUser = async (id) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await apiFetch(`/users/${id}`, { method: "DELETE" });
|
|
|
|
|
|
setUsers((prev) => prev.filter((u) => u.id !== id));
|
|
|
|
|
|
} catch (e) { setErr(e.message); }
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const th = {
|
|
|
|
|
|
padding: "8px 12px", fontSize: 11, color: T.muted, fontWeight: 600,
|
|
|
|
|
|
textAlign: "left", borderBottom: `1px solid ${T.border}`,
|
|
|
|
|
|
background: T.card, textTransform: "uppercase", letterSpacing: "0.05em",
|
|
|
|
|
|
};
|
|
|
|
|
|
const td = { padding: "10px 12px", fontSize: 13, color: T.text, borderBottom: `1px solid ${T.border}` };
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: "fixed", inset: 0, zIndex: 400,
|
|
|
|
|
|
background: "rgba(0,0,0,0.55)", backdropFilter: "blur(4px)",
|
|
|
|
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
|
style={{
|
2026-09-04 09:19:23 +02:00
|
|
|
|
background: T.popupBg, border: `1px solid ${T.border}`,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
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 */}
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
display: "flex", alignItems: "center", justifyContent: "space-between",
|
|
|
|
|
|
padding: "18px 22px", borderBottom: `1px solid ${T.border}`, flexShrink: 0,
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<span style={{ fontWeight: 700, fontSize: 15, color: T.text }}>Gebruikersbeheer</span>
|
2026-09-15 14:02:17 +02:00
|
|
|
|
<span style={{ marginLeft: 10, fontSize: 11, fontWeight: 600, color: T.accentTekst,
|
|
|
|
|
|
background: T.inputBg, borderRadius: 99, padding: "2px 8px", border: `1px solid ${T.inputBorder}`,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}>
|
|
|
|
|
|
{users.length} gebruiker{users.length !== 1 ? "s" : ""}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button onClick={onClose} style={{
|
|
|
|
|
|
background: "transparent", border: "none", cursor: "pointer",
|
|
|
|
|
|
color: T.muted, fontSize: 20, lineHeight: 1, padding: 4,
|
|
|
|
|
|
}}>×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ padding: "16px 22px", overflowY: "auto", flex: 1 }}>
|
|
|
|
|
|
{loadErr && (
|
|
|
|
|
|
<div style={{ color: RED, fontSize: 13, marginBottom: 12 }}>{loadErr}</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Tabel */}
|
|
|
|
|
|
<div style={{ overflowX: "auto", marginBottom: 16 }}>
|
|
|
|
|
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th style={th}>Naam</th>
|
|
|
|
|
|
<th style={th}>E-mail</th>
|
|
|
|
|
|
<th style={th}>Aangemaakt</th>
|
|
|
|
|
|
<th style={th}>Admin</th>
|
|
|
|
|
|
<th style={th}></th>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{users.map((u) => {
|
|
|
|
|
|
const initials = u.naam.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2);
|
|
|
|
|
|
const isSelf = u.id === currentUser?.id || u.email === currentUser?.email;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<tr key={u.id} style={{ opacity: u.actief ? 1 : 0.5 }}>
|
|
|
|
|
|
<td style={td}>
|
|
|
|
|
|
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
width: 32, height: 32, borderRadius: "50%",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: T.inputBg, display: "flex",
|
2026-09-04 09:19:14 +02:00
|
|
|
|
alignItems: "center", justifyContent: "center",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
fontSize: 12, fontWeight: 700, color: T.accentTekst, flexShrink: 0,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}>
|
|
|
|
|
|
{initials}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span style={{ fontWeight: 600 }}>{u.naam}</span>
|
|
|
|
|
|
{isSelf && (
|
|
|
|
|
|
<span style={{
|
2026-09-15 14:02:17 +02:00
|
|
|
|
fontSize: 10, fontWeight: 700, color: T.accentTekst,
|
|
|
|
|
|
background: T.inputBg, borderRadius: 99,
|
|
|
|
|
|
padding: "1px 7px", border: `1px solid ${T.inputBorder}`,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}>jij</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td style={{ ...td, color: T.muted }}>{u.email}</td>
|
|
|
|
|
|
<td style={{ ...td, color: T.muted }}>{fmtDatum(u.aangemaakt)}</td>
|
|
|
|
|
|
<td style={td}>
|
|
|
|
|
|
{u.rol === "Admin"
|
|
|
|
|
|
? <span style={{ fontSize: 18 }}>⭐</span>
|
|
|
|
|
|
: <span style={{ fontSize: 13, color: T.muted }}>—</span>
|
|
|
|
|
|
}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
<td style={{ ...td, whiteSpace: "nowrap" }}>
|
|
|
|
|
|
<div style={{ display: "flex", gap: 6, alignItems: "center" }}>
|
|
|
|
|
|
<button onClick={() => openEdit(u)} style={{
|
2026-09-15 14:02:17 +02:00
|
|
|
|
padding: "3px 8px", background: T.inputBg,
|
|
|
|
|
|
border: `1px solid ${T.inputBorder}`, borderRadius: 6,
|
|
|
|
|
|
color: T.accentTekst, cursor: "pointer", fontSize: 12,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}>✏️</button>
|
|
|
|
|
|
{!isSelf && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<button onClick={() => toggleActief(u)} style={{
|
|
|
|
|
|
padding: "3px 8px", borderRadius: 6, cursor: "pointer",
|
|
|
|
|
|
fontSize: 11, fontWeight: 700,
|
|
|
|
|
|
background: u.actief ? "rgba(16,185,129,0.12)" : "rgba(239,68,68,0.1)",
|
|
|
|
|
|
border: `1px solid ${u.actief ? "rgba(16,185,129,0.4)" : "rgba(239,68,68,0.3)"}`,
|
|
|
|
|
|
color: u.actief ? GREEN : RED,
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{u.actief ? "Actief" : "Inactief"}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button onClick={() => removeUser(u.id)} style={{
|
|
|
|
|
|
padding: "3px 8px", background: "rgba(239,68,68,0.1)",
|
|
|
|
|
|
border: "1px solid rgba(239,68,68,0.25)", borderRadius: 6,
|
|
|
|
|
|
color: RED, cursor: "pointer", fontSize: 13,
|
|
|
|
|
|
}}>×</button>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{err && (
|
|
|
|
|
|
<div style={{ color: RED, fontSize: 12, marginBottom: 12, padding: "6px 10px", background: "rgba(239,68,68,0.1)", borderRadius: 8, border: "1px solid rgba(239,68,68,0.25)" }}>
|
|
|
|
|
|
{err}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Bewerken modal */}
|
|
|
|
|
|
{editUser && (
|
|
|
|
|
|
<div style={{
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: darkMode ? "rgba(124,58,237,0.06)" : T.inputBg,
|
|
|
|
|
|
border: `1px solid ${T.inputBorder}`, borderRadius: 10, padding: "16px", marginBottom: 16,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
}}>
|
2026-09-15 14:02:17 +02:00
|
|
|
|
<div style={{ fontSize: 11, color: T.accentTekst, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 12 }}>
|
2026-09-04 09:19:14 +02:00
|
|
|
|
{editUser.naam} bewerken
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 10 }}>
|
|
|
|
|
|
<input placeholder="Naam" value={editNaam} onChange={(e) => setEditNaam(e.target.value)} style={is} />
|
|
|
|
|
|
<input placeholder="E-mail" value={editEmail} onChange={(e) => setEditEmail(e.target.value)} style={is} />
|
|
|
|
|
|
<input placeholder="Nieuw wachtwoord (optioneel)" type="password" value={editWw} onChange={(e) => setEditWw(e.target.value)} style={{ ...is, gridColumn: "1 / -1" }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
|
|
|
|
<button onClick={() => { setEditUser(null); setErr(""); }} style={{
|
|
|
|
|
|
flex: 1, padding: "8px", background: "transparent",
|
|
|
|
|
|
border: `1px solid ${T.border}`, borderRadius: 8,
|
|
|
|
|
|
color: T.muted, cursor: "pointer", fontSize: 13, fontWeight: 600,
|
|
|
|
|
|
}}>Annuleren</button>
|
|
|
|
|
|
<button onClick={saveEdit} disabled={saving} style={{
|
|
|
|
|
|
flex: 2, padding: "8px",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: T.accentGradient,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
border: "none", borderRadius: 8, color: "#fff",
|
|
|
|
|
|
fontWeight: 700, fontSize: 13, cursor: saving ? "not-allowed" : "pointer",
|
|
|
|
|
|
}}>{saving ? "Opslaan..." : "Opslaan"}</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Nieuwe gebruiker */}
|
|
|
|
|
|
{showAdd ? (
|
|
|
|
|
|
<div style={{
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: darkMode ? "rgba(124,58,237,0.06)" : T.inputBg,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
border: `1px solid ${T.border}`, borderRadius: 10, padding: "16px",
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{ fontSize: 11, color: T.muted, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: 12 }}>
|
|
|
|
|
|
Nieuwe gebruiker
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10, marginBottom: 10 }}>
|
|
|
|
|
|
<input placeholder="Naam" value={nieuwNaam} onChange={(e) => setNieuwNaam(e.target.value)} style={is} />
|
|
|
|
|
|
<input placeholder="E-mail" value={nieuwEmail} onChange={(e) => setNieuwEmail(e.target.value)} style={is} />
|
|
|
|
|
|
<input placeholder="Wachtwoord" type="password" value={nieuwWw} onChange={(e) => setNieuwWw(e.target.value)} style={is} />
|
|
|
|
|
|
<RolSelect value={nieuwRol} onChange={setNieuwRol} opties={rollen} T={T} darkMode={darkMode} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: "flex", gap: 8 }}>
|
|
|
|
|
|
<button onClick={() => { setShowAdd(false); setErr(""); }} style={{
|
|
|
|
|
|
flex: 1, padding: "8px", background: "transparent",
|
|
|
|
|
|
border: `1px solid ${T.border}`, borderRadius: 8,
|
|
|
|
|
|
color: T.muted, cursor: "pointer", fontSize: 13, fontWeight: 600,
|
|
|
|
|
|
}}>Annuleren</button>
|
|
|
|
|
|
<button onClick={addUser} disabled={saving} style={{
|
|
|
|
|
|
flex: 2, padding: "8px",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: T.accentGradient,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
border: "none", borderRadius: 8, color: "#fff",
|
|
|
|
|
|
fontWeight: 700, fontSize: 13, cursor: saving ? "not-allowed" : "pointer",
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{saving ? "Opslaan..." : "Toevoegen"}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<button onClick={() => setShowAdd(true)} style={{
|
|
|
|
|
|
padding: "8px 18px",
|
2026-09-15 14:02:17 +02:00
|
|
|
|
background: T.accentGradient,
|
2026-09-04 09:19:14 +02:00
|
|
|
|
border: "none", borderRadius: 8, color: "#fff",
|
|
|
|
|
|
fontWeight: 700, fontSize: 13, cursor: "pointer",
|
|
|
|
|
|
}}>+ Nieuwe gebruiker</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|