feat(events): organizer name + reply-to fields in the event editor #148
2 changed files with 69 additions and 0 deletions
|
|
@ -71,6 +71,10 @@ interface Props {
|
||||||
autoApprove?: boolean
|
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<Props>()
|
const props = defineProps<Props>()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:open': [value: boolean]
|
'update:open': [value: boolean]
|
||||||
|
|
@ -142,8 +146,15 @@ const formSchema = toTypedSchema(
|
||||||
price_per_ticket: z.number().min(0).default(0),
|
price_per_ticket: z.number().min(0).default(0),
|
||||||
email_notifications: z.boolean().default(false),
|
email_notifications: z.boolean().default(false),
|
||||||
nostr_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_subject: z.string().max(200).default(''),
|
||||||
notification_body: z.string().max(2000).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) => {
|
.superRefine((v, ctx) => {
|
||||||
// End must not precede start. Compare on the folded date+time
|
// End must not precede start. Compare on the folded date+time
|
||||||
|
|
@ -191,8 +202,11 @@ const form = useForm({
|
||||||
price_per_ticket: 0,
|
price_per_ticket: 0,
|
||||||
email_notifications: false,
|
email_notifications: false,
|
||||||
nostr_notifications: false,
|
nostr_notifications: false,
|
||||||
|
copy_to_organizer: false,
|
||||||
notification_subject: '',
|
notification_subject: '',
|
||||||
notification_body: '',
|
notification_body: '',
|
||||||
|
organizer_name: '',
|
||||||
|
reply_to_email: '',
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -297,8 +311,11 @@ async function populateFromEvent(event: TicketedEvent) {
|
||||||
price_per_ticket: event.price_per_ticket ?? 0,
|
price_per_ticket: event.price_per_ticket ?? 0,
|
||||||
email_notifications: event.extra?.email_notifications ?? false,
|
email_notifications: event.extra?.email_notifications ?? false,
|
||||||
nostr_notifications: event.extra?.nostr_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_subject: event.extra?.notification_subject ?? '',
|
||||||
notification_body: event.extra?.notification_body ?? '',
|
notification_body: event.extra?.notification_body ?? '',
|
||||||
|
organizer_name: event.extra?.organizer_name ?? '',
|
||||||
|
reply_to_email: event.extra?.reply_to_email ?? '',
|
||||||
})
|
})
|
||||||
selectedCategories.value = [...(event.categories ?? [])]
|
selectedCategories.value = [...(event.categories ?? [])]
|
||||||
if (event.banner) {
|
if (event.banner) {
|
||||||
|
|
@ -423,8 +440,11 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
payment_methods: formValues.payment_methods,
|
payment_methods: formValues.payment_methods,
|
||||||
email_notifications: formValues.email_notifications,
|
email_notifications: formValues.email_notifications,
|
||||||
nostr_notifications: formValues.nostr_notifications,
|
nostr_notifications: formValues.nostr_notifications,
|
||||||
|
copy_to_organizer: formValues.copy_to_organizer,
|
||||||
notification_subject: formValues.notification_subject,
|
notification_subject: formValues.notification_subject,
|
||||||
notification_body: formValues.notification_body,
|
notification_body: formValues.notification_body,
|
||||||
|
organizer_name: formValues.organizer_name,
|
||||||
|
reply_to_email: formValues.reply_to_email || null,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditMode.value) {
|
if (isEditMode.value) {
|
||||||
|
|
@ -826,6 +846,18 @@ const handleOpenChange = (open: boolean) => {
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ value, handleChange }" name="copy_to_organizer">
|
||||||
|
<FormItem class="flex flex-row items-center justify-between rounded-md border p-3 sm:col-span-2">
|
||||||
|
<div class="space-y-0.5">
|
||||||
|
<FormLabel class="text-sm">Send me a copy of each ticket email</FormLabel>
|
||||||
|
<FormDescription class="text-xs">Goes to the reply-to address below, or your account email.</FormDescription>
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch :model-value="value as boolean" @update:model-value="handleChange" />
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" name="notification_subject">
|
<FormField v-slot="{ componentField }" name="notification_subject">
|
||||||
|
|
@ -851,6 +883,35 @@ const handleOpenChange = (open: boolean) => {
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
<!-- Sender identity: the From address stays the instance
|
||||||
|
mailbox (that is what DKIM signs); the organizer only
|
||||||
|
controls the display name and where replies land. -->
|
||||||
|
<FormField v-slot="{ componentField }" name="organizer_name">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel class="text-sm">Organizer name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="Château du Faune" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription class="text-xs">
|
||||||
|
Ticket emails arrive from "Organizer name via {{ siteName }}".
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="reply_to_email">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel class="text-sm">Reply-to email</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="email" inputmode="email" autocapitalize="none" placeholder="you@example.com" v-bind="componentField" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription class="text-xs">
|
||||||
|
Where attendee replies go. Leave blank to use your account email.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,14 @@ export interface EventExtra {
|
||||||
nostr_notifications: boolean
|
nostr_notifications: boolean
|
||||||
notification_subject: string
|
notification_subject: string
|
||||||
notification_body: string
|
notification_body: string
|
||||||
|
/** Shown as "<organizer_name> via <site>" 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
|
/** Rails the organizer enabled (`lightning`, `fiat`). Empty / absent
|
||||||
* means the legacy rule: Lightning always, fiat iff `allow_fiat`.
|
* means the legacy rule: Lightning always, fiat iff `allow_fiat`.
|
||||||
* Same shape as upstream v2's `extra.payment_methods`. */
|
* Same shape as upstream v2's `extra.payment_methods`. */
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue