feat(portal): horizontal navbar layout with avatar menu and profile restructuring
Replace the simple inline header with a proper Vuexy-style horizontal navbar featuring left (logo + event switcher), center (conditional menu items based on approval status), and right (avatar dropdown with profile link and logout) sections. Move profile page from /profile to /profiel as a platform-level page with "Mijn evenementen" section, removing the event-scoped status card and remarks field. Registration and success pages now use the portal layout with hideEventMenu meta so they get the navbar when logged in but no event menu items. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
426
apps/portal/src/pages/profiel.vue
Normal file
426
apps/portal/src/pages/profiel.vue
Normal file
@@ -0,0 +1,426 @@
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import { usePortalStore } from '@/stores/usePortalStore'
|
||||
import { useUpdateProfile, useUpdatePassword } from '@/composables/api/usePortalProfile'
|
||||
|
||||
definePage({
|
||||
name: 'portal-profiel',
|
||||
meta: {
|
||||
layout: 'portal',
|
||||
requiresAuth: true,
|
||||
hideEventMenu: true,
|
||||
},
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const portal = usePortalStore()
|
||||
const router = useRouter()
|
||||
|
||||
const updateProfileMutation = useUpdateProfile()
|
||||
const updatePasswordMutation = useUpdatePassword()
|
||||
|
||||
const snackbar = ref(false)
|
||||
const snackbarMessage = ref('')
|
||||
const snackbarColor = ref('success')
|
||||
|
||||
// Profile form
|
||||
const profileForm = ref({
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
phone: '',
|
||||
date_of_birth: '',
|
||||
})
|
||||
const profileError = ref<string | null>(null)
|
||||
|
||||
// Password form
|
||||
const passwordForm = ref({
|
||||
current_password: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
})
|
||||
const passwordError = ref<string | null>(null)
|
||||
const passwordFieldErrors = ref<Record<string, string>>({})
|
||||
const showCurrentPassword = ref(false)
|
||||
const showNewPassword = ref(false)
|
||||
const showConfirmPassword = ref(false)
|
||||
|
||||
// Populate profile form from auth user / current person data
|
||||
watch(
|
||||
[() => authStore.user, () => portal.currentPerson],
|
||||
([user, person]) => {
|
||||
profileForm.value = {
|
||||
first_name: person?.first_name ?? user?.first_name ?? '',
|
||||
last_name: person?.last_name ?? user?.last_name ?? '',
|
||||
phone: person?.phone ?? '',
|
||||
date_of_birth: person?.date_of_birth ?? '',
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// Status helpers for event list
|
||||
function statusColor(status: string): string {
|
||||
if (status === 'approved') return 'success'
|
||||
if (status === 'pending' || status === 'applied' || status === 'invited') return 'warning'
|
||||
if (status === 'rejected') return 'error'
|
||||
|
||||
return 'secondary'
|
||||
}
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
const map: Record<string, string> = {
|
||||
pending: 'In behandeling',
|
||||
applied: 'In behandeling',
|
||||
invited: 'Uitgenodigd',
|
||||
approved: 'Goedgekeurd',
|
||||
rejected: 'Afgewezen',
|
||||
no_show: 'Niet verschenen',
|
||||
}
|
||||
|
||||
return map[status] ?? status
|
||||
}
|
||||
|
||||
function formatEventDates(startDate: string, endDate: string): string {
|
||||
try {
|
||||
const start = new Date(`${startDate}T12:00:00`)
|
||||
const end = new Date(`${endDate}T12:00:00`)
|
||||
const opts: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }
|
||||
|
||||
return `${start.toLocaleDateString('nl-NL', opts)} – ${end.toLocaleDateString('nl-NL', opts)}`
|
||||
}
|
||||
catch {
|
||||
return `${startDate} – ${endDate}`
|
||||
}
|
||||
}
|
||||
|
||||
function viewEvent(eventId: string) {
|
||||
portal.setActiveEvent(eventId)
|
||||
router.push('/dashboard')
|
||||
}
|
||||
|
||||
async function saveProfile() {
|
||||
profileError.value = null
|
||||
|
||||
if (!portal.activeEventId) return
|
||||
|
||||
try {
|
||||
const result = await updateProfileMutation.mutateAsync({
|
||||
event_id: portal.activeEventId,
|
||||
...profileForm.value,
|
||||
phone: profileForm.value.phone || null,
|
||||
date_of_birth: profileForm.value.date_of_birth || null,
|
||||
})
|
||||
|
||||
// Refresh person data and auth user
|
||||
await Promise.all([
|
||||
portal.fetchCurrentPerson(),
|
||||
authStore.fetchUser(),
|
||||
])
|
||||
|
||||
snackbarMessage.value = result.message
|
||||
snackbarColor.value = 'success'
|
||||
snackbar.value = true
|
||||
}
|
||||
catch (err: any) {
|
||||
const data = err?.response?.data
|
||||
if (data?.errors) {
|
||||
const firstError = Object.values(data.errors).flat()[0] as string
|
||||
profileError.value = firstError
|
||||
}
|
||||
else {
|
||||
profileError.value = data?.message ?? 'Er is een fout opgetreden.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function savePassword() {
|
||||
passwordError.value = null
|
||||
passwordFieldErrors.value = {}
|
||||
|
||||
try {
|
||||
const result = await updatePasswordMutation.mutateAsync(passwordForm.value)
|
||||
|
||||
passwordForm.value = {
|
||||
current_password: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
}
|
||||
|
||||
snackbarMessage.value = result.message
|
||||
snackbarColor.value = 'success'
|
||||
snackbar.value = true
|
||||
}
|
||||
catch (err: any) {
|
||||
const data = err?.response?.data
|
||||
if (data?.errors) {
|
||||
passwordFieldErrors.value = {}
|
||||
for (const [key, messages] of Object.entries(data.errors)) {
|
||||
passwordFieldErrors.value[key] = (messages as string[])[0]
|
||||
}
|
||||
}
|
||||
else {
|
||||
passwordError.value = data?.message ?? 'Er is een fout opgetreden.'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow justify="center">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
lg="6"
|
||||
>
|
||||
<h4 class="text-h4 mb-4">
|
||||
Mijn Profiel
|
||||
</h4>
|
||||
|
||||
<!-- Profile form -->
|
||||
<VCard class="mb-4">
|
||||
<VCardTitle>Persoonlijke gegevens</VCardTitle>
|
||||
<VCardText>
|
||||
<VAlert
|
||||
v-if="profileError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ profileError }}
|
||||
</VAlert>
|
||||
|
||||
<VForm @submit.prevent="saveProfile">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.first_name"
|
||||
label="Voornaam"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.last_name"
|
||||
label="Achternaam"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
:model-value="authStore.user?.email"
|
||||
label="E-mailadres"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
readonly
|
||||
prepend-inner-icon="tabler-lock"
|
||||
/>
|
||||
<p class="text-caption text-medium-emphasis mt-1 mb-0">
|
||||
Je e-mailadres kan niet worden gewijzigd.
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.phone"
|
||||
label="Telefoonnummer"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
prepend-inner-icon="tabler-phone"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.date_of_birth"
|
||||
label="Geboortedatum"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
type="date"
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<VIcon
|
||||
icon="tabler-calendar"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<div class="d-flex justify-end mt-4">
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="updateProfileMutation.isPending.value"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Password change -->
|
||||
<VCard class="mb-4">
|
||||
<VCardTitle>Wachtwoord wijzigen</VCardTitle>
|
||||
<VCardText>
|
||||
<VAlert
|
||||
v-if="passwordError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ passwordError }}
|
||||
</VAlert>
|
||||
|
||||
<VForm @submit.prevent="savePassword">
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="passwordForm.current_password"
|
||||
label="Huidig wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showCurrentPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showCurrentPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.current_password"
|
||||
@click:append-inner="showCurrentPassword = !showCurrentPassword"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="passwordForm.password"
|
||||
label="Nieuw wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showNewPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.password"
|
||||
@click:append-inner="showNewPassword = !showNewPassword"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="passwordForm.password_confirmation"
|
||||
label="Bevestig nieuw wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showConfirmPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.password_confirmation"
|
||||
@click:append-inner="showConfirmPassword = !showConfirmPassword"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<div class="d-flex justify-end mt-4">
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="updatePasswordMutation.isPending.value"
|
||||
>
|
||||
Wachtwoord wijzigen
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- My events -->
|
||||
<VCard v-if="portal.userEvents.length > 0">
|
||||
<VCardTitle>Mijn evenementen</VCardTitle>
|
||||
<VCardText class="pa-0">
|
||||
<VList>
|
||||
<VListItem
|
||||
v-for="ev in portal.userEvents"
|
||||
:key="ev.event_id"
|
||||
class="py-3"
|
||||
>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-calendar-event"
|
||||
size="24"
|
||||
color="primary"
|
||||
class="me-1"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<VListItemTitle class="font-weight-medium">
|
||||
{{ ev.event_name }}
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle class="text-caption mt-1">
|
||||
{{ formatEventDates(ev.start_date, ev.end_date) }}
|
||||
</VListItemSubtitle>
|
||||
|
||||
<template #append>
|
||||
<div class="d-flex align-center gap-2">
|
||||
<VChip
|
||||
:color="statusColor(ev.person_status)"
|
||||
size="small"
|
||||
label
|
||||
>
|
||||
{{ statusLabel(ev.person_status) }}
|
||||
</VChip>
|
||||
<VBtn
|
||||
variant="text"
|
||||
color="primary"
|
||||
size="small"
|
||||
@click="viewEvent(ev.event_id)"
|
||||
>
|
||||
Bekijk
|
||||
</VBtn>
|
||||
</div>
|
||||
</template>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- No events -->
|
||||
<VAlert
|
||||
v-else
|
||||
type="info"
|
||||
variant="tonal"
|
||||
>
|
||||
Je bent nog niet aangemeld voor een evenement.
|
||||
</VAlert>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<VSnackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="4000"
|
||||
>
|
||||
{{ snackbarMessage }}
|
||||
</VSnackbar>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
@@ -1,389 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useAuthStore } from '@/stores/useAuthStore'
|
||||
import { usePortalStore } from '@/stores/usePortalStore'
|
||||
import { useUpdateProfile, useUpdatePassword } from '@/composables/api/usePortalProfile'
|
||||
|
||||
definePage({
|
||||
name: 'portal-profile',
|
||||
meta: {
|
||||
layout: 'portal',
|
||||
requiresAuth: true,
|
||||
},
|
||||
})
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const portal = usePortalStore()
|
||||
|
||||
const updateProfileMutation = useUpdateProfile()
|
||||
const updatePasswordMutation = useUpdatePassword()
|
||||
|
||||
const snackbar = ref(false)
|
||||
const snackbarMessage = ref('')
|
||||
const snackbarColor = ref('success')
|
||||
|
||||
// Profile form
|
||||
const profileForm = ref({
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
phone: '',
|
||||
date_of_birth: '',
|
||||
remarks: '',
|
||||
})
|
||||
const profileError = ref<string | null>(null)
|
||||
|
||||
// Password form
|
||||
const passwordForm = ref({
|
||||
current_password: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
})
|
||||
const passwordError = ref<string | null>(null)
|
||||
const passwordFieldErrors = ref<Record<string, string>>({})
|
||||
const showCurrentPassword = ref(false)
|
||||
const showNewPassword = ref(false)
|
||||
const showConfirmPassword = ref(false)
|
||||
|
||||
// Populate profile form from current person data
|
||||
watch(() => portal.currentPerson, (person) => {
|
||||
if (person) {
|
||||
profileForm.value = {
|
||||
first_name: person.first_name ?? authStore.user?.first_name ?? '',
|
||||
last_name: person.last_name ?? authStore.user?.last_name ?? '',
|
||||
phone: person.phone ?? '',
|
||||
date_of_birth: person.date_of_birth ?? '',
|
||||
remarks: person.remarks ?? '',
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
const statusConfig: Record<string, { label: string; color: string }> = {
|
||||
pending: { label: 'In afwachting', color: 'warning' },
|
||||
approved: { label: 'Goedgekeurd', color: 'success' },
|
||||
rejected: { label: 'Afgewezen', color: 'error' },
|
||||
}
|
||||
|
||||
const effectiveStatus = computed(() => {
|
||||
const s = portal.currentPerson?.status ?? portal.activeEvent?.person_status ?? 'pending'
|
||||
|
||||
return statusConfig[s] ?? statusConfig.pending
|
||||
})
|
||||
|
||||
async function saveProfile() {
|
||||
profileError.value = null
|
||||
|
||||
if (!portal.activeEventId) return
|
||||
|
||||
try {
|
||||
const result = await updateProfileMutation.mutateAsync({
|
||||
event_id: portal.activeEventId,
|
||||
...profileForm.value,
|
||||
phone: profileForm.value.phone || null,
|
||||
date_of_birth: profileForm.value.date_of_birth || null,
|
||||
remarks: profileForm.value.remarks || null,
|
||||
})
|
||||
|
||||
// Refresh person data and auth user
|
||||
await Promise.all([
|
||||
portal.fetchCurrentPerson(),
|
||||
authStore.fetchUser(),
|
||||
])
|
||||
|
||||
snackbarMessage.value = result.message
|
||||
snackbarColor.value = 'success'
|
||||
snackbar.value = true
|
||||
}
|
||||
catch (err: any) {
|
||||
const data = err?.response?.data
|
||||
if (data?.errors) {
|
||||
const firstError = Object.values(data.errors).flat()[0] as string
|
||||
profileError.value = firstError
|
||||
}
|
||||
else {
|
||||
profileError.value = data?.message ?? 'Er is een fout opgetreden.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function savePassword() {
|
||||
passwordError.value = null
|
||||
passwordFieldErrors.value = {}
|
||||
|
||||
try {
|
||||
const result = await updatePasswordMutation.mutateAsync(passwordForm.value)
|
||||
|
||||
passwordForm.value = {
|
||||
current_password: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
}
|
||||
|
||||
snackbarMessage.value = result.message
|
||||
snackbarColor.value = 'success'
|
||||
snackbar.value = true
|
||||
}
|
||||
catch (err: any) {
|
||||
const data = err?.response?.data
|
||||
if (data?.errors) {
|
||||
passwordFieldErrors.value = {}
|
||||
for (const [key, messages] of Object.entries(data.errors)) {
|
||||
passwordFieldErrors.value[key] = (messages as string[])[0]
|
||||
}
|
||||
}
|
||||
else {
|
||||
passwordError.value = data?.message ?? 'Er is een fout opgetreden.'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VRow justify="center">
|
||||
<VCol
|
||||
cols="12"
|
||||
md="8"
|
||||
lg="6"
|
||||
>
|
||||
<h4 class="text-h4 mb-4">
|
||||
Mijn Profiel
|
||||
</h4>
|
||||
|
||||
<!-- Loading state -->
|
||||
<VSkeletonLoader
|
||||
v-if="portal.isLoadingPerson && !portal.currentPerson"
|
||||
type="card"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
<template v-else-if="portal.currentPerson">
|
||||
<!-- Status & Event info -->
|
||||
<VCard class="mb-4">
|
||||
<VCardText>
|
||||
<div class="d-flex align-center justify-space-between flex-wrap gap-2">
|
||||
<div>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Evenement
|
||||
</div>
|
||||
<div class="text-body-1 font-weight-medium">
|
||||
{{ portal.activeEvent?.event_name }}
|
||||
</div>
|
||||
</div>
|
||||
<VChip
|
||||
:color="effectiveStatus.color"
|
||||
variant="tonal"
|
||||
size="small"
|
||||
>
|
||||
{{ effectiveStatus.label }}
|
||||
</VChip>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Profile form -->
|
||||
<VCard class="mb-4">
|
||||
<VCardTitle>Persoonlijke gegevens</VCardTitle>
|
||||
<VCardText>
|
||||
<VAlert
|
||||
v-if="profileError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ profileError }}
|
||||
</VAlert>
|
||||
|
||||
<VForm @submit.prevent="saveProfile">
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.first_name"
|
||||
label="Voornaam"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.last_name"
|
||||
label="Achternaam"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
:model-value="portal.currentPerson.email || authStore.user?.email"
|
||||
label="E-mailadres"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
readonly
|
||||
prepend-inner-icon="tabler-lock"
|
||||
/>
|
||||
<p class="text-caption text-medium-emphasis mt-1 mb-0">
|
||||
Je e-mailadres kan niet worden gewijzigd.
|
||||
</p>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.phone"
|
||||
label="Telefoonnummer"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
prepend-inner-icon="tabler-phone"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="profileForm.date_of_birth"
|
||||
label="Geboortedatum"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
type="date"
|
||||
>
|
||||
<template #prepend-inner>
|
||||
<VIcon
|
||||
icon="tabler-calendar"
|
||||
size="20"
|
||||
/>
|
||||
</template>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<VTextarea
|
||||
v-model="profileForm.remarks"
|
||||
label="Opmerkingen"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
rows="3"
|
||||
placeholder="Allergieën, dieetwensen, overige opmerkingen..."
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<div class="d-flex justify-end mt-4">
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="updateProfileMutation.isPending.value"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Password change -->
|
||||
<VCard>
|
||||
<VCardTitle>Wachtwoord wijzigen</VCardTitle>
|
||||
<VCardText>
|
||||
<VAlert
|
||||
v-if="passwordError"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mb-4"
|
||||
>
|
||||
{{ passwordError }}
|
||||
</VAlert>
|
||||
|
||||
<VForm @submit.prevent="savePassword">
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<VTextField
|
||||
v-model="passwordForm.current_password"
|
||||
label="Huidig wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showCurrentPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showCurrentPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.current_password"
|
||||
@click:append-inner="showCurrentPassword = !showCurrentPassword"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="passwordForm.password"
|
||||
label="Nieuw wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showNewPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showNewPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.password"
|
||||
@click:append-inner="showNewPassword = !showNewPassword"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="passwordForm.password_confirmation"
|
||||
label="Bevestig nieuw wachtwoord"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
hide-details="auto"
|
||||
:type="showConfirmPassword ? 'text' : 'password'"
|
||||
:append-inner-icon="showConfirmPassword ? 'tabler-eye-off' : 'tabler-eye'"
|
||||
:error-messages="passwordFieldErrors.password_confirmation"
|
||||
@click:append-inner="showConfirmPassword = !showConfirmPassword"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<div class="d-flex justify-end mt-4">
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="updatePasswordMutation.isPending.value"
|
||||
>
|
||||
Wachtwoord wijzigen
|
||||
</VBtn>
|
||||
</div>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</template>
|
||||
|
||||
<VAlert
|
||||
v-else
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
>
|
||||
We konden je profiel niet laden. Probeer het later opnieuw.
|
||||
</VAlert>
|
||||
|
||||
<!-- Snackbar -->
|
||||
<VSnackbar
|
||||
v-model="snackbar"
|
||||
:color="snackbarColor"
|
||||
:timeout="4000"
|
||||
>
|
||||
{{ snackbarMessage }}
|
||||
</VSnackbar>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</template>
|
||||
@@ -21,8 +21,9 @@ import type {
|
||||
definePage({
|
||||
name: 'volunteer-register',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'portal',
|
||||
requiresAuth: false,
|
||||
hideEventMenu: true,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@ import { useAuthStore } from '@/stores/useAuthStore'
|
||||
definePage({
|
||||
name: 'register-success',
|
||||
meta: {
|
||||
layout: 'blank',
|
||||
layout: 'portal',
|
||||
requiresAuth: false,
|
||||
hideEventMenu: true,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user