From 663e32e7a477246973ad23118f54399a0bdffa47 Mon Sep 17 00:00:00 2001 From: Padreug Date: Sat, 23 May 2026 19:44:43 +0200 Subject: [PATCH] fix(activities): normalize 'sat' vs 'sats' across fiat conditionals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TicketApiService.getCurrencies() returns 'sats' (plural) while the schema, initialValues, and existing comparisons used 'sat' (singular) — a pre-existing inconsistency in the events extension surface. The new payment-rails conditionals tripped on it: as soon as the user picked the populated 'sats' option from the price-currency dropdown, form.values.currency became 'sats', the `=== 'sat'` check failed, and the Fiat currency dropdown stayed hidden even with the toggle on. Normalize all the new comparisons to accept both spellings: - FiatToggleField: isSatDenomination(d) helper drives both the v-show and the auto-mirror watch. - CreateEventDialog Zod superRefine: same accept-both rule on the require-fiat_currency branch. - PurchaseTicketDialog: isPriceInSats computed drives the Lightning-sats badge AND the PriceConversionPreview render condition AND the inverse conversion watcher's bail-out. Also flip FiatToggleField to drive dropdown visibility from the outer FormField's slot value rather than useFormContext — slot bindings are guaranteed reactive, sidesteps the public-form-context indirection that earlier left allowFiat stale in the child's template. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/CreateEventDialog.vue | 8 +- .../components/PurchaseTicketDialog.vue | 9 ++- .../components/payments/FiatToggleField.vue | 80 ++++++++++--------- 3 files changed, 53 insertions(+), 44 deletions(-) diff --git a/src/modules/activities/components/CreateEventDialog.vue b/src/modules/activities/components/CreateEventDialog.vue index 5f9d45b..739e300 100644 --- a/src/modules/activities/components/CreateEventDialog.vue +++ b/src/modules/activities/components/CreateEventDialog.vue @@ -142,8 +142,10 @@ const formSchema = toTypedSchema( } // When the price is in sats and the organizer also accepts fiat, // they MUST choose a settle currency. Other price denominations - // mirror themselves into fiat_currency automatically. - if (v.allow_fiat && v.currency === 'sat' && !v.fiat_currency) { + // 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) { ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['fiat_currency'], @@ -642,7 +644,7 @@ const handleOpenChange = (open: boolean) => { allow-fiat-field="allow_fiat" fiat-currency-field="fiat_currency" :denomination="form.values.currency ?? 'sat'" - :available-fiat-currencies="availableCurrencies.filter(c => c !== 'sat')" + :available-fiat-currencies="availableCurrencies.filter(c => c !== 'sat' && c !== 'sats')" :disabled="isLoading" /> diff --git a/src/modules/activities/components/PurchaseTicketDialog.vue b/src/modules/activities/components/PurchaseTicketDialog.vue index db126cc..22eb1b8 100644 --- a/src/modules/activities/components/PurchaseTicketDialog.vue +++ b/src/modules/activities/components/PurchaseTicketDialog.vue @@ -66,6 +66,9 @@ const isFiatPending = ref(false) const fiatError = ref(null) const canChooseFiat = computed(() => Boolean(props.event.allow_fiat)) +const isPriceInSats = computed( + () => props.event.currency === 'sat' || props.event.currency === 'sats', +) // Lightning-button badge: when the price is denominated in fiat, show // the live sat equivalent so the buyer knows roughly what their wallet @@ -74,7 +77,7 @@ const lightningSats = ref(null) watch( () => [props.event.currency, props.event.price_per_ticket, props.isOpen] as const, async ([cur, amt, open]) => { - if (!open || cur === 'sat' || !amt) { + if (!open || !amt || cur === 'sat' || cur === 'sats') { lightningSats.value = null return } @@ -97,7 +100,7 @@ const paymentMethods = computed(() => { icon: Zap, available: true, badge: - props.event.currency !== 'sat' && lightningSats.value + !isPriceInSats.value && lightningSats.value ? `≈ ${Math.round(lightningSats.value).toLocaleString()} sats` : undefined, } @@ -304,7 +307,7 @@ onUnmounted(() => { {{ formatEventPrice(event.price_per_ticket, event.currency) }} -import { computed, watch } from 'vue' +import { watch } from 'vue' import { useFormContext } from 'vee-validate' import { FormControl, @@ -38,27 +38,31 @@ const props = defineProps<{ disabled?: boolean }>() -const form = useFormContext() const { hasAnyProvider, refresh } = useFiatProviders() +const form = useFormContext() // Refresh once on mount so the disabled-state reflects providers the // user may have just configured in another tab. refresh() -const allowFiatValue = computed(() => - Boolean(form.values[props.allowFiatField as keyof typeof form.values]), -) - -const showCurrencyDropdown = computed( - () => allowFiatValue.value && props.denomination === 'sat', -) +// "sat" / "sats" appear interchangeably across the LNbits events +// extension and the webapp's currency lists — treat both as the +// BTC-denominated case for the conditional + auto-mirror. +function isSatDenomination(d: string): boolean { + return d === 'sat' || d === 'sats' +} // When the price is denominated in a fiat currency, the rail currency // MUST match it — silently mirror so backend payload stays consistent. watch( () => props.denomination, (d) => { - if (d && d !== 'sat' && form.values[props.fiatCurrencyField as keyof typeof form.values] !== d) { + if (!form) return + if ( + d && + !isSatDenomination(d) && + form.values[props.fiatCurrencyField as keyof typeof form.values] !== d + ) { form.setFieldValue(props.fiatCurrencyField, d) } }, @@ -67,8 +71,8 @@ watch(