Skip to content

Commit 30e7166

Browse files
committed
feat: create vent
1 parent 0edac70 commit 30e7166

File tree

8 files changed

+263
-44
lines changed

8 files changed

+263
-44
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<template>
2+
<UModal
3+
v-model:open="open"
4+
:title="$t('event.new')"
5+
description="Add a new event to the database"
6+
>
7+
<UButton icon="i-lucide:plus" :label="$t('event.new')" />
8+
9+
<template #body>
10+
<UForm :state="state" :schema="schema" class="space-y-4" @submit="onSubmit">
11+
<UFormField
12+
required
13+
name="title"
14+
:label="$t('event.title')"
15+
:placeholder="fields.name.placeholder"
16+
:description="$t('validation.provide-in-language', { language: 'English' })"
17+
>
18+
<UInput
19+
v-model="state.title"
20+
class="w-full"
21+
@change="state.slug = slugify(state.title)"
22+
/>
23+
</UFormField>
24+
<UFormField name="cover_url" :label="$t('event.cover-url')">
25+
<UInput
26+
v-model="state.cover_url"
27+
type="url"
28+
class="w-full"
29+
placeholder="https://example.com/cover.jpg"
30+
:cover="state.cover_url ? { src: state.cover_url, alt: '' } : undefined"
31+
>
32+
<template v-if="state.cover_url?.length" #trailing>
33+
<UButton
34+
size="sm"
35+
variant="link"
36+
color="neutral"
37+
icon="i-lucide:circle-x"
38+
:aria-label="$t('general.clear')"
39+
@click="state.cover_url = undefined"
40+
/>
41+
</template>
42+
</UInput>
43+
</UFormField>
44+
<UFormField
45+
required
46+
name="slug"
47+
:label="$t('general.slug')"
48+
description="The unique identifier for this event in the URL."
49+
>
50+
<UInput v-model="state.slug" class="w-full" @input="normalizeSlug" />
51+
</UFormField>
52+
<fieldset class="flex justify-between">
53+
<UFormField
54+
name="start_year"
55+
:label="$t('event.start-year')"
56+
:class="state.start_year ? 'w-50' : 'w-full'"
57+
:description="state.start_year ? undefined : 'A negative number means before Christ'"
58+
>
59+
<UInputNumber
60+
v-model="state.start_year"
61+
:max="2025"
62+
:min="-4026"
63+
class="w-full"
64+
:decrement="false"
65+
:increment="false"
66+
/>
67+
</UFormField>
68+
<UFormField
69+
v-if="state.start_year"
70+
required
71+
class="w-50"
72+
name="start_precision"
73+
:label="$t('date.precision')"
74+
>
75+
<USelect
76+
v-model="state.start_precision"
77+
class="w-full"
78+
:items="fields.datePrecision.items"
79+
/>
80+
</UFormField>
81+
</fieldset>
82+
<fieldset class="flex justify-between">
83+
<UFormField
84+
name="end_year"
85+
:label="$t('event.end-year')"
86+
:class="state.end_year ? 'w-50' : 'w-full'"
87+
:description="state.end_year ? undefined : 'A negative number means before Christ'"
88+
>
89+
<UInputNumber
90+
v-model="state.end_year"
91+
:max="2025"
92+
:min="-4026"
93+
class="w-full"
94+
:decrement="false"
95+
:increment="false"
96+
/>
97+
</UFormField>
98+
<UFormField
99+
v-if="state.end_year"
100+
required
101+
class="w-50"
102+
name="end_precision"
103+
:label="$t('date.precision')"
104+
>
105+
<USelect
106+
v-model="state.end_precision"
107+
class="w-full"
108+
:items="fields.datePrecision.items"
109+
/>
110+
</UFormField>
111+
</fieldset>
112+
<div class="flex justify-end gap-2">
113+
<UButton
114+
type="reset"
115+
color="neutral"
116+
variant="subtle"
117+
:label="$t('general.cancel')"
118+
@click="open = false"
119+
/>
120+
<UButton type="submit" color="primary" variant="solid" :label="$t('general.create')" />
121+
</div>
122+
</UForm>
123+
</template>
124+
</UModal>
125+
</template>
126+
<script setup lang="ts">
127+
import type { FormSubmitEvent } from '@nuxt/ui'
128+
129+
import { z } from 'zod'
130+
131+
import EventsRelatedCard from './EventsRelatedCard.vue'
132+
133+
const open = ref(false)
134+
135+
const { t } = useI18n()
136+
const { fields, rules } = useForm()
137+
138+
const schema = z
139+
.object({
140+
cover_url: rules.url(t('event.cover-url')).optional(),
141+
end_precision: rules.datePrecision(t('date.precision')).optional(),
142+
end_year: rules.year(t('event.end-year')).optional(),
143+
slug: rules.slug,
144+
start_precision: rules.datePrecision(t('date.precision')).optional(),
145+
start_year: rules.year(t('event.start-year')).optional(),
146+
title: rules.name
147+
})
148+
.refine(
149+
(data) => {
150+
if (!data.start_year || !data.end_year) return true
151+
return data.end_year >= data.start_year
152+
},
153+
{
154+
message: t('validation.after-or-equal-to', {
155+
date: t('event.start-year'),
156+
field: t('event.end-year')
157+
}),
158+
path: ['end_year']
159+
}
160+
)
161+
.refine((data) => !data.start_year || !!data.start_precision, {
162+
message: t('validation.required', { field: t('date.precision') }),
163+
path: ['start_precision']
164+
})
165+
.refine((data) => !data.end_year || !!data.end_precision, {
166+
message: t('validation.required', { field: t('date.precision') }),
167+
path: ['end_precision']
168+
})
169+
170+
type Schema = z.output<typeof schema>
171+
172+
const state = reactive<Partial<Schema>>({})
173+
174+
const resetState = () => {
175+
state.title = undefined
176+
state.cover_url = undefined
177+
state.slug = undefined
178+
state.start_year = undefined
179+
state.start_precision = undefined
180+
state.end_year = undefined
181+
state.end_precision = undefined
182+
}
183+
184+
const normalizeSlug = (e: Event) => {
185+
const input = e.target as HTMLInputElement
186+
input.value = slugify(input.value, false)
187+
state.slug = input.value
188+
}
189+
190+
const supabase = useSupabaseClient()
191+
192+
const { showError, showSuccess } = useFlash()
193+
async function onSubmit(event: FormSubmitEvent<Schema>) {
194+
const { error } = await supabase.from('events').insert(event.data)
195+
196+
if (error) {
197+
showError({
198+
description: t('feedback.could-not-save', { item: event.data.title })
199+
})
200+
} else {
201+
showSuccess({
202+
description: t('feedback.saved-successfully', { item: event.data.title })
203+
})
204+
open.value = false
205+
resetState()
206+
refreshNuxtData('events')
207+
}
208+
}
209+
</script>

app/components/events/EventsInfoCard.vue

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
11
<template>
22
<UCard v-if="event">
33
<div class="flex items-center gap-4">
4-
<UAvatar
5-
alt=""
6-
size="xl"
7-
icon="i-lucide-image"
8-
class="w-28 h-28 md:w-32 md:h-32"
9-
:src="event.cover_url ?? undefined"
10-
/>
4+
<AvatarModal v-model="newAvatar" :edit="edit" type="event" :current="event.cover_url" />
5+
116
<div>
127
<h2 class="text-lg font-semibold">{{ translate(event.title) }}</h2>
138
<div v-if="!edit" class="text-sm text-zinc-500">
@@ -108,6 +103,8 @@ const aliases = computed(() => {
108103
109104
const { fields, rules } = useForm()
110105
106+
const newAvatar = ref(event.value?.cover_url ?? null)
107+
111108
const schema = z
112109
.object({
113110
aliases: z.array(z.string()).optional(),
@@ -158,6 +155,7 @@ async function onSubmit(submitEvent: FormSubmitEvent<Schema>) {
158155
const newData = {
159156
...event.value,
160157
aliases: submitEvent.data.aliases ?? [],
158+
cover_url: newAvatar.value,
161159
end_precision: submitEvent.data.end_precision ?? null,
162160
end_year: submitEvent.data.end_year ?? null,
163161
start_precision: submitEvent.data.start_precision ?? null,

app/components/people/PeopleAddModal.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
204204
})
205205
open.value = false
206206
resetState()
207+
refreshNuxtData('people')
207208
}
208209
}
209210
</script>

app/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"delete": "Delete event",
4545
"edit": "Edit event",
4646
"end-year": "End year",
47+
"new": "New event",
4748
"start-year": "Start year",
4849
"title": "Title",
4950
"view": "View event"

app/locales/nl.json

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,22 @@
3838
"year": "Jaar"
3939
},
4040
"event": {
41-
"cover": "Omslag",
42-
"cover-url": "Omslag-URL",
43-
"create": "Maak evenement",
44-
"delete": "Verwijder evenement",
45-
"edit": "Bewerk evenement",
41+
"cover": "Cover",
42+
"cover-url": "Cover URL",
43+
"create": "Maak gebeurtenis",
44+
"delete": "Verwijder gebeurtenis",
45+
"edit": "Bewerk gebeurtenis",
4646
"end-year": "Eindejaar",
47+
"new": "Nieuw gebeurtenis",
4748
"start-year": "Beginjaar",
4849
"title": "Titel",
49-
"view": "Bekijk evenement"
50+
"view": "Bekijk gebeurtenis"
5051
},
5152
"events": {
5253
"description": "Een overzicht van Bijbelse gebeurtenissen.",
53-
"not-found": "Geen evenementen gevonden.",
54-
"search": "Zoek evenementen...",
55-
"title": "Evenementen"
54+
"not-found": "Geen gebeurtenissen gevonden.",
55+
"search": "Zoek gebeurtenissen...",
56+
"title": "Gebeurtenissen"
5657
},
5758
"feedback": {
5859
"could-not-delete": "{item} kon niet worden verwijderd.",
@@ -119,7 +120,7 @@
119120
},
120121
"person": {
121122
"avatar": "Avatar",
122-
"avatar-url": "Avatar-URL",
123+
"avatar-url": "Avatar URL",
123124
"birth-year": "Geboortejaar",
124125
"create": "Maak persoon",
125126
"death-year": "Sterfjaar",
@@ -194,7 +195,7 @@
194195
"min-length": "{field} moet minimaal {length} karakters bevatten.",
195196
"not-zero": "{field} mag niet nul zijn.",
196197
"passwords-do-not-match": "Wachtwoorden komen niet overeen.",
197-
"provide-in-language": "Geef tekst op in het {taal}.",
198+
"provide-in-language": "Geef tekst op in het {language}.",
198199
"required": "{field} is verplicht."
199200
}
200201
}

app/pages/events/[slug].vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ useSchemaOrg(
117117
dateModified: event.value.updated_at,
118118
datePublished: event.value.created_at,
119119
image: event.value.cover_url,
120-
mainEntity: definePerson({
120+
mainEntity: defineEvent({
121121
description: description.value,
122122
image: event.value.cover_url,
123123
name: title.value

app/pages/events/index.vue

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</template>
88

99
<template #right>
10-
<!-- <LazyPeopleAddModal v-if="can('events.create')" /> -->
10+
<LazyEventsAddModal v-if="can('events.create')" />
1111
</template>
1212
</UDashboardNavbar>
1313
</template>
@@ -29,6 +29,7 @@
2929
import type { TableColumn } from '@nuxt/ui'
3030
3131
const { t } = useI18n()
32+
const { can } = useUserStore()
3233
const supabase = useSupabaseClient()
3334
3435
const {
@@ -67,18 +68,22 @@ const columns = computed((): TableColumn<Tables<'events'>>[] => [
6768
actionCell([
6869
{ label: t('general.actions'), type: 'label' },
6970
{ icon: 'i-lucide:list', label: t('event.view'), to: `/events/${row.original.slug}` },
70-
{ type: 'separator' },
71-
{
72-
color: 'error',
73-
icon: 'i-lucide:trash',
74-
label: t('event.delete'),
75-
onSelect() {
76-
toast.add({
77-
description: 'The event has been deleted.',
78-
title: 'Event deleted'
79-
})
80-
}
81-
}
71+
...(can('events.delete')
72+
? [
73+
{ type: 'separator' as const },
74+
{
75+
color: 'error' as const,
76+
icon: 'i-lucide:trash',
77+
label: t('event.delete'),
78+
onSelect() {
79+
toast.add({
80+
description: 'The event has been deleted.',
81+
title: 'Event deleted'
82+
})
83+
}
84+
}
85+
]
86+
: [])
8287
]),
8388
id: 'actions'
8489
}

0 commit comments

Comments
 (0)