Refactor code structure for improved readability and maintainability
This commit is contained in:
32
src/App.css
32
src/App.css
@@ -448,6 +448,28 @@
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.inline-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.inline-field select {
|
||||
border-radius: 12px;
|
||||
border: 1px solid #e2e8f0;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.app {
|
||||
padding: 40px 6vw 56px;
|
||||
@@ -540,6 +562,16 @@
|
||||
color: #cbd5f5;
|
||||
}
|
||||
|
||||
.inline-field select {
|
||||
background: #1e1b4b;
|
||||
border-color: #312e81;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
color: #cbd5f5;
|
||||
}
|
||||
|
||||
.field input,
|
||||
.field select,
|
||||
.field textarea {
|
||||
|
||||
@@ -7,19 +7,30 @@ export default function LicenseManager() {
|
||||
const { user, token, authFetch, login, register: registerUser, loading: authLoading } = useAuth();
|
||||
const [licenses, setLicenses] = useState([]);
|
||||
const [plugins, setPlugins] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [selectedPluginId, setSelectedPluginId] = useState("");
|
||||
const [selectedOwnerId, setSelectedOwnerId] = useState("");
|
||||
const [label, setLabel] = useState("");
|
||||
const [note, setNote] = useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState(null);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const [creatingLicense, setCreatingLicense] = useState(false);
|
||||
const [creatingUser, setCreatingUser] = useState(false);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [lastSync, setLastSync] = useState(null);
|
||||
const [formStatus, setFormStatus] = useState(null);
|
||||
const [userFormStatus, setUserFormStatus] = useState(null);
|
||||
const [verifyStatus, setVerifyStatus] = useState(null);
|
||||
const [verifying, setVerifying] = useState(false);
|
||||
const [verifyKey, setVerifyKey] = useState("");
|
||||
const [verifyHostname, setVerifyHostname] = useState("");
|
||||
const [newUserForm, setNewUserForm] = useState({
|
||||
username: "",
|
||||
name: "",
|
||||
email: "",
|
||||
password: "",
|
||||
isAdmin: false
|
||||
});
|
||||
|
||||
const isAuthenticated = Boolean(user && token);
|
||||
|
||||
@@ -57,8 +68,50 @@ export default function LicenseManager() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated) {
|
||||
setUsers([]);
|
||||
setSelectedOwnerId("");
|
||||
return;
|
||||
}
|
||||
if (user && !selectedOwnerId) {
|
||||
setSelectedOwnerId(String(user.id));
|
||||
}
|
||||
}, [isAuthenticated, selectedOwnerId, user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated || !user) return;
|
||||
if (!user.isAdmin) {
|
||||
setUsers([user]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await authFetch("/api/admin/users");
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || "Kon gebruikers niet laden.");
|
||||
}
|
||||
if (cancelled) return;
|
||||
setUsers(data.items || []);
|
||||
if (!selectedOwnerId && (data.items || []).length > 0) {
|
||||
setSelectedOwnerId(String(data.items[0].id));
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
setFormStatus({ variant: "error", message: err.message });
|
||||
}
|
||||
}
|
||||
}
|
||||
loadUsers();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [authFetch, isAuthenticated, selectedOwnerId, user]);
|
||||
|
||||
const refreshLicenses = useCallback(
|
||||
async (showStatus = true) => {
|
||||
async (showStatus = true, overrideUserId) => {
|
||||
if (!token) {
|
||||
setLicenses([]);
|
||||
setLastSync(null);
|
||||
@@ -72,7 +125,14 @@ export default function LicenseManager() {
|
||||
}
|
||||
setRefreshing(true);
|
||||
try {
|
||||
const response = await authFetch("/api/licenses");
|
||||
const ownerIdToUse =
|
||||
overrideUserId ||
|
||||
(user?.isAdmin ? selectedOwnerId || (user ? String(user.id) : "") : user ? String(user.id) : "");
|
||||
let url = "/api/licenses";
|
||||
if (user?.isAdmin && ownerIdToUse) {
|
||||
url += `?userId=${ownerIdToUse}`;
|
||||
}
|
||||
const response = await authFetch(url);
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (response.status === 401) {
|
||||
throw new Error("Sessie verlopen, log opnieuw in.");
|
||||
@@ -90,7 +150,7 @@ export default function LicenseManager() {
|
||||
setRefreshing(false);
|
||||
}
|
||||
},
|
||||
[authFetch, token]
|
||||
[authFetch, selectedOwnerId, token, user]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -125,7 +185,7 @@ export default function LicenseManager() {
|
||||
setFormStatus({ variant: "error", message: "Selecteer een plugin." });
|
||||
return;
|
||||
}
|
||||
setCreating(true);
|
||||
setCreatingLicense(true);
|
||||
try {
|
||||
const payload = {
|
||||
label:
|
||||
@@ -140,10 +200,14 @@ export default function LicenseManager() {
|
||||
baseUrl: selectedPlugin.baseUrl
|
||||
}
|
||||
};
|
||||
const requestBody = {
|
||||
...payload,
|
||||
userId: user?.isAdmin ? Number(selectedOwnerId || user.id) : undefined
|
||||
};
|
||||
const response = await authFetch("/api/licenses", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (response.status === 401) {
|
||||
@@ -159,7 +223,34 @@ export default function LicenseManager() {
|
||||
} catch (err) {
|
||||
setFormStatus({ variant: "error", message: err.message });
|
||||
} finally {
|
||||
setCreating(false);
|
||||
setCreatingLicense(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCreateUser(event) {
|
||||
event.preventDefault();
|
||||
setUserFormStatus(null);
|
||||
setCreatingUser(true);
|
||||
try {
|
||||
const response = await authFetch("/api/admin/users", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(newUserForm)
|
||||
});
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || "Gebruiker aanmaken mislukt.");
|
||||
}
|
||||
setUserFormStatus({ variant: "success", message: "Gebruiker aangemaakt." });
|
||||
setNewUserForm({ username: "", name: "", email: "", password: "", isAdmin: false });
|
||||
setUsers((prev) => [...prev, data.user]);
|
||||
if (!selectedOwnerId) {
|
||||
setSelectedOwnerId(String(data.user.id));
|
||||
}
|
||||
} catch (err) {
|
||||
setUserFormStatus({ variant: "error", message: err.message });
|
||||
} finally {
|
||||
setCreatingUser(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,6 +327,24 @@ export default function LicenseManager() {
|
||||
<span>Actieve licenties: {licenses.length}</span>
|
||||
<span>Laatste update: {formatDateTime(lastSync)}</span>
|
||||
{user && <span>Ingelogd als: {user.email}</span>}
|
||||
{user?.isAdmin && users.length > 0 && (
|
||||
<label className="inline-field">
|
||||
<span>Licenties gebruiker</span>
|
||||
<select
|
||||
value={selectedOwnerId}
|
||||
onChange={(event) => {
|
||||
setSelectedOwnerId(event.target.value);
|
||||
refreshLicenses(true, event.target.value);
|
||||
}}
|
||||
>
|
||||
{users.map((u) => (
|
||||
<option key={u.id} value={u.id}>
|
||||
{u.name} ({u.email})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isLoadingState && <div className="state">Bezig met laden…</div>}
|
||||
@@ -283,8 +392,20 @@ export default function LicenseManager() {
|
||||
rows={3}
|
||||
/>
|
||||
</label>
|
||||
<button className="cta" type="submit" disabled={creating || !selectedPlugin}>
|
||||
{creating ? "Aanmaken…" : "Licentie aanmaken"}
|
||||
{user?.isAdmin && (
|
||||
<label className="field">
|
||||
<span>Licentie voor gebruiker</span>
|
||||
<select value={selectedOwnerId} onChange={(event) => setSelectedOwnerId(event.target.value)}>
|
||||
{users.map((u) => (
|
||||
<option key={u.id} value={u.id}>
|
||||
{u.name} ({u.email})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
<button className="cta" type="submit" disabled={creatingLicense || !selectedPlugin}>
|
||||
{creatingLicense ? "Aanmaken…" : "Licentie aanmaken"}
|
||||
</button>
|
||||
</form>
|
||||
{formStatus && (
|
||||
@@ -340,6 +461,69 @@ export default function LicenseManager() {
|
||||
</article>
|
||||
</section>
|
||||
|
||||
{user?.isAdmin && (
|
||||
<section className="license-forms">
|
||||
<article className="card">
|
||||
<h2>Gebruiker toevoegen</h2>
|
||||
<p className="hint">Admins kunnen extra gebruikers aanmaken en direct licenties toewijzen.</p>
|
||||
<form className="form-grid" onSubmit={handleCreateUser}>
|
||||
<label className="field">
|
||||
<span>Gebruikersnaam</span>
|
||||
<input
|
||||
value={newUserForm.username}
|
||||
onChange={(event) => setNewUserForm((prev) => ({ ...prev, username: event.target.value }))}
|
||||
placeholder="gebruikersnaam"
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Naam</span>
|
||||
<input
|
||||
value={newUserForm.name}
|
||||
onChange={(event) => setNewUserForm((prev) => ({ ...prev, name: event.target.value }))}
|
||||
placeholder="Volledige naam"
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>E-mail</span>
|
||||
<input
|
||||
type="email"
|
||||
value={newUserForm.email}
|
||||
onChange={(event) => setNewUserForm((prev) => ({ ...prev, email: event.target.value }))}
|
||||
placeholder="naam@bedrijf.nl"
|
||||
/>
|
||||
</label>
|
||||
<label className="field">
|
||||
<span>Wachtwoord</span>
|
||||
<input
|
||||
type="password"
|
||||
value={newUserForm.password}
|
||||
onChange={(event) => setNewUserForm((prev) => ({ ...prev, password: event.target.value }))}
|
||||
placeholder="Minimaal 8 karakters"
|
||||
/>
|
||||
</label>
|
||||
<label className="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={newUserForm.isAdmin}
|
||||
onChange={(event) =>
|
||||
setNewUserForm((prev) => ({ ...prev, isAdmin: event.target.checked }))
|
||||
}
|
||||
/>
|
||||
<span>Maak deze gebruiker admin</span>
|
||||
</label>
|
||||
<button className="cta" type="submit" disabled={creatingUser}>
|
||||
{creatingUser ? "Bezig…" : "Gebruiker aanmaken"}
|
||||
</button>
|
||||
</form>
|
||||
{userFormStatus && (
|
||||
<div className={`state inline ${userFormStatus.variant === "error" ? "error" : "success"}`}>
|
||||
{userFormStatus.message}
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{isAuthenticated ? (
|
||||
<section className="license-grid">
|
||||
{sortedLicenses.length === 0 ? (
|
||||
|
||||
Reference in New Issue
Block a user