diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index ee8f487..1c25524 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -71,6 +71,10 @@ interface Props { autoApprove?: boolean } +// Instance name used in the From display name of ticket emails +// (the LNbits site title on the backend; the standalone's brand here). +const siteName = (import.meta.env.VITE_APP_NAME as string) || 'Events' + const props = defineProps() const emit = defineEmits<{ 'update:open': [value: boolean] @@ -142,8 +146,15 @@ const formSchema = toTypedSchema( price_per_ticket: z.number().min(0).default(0), email_notifications: z.boolean().default(false), nostr_notifications: z.boolean().default(false), + copy_to_organizer: z.boolean().default(false), notification_subject: z.string().max(200).default(''), notification_body: z.string().max(2000).default(''), + organizer_name: z.string().trim().max(100).default(''), + reply_to_email: z + .string() + .trim() + .default('') + .refine((v) => v === '' || z.string().email().safeParse(v).success, 'Invalid email address'), }) .superRefine((v, ctx) => { // End must not precede start. Compare on the folded date+time @@ -191,8 +202,11 @@ const form = useForm({ price_per_ticket: 0, email_notifications: false, nostr_notifications: false, + copy_to_organizer: false, notification_subject: '', notification_body: '', + organizer_name: '', + reply_to_email: '', } }) @@ -297,8 +311,11 @@ async function populateFromEvent(event: TicketedEvent) { price_per_ticket: event.price_per_ticket ?? 0, email_notifications: event.extra?.email_notifications ?? false, nostr_notifications: event.extra?.nostr_notifications ?? false, + copy_to_organizer: event.extra?.copy_to_organizer ?? false, notification_subject: event.extra?.notification_subject ?? '', notification_body: event.extra?.notification_body ?? '', + organizer_name: event.extra?.organizer_name ?? '', + reply_to_email: event.extra?.reply_to_email ?? '', }) selectedCategories.value = [...(event.categories ?? [])] if (event.banner) { @@ -423,8 +440,11 @@ const onSubmit = form.handleSubmit(async (formValues) => { payment_methods: formValues.payment_methods, email_notifications: formValues.email_notifications, nostr_notifications: formValues.nostr_notifications, + copy_to_organizer: formValues.copy_to_organizer, notification_subject: formValues.notification_subject, notification_body: formValues.notification_body, + organizer_name: formValues.organizer_name, + reply_to_email: formValues.reply_to_email || null, } if (isEditMode.value) { @@ -826,6 +846,18 @@ const handleOpenChange = (open: boolean) => { + + + +
+ Send me a copy of each ticket email + Goes to the reply-to address below, or your account email. +
+ + + +
+
@@ -851,6 +883,35 @@ const handleOpenChange = (open: boolean) => { + + + + + Organizer name + + + + + Ticket emails arrive from "Organizer name via {{ siteName }}". + + + + + + + + Reply-to email + + + + + Where attendee replies go. Leave blank to use your account email. + + + + diff --git a/src/modules/events/types/ticket.ts b/src/modules/events/types/ticket.ts index d42b3c2c..f1b46fd 100644 --- a/src/modules/events/types/ticket.ts +++ b/src/modules/events/types/ticket.ts @@ -26,6 +26,14 @@ export interface EventExtra { nostr_notifications: boolean notification_subject: string notification_body: string + /** Shown as " via " in the From line of ticket + * emails. Optional. */ + organizer_name?: string + /** Reply-To for ticket emails; null/absent = the organizer's LNbits + * account email. */ + reply_to_email?: string | null + /** BCC every ticket email to the reply-to address. */ + copy_to_organizer?: boolean /** Rails the organizer enabled (`lightning`, `fiat`). Empty / absent * means the legacy rule: Lightning always, fiat iff `allow_fiat`. * Same shape as upstream v2's `extra.payment_methods`. */