From 691f8df83088d6ed10eb41a0c2d64ff754705976 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 20 May 2026 01:24:15 +0200 Subject: [PATCH] feat(activities): capture optional start/end time on event creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fold a time into the existing event_start_date / event_end_date strings ("2026-05-25" or "2026-05-25T10:00") rather than introducing parallel fields. Presence of "T" toggles which NIP-52 kind the events-extension publisher emits (31922 date-only vs 31923 time-based). CreateEventDialog gets optional HH:MM inputs next to the start date and the (already-collapsible) end date — stacked below sm breakpoint so the iPhone SE doesn't get the time pushed off-screen by the native date input's intrinsic min-width. EventsPage.formatDate shows the time portion when present. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/CreateEventDialog.vue | 89 ++++++++++++++----- src/modules/activities/types/ticket.ts | 4 + src/modules/activities/views/EventsPage.vue | 5 +- 3 files changed, 75 insertions(+), 23 deletions(-) diff --git a/src/modules/activities/components/CreateEventDialog.vue b/src/modules/activities/components/CreateEventDialog.vue index 3eb5b2a..5f3a605 100644 --- a/src/modules/activities/components/CreateEventDialog.vue +++ b/src/modules/activities/components/CreateEventDialog.vue @@ -62,7 +62,9 @@ const formSchema = toTypedSchema(z.object({ 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_start_time: z.string().optional().default(''), event_end_date: z.string().optional().default(''), + event_end_time: z.string().optional().default(''), location: z.string().max(500).optional().default(''), banner: z.string().optional().default(''), currency: z.string().default("sat"), @@ -76,7 +78,9 @@ const form = useForm({ name: '', info: '', event_start_date: '', + event_start_time: '', event_end_date: '', + event_end_time: '', location: '', banner: '', currency: 'sat', @@ -85,6 +89,15 @@ const form = useForm({ } }) +// Fold a date input ("YYYY-MM-DD") and an optional time input ("HH:MM") +// into the events-extension wire format: date-only when no time given, +// ISO 8601 datetime otherwise. The publisher switches NIP-52 kinds on +// the "T" delimiter. +function foldDateTime(date: string, time: string): string { + if (!date) return '' + return time ? `${date}T${time}` : date +} + const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService | null @@ -144,13 +157,21 @@ const onSubmit = form.handleSubmit(async (formValues) => { try { const eventData: CreateEventRequest = { name: formValues.name, - event_start_date: formValues.event_start_date, + event_start_date: foldDateTime( + formValues.event_start_date, + formValues.event_start_time + ), wallet: preferredWallet.id, } // 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.event_end_date) { + eventData.event_end_date = foldDateTime( + formValues.event_end_date, + formValues.event_end_time + ) + } if (formValues.location) eventData.location = formValues.location if (formValues.banner) eventData.banner = formValues.banner if (formValues.currency) eventData.currency = formValues.currency @@ -207,16 +228,28 @@ const handleOpenChange = (open: boolean) => { - - - - Start date * - - - - - - + +
+ + + Start date * + + + + + + + + + + Start time + + + + + + +
@@ -329,16 +362,28 @@ const handleOpenChange = (open: boolean) => { - - - End date - - - - Defaults to start date if not set - - - +
+ + + End date + + + + Defaults to start date + + + + + + + End time + + + + + + +
diff --git a/src/modules/activities/types/ticket.ts b/src/modules/activities/types/ticket.ts index 8943421..5384ddf 100644 --- a/src/modules/activities/types/ticket.ts +++ b/src/modules/activities/types/ticket.ts @@ -44,6 +44,10 @@ export interface TicketPaymentStatus { /** * LNbits events extension event (database-backed ticketed event). * Corresponds to the Event model in the events extension. + * + * event_start_date / event_end_date are ISO 8601 — either date-only + * ("2026-05-19") or with a time ("2026-05-19T18:30"). Presence of "T" + * switches the publisher between NIP-52 kind 31922 and 31923. */ export interface TicketedEvent { id: string diff --git a/src/modules/activities/views/EventsPage.vue b/src/modules/activities/views/EventsPage.vue index 0181dc1..ab4feb8 100644 --- a/src/modules/activities/views/EventsPage.vue +++ b/src/modules/activities/views/EventsPage.vue @@ -33,7 +33,10 @@ function formatDate(dateStr: string | null | undefined) { if (!dateStr) return 'Date not available' const date = new Date(dateStr) if (isNaN(date.getTime())) return 'Invalid date' - return format(date, 'MMMM do, yyyy') + // Presence of "T" in the wire value marks a time-based event (NIP-52 + // kind 31923 on our publisher). Show time only when one was set. + const hasTime = dateStr.includes('T') + return format(date, hasTime ? 'MMMM do, yyyy p' : 'MMMM do, yyyy') } function handlePurchaseClick(event: {