feat: platform admin frontend — pages, composables, navigation, impersonation
Build the frontend for platform admin in apps/app/: - TypeScript types (admin.ts) and API composable (useAdmin.ts) with TanStack Query for all admin endpoints - ImpersonationStore (Pinia) + ImpersonationBanner component integrated in the main layout, with token-based session management - Platform navigation section (conditionally shown for super_admin users) - Route guard blocking /platform/* for non-super_admin users - 6 pages: dashboard with stats cards, organisations list/detail, users list/detail with impersonation, activity log with expandable rows - All pages implement loading/error/empty states per conventions - Vite build passes cleanly Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
404
apps/app/src/pages/platform/users/[id].vue
Normal file
404
apps/app/src/pages/platform/users/[id].vue
Normal file
@@ -0,0 +1,404 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
useAdminUser,
|
||||
useUpdateAdminUser,
|
||||
useStartImpersonation,
|
||||
} from '@/composables/api/useAdmin'
|
||||
import { useImpersonationStore } from '@/stores/useImpersonationStore'
|
||||
import type { AdminUser, UpdateAdminUserPayload } from '@/types/admin'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
navActiveLink: 'platform-users',
|
||||
},
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const impersonationStore = useImpersonationStore()
|
||||
|
||||
const userId = computed(() => String((route.params as { id: string }).id))
|
||||
|
||||
const { data: user, isLoading, isError, refetch } = useAdminUser(userId)
|
||||
|
||||
const roleColorMap: Record<string, string> = {
|
||||
super_admin: 'error',
|
||||
org_admin: 'purple',
|
||||
org_member: 'info',
|
||||
event_manager: 'cyan',
|
||||
}
|
||||
|
||||
const platformRoleOptions = [
|
||||
{ title: 'Super Admin', value: 'super_admin' },
|
||||
]
|
||||
|
||||
// Edit dialog
|
||||
const isEditDialogOpen = ref(false)
|
||||
const editForm = ref<UpdateAdminUserPayload>({})
|
||||
const { mutate: updateUser, isPending: isUpdating } = useUpdateAdminUser()
|
||||
const showEditSuccess = ref(false)
|
||||
|
||||
function openEditDialog() {
|
||||
if (!user.value) return
|
||||
editForm.value = {
|
||||
first_name: user.value.first_name,
|
||||
last_name: user.value.last_name,
|
||||
email: user.value.email,
|
||||
timezone: user.value.timezone,
|
||||
locale: user.value.locale,
|
||||
roles: user.value.roles.filter(r => ['super_admin', 'support_agent'].includes(r)),
|
||||
}
|
||||
isEditDialogOpen.value = true
|
||||
}
|
||||
|
||||
function submitEdit() {
|
||||
updateUser(
|
||||
{ id: userId.value, payload: editForm.value },
|
||||
{
|
||||
onSuccess: () => {
|
||||
isEditDialogOpen.value = false
|
||||
showEditSuccess.value = true
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Impersonation
|
||||
const isImpersonateDialogOpen = ref(false)
|
||||
const { mutate: startImpersonation, isPending: isImpersonating } = useStartImpersonation()
|
||||
|
||||
function confirmImpersonate() {
|
||||
startImpersonation(userId.value, {
|
||||
onSuccess: (result) => {
|
||||
isImpersonateDialogOpen.value = false
|
||||
impersonationStore.startImpersonation(result.token, result.user, result.admin_id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('nl-NL', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
}
|
||||
|
||||
function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map(p => p[0])
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Loading -->
|
||||
<VSkeletonLoader
|
||||
v-if="isLoading"
|
||||
type="card, card"
|
||||
/>
|
||||
|
||||
<!-- Error -->
|
||||
<VAlert
|
||||
v-else-if="isError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
>
|
||||
Kon gebruiker niet laden.
|
||||
<template #append>
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="refetch()"
|
||||
>
|
||||
Opnieuw proberen
|
||||
</VBtn>
|
||||
</template>
|
||||
</VAlert>
|
||||
|
||||
<template v-else-if="user">
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-center justify-space-between mb-6">
|
||||
<div class="d-flex align-center gap-x-3">
|
||||
<VBtn
|
||||
icon="tabler-arrow-left"
|
||||
variant="text"
|
||||
size="small"
|
||||
:to="{ name: 'platform-users' }"
|
||||
/>
|
||||
<VAvatar
|
||||
v-if="user.avatar"
|
||||
size="44"
|
||||
:image="user.avatar"
|
||||
/>
|
||||
<VAvatar
|
||||
v-else
|
||||
size="44"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<span>{{ getInitials(user.full_name) }}</span>
|
||||
</VAvatar>
|
||||
<div>
|
||||
<h4 class="text-h4">
|
||||
{{ user.full_name }}
|
||||
<VChip
|
||||
v-for="role in user.roles"
|
||||
:key="role"
|
||||
:color="roleColorMap[role] ?? 'default'"
|
||||
size="small"
|
||||
class="ms-1"
|
||||
>
|
||||
{{ role }}
|
||||
</VChip>
|
||||
</h4>
|
||||
<p class="text-body-2 text-disabled mb-0">
|
||||
{{ user.email }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-x-2">
|
||||
<VBtn
|
||||
v-if="!user.is_super_admin"
|
||||
variant="tonal"
|
||||
color="warning"
|
||||
prepend-icon="tabler-user-share"
|
||||
@click="isImpersonateDialogOpen = true"
|
||||
>
|
||||
Inloggen als
|
||||
</VBtn>
|
||||
<VBtn
|
||||
prepend-icon="tabler-edit"
|
||||
@click="openEditDialog"
|
||||
>
|
||||
Bewerken
|
||||
</VBtn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Info -->
|
||||
<VRow class="mb-6">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardTitle>Profiel</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="6"
|
||||
>
|
||||
<p class="text-body-2 text-disabled mb-1">
|
||||
Tijdzone
|
||||
</p>
|
||||
<p class="text-body-1">
|
||||
{{ user.timezone }}
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="6"
|
||||
>
|
||||
<p class="text-body-2 text-disabled mb-1">
|
||||
Taal
|
||||
</p>
|
||||
<p class="text-body-1">
|
||||
{{ user.locale === 'nl' ? 'Nederlands' : 'English' }}
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="6"
|
||||
>
|
||||
<p class="text-body-2 text-disabled mb-1">
|
||||
E-mail geverifieerd
|
||||
</p>
|
||||
<p class="text-body-1">
|
||||
<VIcon
|
||||
:icon="user.email_verified_at ? 'tabler-circle-check' : 'tabler-circle-x'"
|
||||
:color="user.email_verified_at ? 'success' : 'error'"
|
||||
size="18"
|
||||
class="me-1"
|
||||
/>
|
||||
{{ user.email_verified_at ? formatDate(user.email_verified_at) : 'Niet geverifieerd' }}
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="6"
|
||||
>
|
||||
<p class="text-body-2 text-disabled mb-1">
|
||||
Aangemaakt
|
||||
</p>
|
||||
<p class="text-body-1">
|
||||
{{ formatDate(user.created_at) }}
|
||||
</p>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- Organisations -->
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VCard>
|
||||
<VCardTitle>Organisaties</VCardTitle>
|
||||
<VCardText
|
||||
v-if="user.organisations.length === 0"
|
||||
class="text-disabled"
|
||||
>
|
||||
Geen organisaties
|
||||
</VCardText>
|
||||
<VList
|
||||
v-else
|
||||
lines="one"
|
||||
>
|
||||
<VListItem
|
||||
v-for="org in user.organisations"
|
||||
:key="org.id"
|
||||
:to="{ name: 'platform-organisations-id', params: { id: org.id } }"
|
||||
>
|
||||
<VListItemTitle>{{ org.name }}</VListItemTitle>
|
||||
<template #append>
|
||||
<VChip
|
||||
:color="roleColorMap[org.role] ?? 'default'"
|
||||
size="x-small"
|
||||
>
|
||||
{{ org.role }}
|
||||
</VChip>
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCard>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<VDialog
|
||||
v-model="isEditDialogOpen"
|
||||
max-width="500"
|
||||
>
|
||||
<VCard title="Gebruiker bewerken">
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editForm.first_name"
|
||||
label="Voornaam"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editForm.last_name"
|
||||
label="Achternaam"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="editForm.email"
|
||||
label="E-mail"
|
||||
type="email"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="editForm.timezone"
|
||||
label="Tijdzone"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="editForm.locale"
|
||||
:items="[{ title: 'Nederlands', value: 'nl' }, { title: 'English', value: 'en' }]"
|
||||
label="Taal"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppSelect
|
||||
v-model="editForm.roles"
|
||||
:items="platformRoleOptions"
|
||||
label="Platform rollen"
|
||||
multiple
|
||||
chips
|
||||
closable-chips
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="tonal"
|
||||
@click="isEditDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
:loading="isUpdating"
|
||||
@click="submitEdit"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Impersonate Dialog -->
|
||||
<VDialog
|
||||
v-model="isImpersonateDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Inloggen als gebruiker">
|
||||
<VCardText>
|
||||
Je gaat inloggen als <strong>{{ user?.full_name }}</strong>
|
||||
({{ user?.email }}). Wil je doorgaan?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isImpersonateDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="warning"
|
||||
:loading="isImpersonating"
|
||||
@click="confirmImpersonate"
|
||||
>
|
||||
Doorgaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Success snackbar -->
|
||||
<VSnackbar
|
||||
v-model="showEditSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Gebruiker bijgewerkt
|
||||
</VSnackbar>
|
||||
</div>
|
||||
</template>
|
||||
364
apps/app/src/pages/platform/users/index.vue
Normal file
364
apps/app/src/pages/platform/users/index.vue
Normal file
@@ -0,0 +1,364 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
useAdminUsers,
|
||||
useStartImpersonation,
|
||||
useDeleteAdminUser,
|
||||
} from '@/composables/api/useAdmin'
|
||||
import { useImpersonationStore } from '@/stores/useImpersonationStore'
|
||||
import type { AdminUser } from '@/types/admin'
|
||||
|
||||
definePage({
|
||||
meta: {
|
||||
navActiveLink: 'platform-users',
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const impersonationStore = useImpersonationStore()
|
||||
|
||||
const search = ref('')
|
||||
const searchDebounced = refDebounced(search, 400)
|
||||
const organisationFilter = ref('')
|
||||
const roleFilter = ref('')
|
||||
const page = ref(1)
|
||||
const itemsPerPage = ref(15)
|
||||
|
||||
const params = computed(() => ({
|
||||
page: page.value,
|
||||
per_page: itemsPerPage.value,
|
||||
search: searchDebounced.value || undefined,
|
||||
organisation_id: organisationFilter.value || undefined,
|
||||
role: roleFilter.value || undefined,
|
||||
}))
|
||||
|
||||
const { data, isLoading, isError, refetch } = useAdminUsers(params)
|
||||
|
||||
const users = computed(() => data.value?.data ?? [])
|
||||
const totalItems = computed(() => data.value?.meta?.total ?? 0)
|
||||
|
||||
const roleOptions = [
|
||||
{ title: 'Alle rollen', value: '' },
|
||||
{ title: 'Super Admin', value: 'super_admin' },
|
||||
{ title: 'Org Admin', value: 'org_admin' },
|
||||
{ title: 'Org Member', value: 'org_member' },
|
||||
{ title: 'Event Manager', value: 'event_manager' },
|
||||
]
|
||||
|
||||
const headers = [
|
||||
{ title: 'Naam', key: 'full_name' },
|
||||
{ title: 'E-mail', key: 'email' },
|
||||
{ title: 'Organisaties', key: 'organisations', sortable: false },
|
||||
{ title: 'Rollen', key: 'roles', sortable: false },
|
||||
{ title: 'Geverifieerd', key: 'email_verified_at', sortable: false, align: 'center' as const },
|
||||
{ title: 'Aangemaakt', key: 'created_at', sortable: false },
|
||||
{ title: '', key: 'actions', sortable: false, align: 'end' as const },
|
||||
]
|
||||
|
||||
// Impersonation
|
||||
const isImpersonateDialogOpen = ref(false)
|
||||
const userToImpersonate = ref<AdminUser | null>(null)
|
||||
const { mutate: startImpersonation, isPending: isImpersonating } = useStartImpersonation()
|
||||
|
||||
function openImpersonateDialog(user: AdminUser) {
|
||||
userToImpersonate.value = user
|
||||
isImpersonateDialogOpen.value = true
|
||||
}
|
||||
|
||||
function confirmImpersonate() {
|
||||
if (!userToImpersonate.value) return
|
||||
|
||||
startImpersonation(userToImpersonate.value.id, {
|
||||
onSuccess: (result) => {
|
||||
isImpersonateDialogOpen.value = false
|
||||
impersonationStore.startImpersonation(result.token, result.user, result.admin_id)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete
|
||||
const isDeleteDialogOpen = ref(false)
|
||||
const userToDelete = ref<AdminUser | null>(null)
|
||||
const { mutate: deleteUser, isPending: isDeleting } = useDeleteAdminUser()
|
||||
const showDeleteSuccess = ref(false)
|
||||
|
||||
function openDeleteDialog(user: AdminUser) {
|
||||
userToDelete.value = user
|
||||
isDeleteDialogOpen.value = true
|
||||
}
|
||||
|
||||
function confirmDelete() {
|
||||
if (!userToDelete.value) return
|
||||
|
||||
deleteUser(userToDelete.value.id, {
|
||||
onSuccess: () => {
|
||||
isDeleteDialogOpen.value = false
|
||||
userToDelete.value = null
|
||||
showDeleteSuccess.value = true
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('nl-NL', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
function getInitials(name: string): string {
|
||||
return name
|
||||
.split(' ')
|
||||
.map(p => p[0])
|
||||
.filter(Boolean)
|
||||
.slice(0, 2)
|
||||
.join('')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
function onRowClick(_event: Event, { item }: { item: AdminUser }) {
|
||||
router.push({ name: 'platform-users-id', params: { id: item.id } })
|
||||
}
|
||||
|
||||
function onUpdateOptions(options: { page: number; itemsPerPage: number }) {
|
||||
page.value = options.page
|
||||
itemsPerPage.value = options.itemsPerPage
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex align-center justify-space-between mb-6">
|
||||
<div>
|
||||
<h4 class="text-h4">
|
||||
Gebruikers
|
||||
</h4>
|
||||
<p class="text-body-1 text-disabled mb-0">
|
||||
Alle gebruikers op het platform
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<VAlert
|
||||
v-if="isError"
|
||||
type="error"
|
||||
class="mb-4"
|
||||
>
|
||||
Kon gebruikers niet laden.
|
||||
<template #append>
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="refetch()"
|
||||
>
|
||||
Opnieuw proberen
|
||||
</VBtn>
|
||||
</template>
|
||||
</VAlert>
|
||||
|
||||
<VCard>
|
||||
<!-- Filters -->
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="search"
|
||||
placeholder="Zoek op naam of e-mail..."
|
||||
prepend-inner-icon="tabler-search"
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="3"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="roleFilter"
|
||||
:items="roleOptions"
|
||||
placeholder="Rol"
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
|
||||
<VDataTableServer
|
||||
:headers="headers"
|
||||
:items="users"
|
||||
:items-length="totalItems"
|
||||
:loading="isLoading"
|
||||
:items-per-page="itemsPerPage"
|
||||
:page="page"
|
||||
hover
|
||||
@update:options="onUpdateOptions"
|
||||
@click:row="onRowClick"
|
||||
>
|
||||
<template #item.full_name="{ item }">
|
||||
<div class="d-flex align-center gap-x-3">
|
||||
<VAvatar
|
||||
v-if="item.avatar"
|
||||
size="34"
|
||||
:image="item.avatar"
|
||||
/>
|
||||
<VAvatar
|
||||
v-else
|
||||
size="34"
|
||||
color="primary"
|
||||
variant="tonal"
|
||||
>
|
||||
<span class="text-sm">{{ getInitials(item.full_name) }}</span>
|
||||
</VAvatar>
|
||||
<span>{{ item.full_name }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.organisations="{ item }">
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<VChip
|
||||
v-for="org in item.organisations.slice(0, 3)"
|
||||
:key="org.id"
|
||||
size="x-small"
|
||||
>
|
||||
{{ org.name }}
|
||||
</VChip>
|
||||
<VChip
|
||||
v-if="item.organisations.length > 3"
|
||||
size="x-small"
|
||||
color="secondary"
|
||||
>
|
||||
+{{ item.organisations.length - 3 }}
|
||||
</VChip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.roles="{ item }">
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<VChip
|
||||
v-for="role in item.roles"
|
||||
:key="role"
|
||||
size="x-small"
|
||||
:color="role === 'super_admin' ? 'error' : 'primary'"
|
||||
>
|
||||
{{ role }}
|
||||
</VChip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #item.email_verified_at="{ item }">
|
||||
<VIcon
|
||||
:icon="item.email_verified_at ? 'tabler-circle-check' : 'tabler-circle-x'"
|
||||
:color="item.email_verified_at ? 'success' : 'error'"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #item.created_at="{ item }">
|
||||
{{ formatDate(item.created_at) }}
|
||||
</template>
|
||||
|
||||
<template #item.actions="{ item }">
|
||||
<div
|
||||
class="d-flex gap-x-1 justify-end"
|
||||
@click.stop
|
||||
>
|
||||
<VBtn
|
||||
icon="tabler-user-share"
|
||||
variant="text"
|
||||
size="small"
|
||||
:disabled="item.is_super_admin"
|
||||
@click="openImpersonateDialog(item)"
|
||||
/>
|
||||
<VBtn
|
||||
icon="tabler-trash"
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
@click="openDeleteDialog(item)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Empty -->
|
||||
<template #no-data>
|
||||
<div class="text-center pa-4 text-disabled">
|
||||
<VIcon
|
||||
icon="tabler-users-minus"
|
||||
size="48"
|
||||
class="mb-2"
|
||||
/>
|
||||
<p>Geen gebruikers gevonden</p>
|
||||
</div>
|
||||
</template>
|
||||
</VDataTableServer>
|
||||
</VCard>
|
||||
|
||||
<!-- Impersonate Dialog -->
|
||||
<VDialog
|
||||
v-model="isImpersonateDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Inloggen als gebruiker">
|
||||
<VCardText>
|
||||
Je gaat inloggen als <strong>{{ userToImpersonate?.full_name }}</strong>
|
||||
({{ userToImpersonate?.email }}). Wil je doorgaan?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isImpersonateDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="warning"
|
||||
:loading="isImpersonating"
|
||||
@click="confirmImpersonate"
|
||||
>
|
||||
Doorgaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Delete Dialog -->
|
||||
<VDialog
|
||||
v-model="isDeleteDialogOpen"
|
||||
max-width="400"
|
||||
>
|
||||
<VCard title="Gebruiker verwijderen">
|
||||
<VCardText>
|
||||
Weet je zeker dat je <strong>{{ userToDelete?.full_name }}</strong> wilt verwijderen?
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="isDeleteDialogOpen = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="error"
|
||||
:loading="isDeleting"
|
||||
@click="confirmDelete"
|
||||
>
|
||||
Verwijderen
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<!-- Snackbars -->
|
||||
<VSnackbar
|
||||
v-model="showDeleteSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Gebruiker verwijderd
|
||||
</VSnackbar>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user