fix(activities): normalize 'sat' vs 'sats' across fiat conditionals
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) <noreply@anthropic.com>
This commit is contained in:
parent
d6efbd2c65
commit
a4200749ae
3 changed files with 53 additions and 44 deletions
|
|
@ -142,8 +142,10 @@ const formSchema = toTypedSchema(
|
||||||
}
|
}
|
||||||
// When the price is in sats and the organizer also accepts fiat,
|
// When the price is in sats and the organizer also accepts fiat,
|
||||||
// they MUST choose a settle currency. Other price denominations
|
// they MUST choose a settle currency. Other price denominations
|
||||||
// mirror themselves into fiat_currency automatically.
|
// mirror themselves into fiat_currency automatically. The events
|
||||||
if (v.allow_fiat && v.currency === 'sat' && !v.fiat_currency) {
|
// 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({
|
ctx.addIssue({
|
||||||
code: z.ZodIssueCode.custom,
|
code: z.ZodIssueCode.custom,
|
||||||
path: ['fiat_currency'],
|
path: ['fiat_currency'],
|
||||||
|
|
@ -642,7 +644,7 @@ const handleOpenChange = (open: boolean) => {
|
||||||
allow-fiat-field="allow_fiat"
|
allow-fiat-field="allow_fiat"
|
||||||
fiat-currency-field="fiat_currency"
|
fiat-currency-field="fiat_currency"
|
||||||
:denomination="form.values.currency ?? 'sat'"
|
: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"
|
:disabled="isLoading"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,9 @@ const isFiatPending = ref(false)
|
||||||
const fiatError = ref<string | null>(null)
|
const fiatError = ref<string | null>(null)
|
||||||
|
|
||||||
const canChooseFiat = computed(() => Boolean(props.event.allow_fiat))
|
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
|
// Lightning-button badge: when the price is denominated in fiat, show
|
||||||
// the live sat equivalent so the buyer knows roughly what their wallet
|
// the live sat equivalent so the buyer knows roughly what their wallet
|
||||||
|
|
@ -74,7 +77,7 @@ const lightningSats = ref<number | null>(null)
|
||||||
watch(
|
watch(
|
||||||
() => [props.event.currency, props.event.price_per_ticket, props.isOpen] as const,
|
() => [props.event.currency, props.event.price_per_ticket, props.isOpen] as const,
|
||||||
async ([cur, amt, open]) => {
|
async ([cur, amt, open]) => {
|
||||||
if (!open || cur === 'sat' || !amt) {
|
if (!open || !amt || cur === 'sat' || cur === 'sats') {
|
||||||
lightningSats.value = null
|
lightningSats.value = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +100,7 @@ const paymentMethods = computed<PaymentMethodEntry[]>(() => {
|
||||||
icon: Zap,
|
icon: Zap,
|
||||||
available: true,
|
available: true,
|
||||||
badge:
|
badge:
|
||||||
props.event.currency !== 'sat' && lightningSats.value
|
!isPriceInSats.value && lightningSats.value
|
||||||
? `≈ ${Math.round(lightningSats.value).toLocaleString()} sats`
|
? `≈ ${Math.round(lightningSats.value).toLocaleString()} sats`
|
||||||
: undefined,
|
: undefined,
|
||||||
}
|
}
|
||||||
|
|
@ -304,7 +307,7 @@ onUnmounted(() => {
|
||||||
<span class="text-sm font-medium">{{ formatEventPrice(event.price_per_ticket, event.currency) }}</span>
|
<span class="text-sm font-medium">{{ formatEventPrice(event.price_per_ticket, event.currency) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<PriceConversionPreview
|
<PriceConversionPreview
|
||||||
v-if="canChooseFiat && event.currency === 'sat' && event.fiat_currency"
|
v-if="canChooseFiat && isPriceInSats && event.fiat_currency"
|
||||||
:amount="event.price_per_ticket"
|
:amount="event.price_per_ticket"
|
||||||
from="sat"
|
from="sat"
|
||||||
:to="event.fiat_currency"
|
:to="event.fiat_currency"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, watch } from 'vue'
|
import { watch } from 'vue'
|
||||||
import { useFormContext } from 'vee-validate'
|
import { useFormContext } from 'vee-validate'
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
|
|
@ -38,27 +38,31 @@ const props = defineProps<{
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const form = useFormContext()
|
|
||||||
const { hasAnyProvider, refresh } = useFiatProviders()
|
const { hasAnyProvider, refresh } = useFiatProviders()
|
||||||
|
const form = useFormContext()
|
||||||
|
|
||||||
// Refresh once on mount so the disabled-state reflects providers the
|
// Refresh once on mount so the disabled-state reflects providers the
|
||||||
// user may have just configured in another tab.
|
// user may have just configured in another tab.
|
||||||
refresh()
|
refresh()
|
||||||
|
|
||||||
const allowFiatValue = computed(() =>
|
// "sat" / "sats" appear interchangeably across the LNbits events
|
||||||
Boolean(form.values[props.allowFiatField as keyof typeof form.values]),
|
// extension and the webapp's currency lists — treat both as the
|
||||||
)
|
// BTC-denominated case for the conditional + auto-mirror.
|
||||||
|
function isSatDenomination(d: string): boolean {
|
||||||
const showCurrencyDropdown = computed(
|
return d === 'sat' || d === 'sats'
|
||||||
() => allowFiatValue.value && props.denomination === 'sat',
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// When the price is denominated in a fiat currency, the rail currency
|
// When the price is denominated in a fiat currency, the rail currency
|
||||||
// MUST match it — silently mirror so backend payload stays consistent.
|
// MUST match it — silently mirror so backend payload stays consistent.
|
||||||
watch(
|
watch(
|
||||||
() => props.denomination,
|
() => props.denomination,
|
||||||
(d) => {
|
(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)
|
form.setFieldValue(props.fiatCurrencyField, d)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -67,8 +71,8 @@ watch(
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 items-end">
|
<FormField v-slot="{ value: allowFiat, handleChange: setAllowFiat }" :name="allowFiatField">
|
||||||
<FormField v-slot="{ value, handleChange }" :name="allowFiatField">
|
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 items-end">
|
||||||
<FormItem class="sm:col-span-2 flex flex-row items-center justify-between rounded-md border p-3">
|
<FormItem class="sm:col-span-2 flex flex-row items-center justify-between rounded-md border p-3">
|
||||||
<div class="space-y-0.5">
|
<div class="space-y-0.5">
|
||||||
<FormLabel>Also accept fiat</FormLabel>
|
<FormLabel>Also accept fiat</FormLabel>
|
||||||
|
|
@ -93,35 +97,35 @@ watch(
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
<Switch
|
<Switch
|
||||||
v-else
|
v-else
|
||||||
:model-value="value as boolean"
|
:model-value="allowFiat as boolean"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@update:model-value="handleChange"
|
@update:model-value="setAllowFiat"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
|
||||||
|
|
||||||
<FormField v-slot="{ componentField }" :name="fiatCurrencyField">
|
<FormField v-slot="{ componentField }" :name="fiatCurrencyField">
|
||||||
<FormItem v-show="showCurrencyDropdown">
|
<FormItem v-show="(allowFiat as boolean) && isSatDenomination(denomination)">
|
||||||
<FormLabel>Fiat currency</FormLabel>
|
<FormLabel>Fiat currency</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select v-bind="componentField" :disabled="disabled">
|
<Select v-bind="componentField" :disabled="disabled">
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="USD" />
|
<SelectValue placeholder="USD" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem
|
<SelectItem
|
||||||
v-for="c in availableFiatCurrencies"
|
v-for="c in availableFiatCurrencies"
|
||||||
:key="c"
|
:key="c"
|
||||||
:value="c"
|
:value="c"
|
||||||
>
|
>
|
||||||
{{ c }}
|
{{ c }}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
</FormField>
|
</FormField>
|
||||||
</div>
|
</div>
|
||||||
|
</FormField>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue