feat(activities): notification config on event create + edit
CreateEventDialog gains a collapsible "Buyer notifications" section exposing the EventExtra fields added upstream in v1.4.0 / v1.6.0: - email_notifications + nostr_notifications switches — opt buyers into email and NIP-04 Nostr DM ticket confirmations. - notification_subject + notification_body inputs — let organizers customize the message. Empty falls back to extension defaults. Submit handler builds `extra` by overlaying onto the existing event.extra so unrelated fields the LNbits admin UI sets (promo_codes, conditional, min_tickets) survive the round-trip through the webapp. Populate-from-event mirrors the same. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a4200749ae
commit
9f38611f4f
1 changed files with 90 additions and 0 deletions
|
|
@ -24,6 +24,9 @@ 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 { Switch } from '@/components/ui/switch'
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
|
||||
import { Bell, ChevronDown } from 'lucide-vue-next'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -125,6 +128,10 @@ const formSchema = toTypedSchema(
|
|||
fiat_currency: z.string().default("USD"),
|
||||
amount_tickets: z.number().min(0).max(100000).default(0),
|
||||
price_per_ticket: z.number().min(0).default(0),
|
||||
email_notifications: z.boolean().default(false),
|
||||
nostr_notifications: z.boolean().default(false),
|
||||
notification_subject: z.string().max(200).default(''),
|
||||
notification_body: z.string().max(2000).default(''),
|
||||
})
|
||||
.superRefine((v, ctx) => {
|
||||
// End must not precede start. Compare on the folded date+time
|
||||
|
|
@ -170,6 +177,10 @@ const form = useForm({
|
|||
fiat_currency: 'USD',
|
||||
amount_tickets: 0,
|
||||
price_per_ticket: 0,
|
||||
email_notifications: false,
|
||||
nostr_notifications: false,
|
||||
notification_subject: '',
|
||||
notification_body: '',
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -192,6 +203,7 @@ function splitDateTime(value: string | null | undefined): { date: string; time:
|
|||
// When `true`, suppress the auto-mirror watcher so we don't clobber an
|
||||
// edit-mode population with start-date side effects mid-setValues.
|
||||
const isPopulating = ref(false)
|
||||
const notificationsOpen = ref(false)
|
||||
|
||||
// Auto-mirror end date to start: when the user picks a start date,
|
||||
// surface that same date in the end-date picker so a one-day event
|
||||
|
|
@ -235,6 +247,10 @@ async function populateFromEvent(event: TicketedEvent) {
|
|||
fiat_currency: event.fiat_currency ?? 'USD',
|
||||
amount_tickets: event.amount_tickets ?? 0,
|
||||
price_per_ticket: event.price_per_ticket ?? 0,
|
||||
email_notifications: event.extra?.email_notifications ?? false,
|
||||
nostr_notifications: event.extra?.nostr_notifications ?? false,
|
||||
notification_subject: event.extra?.notification_subject ?? '',
|
||||
notification_body: event.extra?.notification_body ?? '',
|
||||
})
|
||||
selectedCategories.value = [...(event.categories ?? [])]
|
||||
if (event.banner) {
|
||||
|
|
@ -349,6 +365,18 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
|||
if (formValues.price_per_ticket) eventData.price_per_ticket = formValues.price_per_ticket
|
||||
if (selectedCategories.value.length > 0) eventData.categories = selectedCategories.value
|
||||
|
||||
// Notification config goes inside the `extra` envelope. On edit
|
||||
// overlay onto the existing event.extra so unrelated fields the
|
||||
// LNbits admin UI sets (promo_codes, conditional, min_tickets)
|
||||
// survive the round-trip.
|
||||
eventData.extra = {
|
||||
...(props.event?.extra ?? {}),
|
||||
email_notifications: formValues.email_notifications,
|
||||
nostr_notifications: formValues.nostr_notifications,
|
||||
notification_subject: formValues.notification_subject,
|
||||
notification_body: formValues.notification_body,
|
||||
}
|
||||
|
||||
if (isEditMode.value) {
|
||||
if (!props.onUpdateEvent || !props.event?.id) {
|
||||
toastService.error('Update handler missing')
|
||||
|
|
@ -649,6 +677,68 @@ const handleOpenChange = (open: boolean) => {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<!-- Ticket buyer notifications (collapsible). The backend
|
||||
sends email + NIP-04 Nostr DM confirmations on
|
||||
payment when these are on. notification_subject /
|
||||
body let the organizer customize the message; empty
|
||||
strings fall back to the extension's defaults. -->
|
||||
<Collapsible v-model:open="notificationsOpen">
|
||||
<CollapsibleTrigger as-child>
|
||||
<Button type="button" variant="ghost" size="sm" class="w-full justify-between text-muted-foreground">
|
||||
<span class="flex items-center gap-1.5">
|
||||
<Bell class="w-4 h-4" />
|
||||
Buyer notifications
|
||||
</span>
|
||||
<ChevronDown class="w-4 h-4 transition-transform" :class="{ 'rotate-180': notificationsOpen }" />
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent class="space-y-3 pt-2">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||
<FormField v-slot="{ value, handleChange }" name="email_notifications">
|
||||
<FormItem class="flex flex-row items-center justify-between rounded-md border p-3">
|
||||
<FormLabel class="text-sm">Email confirmation</FormLabel>
|
||||
<FormControl>
|
||||
<Switch :model-value="value as boolean" @update:model-value="handleChange" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="nostr_notifications">
|
||||
<FormItem class="flex flex-row items-center justify-between rounded-md border p-3">
|
||||
<FormLabel class="text-sm">Nostr DM confirmation</FormLabel>
|
||||
<FormControl>
|
||||
<Switch :model-value="value as boolean" @update:model-value="handleChange" />
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="notification_subject">
|
||||
<FormItem>
|
||||
<FormLabel class="text-sm">Subject</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your ticket for {event_name}" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription class="text-xs">Leave blank to use the default.</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="notification_body">
|
||||
<FormItem>
|
||||
<FormLabel class="text-sm">Body</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea placeholder="See you there!" rows="3" v-bind="componentField" />
|
||||
</FormControl>
|
||||
<FormDescription class="text-xs">
|
||||
Leave blank to use the default. The ticket link is appended automatically.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end gap-3 pt-2">
|
||||
<Button type="button" variant="outline" @click="handleOpenChange(false)" :disabled="isLoading">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue