Files
kestrelos/app/pages/members.vue
Madison Grubb 4e51ca5509
All checks were successful
ci/woodpecker/pr/pr Pipeline was successful
new nav system
2026-02-14 22:47:05 -05:00

193 lines
4.9 KiB
Vue

<template>
<div class="p-6">
<h2 class="kestrel-page-heading mb-2">
Members
</h2>
<p
v-if="!user"
class="text-sm text-kestrel-muted"
>
Sign in to view members.
</p>
<p
v-else-if="!canEditPois"
class="text-sm text-kestrel-muted"
>
You don't have access to the members list.
</p>
<template v-else>
<p
v-if="isAdmin"
class="mb-3 text-xs text-kestrel-muted"
>
Only admins can change roles and manage local users. OIDC users are managed via your identity provider.
</p>
<div
v-if="isAdmin"
class="mb-3 flex justify-start"
>
<button
type="button"
class="rounded border border-kestrel-accent bg-kestrel-accent/10 px-3 py-1.5 text-sm font-medium text-kestrel-accent hover:bg-kestrel-accent-dim"
@click="openAddUserModal"
>
Add user
</button>
</div>
<MembersTable
:users="users"
:role-by-user-id="roleByUserId"
:role-options="roleOptions"
:is-admin="isAdmin"
:current-user-id="user?.id ?? null"
:open-role-dropdown-id="openRoleDropdownId"
@toggle-role-dropdown="toggleRoleDropdown"
@close-role-dropdown="openRoleDropdownId = null"
@select-role="selectRole"
@save-role="saveRole"
@edit-user="openEditUser"
@delete-confirm="openDeleteConfirm"
/>
<!-- Add user modal -->
<AddUserModal
:show="addUserModalOpen"
:submit-error="createError"
@close="closeAddUserModal"
@submit="onAddUserSubmit"
/>
<DeleteUserConfirmModal
:user="deleteConfirmUser"
@close="deleteConfirmUser = null"
@confirm="confirmDeleteUser"
/>
<EditUserModal
:user="editUserModal"
:submit-error="editError"
@close="editUserModal = null"
@submit="onEditUserSubmit"
/>
</template>
</div>
</template>
<script setup>
const { user, isAdmin, canEditPois, refresh: refreshUser } = useUser()
const { data: usersData, refresh: refreshUsers } = useAsyncData(
'users',
() => $fetch('/api/users').catch(() => []),
{ default: () => [] },
)
const users = computed(() => Object.freeze([...(usersData.value ?? [])]))
const roleOptions = ['admin', 'leader', 'member']
const pendingRoleUpdates = ref({})
const roleByUserId = computed(() => {
const base = Object.fromEntries(users.value.map(u => [u.id, u.role]))
return { ...base, ...pendingRoleUpdates.value }
})
const openRoleDropdownId = ref(null)
const addUserModalOpen = ref(false)
const createError = ref('')
const editUserModal = ref(null)
const editError = ref('')
const deleteConfirmUser = ref(null)
watch(user, () => {
if (canEditPois.value) refreshUsers()
}, { immediate: true })
function toggleRoleDropdown(userId) {
openRoleDropdownId.value = openRoleDropdownId.value === userId ? null : userId
}
function selectRole(userId, role) {
pendingRoleUpdates.value = { ...pendingRoleUpdates.value, [userId]: role }
openRoleDropdownId.value = null
}
async function saveRole(id) {
const role = roleByUserId.value[id]
if (!role) return
try {
await $fetch(`/api/users/${id}`, { method: 'PATCH', body: { role } })
await refreshUsers()
pendingRoleUpdates.value = Object.fromEntries(
Object.entries(pendingRoleUpdates.value).filter(([k]) => k !== id),
)
}
catch {
// could set error state
}
}
function openAddUserModal() {
addUserModalOpen.value = true
createError.value = ''
}
function closeAddUserModal() {
addUserModalOpen.value = false
createError.value = ''
}
async function onAddUserSubmit(payload) {
createError.value = ''
try {
await $fetch('/api/users', {
method: 'POST',
body: {
identifier: payload.identifier,
password: payload.password,
role: payload.role,
},
})
closeAddUserModal()
await refreshUsers()
}
catch (e) {
createError.value = e.data?.message || e.message || 'Failed to create user'
}
}
function openEditUser(u) {
editUserModal.value = u
editError.value = ''
}
async function onEditUserSubmit(payload) {
const u = editUserModal.value
if (!u) return
editError.value = ''
const body = { identifier: payload.identifier.trim() }
if (payload.password) body.password = payload.password
try {
await $fetch(`/api/users/${u.id}`, { method: 'PATCH', body })
editUserModal.value = null
await refreshUsers()
await refreshUser()
}
catch (e) {
editError.value = e.data?.message || e.message || 'Failed to update user'
}
}
function openDeleteConfirm(u) {
deleteConfirmUser.value = u
}
async function confirmDeleteUser() {
const u = deleteConfirmUser.value
if (!u) return
try {
await $fetch(`/api/users/${u.id}`, { method: 'DELETE' })
deleteConfirmUser.value = null
await refreshUsers()
}
catch (e) {
alert(e.data?.message || e.message || 'Failed to remove user')
}
}
</script>