Compare commits
1 commit
3d47862181
...
0e15f74834
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e15f74834 |
3 changed files with 59 additions and 222 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"id": "events",
|
"id": "events",
|
||||||
"version": "1.6.1-aio.14",
|
"version": "1.6.1-aio.11",
|
||||||
"name": "Events",
|
"name": "Events",
|
||||||
"repo": "https://git.atitlan.io/aiolabs/events",
|
"repo": "https://git.atitlan.io/aiolabs/events",
|
||||||
"short_description": "Sell and register event tickets",
|
"short_description": "Sell and register event tickets",
|
||||||
|
|
|
||||||
|
|
@ -67,15 +67,13 @@ window.PageEvents = {
|
||||||
name: 'event_start_date',
|
name: 'event_start_date',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Start date',
|
label: 'Start date',
|
||||||
field: 'event_start_date',
|
field: 'event_start_date'
|
||||||
format: val => this.formatEventDate(val)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'event_end_date',
|
name: 'event_end_date',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'End date',
|
label: 'End date',
|
||||||
field: 'event_end_date',
|
field: 'event_end_date'
|
||||||
format: val => this.formatEventDate(val)
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'closing_date',
|
name: 'closing_date',
|
||||||
|
|
@ -151,58 +149,12 @@ window.PageEvents = {
|
||||||
rowsPerPage: 10
|
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: {
|
formDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
data: {
|
data: {
|
||||||
currency: 'sats',
|
currency: 'sats',
|
||||||
allow_fiat: false,
|
allow_fiat: false,
|
||||||
fiat_currency: 'GBP',
|
fiat_currency: 'GBP',
|
||||||
location: '',
|
|
||||||
categories: [],
|
|
||||||
extra: {
|
extra: {
|
||||||
payment_methods: ['lightning'],
|
payment_methods: ['lightning'],
|
||||||
promo_codes: [],
|
promo_codes: [],
|
||||||
|
|
@ -215,27 +167,6 @@ 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: {
|
methods: {
|
||||||
isFiatCurrency(currency) {
|
isFiatCurrency(currency) {
|
||||||
return !['sat', 'sats'].includes((currency || '').toLowerCase())
|
return !['sat', 'sats'].includes((currency || '').toLowerCase())
|
||||||
|
|
@ -453,62 +384,29 @@ window.PageEvents = {
|
||||||
},
|
},
|
||||||
splitDateTime(value) {
|
splitDateTime(value) {
|
||||||
// Inverse of foldDateTime: split a stored string back into the
|
// Inverse of foldDateTime: split a stored string back into the
|
||||||
// day/time pieces the form inputs bind to. Slicing to HH:MM also
|
// day/time pieces the form inputs bind to.
|
||||||
// drops the seconds + offset suffix withLocalTzOffset stamps on
|
|
||||||
// submit, so the organizer sees the wall-clock they entered.
|
|
||||||
if (!value) return {day: '', time: ''}
|
if (!value) return {day: '', time: ''}
|
||||||
const [day, time = ''] = value.split('T')
|
const [day, time = ''] = value.split('T')
|
||||||
// Time inputs only accept HH:MM, drop any seconds we stored.
|
// Time inputs only accept HH:MM, drop any seconds we stored.
|
||||||
return {day, time: time.slice(0, 5)}
|
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() {
|
sendEventData() {
|
||||||
const wallet = _.findWhere(this.g.user.wallets, {
|
const wallet = _.findWhere(this.g.user.wallets, {
|
||||||
id: this.formDialog.data.wallet
|
id: this.formDialog.data.wallet
|
||||||
})
|
})
|
||||||
const data = {...this.formDialog.data}
|
const data = {...this.formDialog.data}
|
||||||
data.event_start_date = this.withLocalTzOffset(
|
data.event_start_date = this.foldDateTime(
|
||||||
this.foldDateTime(data.event_start_day, data.event_start_time)
|
data.event_start_day,
|
||||||
|
data.event_start_time
|
||||||
)
|
)
|
||||||
data.event_end_date = this.withLocalTzOffset(
|
data.event_end_date = this.foldDateTime(
|
||||||
this.foldDateTime(data.event_end_day, data.event_end_time)
|
data.event_end_day,
|
||||||
|
data.event_end_time
|
||||||
)
|
)
|
||||||
delete data.event_start_day
|
delete data.event_start_day
|
||||||
delete data.event_start_time
|
delete data.event_start_time
|
||||||
delete data.event_end_day
|
delete data.event_end_day
|
||||||
delete data.event_end_time
|
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) {
|
if (data.extra?.promo_codes) {
|
||||||
data.extra.promo_codes = data.extra.promo_codes
|
data.extra.promo_codes = data.extra.promo_codes
|
||||||
|
|
@ -529,13 +427,11 @@ window.PageEvents = {
|
||||||
// allow_fiat stays the fiat-currency carrier the backend and the
|
// allow_fiat stays the fiat-currency carrier the backend and the
|
||||||
// NIP-52 tags read; keep it in lockstep with the checkbox list.
|
// NIP-52 tags read; keep it in lockstep with the checkbox list.
|
||||||
data.allow_fiat = methods.includes('fiat')
|
data.allow_fiat = methods.includes('fiat')
|
||||||
if (this.isFiatCurrency(data.currency)) {
|
if (!this.isFiatCurrency(data.currency)) {
|
||||||
// A fiat-priced event settles in its price currency; mirror it so
|
if (!data.allow_fiat) {
|
||||||
// the payload (and the tickets_fiat_currency tag) stay coherent.
|
|
||||||
data.fiat_currency = data.currency
|
|
||||||
} else if (!data.allow_fiat) {
|
|
||||||
data.fiat_currency = 'GBP'
|
data.fiat_currency = 'GBP'
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (data.id) {
|
if (data.id) {
|
||||||
this.updateEvent(wallet, data)
|
this.updateEvent(wallet, data)
|
||||||
|
|
@ -559,8 +455,6 @@ window.PageEvents = {
|
||||||
? data.extra.payment_methods
|
? data.extra.payment_methods
|
||||||
: ['lightning', ...(data.allow_fiat ? ['fiat'] : [])]
|
: ['lightning', ...(data.allow_fiat ? ['fiat'] : [])]
|
||||||
},
|
},
|
||||||
location: data.location || '',
|
|
||||||
categories: [...(data.categories || [])],
|
|
||||||
event_start_day: start.day,
|
event_start_day: start.day,
|
||||||
event_start_time: start.time,
|
event_start_time: start.time,
|
||||||
event_end_day: end.day,
|
event_end_day: end.day,
|
||||||
|
|
@ -571,8 +465,6 @@ window.PageEvents = {
|
||||||
currency: 'sats',
|
currency: 'sats',
|
||||||
allow_fiat: false,
|
allow_fiat: false,
|
||||||
fiat_currency: 'GBP',
|
fiat_currency: 'GBP',
|
||||||
location: '',
|
|
||||||
categories: [],
|
|
||||||
event_start_day: '',
|
event_start_day: '',
|
||||||
event_start_time: '',
|
event_start_time: '',
|
||||||
event_end_day: '',
|
event_end_day: '',
|
||||||
|
|
@ -599,8 +491,6 @@ window.PageEvents = {
|
||||||
currency: 'sats',
|
currency: 'sats',
|
||||||
allow_fiat: false,
|
allow_fiat: false,
|
||||||
fiat_currency: 'GBP',
|
fiat_currency: 'GBP',
|
||||||
location: '',
|
|
||||||
categories: [],
|
|
||||||
extra: {
|
extra: {
|
||||||
payment_methods: ['lightning'],
|
payment_methods: ['lightning'],
|
||||||
email_notifications: false,
|
email_notifications: false,
|
||||||
|
|
|
||||||
|
|
@ -479,10 +479,8 @@
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model.trim="formDialog.data.name"
|
v-model.trim="formDialog.data.name"
|
||||||
type="text"
|
type="name"
|
||||||
label="Title of event *"
|
label="Title of event "
|
||||||
lazy-rules
|
|
||||||
:rules="[val => !!val || 'Title is required']"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col q-pl-sm">
|
<div class="col q-pl-sm">
|
||||||
|
|
@ -506,30 +504,6 @@
|
||||||
label="Info about the event"
|
label="Info about the event"
|
||||||
hint="Markdown supported"
|
hint="Markdown supported"
|
||||||
></q-input>
|
></q-input>
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialog.data.location"
|
|
||||||
type="text"
|
|
||||||
label="Location"
|
|
||||||
hint="Venue or address, e.g. Salle des fêtes, Foix. Published as the NIP-52 location tag."
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="place"></q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
multiple
|
|
||||||
use-chips
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
v-model="formDialog.data.categories"
|
|
||||||
:options="categoryOptions"
|
|
||||||
label="Categories"
|
|
||||||
hint="Published as NIP-52 hashtags so clients can filter the feed."
|
|
||||||
></q-select>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
|
|
@ -538,16 +512,25 @@
|
||||||
label="Image URL"
|
label="Image URL"
|
||||||
hint="Optional banner image to display on the event page"
|
hint="Optional banner image to display on the event page"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<div class="col-4">Ticket closing date</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model.trim="formDialog.data.closing_date"
|
||||||
|
type="date"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm">
|
||||||
<div class="col-4">Event begins *</div>
|
<div class="col-4">Event begins</div>
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model.trim="formDialog.data.event_start_day"
|
v-model.trim="formDialog.data.event_start_day"
|
||||||
type="date"
|
type="date"
|
||||||
lazy-rules
|
|
||||||
:rules="[val => !!val || 'Start date is required']"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
|
|
@ -569,10 +552,6 @@
|
||||||
dense
|
dense
|
||||||
v-model.trim="formDialog.data.event_end_day"
|
v-model.trim="formDialog.data.event_end_day"
|
||||||
type="date"
|
type="date"
|
||||||
hint="Defaults to the start date"
|
|
||||||
lazy-rules
|
|
||||||
reactive-rules
|
|
||||||
:rules="[validateEndDate]"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3">
|
<div class="col-3">
|
||||||
|
|
@ -585,18 +564,6 @@
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-sm">
|
|
||||||
<div class="col-4">Ticket sales close</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialog.data.closing_date"
|
|
||||||
type="date"
|
|
||||||
hint="Optional. Defaults to the event end date."
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-select
|
<q-select
|
||||||
|
|
@ -604,7 +571,7 @@
|
||||||
dense
|
dense
|
||||||
v-model="formDialog.data.currency"
|
v-model="formDialog.data.currency"
|
||||||
type="text"
|
type="text"
|
||||||
label="Price currency"
|
label="Unit"
|
||||||
:options="currencies"
|
:options="currencies"
|
||||||
></q-select>
|
></q-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -614,9 +581,7 @@
|
||||||
dense
|
dense
|
||||||
v-model.number="formDialog.data.amount_tickets"
|
v-model.number="formDialog.data.amount_tickets"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
label="Amount of tickets "
|
||||||
label="Amount of tickets"
|
|
||||||
hint="0 = unlimited"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
@ -635,57 +600,34 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-mt-sm">
|
<div class="q-mt-sm">
|
||||||
<div class="text-subtitle2">Payment methods *</div>
|
<div class="text-caption text-grey-7">Payment methods *</div>
|
||||||
<div class="text-caption text-grey-7">
|
<q-option-group
|
||||||
Pick the rails buyers can pay with. Untick Lightning for a
|
|
||||||
card-only sale.
|
|
||||||
</div>
|
|
||||||
<div class="row q-col-gutter-md q-mt-xs">
|
|
||||||
<div
|
|
||||||
v-for="opt in paymentMethodOptions"
|
|
||||||
:key="opt.value"
|
|
||||||
class="col-12 col-sm-6"
|
|
||||||
>
|
|
||||||
<!-- The span carries the tooltip: a disabled checkbox
|
|
||||||
swallows pointer events, the wrapper still hovers. -->
|
|
||||||
<span class="inline-block full-width">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="formDialog.data.extra.payment_methods"
|
v-model="formDialog.data.extra.payment_methods"
|
||||||
:val="opt.value"
|
type="checkbox"
|
||||||
:label="opt.label"
|
inline
|
||||||
:disable="opt.value === 'fiat' && !hasFiatProvider"
|
:options="paymentMethodOptions"
|
||||||
></q-checkbox>
|
></q-option-group>
|
||||||
<div class="text-caption text-grey-7 q-pl-lg">
|
<div class="text-caption text-grey-7">
|
||||||
<span v-text="opt.hint"></span>
|
Card / fiat checkout goes through the fiat provider configured on
|
||||||
<span
|
this LNbits instance. Untick Lightning for a card-only event.
|
||||||
v-if="opt.value === 'fiat' && hasFiatProvider"
|
|
||||||
v-text="' (' + fiatProviderNames + ')'"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
<q-tooltip
|
|
||||||
v-if="opt.value === 'fiat' && !hasFiatProvider"
|
|
||||||
max-width="280px"
|
|
||||||
>
|
|
||||||
Your LNbits user has no fiat provider configured. Ask the
|
|
||||||
instance admin to enable Stripe, PayPal or Square (Admin →
|
|
||||||
Fiat providers) to accept card payments.
|
|
||||||
</q-tooltip>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-select
|
<q-select
|
||||||
v-if="acceptsFiat && isSatPrice"
|
v-if="
|
||||||
|
acceptsFiat &&
|
||||||
|
['sat', 'sats'].includes(
|
||||||
|
(formDialog.data.currency || '').toLowerCase()
|
||||||
|
)
|
||||||
|
"
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model="formDialog.data.fiat_currency"
|
v-model="formDialog.data.fiat_currency"
|
||||||
label="Fiat currency"
|
label="Fiat checkout currency"
|
||||||
hint="Currency card buyers are charged in."
|
:options="
|
||||||
:options="fiatCurrencyOptions"
|
currencies.filter(
|
||||||
lazy-rules
|
c => !['sat', 'sats'].includes((c || '').toLowerCase())
|
||||||
:rules="[
|
)
|
||||||
val => !!val || 'Pick a fiat currency for buyers paying by card'
|
"
|
||||||
]"
|
|
||||||
></q-select>
|
></q-select>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
group="advanced"
|
group="advanced"
|
||||||
|
|
@ -859,9 +801,14 @@
|
||||||
unelevated
|
unelevated
|
||||||
color="primary"
|
color="primary"
|
||||||
:disable="
|
:disable="
|
||||||
!formDialog.data.wallet ||
|
formDialog.data.wallet == null ||
|
||||||
!formDialog.data.name ||
|
formDialog.data.name == null ||
|
||||||
!formDialog.data.event_start_day
|
formDialog.data.info == null ||
|
||||||
|
formDialog.data.closing_date == null ||
|
||||||
|
formDialog.data.event_start_day == null ||
|
||||||
|
formDialog.data.event_end_day == null ||
|
||||||
|
formDialog.data.amount_tickets == null ||
|
||||||
|
formDialog.data.price_per_ticket == null
|
||||||
"
|
"
|
||||||
type="submit"
|
type="submit"
|
||||||
>Create Event</q-btn
|
>Create Event</q-btn
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue