feat(05-11): add adminDeleteUser API function + inline delete confirmation panel

- Export adminDeleteUser(id, adminPassword) from client.js — sends JSON body to DELETE /api/admin/users/{id}
- AdminUsersTab: add confirmDelete, deletePassword, deleteError state refs
- AdminUsersTab: add startDelete, cancelDelete, confirmDoDelete functions (mutually exclusive with deactivate panel)
- AdminUsersTab: Delete button added to active and deactivated user rows
- AdminUsersTab: inline password confirmation panel with Argon2 verification via backend
This commit is contained in:
curo1305
2026-05-30 11:39:10 +02:00
parent 390a693ec6
commit 72687212a1
2 changed files with 92 additions and 0 deletions
+8
View File
@@ -269,6 +269,14 @@ export function adminUpdateAiConfig(id, provider, model) {
}) })
} }
export function adminDeleteUser(id, adminPassword) {
return request(`/api/admin/users/${id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ admin_password: adminPassword }),
})
}
// ── Folders ─────────────────────────────────────────────────────────────────── // ── Folders ───────────────────────────────────────────────────────────────────
export function listFolders(parentId = null) { export function listFolders(parentId = null) {
@@ -171,6 +171,43 @@
</div> </div>
</div> </div>
<!-- Inline delete confirmation panel -->
<div v-else-if="confirmDelete === user.id" class="space-y-2">
<p class="text-xs text-red-700 font-semibold">
Permanently delete <span class="font-bold">{{ user.email }}</span>?
This will erase all their documents, cloud connections, and quota data. This cannot be undone.
</p>
<div>
<label class="block text-xs text-gray-700 mb-1 font-semibold">Your admin password to confirm</label>
<input
v-model="deletePassword"
type="password"
autocomplete="current-password"
placeholder="Admin password"
class="block w-full rounded-lg px-2 py-1.5 text-xs border border-red-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-red-500 transition-colors"
@keydown.enter.prevent="confirmDoDelete(user.id)"
/>
</div>
<p v-if="deleteError" class="text-xs text-red-600">{{ deleteError }}</p>
<div class="flex items-center gap-2">
<button
@click="confirmDoDelete(user.id)"
:disabled="pendingAction[user.id] || !deletePassword"
class="text-red-700 hover:text-red-800 text-sm font-semibold disabled:opacity-50"
>
<span v-if="pendingAction[user.id]" class="flex items-center gap-1">
<span class="animate-spin rounded-full border-2 border-current border-t-transparent w-3 h-3"></span>
Deleting
</span>
<span v-else>Delete permanently</span>
</button>
<span class="text-gray-300">·</span>
<button @click="cancelDelete" class="text-gray-500 hover:text-gray-700 text-sm">
Cancel
</button>
</div>
</div>
<!-- Normal actions --> <!-- Normal actions -->
<div v-else class="flex items-center gap-2"> <div v-else class="flex items-center gap-2">
<span v-if="pendingAction[user.id]" class="flex items-center gap-1 text-gray-400 text-sm"> <span v-if="pendingAction[user.id]" class="flex items-center gap-1 text-gray-400 text-sm">
@@ -191,6 +228,13 @@
> >
Deactivate Deactivate
</button> </button>
<span class="text-gray-300">·</span>
<button
@click="startDelete(user.id)"
class="text-red-800 hover:text-red-900 text-sm font-semibold"
>
Delete
</button>
</template> </template>
<template v-else> <template v-else>
@@ -200,6 +244,13 @@
> >
Reactivate Reactivate
</button> </button>
<span class="text-gray-300">·</span>
<button
@click="startDelete(user.id)"
class="text-red-800 hover:text-red-900 text-sm font-semibold"
>
Delete
</button>
</template> </template>
</div> </div>
</td> </td>
@@ -221,6 +272,9 @@ const users = ref([])
const loading = ref(false) const loading = ref(false)
const showCreateForm = ref(false) const showCreateForm = ref(false)
const confirmDeactivate = ref(null) const confirmDeactivate = ref(null)
const confirmDelete = ref(null)
const deletePassword = ref('')
const deleteError = ref(null)
const pendingAction = reactive({}) const pendingAction = reactive({})
const actionError = ref(null) const actionError = ref(null)
const creating = ref(false) const creating = ref(false)
@@ -308,6 +362,36 @@ async function submitCreate() {
function startDeactivate(id) { function startDeactivate(id) {
confirmDeactivate.value = id confirmDeactivate.value = id
confirmDelete.value = null
deletePassword.value = ''
deleteError.value = null
}
function startDelete(id) {
confirmDelete.value = id
deletePassword.value = ''
deleteError.value = null
confirmDeactivate.value = null
}
function cancelDelete() {
confirmDelete.value = null
deletePassword.value = ''
deleteError.value = null
}
async function confirmDoDelete(id) {
pendingAction[id] = true
deleteError.value = null
try {
await api.adminDeleteUser(id, deletePassword.value)
users.value = users.value.filter(u => u.id !== id)
cancelDelete()
} catch (e) {
deleteError.value = e.message
} finally {
delete pendingAction[id]
}
} }
async function confirmDoDeactivate(id) { async function confirmDoDeactivate(id) {