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
855 lines
30 KiB
Vue
855 lines
30 KiB
Vue
<template id="page-events">
|
|
<div class="row q-col-gutter-md">
|
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
<q-card v-if="isAdmin">
|
|
<q-card-section>
|
|
<div class="row items-center justify-between">
|
|
<div class="col">
|
|
<span class="text-subtitle1">Settings</span>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-toggle
|
|
v-model="settings.auto_approve"
|
|
label="Auto-approve events"
|
|
@update:model-value="saveSettings"
|
|
></q-toggle>
|
|
</div>
|
|
</div>
|
|
<q-separator class="q-my-md"></q-separator>
|
|
<div class="row items-center justify-between">
|
|
<div class="col">
|
|
<span class="text-subtitle2">Republish to Nostr</span>
|
|
<div class="text-caption text-grey-7" style="color: #aaa">
|
|
Re-emit every approved event so connected clients pick up the
|
|
latest tag set. Useful after the extension publisher changes
|
|
(e.g. new tickets_* tags) so existing events don't need a
|
|
per-event edit.
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn
|
|
outline
|
|
color="primary"
|
|
icon="cloud_upload"
|
|
label="Republish all"
|
|
:loading="republishing"
|
|
@click="republishAllEvents"
|
|
></q-btn>
|
|
</div>
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center q-gutter-sm">
|
|
<q-btn unelevated color="primary" @click="openEventDialog"
|
|
>New Event</q-btn
|
|
>
|
|
<q-btn
|
|
outline
|
|
color="primary"
|
|
icon="cloud_upload"
|
|
label="Republish mine"
|
|
:loading="republishingMine"
|
|
@click="republishMyEvents"
|
|
></q-btn>
|
|
</div>
|
|
<div class="text-caption q-mt-sm" style="color: #aaa">
|
|
Re-emit your approved events to Nostr relays. Useful after a
|
|
publisher upgrade or if a relay dropped your events.
|
|
</div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card v-if="pendingEvents.length > 0">
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">
|
|
<q-icon name="pending" color="orange" class="q-mr-sm"></q-icon>
|
|
Pending Approvals
|
|
<q-badge
|
|
color="orange"
|
|
:label="pendingEvents.length"
|
|
class="q-ml-sm"
|
|
></q-badge>
|
|
</h5>
|
|
</div>
|
|
</div>
|
|
<q-list separator>
|
|
<q-item v-for="event in pendingEvents" :key="event.id">
|
|
<q-item-section>
|
|
<q-item-label v-text="event.name"></q-item-label>
|
|
<q-item-label caption>
|
|
<span v-text="event.event_start_date"></span>
|
|
—
|
|
<span v-text="event.info.substring(0, 80)"></span
|
|
><span v-if="event.info.length > 80">...</span>
|
|
</q-item-label>
|
|
<q-item-label caption>
|
|
<span v-text="event.amount_tickets"></span> tickets •
|
|
<span v-text="event.price_per_ticket"></span>
|
|
<span v-text="event.currency"></span>
|
|
</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side>
|
|
<div class="row q-gutter-sm">
|
|
<q-btn
|
|
dense
|
|
color="green"
|
|
icon="check_circle"
|
|
label="Approve"
|
|
size="sm"
|
|
@click="approveEvent(event.id)"
|
|
></q-btn>
|
|
<q-btn
|
|
dense
|
|
outline
|
|
color="red"
|
|
icon="block"
|
|
label="Reject"
|
|
size="sm"
|
|
@click="rejectEvent(event.id)"
|
|
></q-btn>
|
|
</div>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Events</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exporteventsCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="events"
|
|
row-key="id"
|
|
:columns="eventsTable.columns"
|
|
v-model:pagination="eventsTable.pagination"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th auto-width></q-th>
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
<span v-text="col.label"></span>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
size="sm"
|
|
color="accent"
|
|
round
|
|
dense
|
|
@click="props.expand = !props.expand"
|
|
:icon="props.expand ? 'expand_less' : 'expand_more'"
|
|
/>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="link"
|
|
:color="$q.dark.isActive ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="'/events/' + props.row.id"
|
|
target="_blank"
|
|
></q-btn>
|
|
<q-btn
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="how_to_reg"
|
|
:color="$q.dark.isActive ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="'/events/register/' + props.row.id"
|
|
target="_blank"
|
|
class="q-ml-xs"
|
|
></q-btn>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
v-if="isAdmin && props.row.status === 'proposed'"
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="approveEvent(props.row.id)"
|
|
icon="check_circle"
|
|
color="green"
|
|
>
|
|
<q-tooltip>Approve</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
v-if="isAdmin && props.row.status === 'proposed'"
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="rejectEvent(props.row.id)"
|
|
icon="block"
|
|
color="red"
|
|
>
|
|
<q-tooltip>Reject</q-tooltip>
|
|
</q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="updateformDialog(props.row.id)"
|
|
icon="edit"
|
|
color="light-blue"
|
|
></q-btn>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteEvent(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
class="q-ml-xs"
|
|
></q-btn>
|
|
</q-td>
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<q-badge
|
|
v-if="col.name === 'status'"
|
|
:color="
|
|
col.value === 'approved'
|
|
? 'green'
|
|
: col.value === 'proposed'
|
|
? 'orange'
|
|
: 'red'
|
|
"
|
|
:label="col.value"
|
|
></q-badge>
|
|
<span v-else v-text="col.value"></span>
|
|
</q-td>
|
|
</q-tr>
|
|
<q-tr v-show="props.expand" :props="props">
|
|
<q-td colspan="100%">
|
|
<div class="q-pa-md">
|
|
<div class="text-subtitle1 q-mb-md">Promo codes</div>
|
|
<div class="column">
|
|
<div
|
|
v-if="props.row.extra.promo_codes.length == 0"
|
|
class="text-caption"
|
|
>
|
|
No promo codes for this event.
|
|
</div>
|
|
<div
|
|
v-for="(code, index) in props.row.extra.promo_codes"
|
|
:key="index"
|
|
class="row items-center q-col-gutter-sm q-mb-sm"
|
|
>
|
|
<div class="col-auto">
|
|
<q-chip
|
|
square
|
|
size="md"
|
|
clickable
|
|
@click="utils.copyText(code.code.toUpperCase())"
|
|
>
|
|
<q-avatar
|
|
icon="bookmark"
|
|
:color="code.active ? 'green' : 'grey'"
|
|
text-color="white"
|
|
></q-avatar>
|
|
<span v-text="code.code.toUpperCase()"></span>
|
|
</q-chip>
|
|
</div>
|
|
<div class="col-auto">
|
|
Discount:
|
|
<span v-text="code.discount_percent"></span>%
|
|
</div>
|
|
<div class="col-auto">
|
|
Status:
|
|
<span
|
|
:class="code.active ? 'text-green' : 'text-grey'"
|
|
v-text="code.active ? 'Active' : 'Inactive'"
|
|
></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">Tickets</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-btn flat color="grey" @click="exportticketsCSV"
|
|
>Export to CSV</q-btn
|
|
>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="tickets"
|
|
row-key="id"
|
|
:columns="ticketsTable.columns"
|
|
v-model:pagination="ticketsTable.pagination"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th auto-width></q-th>
|
|
<q-th auto-width></q-th>
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
<span v-text="col.label"></span>
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td auto-width>
|
|
<q-btn
|
|
unelevated
|
|
dense
|
|
size="xs"
|
|
icon="local_activity"
|
|
:color="$q.dark.isActive ? 'grey-7' : 'grey-5'"
|
|
type="a"
|
|
:href="'/events/ticket/' + props.row.id"
|
|
target="_blank"
|
|
></q-btn>
|
|
</q-td>
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="resendTicketEmail(props.row)"
|
|
icon="email"
|
|
color="primary"
|
|
:disable="!props.row.paid || !props.row.email"
|
|
:loading="resendingTicketEmails.includes(props.row.id)"
|
|
>
|
|
<q-tooltip>Resend ticket email</q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<span v-text="col.value"></span>
|
|
</q-td>
|
|
|
|
<q-td auto-width>
|
|
<q-btn
|
|
flat
|
|
dense
|
|
size="xs"
|
|
@click="deleteTicket(props.row.id)"
|
|
icon="cancel"
|
|
color="pink"
|
|
></q-btn>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card v-if="isAdmin && allUserEvents.length > 0">
|
|
<q-card-section>
|
|
<div class="row items-center no-wrap q-mb-md">
|
|
<div class="col">
|
|
<h5 class="text-subtitle1 q-my-none">
|
|
All Users' Events
|
|
<q-badge
|
|
color="blue"
|
|
:label="allUserEvents.length"
|
|
class="q-ml-sm"
|
|
></q-badge>
|
|
</h5>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
dense
|
|
flat
|
|
:rows="allUserEvents"
|
|
row-key="id"
|
|
:columns="allUsersEventsTable.columns"
|
|
:pagination="{rowsPerPage: 10}"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
<span v-text="col.label"></span>
|
|
</q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
<q-badge
|
|
v-if="col.name === 'status'"
|
|
:color="
|
|
col.value === 'approved'
|
|
? 'green'
|
|
: col.value === 'proposed'
|
|
? 'orange'
|
|
: 'red'
|
|
"
|
|
:label="col.value"
|
|
></q-badge>
|
|
<span v-else v-text="col.value"></span>
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 ellipsis q-my-none">
|
|
<span v-text="SITE_TITLE"></span>
|
|
Events extension
|
|
</h6>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
<q-list>
|
|
<q-expansion-item
|
|
group="extras"
|
|
icon="swap_vertical_circle"
|
|
label="Info"
|
|
:content-inset-level="0.5"
|
|
>
|
|
<q-card>
|
|
<q-card-section>
|
|
<h5 class="text-subtitle1 q-my-none">
|
|
Events: Sell and register ticket waves for an event
|
|
</h5>
|
|
<p>
|
|
Events allows you to make a wave of tickets for an event,
|
|
each ticket is in the form of a unique QRcode, which the
|
|
user presents at registration. Events comes with a shareable
|
|
ticket scanner, which can be used to register attendees.<br />
|
|
<small>
|
|
Created by,
|
|
<a class="text-secondary" href="https://github.com/benarc"
|
|
>Ben Arc</a
|
|
>
|
|
</small>
|
|
</p>
|
|
</q-card-section>
|
|
</q-card>
|
|
<q-btn
|
|
flat
|
|
label="Swagger API"
|
|
type="a"
|
|
href="../docs#/events"
|
|
></q-btn>
|
|
</q-expansion-item>
|
|
</q-list>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
<q-dialog v-model="formDialog.show" position="top">
|
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<q-form @submit="sendEventData" class="q-gutter-md">
|
|
<div class="row">
|
|
<div class="col">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.name"
|
|
type="text"
|
|
label="Title of event *"
|
|
lazy-rules
|
|
:rules="[val => !!val || 'Title is required']"
|
|
></q-input>
|
|
</div>
|
|
<div class="col q-pl-sm">
|
|
<q-select
|
|
filled
|
|
dense
|
|
emit-value
|
|
v-model="formDialog.data.wallet"
|
|
:options="g.user.walletOptions"
|
|
label="Wallet *"
|
|
>
|
|
</q-select>
|
|
</div>
|
|
</div>
|
|
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.info"
|
|
type="textarea"
|
|
label="Info about the event"
|
|
hint="Markdown supported"
|
|
></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
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.banner"
|
|
type="url"
|
|
label="Image URL"
|
|
hint="Optional banner image to display on the event page"
|
|
></q-input>
|
|
<div class="row q-col-gutter-sm">
|
|
<div class="col-4">Event begins *</div>
|
|
<div class="col-5">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.event_start_day"
|
|
type="date"
|
|
lazy-rules
|
|
:rules="[val => !!val || 'Start date is required']"
|
|
></q-input>
|
|
</div>
|
|
<div class="col-3">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.event_start_time"
|
|
type="time"
|
|
hint="Optional"
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row q-col-gutter-sm">
|
|
<div class="col-4">Event ends</div>
|
|
<div class="col-5">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.event_end_day"
|
|
type="date"
|
|
hint="Defaults to the start date"
|
|
lazy-rules
|
|
reactive-rules
|
|
:rules="[validateEndDate]"
|
|
></q-input>
|
|
</div>
|
|
<div class="col-3">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.event_end_time"
|
|
type="time"
|
|
hint="Optional"
|
|
></q-input>
|
|
</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="col">
|
|
<q-select
|
|
filled
|
|
dense
|
|
v-model="formDialog.data.currency"
|
|
type="text"
|
|
label="Price currency"
|
|
:options="currencies"
|
|
></q-select>
|
|
</div>
|
|
<div class="col">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="formDialog.data.amount_tickets"
|
|
type="number"
|
|
min="0"
|
|
label="Amount of tickets"
|
|
hint="0 = unlimited"
|
|
></q-input>
|
|
</div>
|
|
<div class="col">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="formDialog.data.price_per_ticket"
|
|
type="number"
|
|
:label="'Price (' + formDialog.data.currency + ') *'"
|
|
:step="formDialog.data.currency != 'sats' ? '0.01' : '1'"
|
|
:mask="formDialog.data.currency != 'sats' ? '#.##' : '#'"
|
|
fill-mask="0"
|
|
reverse-fill-mask
|
|
:disable="formDialog.data.currency == null"
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
<div class="q-mt-sm">
|
|
<div class="text-subtitle2">Payment methods *</div>
|
|
<div class="text-caption text-grey-7">
|
|
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"
|
|
:val="opt.value"
|
|
:label="opt.label"
|
|
:disable="opt.value === 'fiat' && !hasFiatProvider"
|
|
></q-checkbox>
|
|
<div class="text-caption text-grey-7 q-pl-lg">
|
|
<span v-text="opt.hint"></span>
|
|
<span
|
|
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>
|
|
<q-select
|
|
v-if="acceptsFiat && isSatPrice"
|
|
filled
|
|
dense
|
|
v-model="formDialog.data.fiat_currency"
|
|
label="Fiat currency"
|
|
hint="Currency card buyers are charged in."
|
|
:options="fiatCurrencyOptions"
|
|
lazy-rules
|
|
:rules="[
|
|
val => !!val || 'Pick a fiat currency for buyers paying by card'
|
|
]"
|
|
></q-select>
|
|
<q-expansion-item
|
|
group="advanced"
|
|
icon="settings"
|
|
label="Advanced options"
|
|
>
|
|
<div class="row q-mt-lg">
|
|
<div class="text-subtitle1 q-mb-md">Conditional Events</div>
|
|
<div class="text-caption">
|
|
Make this event conditional if
|
|
<strong>minimum tickets</strong> are sold. User will be asked to
|
|
provide a Lightning Address or LNURL pay for refunds.
|
|
</div>
|
|
<div class="col-8">
|
|
<q-toggle
|
|
v-model="formDialog.data.extra.conditional"
|
|
label="Conditional Event"
|
|
left-label
|
|
></q-toggle>
|
|
</div>
|
|
<div class="col-4">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.number="formDialog.data.extra.min_tickets"
|
|
type="number"
|
|
label="Minimum Tickets"
|
|
:disable="!formDialog.data.extra.conditional"
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
<q-separator class="q-my-md"></q-separator>
|
|
<div class="text-subtitle1 q-mb-md">Promo Codes</div>
|
|
<div class="text-caption">
|
|
Allow users to enter a promo code for discounts.
|
|
</div>
|
|
|
|
<div
|
|
v-for="(code, index) in formDialog.data.extra.promo_codes"
|
|
:key="index"
|
|
class="row q-col-gutter-sm q-mt-md"
|
|
>
|
|
<q-input
|
|
class="col-8"
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.extra.promo_codes[index].code"
|
|
type="text"
|
|
label="Promo Code"
|
|
>
|
|
<template v-slot:before>
|
|
<q-checkbox
|
|
left-label
|
|
v-model="formDialog.data.extra.promo_codes[index].active"
|
|
checked-icon="radio_button_checked"
|
|
unchecked-icon="radio_button_unchecked"
|
|
></q-checkbox>
|
|
<q-tooltip>
|
|
<span
|
|
v-text="
|
|
formDialog.data.extra.promo_codes[index].active
|
|
? 'Active'
|
|
: 'Inactive'
|
|
"
|
|
></span>
|
|
</q-tooltip>
|
|
</template>
|
|
</q-input>
|
|
<q-input
|
|
class="col-4"
|
|
filled
|
|
dense
|
|
v-model.number="
|
|
formDialog.data.extra.promo_codes[index].discount_percent
|
|
"
|
|
type="number"
|
|
label="Discount (%)"
|
|
min="0"
|
|
max="100"
|
|
>
|
|
<template v-slot:after>
|
|
<q-btn
|
|
round
|
|
dense
|
|
flat
|
|
icon="delete"
|
|
@click="formDialog.data.extra.promo_codes.splice(index, 1)"
|
|
></q-btn>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
<div class="col-12 q-mt-md">
|
|
<q-btn
|
|
@click="
|
|
formDialog.data.extra.promo_codes.push({
|
|
code: '',
|
|
discount_percent: 0,
|
|
active: true
|
|
})
|
|
"
|
|
>Add Promo Code</q-btn
|
|
>
|
|
</div>
|
|
<q-separator class="q-my-md"></q-separator>
|
|
<div class="text-subtitle1 q-mb-md">Ticket Delivery</div>
|
|
<div class="text-caption">
|
|
Send the paid ticket link automatically by email or Nostr DM.
|
|
</div>
|
|
<q-toggle
|
|
v-model="formDialog.data.extra.email_notifications"
|
|
label="Email notifications"
|
|
left-label
|
|
></q-toggle>
|
|
<q-toggle
|
|
v-model="formDialog.data.extra.nostr_notifications"
|
|
label="Nostr notifications"
|
|
left-label
|
|
></q-toggle>
|
|
</q-expansion-item>
|
|
|
|
<q-separator class="q-my-md"></q-separator>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.extra.notification_subject"
|
|
type="text"
|
|
label="Ticket notification subject"
|
|
hint="Used as the email subject when sending paid ticket links."
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.extra.notification_body"
|
|
type="textarea"
|
|
label="Ticket notification body"
|
|
hint="Shown before the ticket link in the paid ticket notification."
|
|
></q-input>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
v-if="formDialog.data.id"
|
|
unelevated
|
|
color="primary"
|
|
type="submit"
|
|
>Update Event</q-btn
|
|
>
|
|
<q-btn
|
|
v-else
|
|
unelevated
|
|
color="primary"
|
|
:disable="
|
|
!formDialog.data.wallet ||
|
|
!formDialog.data.name ||
|
|
!formDialog.data.event_start_day
|
|
"
|
|
type="submit"
|
|
>Create Event</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
|
>Cancel</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
</template>
|