From dcb26db685463c63fccaa2d3015ab01d5666dea2 Mon Sep 17 00:00:00 2001 From: Padreug Date: Mon, 27 Apr 2026 18:07:44 +0200 Subject: [PATCH] feat: simplify event creation form, add location + categories - Only title and start date are required - Description, location, categories, image, tickets visible by default - End date and promo codes in collapsible "More options" section - Categories use badge toggles matching the activities module - Use ScrollArea for proper shadcn scrolling - Update CreateEventRequest and TicketedEvent types for new fields Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/CreateEventDialog.vue | 292 +++++++++++------- src/modules/activities/types/ticket.ts | 23 +- 2 files changed, 200 insertions(+), 115 deletions(-) diff --git a/src/modules/activities/components/CreateEventDialog.vue b/src/modules/activities/components/CreateEventDialog.vue index 774514f..2745781 100644 --- a/src/modules/activities/components/CreateEventDialog.vue +++ b/src/modules/activities/components/CreateEventDialog.vue @@ -3,6 +3,7 @@ import { ref, computed, watch } from 'vue' import { useForm } from 'vee-validate' import { toTypedSchema } from '@vee-validate/zod' import * as z from 'zod' +import { useI18n } from 'vue-i18n' import { format } from 'date-fns' import { Dialog, @@ -22,6 +23,9 @@ import { import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Separator } from '@/components/ui/separator' +import { ScrollArea } from '@/components/ui/scroll-area' import { Select, SelectContent, @@ -29,11 +33,17 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select' -import { Calendar, Loader2 } from 'lucide-vue-next' +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible' +import { Calendar, Loader2, ChevronDown, MapPin } from 'lucide-vue-next' import { toastService } from '@/core/services/ToastService' import { injectService, SERVICE_TOKENS } from '@/core/di-container' import type { TicketApiService } from '../services/TicketApiService' import type { CreateEventRequest } from '../types/ticket' +import { ALL_CATEGORIES } from '../types/category' interface Props { open: boolean @@ -46,22 +56,18 @@ const emit = defineEmits<{ 'event-created': [] }>() +const { t } = useI18n() + const formSchema = toTypedSchema(z.object({ - name: z.string().min(1, "Event name is required").max(200, "Name too long"), - info: z.string().min(1, "Event description is required").max(2000, "Description too long"), - event_start_date: z.string().min(1, "Event start date is required"), - event_end_date: z.string().min(1, "Event end date is required"), - currency: z.string().default("sats"), - amount_tickets: z.number().min(1, "Must have at least 1 ticket").max(100000, "Too many tickets"), - price_per_ticket: z.number().min(0, "Price must be 0 or higher"), - banner: z.string().optional(), -}).refine((data) => { - const startDate = new Date(data.event_start_date) - const endDate = new Date(data.event_end_date) - return startDate <= endDate -}, { - message: "Event start date must be before or equal to end date", - path: ["event_end_date"] + name: z.string().min(1, "Title is required").max(200, "Title too long"), + info: z.string().max(2000, "Description too long").optional().default(''), + event_start_date: z.string().min(1, "Start date is required"), + event_end_date: z.string().optional().default(''), + location: z.string().max(500).optional().default(''), + banner: z.string().optional().default(''), + currency: z.string().default("sat"), + amount_tickets: z.number().min(0).max(100000).default(0), + price_per_ticket: z.number().min(0).default(0), })) const form = useForm({ @@ -71,18 +77,21 @@ const form = useForm({ info: '', event_start_date: '', event_end_date: '', - currency: 'sats', - amount_tickets: 100, - price_per_ticket: 1000, - banner: '' + location: '', + banner: '', + currency: 'sat', + amount_tickets: 0, + price_per_ticket: 0, } }) const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService | null -const availableCurrencies = ref(['sats']) +const availableCurrencies = ref(['sat']) const loadingCurrencies = ref(false) +const selectedCategories = ref([]) +const showMoreOptions = ref(false) watch(() => props.open, async (isOpen) => { if (isOpen && ticketApi && !loadingCurrencies.value) { @@ -95,6 +104,10 @@ watch(() => props.open, async (isOpen) => { loadingCurrencies.value = false } } + if (!isOpen) { + selectedCategories.value = [] + showMoreOptions.value = false + } }) const { resetForm, meta } = form @@ -103,6 +116,15 @@ const isLoading = ref(false) const today = computed(() => format(new Date(), 'yyyy-MM-dd')) +function toggleCategory(cat: string) { + const idx = selectedCategories.value.indexOf(cat) + if (idx >= 0) { + selectedCategories.value.splice(idx, 1) + } else { + selectedCategories.value.push(cat) + } +} + const onSubmit = form.handleSubmit(async (formValues) => { if (!isFormValid.value) return @@ -121,14 +143,25 @@ const onSubmit = form.handleSubmit(async (formValues) => { isLoading.value = true try { const eventData: CreateEventRequest = { - ...formValues, + name: formValues.name, + event_start_date: formValues.event_start_date, wallet: preferredWallet.id, - closing_date: formValues.event_end_date } + // Optional fields — only include if provided + if (formValues.info) eventData.info = formValues.info + if (formValues.event_end_date) eventData.event_end_date = formValues.event_end_date + if (formValues.location) eventData.location = formValues.location + if (formValues.banner) eventData.banner = formValues.banner + if (formValues.currency) eventData.currency = formValues.currency + if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets + if (formValues.price_per_ticket) eventData.price_per_ticket = formValues.price_per_ticket + if (selectedCategories.value.length > 0) eventData.categories = selectedCategories.value + await props.onCreateEvent(eventData) - toastService.success('Event created successfully!') + toastService.success('Event submitted!') resetForm() + selectedCategories.value = [] emit('update:open', false) emit('event-created') } catch (error) { @@ -142,6 +175,7 @@ const onSubmit = form.handleSubmit(async (formValues) => { const handleOpenChange = (open: boolean) => { if (!open && !isLoading.value) { resetForm() + selectedCategories.value = [] } emit('update:open', open) } @@ -149,43 +183,34 @@ const handleOpenChange = (open: boolean) => {