feat(app): auth, orgs/events UI, router guards, and dev tooling
- Add Sanctum auth flow (store, composables, login, axios interceptors) - Add dashboard, organisation list/detail, events CRUD dialogs - Wire router guards, navigation, organisation switcher in layout - Replace Vuexy @db types in NavSearchBar; add @iconify/types; themeConfig title typing - Vuetify settings.scss + resolve configFile via fileURLToPath; drop dead path aliases - Root index redirects to dashboard; fix events table route name - API: DevSeeder + DatabaseSeeder updates; docs TEST_SCENARIO; corporate identity assets Made-with: Cursor
This commit is contained in:
179
apps/app/src/components/events/CreateEventDialog.vue
Normal file
179
apps/app/src/components/events/CreateEventDialog.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<script setup lang="ts">
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import { useCreateEvent } from '@/composables/api/useEvents'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
|
||||
const props = defineProps<{
|
||||
orgId: string
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<boolean>({ required: true })
|
||||
|
||||
const orgIdRef = computed(() => props.orgId)
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
slug: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
timezone: 'Europe/Amsterdam',
|
||||
})
|
||||
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const refVForm = ref<VForm>()
|
||||
const showSuccess = ref(false)
|
||||
|
||||
const { mutate: createEvent, isPending } = useCreateEvent(orgIdRef)
|
||||
|
||||
const timezoneOptions = [
|
||||
{ title: 'Europe/Amsterdam', value: 'Europe/Amsterdam' },
|
||||
{ title: 'Europe/London', value: 'Europe/London' },
|
||||
{ title: 'Europe/Paris', value: 'Europe/Paris' },
|
||||
{ title: 'UTC', value: 'UTC' },
|
||||
]
|
||||
|
||||
watch(() => form.value.name, (name) => {
|
||||
form.value.slug = name
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
})
|
||||
|
||||
const endDateRule = (v: string) => {
|
||||
if (!v) return 'Einddatum is verplicht'
|
||||
if (form.value.start_date && v < form.value.start_date) {
|
||||
return 'Einddatum moet op of na startdatum liggen'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
form.value = { name: '', slug: '', start_date: '', end_date: '', timezone: 'Europe/Amsterdam' }
|
||||
errors.value = {}
|
||||
refVForm.value?.resetValidation()
|
||||
}
|
||||
|
||||
function onSubmit() {
|
||||
refVForm.value?.validate().then(({ valid }) => {
|
||||
if (!valid) return
|
||||
|
||||
errors.value = {}
|
||||
|
||||
createEvent(form.value, {
|
||||
onSuccess: () => {
|
||||
modelValue.value = false
|
||||
showSuccess.value = true
|
||||
resetForm()
|
||||
},
|
||||
onError: (err: any) => {
|
||||
const data = err.response?.data
|
||||
if (data?.errors) {
|
||||
errors.value = Object.fromEntries(
|
||||
Object.entries(data.errors).map(([k, v]) => [k, (v as string[])[0]]),
|
||||
)
|
||||
}
|
||||
else if (data?.message) {
|
||||
errors.value = { name: data.message }
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
v-model="modelValue"
|
||||
max-width="550"
|
||||
@after-leave="resetForm"
|
||||
>
|
||||
<VCard title="Nieuw evenement">
|
||||
<VCardText>
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.name"
|
||||
label="Naam"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.name"
|
||||
autofocus
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.slug"
|
||||
label="Slug"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.slug"
|
||||
hint="Wordt gebruikt in de URL"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.start_date"
|
||||
label="Startdatum"
|
||||
type="date"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.start_date"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.end_date"
|
||||
label="Einddatum"
|
||||
type="date"
|
||||
:rules="[requiredValidator, endDateRule]"
|
||||
:error-messages="errors.end_date"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppSelect
|
||||
v-model="form.timezone"
|
||||
label="Tijdzone"
|
||||
:items="timezoneOptions"
|
||||
:error-messages="errors.timezone"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
:loading="isPending"
|
||||
@click="onSubmit"
|
||||
>
|
||||
Aanmaken
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<VSnackbar
|
||||
v-model="showSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Evenement aangemaakt
|
||||
</VSnackbar>
|
||||
</template>
|
||||
199
apps/app/src/components/events/EditEventDialog.vue
Normal file
199
apps/app/src/components/events/EditEventDialog.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<script setup lang="ts">
|
||||
import { VForm } from 'vuetify/components/VForm'
|
||||
import { useUpdateEvent } from '@/composables/api/useEvents'
|
||||
import { requiredValidator } from '@core/utils/validators'
|
||||
import type { EventStatus, EventType } from '@/types/event'
|
||||
|
||||
const props = defineProps<{
|
||||
event: EventType
|
||||
orgId: string
|
||||
}>()
|
||||
|
||||
const modelValue = defineModel<boolean>({ required: true })
|
||||
|
||||
const orgIdRef = computed(() => props.orgId)
|
||||
const eventIdRef = computed(() => props.event.id)
|
||||
|
||||
const form = ref({
|
||||
name: '',
|
||||
slug: '',
|
||||
start_date: '',
|
||||
end_date: '',
|
||||
timezone: '',
|
||||
status: '' as EventStatus,
|
||||
})
|
||||
|
||||
const errors = ref<Record<string, string>>({})
|
||||
const refVForm = ref<VForm>()
|
||||
const showSuccess = ref(false)
|
||||
|
||||
const { mutate: updateEvent, isPending } = useUpdateEvent(orgIdRef, eventIdRef)
|
||||
|
||||
const timezoneOptions = [
|
||||
{ title: 'Europe/Amsterdam', value: 'Europe/Amsterdam' },
|
||||
{ title: 'Europe/London', value: 'Europe/London' },
|
||||
{ title: 'Europe/Paris', value: 'Europe/Paris' },
|
||||
{ title: 'UTC', value: 'UTC' },
|
||||
]
|
||||
|
||||
const statusOptions: { title: string; value: EventStatus }[] = [
|
||||
{ title: 'Draft', value: 'draft' },
|
||||
{ title: 'Published', value: 'published' },
|
||||
{ title: 'Registration Open', value: 'registration_open' },
|
||||
{ title: 'Build-up', value: 'buildup' },
|
||||
{ title: 'Show Day', value: 'showday' },
|
||||
{ title: 'Tear-down', value: 'teardown' },
|
||||
{ title: 'Closed', value: 'closed' },
|
||||
]
|
||||
|
||||
const endDateRule = (v: string) => {
|
||||
if (!v) return 'Einddatum is verplicht'
|
||||
if (form.value.start_date && v < form.value.start_date) {
|
||||
return 'Einddatum moet op of na startdatum liggen'
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
watch(() => props.event, (ev) => {
|
||||
form.value = {
|
||||
name: ev.name,
|
||||
slug: ev.slug,
|
||||
start_date: ev.start_date,
|
||||
end_date: ev.end_date,
|
||||
timezone: ev.timezone,
|
||||
status: ev.status,
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
function onSubmit() {
|
||||
refVForm.value?.validate().then(({ valid }) => {
|
||||
if (!valid) return
|
||||
|
||||
errors.value = {}
|
||||
|
||||
updateEvent(form.value, {
|
||||
onSuccess: () => {
|
||||
modelValue.value = false
|
||||
showSuccess.value = true
|
||||
},
|
||||
onError: (err: any) => {
|
||||
const data = err.response?.data
|
||||
if (data?.errors) {
|
||||
errors.value = Object.fromEntries(
|
||||
Object.entries(data.errors).map(([k, v]) => [k, (v as string[])[0]]),
|
||||
)
|
||||
}
|
||||
else if (data?.message) {
|
||||
errors.value = { name: data.message }
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VDialog
|
||||
v-model="modelValue"
|
||||
max-width="550"
|
||||
>
|
||||
<VCard title="Evenement bewerken">
|
||||
<VCardText>
|
||||
<VForm
|
||||
ref="refVForm"
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.name"
|
||||
label="Naam"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.name"
|
||||
autofocus
|
||||
/>
|
||||
</VCol>
|
||||
<VCol cols="12">
|
||||
<AppTextField
|
||||
v-model="form.slug"
|
||||
label="Slug"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.slug"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.start_date"
|
||||
label="Startdatum"
|
||||
type="date"
|
||||
:rules="[requiredValidator]"
|
||||
:error-messages="errors.start_date"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppTextField
|
||||
v-model="form.end_date"
|
||||
label="Einddatum"
|
||||
type="date"
|
||||
:rules="[requiredValidator, endDateRule]"
|
||||
:error-messages="errors.end_date"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="form.timezone"
|
||||
label="Tijdzone"
|
||||
:items="timezoneOptions"
|
||||
:error-messages="errors.timezone"
|
||||
/>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<AppSelect
|
||||
v-model="form.status"
|
||||
label="Status"
|
||||
:items="statusOptions"
|
||||
:error-messages="errors.status"
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</VCardText>
|
||||
<VCardActions>
|
||||
<VSpacer />
|
||||
<VBtn
|
||||
variant="text"
|
||||
@click="modelValue = false"
|
||||
>
|
||||
Annuleren
|
||||
</VBtn>
|
||||
<VBtn
|
||||
color="primary"
|
||||
:loading="isPending"
|
||||
@click="onSubmit"
|
||||
>
|
||||
Opslaan
|
||||
</VBtn>
|
||||
</VCardActions>
|
||||
</VCard>
|
||||
</VDialog>
|
||||
|
||||
<VSnackbar
|
||||
v-model="showSuccess"
|
||||
color="success"
|
||||
:timeout="3000"
|
||||
>
|
||||
Evenement bijgewerkt
|
||||
</VSnackbar>
|
||||
</template>
|
||||
Reference in New Issue
Block a user