diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index 3f50231..ee8f487 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -41,7 +41,15 @@ import { injectService, SERVICE_TOKENS } from '@/core/di-container' import ImageUpload from '@/modules/base/components/ImageUpload.vue' import DatePicker from '@/modules/base/components/DatePicker.vue' import TimePicker from '@/modules/base/components/TimePicker.vue' -import FiatToggleField from '@/modules/base/components/payments/FiatToggleField.vue' +import { Checkbox } from '@/components/ui/checkbox' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { useFiatProviders } from '@/modules/base/composables/useFiatProviders' +import { CreditCard } from 'lucide-vue-next' import { Alert, AlertDescription } from '@/components/ui/alert' import type { ImageUploadService, UploadedImage } from '@/modules/base/services/ImageUploadService' import type { TicketApiService } from '../services/TicketApiService' @@ -124,7 +132,11 @@ const formSchema = toTypedSchema( event_end_time: z.string().optional().default(''), location: z.string().max(500).optional().default(''), currency: z.string().default("sat"), - allow_fiat: z.boolean().default(false), + // Rails buyers may use. `allow_fiat` on the wire is derived from + // this list (fiat ∈ list) so older extensions / clients keep working. + payment_methods: z + .array(z.enum(['lightning', 'fiat'])) + .min(1, 'Enable at least one payment method'), 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), @@ -152,7 +164,7 @@ const formSchema = toTypedSchema( // mirror themselves into fiat_currency automatically. The events // extension uses 'sat' and 'sats' interchangeably — accept both. const isSat = v.currency === 'sat' || v.currency === 'sats' - if (v.allow_fiat && isSat && !v.fiat_currency) { + if (v.payment_methods.includes('fiat') && isSat && !v.fiat_currency) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['fiat_currency'], @@ -173,7 +185,7 @@ const form = useForm({ event_end_time: '', location: '', currency: 'sat', - allow_fiat: false, + payment_methods: ['lightning'], fiat_currency: 'USD', amount_tickets: 0, price_per_ticket: 0, @@ -184,6 +196,42 @@ const form = useForm({ } }) +/** Rails an existing event has on; legacy events (no list) derive + * from allow_fiat. */ +function paymentMethodsOf(event: TicketedEvent): Array<'lightning' | 'fiat'> { + const explicit = (event.extra?.payment_methods ?? []).filter( + (m): m is 'lightning' | 'fiat' => m === 'lightning' || m === 'fiat', + ) + if (explicit.length > 0) return explicit + return event.allow_fiat ? ['lightning', 'fiat'] : ['lightning'] +} + +const { hasAnyProvider, refresh: refreshFiatProviders } = useFiatProviders() +// Refresh once so the Card checkbox reflects providers the organizer may +// have just configured in LNbits. +refreshFiatProviders() + +const PAYMENT_METHOD_OPTIONS = [ + { id: 'lightning' as const, label: 'Lightning', icon: Zap, hint: 'Pay with any Lightning wallet' }, + { id: 'fiat' as const, label: 'Card', icon: CreditCard, hint: 'Card or bank through your configured fiat provider' }, +] + +function isSatDenomination(d: string | undefined): boolean { + return d === 'sat' || d === 'sats' +} + +// When the price is denominated in a fiat currency, the rail currency +// MUST match it — silently mirror so the backend payload stays consistent. +watch( + () => form.values.currency, + (d) => { + if (d && !isSatDenomination(d) && form.values.fiat_currency !== d) { + form.setFieldValue('fiat_currency', d) + } + }, + { immediate: true }, +) + interface BannerImage extends UploadedImage { isPrimary: boolean } @@ -243,7 +291,7 @@ async function populateFromEvent(event: TicketedEvent) { event_end_time: end.time, location: event.location ?? '', currency: event.currency ?? 'sat', - allow_fiat: event.allow_fiat ?? false, + payment_methods: paymentMethodsOf(event), fiat_currency: event.fiat_currency ?? 'USD', amount_tickets: event.amount_tickets ?? 0, price_per_ticket: event.price_per_ticket ?? 0, @@ -354,11 +402,12 @@ const onSubmit = form.handleSubmit(async (formValues) => { eventData.banner = null } if (formValues.currency) eventData.currency = formValues.currency - // allow_fiat always sends so a true→false flip propagates on edit; - // fiat_currency only sends when fiat is on (no point persisting a - // rail-currency the backend won't use). - eventData.allow_fiat = formValues.allow_fiat - if (formValues.allow_fiat && formValues.fiat_currency) { + // allow_fiat always sends (derived from the rail list) so a + // true→false flip propagates on edit; fiat_currency only sends when + // fiat is on (no point persisting a rail-currency the backend won't use). + const acceptsFiat = formValues.payment_methods.includes('fiat') + eventData.allow_fiat = acceptsFiat + if (acceptsFiat && formValues.fiat_currency) { eventData.fiat_currency = formValues.fiat_currency } if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets @@ -371,6 +420,7 @@ const onSubmit = form.handleSubmit(async (formValues) => { // survive the round-trip. eventData.extra = { ...(props.event?.extra ?? {}), + payment_methods: formValues.payment_methods, email_notifications: formValues.email_notifications, nostr_notifications: formValues.nostr_notifications, notification_subject: formValues.notification_subject, @@ -660,26 +710,87 @@ const handleOpenChange = (open: boolean) => { -
Payment methods
-- Lightning is always available. Enable fiat to also accept - card and bank payments through your configured provider. -
-{{ opt.hint }}
+