feat: frontend member management

- Leden pagina met VDataTable, rol chips, uitnodigingen sectie
- InviteMemberDialog + EditMemberRoleDialog
- Publieke acceptatiepagina /invitations/[token]
- Router guard uitgebreid met requiresAuth: false support
- MemberCollection backend uitgebreid met volledige pending_invitations lijst
This commit is contained in:
2026-04-08 01:50:38 +02:00
parent 9acb27af3a
commit 230e11cc8d
12 changed files with 1092 additions and 5 deletions

View File

@@ -0,0 +1,142 @@
<script setup lang="ts">
import { VForm } from 'vuetify/components/VForm'
import { useUpdateMemberRole } from '@/composables/api/useMembers'
import { useAuthStore } from '@/stores/useAuthStore'
import { requiredValidator } from '@core/utils/validators'
import type { Member, OrganisationRole } from '@/types/member'
const props = defineProps<{
orgId: string
member: Member
}>()
const modelValue = defineModel<boolean>({ required: true })
const authStore = useAuthStore()
const orgIdRef = computed(() => props.orgId)
const role = ref<OrganisationRole>(props.member.role as OrganisationRole)
const errors = ref<Record<string, string>>({})
const refVForm = ref<VForm>()
const showSuccess = ref(false)
const isSelf = computed(() => authStore.user?.id === props.member.id)
const roleOptions: Array<{ title: string; value: OrganisationRole }> = [
{ title: 'Organisatie Beheerder', value: 'org_admin' },
{ title: 'Organisatie Lid', value: 'org_member' },
{ title: 'Evenement Manager', value: 'event_manager' },
{ title: 'Staf Coördinator', value: 'staff_coordinator' },
{ title: 'Vrijwilliger Coördinator', value: 'volunteer_coordinator' },
]
const { mutate: updateRole, isPending } = useUpdateMemberRole(orgIdRef)
watch(() => props.member, (m) => {
role.value = m.role as OrganisationRole
}, { immediate: true })
function onSubmit() {
if (isSelf.value) return
refVForm.value?.validate().then(({ valid }) => {
if (!valid) return
errors.value = {}
updateRole(
{ userId: props.member.id, role: role.value },
{
onSuccess: () => {
modelValue.value = false
showSuccess.value = true
},
onError: (err: any) => {
const data = err.response?.data
if (data?.errors) {
errors.value = { role: data.errors.role?.[0] ?? '' }
}
else if (data?.message) {
errors.value = { role: data.message }
}
},
},
)
})
}
</script>
<template>
<VDialog
v-model="modelValue"
max-width="500"
>
<VCard title="Rol wijzigen">
<VCardText>
<VRow class="mb-4">
<VCol cols="12">
<div class="text-body-1">
<strong>{{ member.name }}</strong>
</div>
<div class="text-body-2 text-disabled">
{{ member.email }}
</div>
</VCol>
</VRow>
<VForm
ref="refVForm"
@submit.prevent="onSubmit"
>
<VSelect
v-model="role"
label="Rol"
:items="roleOptions"
:rules="[requiredValidator]"
:error-messages="errors.role"
/>
</VForm>
</VCardText>
<VCardActions>
<VSpacer />
<VBtn
variant="text"
@click="modelValue = false"
>
Annuleren
</VBtn>
<VTooltip
v-if="isSelf"
text="Je kunt je eigen rol niet wijzigen"
>
<template #activator="{ props: tooltipProps }">
<div v-bind="tooltipProps">
<VBtn
color="primary"
disabled
>
Opslaan
</VBtn>
</div>
</template>
</VTooltip>
<VBtn
v-else
color="primary"
:loading="isPending"
@click="onSubmit"
>
Opslaan
</VBtn>
</VCardActions>
</VCard>
</VDialog>
<VSnackbar
v-model="showSuccess"
color="success"
:timeout="3000"
>
Rol bijgewerkt
</VSnackbar>
</template>