feat(admin): event form parity with the webapp (1.6.1-aio.13)
The LNbits admin form lagged the webapp's CreateEventDialog:
- Payment methods never rendered. c2d9a96 wired the template to
`paymentMethodOptions` / `acceptsFiat` but never defined them, so the
q-option-group got `options=undefined` and the fiat-currency select
was gated on `undefined`. Rails are now two q-checkboxes; Card is
disabled with an explanatory tooltip when `g.user.fiat_providers` is
empty (same rule as the webapp) and names the providers otherwise.
- Location (NIP-52 `location` tag) and Categories (NIP-52 `t` tags,
same 25-item list as the webapp's category.ts) were missing from the
form even though the model, CRUD and publisher already carry them.
- Datetimes are stamped with the browser's UTC offset on submit, as the
webapp does; `_to_unix` treats naive values as UTC, so 18:00 CEST
entered here went out on Nostr as 18:00 UTC. Table columns render
"YYYY-MM-DD HH:MM" instead of the raw ISO string.
- Validation: title + start date required, end >= start on the folded
date+time, fiat currency required when a sat-priced event accepts
card. Create is enabled once wallet + title + start are set; info,
closing date, tickets and price were all effectively required before
because the disable check compared undefined fields to null.
- Labels follow the payment-rails vocabulary: "Unit" -> "Price
currency", "Fiat checkout currency" -> "Fiat currency"; ticket
closing date and end date explain their defaults.
- A fiat-priced event mirrors `fiat_currency = currency` on save so the
payload and the `tickets_fiat_currency` tag stay coherent.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018b1bDExMX7W3a47wcgFUjb
This commit is contained in:
parent
f11e84d967
commit
f835766935
3 changed files with 222 additions and 59 deletions
|
|
@ -67,13 +67,15 @@ window.PageEvents = {
|
|||
name: 'event_start_date',
|
||||
align: 'left',
|
||||
label: 'Start date',
|
||||
field: 'event_start_date'
|
||||
field: 'event_start_date',
|
||||
format: val => this.formatEventDate(val)
|
||||
},
|
||||
{
|
||||
name: 'event_end_date',
|
||||
align: 'left',
|
||||
label: 'End date',
|
||||
field: 'event_end_date'
|
||||
field: 'event_end_date',
|
||||
format: val => this.formatEventDate(val)
|
||||
},
|
||||
{
|
||||
name: 'closing_date',
|
||||
|
|
@ -149,12 +151,58 @@ window.PageEvents = {
|
|||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
// Rails an organizer can enable per event. Mirrors the webapp's
|
||||
// CreateEventDialog; `fiat` is rendered disabled when the LNbits user
|
||||
// has no fiat provider (see `hasFiatProvider`).
|
||||
paymentMethodOptions: [
|
||||
{
|
||||
value: 'lightning',
|
||||
label: 'Lightning',
|
||||
hint: 'Pay with any Lightning wallet'
|
||||
},
|
||||
{
|
||||
value: 'fiat',
|
||||
label: 'Card',
|
||||
hint: 'Card or bank through your configured fiat provider'
|
||||
}
|
||||
],
|
||||
// Same list the webapp offers (src/modules/events/types/category.ts);
|
||||
// published as NIP-52 `t` tags so both clients filter on one vocabulary.
|
||||
categoryOptions: [
|
||||
'concert',
|
||||
'workshop',
|
||||
'market',
|
||||
'festival',
|
||||
'exhibition',
|
||||
'sport',
|
||||
'theater',
|
||||
'cinema',
|
||||
'party',
|
||||
'talk',
|
||||
'conference',
|
||||
'meetup',
|
||||
'food',
|
||||
'outdoor',
|
||||
'kids',
|
||||
'wellness',
|
||||
'technology',
|
||||
'art',
|
||||
'music',
|
||||
'dance',
|
||||
'literature',
|
||||
'comedy',
|
||||
'charity',
|
||||
'tradition',
|
||||
'other'
|
||||
].map(c => ({label: c.charAt(0).toUpperCase() + c.slice(1), value: c})),
|
||||
formDialog: {
|
||||
show: false,
|
||||
data: {
|
||||
currency: 'sats',
|
||||
allow_fiat: false,
|
||||
fiat_currency: 'GBP',
|
||||
location: '',
|
||||
categories: [],
|
||||
extra: {
|
||||
payment_methods: ['lightning'],
|
||||
promo_codes: [],
|
||||
|
|
@ -165,6 +213,27 @@ window.PageEvents = {
|
|||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasFiatProvider() {
|
||||
return (this.g.user?.fiat_providers || []).length > 0
|
||||
},
|
||||
fiatProviderNames() {
|
||||
return (this.g.user?.fiat_providers || [])
|
||||
.map(p => p.charAt(0).toUpperCase() + p.slice(1))
|
||||
.join(', ')
|
||||
},
|
||||
acceptsFiat() {
|
||||
return (this.formDialog.data.extra?.payment_methods || []).includes(
|
||||
'fiat'
|
||||
)
|
||||
},
|
||||
isSatPrice() {
|
||||
return !this.isFiatCurrency(this.formDialog.data.currency)
|
||||
},
|
||||
fiatCurrencyOptions() {
|
||||
return this.currencies.filter(c => this.isFiatCurrency(c))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isFiatCurrency(currency) {
|
||||
return !['sat', 'sats'].includes((currency || '').toLowerCase())
|
||||
|
|
@ -382,29 +451,62 @@ window.PageEvents = {
|
|||
},
|
||||
splitDateTime(value) {
|
||||
// Inverse of foldDateTime: split a stored string back into the
|
||||
// day/time pieces the form inputs bind to.
|
||||
// day/time pieces the form inputs bind to. Slicing to HH:MM also
|
||||
// drops the seconds + offset suffix withLocalTzOffset stamps on
|
||||
// submit, so the organizer sees the wall-clock they entered.
|
||||
if (!value) return {day: '', time: ''}
|
||||
const [day, time = ''] = value.split('T')
|
||||
// Time inputs only accept HH:MM, drop any seconds we stored.
|
||||
return {day, time: time.slice(0, 5)}
|
||||
},
|
||||
withLocalTzOffset(value) {
|
||||
// Stamp the browser's UTC offset on a "YYYY-MM-DDTHH:MM" value.
|
||||
// The publisher's `_to_unix` treats a naive datetime as UTC, so an
|
||||
// event entered as 18:00 in CEST would otherwise go out on Nostr
|
||||
// as 18:00 UTC. Same transform the webapp applies; date-only
|
||||
// values pass through unchanged (they map to NIP-52 kind 31922).
|
||||
if (!value || !value.includes('T')) return value
|
||||
const offMin = -new Date(value).getTimezoneOffset()
|
||||
const sign = offMin >= 0 ? '+' : '-'
|
||||
const abs = Math.abs(offMin)
|
||||
const hh = String(Math.floor(abs / 60)).padStart(2, '0')
|
||||
const mm = String(abs % 60).padStart(2, '0')
|
||||
return `${value}:00${sign}${hh}:${mm}`
|
||||
},
|
||||
formatEventDate(value) {
|
||||
// Table display: "YYYY-MM-DD" or "YYYY-MM-DD HH:MM".
|
||||
if (!value) return ''
|
||||
const {day, time} = this.splitDateTime(value)
|
||||
return time ? `${day} ${time}` : day
|
||||
},
|
||||
validateEndDate() {
|
||||
// Cross-field rule for the end-day input: end >= start, compared
|
||||
// on the folded date+time so an equal-day earlier time is caught.
|
||||
const d = this.formDialog.data
|
||||
const start = this.foldDateTime(d.event_start_day, d.event_start_time)
|
||||
const end = this.foldDateTime(d.event_end_day, d.event_end_time)
|
||||
if (!start || !end) return true
|
||||
return end >= start || 'End must be on or after start'
|
||||
},
|
||||
sendEventData() {
|
||||
const wallet = _.findWhere(this.g.user.wallets, {
|
||||
id: this.formDialog.data.wallet
|
||||
})
|
||||
const data = {...this.formDialog.data}
|
||||
data.event_start_date = this.foldDateTime(
|
||||
data.event_start_day,
|
||||
data.event_start_time
|
||||
data.event_start_date = this.withLocalTzOffset(
|
||||
this.foldDateTime(data.event_start_day, data.event_start_time)
|
||||
)
|
||||
data.event_end_date = this.foldDateTime(
|
||||
data.event_end_day,
|
||||
data.event_end_time
|
||||
data.event_end_date = this.withLocalTzOffset(
|
||||
this.foldDateTime(data.event_end_day, data.event_end_time)
|
||||
)
|
||||
delete data.event_start_day
|
||||
delete data.event_start_time
|
||||
delete data.event_end_day
|
||||
delete data.event_end_time
|
||||
// Optional NIP-52 fields: blank location is "unset", not "".
|
||||
data.location = (data.location || '').trim() || null
|
||||
data.categories = data.categories || []
|
||||
data.closing_date = data.closing_date || null
|
||||
|
||||
if (data.extra?.promo_codes) {
|
||||
data.extra.promo_codes = data.extra.promo_codes
|
||||
|
|
@ -425,10 +527,12 @@ window.PageEvents = {
|
|||
// allow_fiat stays the fiat-currency carrier the backend and the
|
||||
// NIP-52 tags read; keep it in lockstep with the checkbox list.
|
||||
data.allow_fiat = methods.includes('fiat')
|
||||
if (!this.isFiatCurrency(data.currency)) {
|
||||
if (!data.allow_fiat) {
|
||||
data.fiat_currency = 'GBP'
|
||||
}
|
||||
if (this.isFiatCurrency(data.currency)) {
|
||||
// A fiat-priced event settles in its price currency; mirror it so
|
||||
// the payload (and the tickets_fiat_currency tag) stay coherent.
|
||||
data.fiat_currency = data.currency
|
||||
} else if (!data.allow_fiat) {
|
||||
data.fiat_currency = 'GBP'
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
|
|
@ -453,6 +557,8 @@ window.PageEvents = {
|
|||
? data.extra.payment_methods
|
||||
: ['lightning', ...(data.allow_fiat ? ['fiat'] : [])]
|
||||
},
|
||||
location: data.location || '',
|
||||
categories: [...(data.categories || [])],
|
||||
event_start_day: start.day,
|
||||
event_start_time: start.time,
|
||||
event_end_day: end.day,
|
||||
|
|
@ -463,6 +569,8 @@ window.PageEvents = {
|
|||
currency: 'sats',
|
||||
allow_fiat: false,
|
||||
fiat_currency: 'GBP',
|
||||
location: '',
|
||||
categories: [],
|
||||
event_start_day: '',
|
||||
event_start_time: '',
|
||||
event_end_day: '',
|
||||
|
|
@ -487,6 +595,8 @@ window.PageEvents = {
|
|||
currency: 'sats',
|
||||
allow_fiat: false,
|
||||
fiat_currency: 'GBP',
|
||||
location: '',
|
||||
categories: [],
|
||||
extra: {
|
||||
payment_methods: ['lightning'],
|
||||
email_notifications: false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue