Utenti: creazione su pagina dedicata, come per gli articoli

L'elenco aveva il modulo di creazione appiccicato sopra la tabella: ora
c'è il pulsante "+ Nuovo utente" che porta a /admin/users/new, dove il
modulo ha spazio per etichette vere e per spiegare cosa comporta ogni
ruolo. A creazione avvenuta si torna all'elenco.

Nel menu la voce "Sito" diventa "Vai a sito", più esplicita sul fatto che
porta fuori dal pannello.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 16:30:53 +02:00
parent 03510d3940
commit 15924544b4
3 changed files with 69 additions and 33 deletions
+10 -2
View File
@@ -45,7 +45,7 @@ const attiva = (v: { href: string; exact?: boolean }) =>
{VOCI.filter((v) => v.visibile).map((v) => (
<a href={v.href} class:list={['abar__link', { 'is-active': attiva(v) }]}>{v.label}</a>
))}
<a class="abar__link" href="/" target="_blank" rel="noopener">Sito</a>
<a class="abar__link" href="/" target="_blank" rel="noopener">Vai a sito</a>
<a class="abar__link abar__link--out" href="/admin/logout">Esci</a>
</nav>
)}
@@ -123,7 +123,15 @@ const attiva = (v: { href: string; exact?: boolean }) =>
}
.pill--draft { background: rgba(192,138,62,.18); color: #96682a; }
.aform { max-width: 380px; background: #fff; border: 1px solid #e4dfd7; padding: 26px 24px; }
.aform { max-width: 420px; background: #fff; border: 1px solid #e4dfd7; padding: 26px 24px; }
.alabel {
display: block; margin-bottom: 6px; font-family: var(--font-heading); font-size: .68rem;
font-weight: 600; letter-spacing: .14em; text-transform: uppercase; color: var(--c-text-light);
}
.ahint { margin: -8px 0 16px; font-size: .78rem; color: var(--c-text-light); }
.aactions { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
.abtn--ghost { background: none; color: var(--c-heading); border-color: #ddd6cb; }
.abtn--ghost:hover { background: none; border-color: var(--c-accent); color: var(--c-accent-dark); }
.form-msg { font-size: .88rem; margin-top: 8px; }
.form-msg--ok { color: #5f7c5a; }
+1 -31
View File
@@ -9,21 +9,7 @@ const me = Astro.locals.user!;
<Admin title="Utenti">
<h1>Utenti</h1>
<h2>Nuovo utente</h2>
<form id="new-user" style="max-width:420px">
<input class="afield" name="username" placeholder="Nome utente" required />
<input class="afield" name="password" type="text" placeholder="Password (min 8)" required />
<select class="afield" name="role">
<option value="user">user</option>
<option value="superuser">superuser</option>
<option value="piattaforme">piattaforme</option>
<option value="admin">admin</option>
</select>
<button class="abtn" type="submit">Crea</button>
<span class="form-msg" id="new-msg"></span>
</form>
<h2 style="margin-top:30px">Elenco</h2>
<p><a class="abtn" href="/admin/users/new">+ Nuovo utente</a></p>
<table>
<thead><tr><th>Utente</th><th>Ruolo</th><th></th></tr></thead>
<tbody>
@@ -53,22 +39,6 @@ const me = Astro.locals.user!;
el.className = 'form-msg ' + (ok ? 'form-msg--ok' : 'form-msg--err');
};
document.getElementById('new-user')!.addEventListener('submit', async (e) => {
e.preventDefault();
const f = e.target as HTMLFormElement;
const body = {
username: (f.elements.namedItem('username') as HTMLInputElement).value,
password: (f.elements.namedItem('password') as HTMLInputElement).value,
role: (f.elements.namedItem('role') as HTMLSelectElement).value,
};
const res = await fetch('/api/admin/users', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
});
const msg = document.getElementById('new-msg') as HTMLElement;
if (res.ok) { show(msg, 'Creato ✓', true); setTimeout(() => location.reload(), 600); }
else show(msg, (await res.json()).error ?? 'Errore');
});
document.querySelectorAll<HTMLSelectElement>('.role-sel').forEach((sel) =>
sel.addEventListener('change', async () => {
const res = await fetch(`/api/admin/users/${sel.dataset.id}/role`, {
+58
View File
@@ -0,0 +1,58 @@
---
// Creazione di un utente, su pagina propria come per gli articoli: l'elenco resta pulito e
// il modulo ha spazio per le sue spiegazioni.
import Admin from '../../../layouts/Admin.astro';
export const prerender = false;
---
<Admin title="Nuovo utente">
<h1>Nuovo utente</h1>
<form class="aform" id="new-user">
<label class="alabel" for="username">Nome utente</label>
<input class="afield" id="username" name="username" required autocomplete="off" />
<label class="alabel" for="password">Password</label>
<input class="afield" id="password" name="password" type="text" required minlength="8" autocomplete="off" />
<p class="ahint">Almeno 8 caratteri. Resta in chiaro qui: copiala e consegnala alla persona.</p>
<label class="alabel" for="role">Ruolo</label>
<select class="afield" id="role" name="role">
<option value="user">user — scrive i propri articoli</option>
<option value="superuser">superuser — articoli e contenuti del sito</option>
<option value="piattaforme">piattaforme — solo Campus e Stress Index</option>
<option value="admin">admin — accesso completo</option>
</select>
<div class="aactions">
<button class="abtn" type="submit">Crea utente</button>
<a class="abtn abtn--ghost" href="/admin/users">Annulla</a>
</div>
<p class="form-msg" id="new-msg" hidden></p>
</form>
</Admin>
<script>
const form = document.getElementById('new-user') as HTMLFormElement;
const msg = document.getElementById('new-msg') as HTMLElement;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const dati = new FormData(form);
const res = await fetch('/api/admin/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: String(dati.get('username') ?? ''),
password: String(dati.get('password') ?? ''),
role: String(dati.get('role') ?? 'user'),
}),
});
if (res.ok) {
location.href = '/admin/users';
return;
}
msg.hidden = false;
msg.className = 'form-msg form-msg--err';
msg.textContent = (await res.json()).error ?? 'Creazione non riuscita.';
});
</script>