feat(dashboard): adopt ActionMenu for user row actions

Replaces the inline row-action buttons on the Users page with the new
shared ActionMenu kebab. Drops the redundant `is_active` toggle from the
edit form (Activate/Deactivate already lives in the kebab).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-27 21:21:08 +02:00
parent fc35d59236
commit 6eb32a78bf
2 changed files with 276 additions and 63 deletions

View File

@@ -16,6 +16,7 @@
import { currentUser } from '$lib/auth';
import RoleChip from '$lib/RoleChip.svelte';
import ConfirmModal from '$lib/ConfirmModal.svelte';
import ActionMenu from '$lib/ActionMenu.svelte';
import { generatePassword } from '$lib/password-gen';
const me = $derived($currentUser);
@@ -70,8 +71,7 @@
username: string;
email: string;
instance_role: InstanceRole;
is_active: boolean;
}>({ username: '', email: '', instance_role: 'admin', is_active: true });
}>({ username: '', email: '', instance_role: 'admin' });
let editPending = $state(false);
let editError = $state<string | null>(null);
@@ -161,8 +161,7 @@
editForm = {
username: row.username,
email: row.email ?? '',
instance_role: row.instance_role,
is_active: row.is_active
instance_role: row.instance_role
};
editError = null;
}
@@ -176,7 +175,6 @@
username?: string;
email?: string | null;
instance_role?: InstanceRole;
is_active?: boolean;
} = {};
if (editForm.username !== editTarget.username) patch.username = editForm.username;
if ((editTarget.email ?? '') !== editForm.email.trim()) {
@@ -185,7 +183,6 @@
if (editForm.instance_role !== editTarget.instance_role) {
patch.instance_role = editForm.instance_role;
}
if (editForm.is_active !== editTarget.is_active) patch.is_active = editForm.is_active;
try {
const updated = await api.admins.update(editTarget.id, patch);
admins = admins
@@ -350,19 +347,22 @@
<div>{shortDate(row.created_at)}</div>
<div title={row.last_login_at ?? ''}>{relative(row.last_login_at)}</div>
<div class="actions-col">
<button type="button" class="row-action" onclick={() => openEdit(row)}>Edit</button>
<button type="button" class="row-action" onclick={() => toggleActive(row)}>
{row.is_active ? 'Deactivate' : 'Reactivate'}
</button>
{#if canDelete(row)}
<button
type="button"
class="row-action danger-link"
onclick={() => openDelete(row)}
>
Delete
</button>
{/if}
<ActionMenu
label="User actions for {row.username}"
items={[
{ label: 'Edit', onClick: () => openEdit(row) },
{
label: row.is_active ? 'Deactivate' : 'Reactivate',
onClick: () => toggleActive(row)
},
{
label: 'Delete',
danger: true,
disabled: !canDelete(row),
onClick: () => openDelete(row)
}
]}
/>
</div>
</div>
{/each}
@@ -514,11 +514,6 @@
{/if}
</small>
</label>
<label class="toggle">
<input type="checkbox" bind:checked={editForm.is_active} />
<span>Active</span>
<small>Unchecking signs the user out and expires all their API keys immediately.</small>
</label>
{#if editError}
<div class="error">{editError}</div>
{/if}
@@ -682,7 +677,7 @@
}
.row {
display: grid;
grid-template-columns: 1.3fr 0.7fr 1.5fr 0.9fr 0.8fr 0.9fr 1.6fr;
grid-template-columns: 1.3fr 0.7fr 1.5fr 0.9fr 0.8fr 0.9fr 2.5rem;
align-items: center;
gap: 0.75rem;
padding: 0.7rem 1rem;
@@ -737,29 +732,6 @@
.actions-col {
display: flex;
justify-content: flex-end;
gap: 0.25rem;
flex-wrap: wrap;
}
.row-action {
background: transparent;
color: #cbd5e1;
border: 1px solid #334155;
padding: 0.25rem 0.55rem;
border-radius: 0.25rem;
font-size: 0.75rem;
cursor: pointer;
}
.row-action:hover {
background: #1e293b;
color: #e2e8f0;
}
.danger-link {
color: #fca5a5;
border-color: #7f1d1d;
}
.danger-link:hover {
background: #450a0a;
color: #fecaca;
}
button.primary {
@@ -923,21 +895,6 @@
color: #cbd5e1;
}
.toggle {
display: flex;
flex-direction: column;
gap: 0.25rem;
font-size: 0.85rem;
color: #cbd5e1;
}
.toggle :global(input[type='checkbox']) {
margin-right: 0.4rem;
}
.toggle small {
color: #64748b;
font-size: 0.72rem;
margin-left: 1.3rem;
}
.token-row {
display: flex;