Non-admin event submissions now land in a "proposed" queue that LNbits
admins review before the event becomes ticketable and publicly listed.
- m008 adds events.events.status (proposed/approved/rejected); m010 seeds
an events.settings singleton row with the auto_approve toggle.
- Models: Event/CreateEvent.status, EventsSettings, optional date fields
with sensible defaults (closing_date defaults to event_end_date which
defaults to event_start_date), PublicEvent.status surfaces the workflow
state on the public endpoint.
- crud: get_all/public/pending_events for the admin views; get/update_settings
for the auto_approve toggle; create_event auto-fills missing date defaults.
- views_api:
* POST /api/v1/events accepts wallet invoice keys so anyone can submit;
handler stamps status="proposed" for non-admins when auto_approve is off
* /public, /all, /pending, /settings (GET+PUT), /{id}/{approve,reject},
/{id}/tickets endpoints; literal-prefix routes declared before /{event_id}
so FastAPI matches them correctly
* Public GET /{event_id} bypasses sold-out / closing-window gates for
proposed/rejected events and returns the trimmed PublicEvent so the SFC
can render a "pending approval" banner
* POST /tickets/{event_id} rejects when event.status != "approved"
- Frontend: index.vue gains an admin Settings card, Pending Approvals list,
status badge column and approve/reject row actions, plus an All Users'
Events admin table; index.js gains the data + methods + an isAdmin probe
via GET /events/all; display.vue shows pending/rejected banners and
hides the Buy Ticket form unless status === "approved".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
4.8 KiB
Vue
150 lines
4.8 KiB
Vue
<template id="page-events-display">
|
|
<div v-if="event" class="row q-col-gutter-md justify-center">
|
|
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
|
<q-card>
|
|
<q-img
|
|
v-if="event.banner"
|
|
:src="event.banner"
|
|
transition="slide-up"
|
|
></q-img>
|
|
<q-card-section class="q-pa-none">
|
|
<h3 class="q-my-none q-pa-lg" v-text="event.name"></h3>
|
|
<div v-html="event.info" class="q-pa-lg"></div>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-banner
|
|
v-if="event.status === 'proposed'"
|
|
class="bg-orange-2 text-orange-10"
|
|
rounded
|
|
>
|
|
<template v-slot:avatar>
|
|
<q-icon name="pending" color="orange-10"></q-icon>
|
|
</template>
|
|
<span class="text-weight-medium">Pending approval</span> — this
|
|
event is awaiting an admin review and is not yet open for tickets.
|
|
</q-banner>
|
|
|
|
<q-banner
|
|
v-else-if="event.status === 'rejected'"
|
|
class="bg-red-2 text-red-10"
|
|
rounded
|
|
>
|
|
<template v-slot:avatar>
|
|
<q-icon name="block" color="red-10"></q-icon>
|
|
</template>
|
|
<span class="text-weight-medium">Not approved</span> — this event
|
|
was reviewed and is not being published.
|
|
</q-banner>
|
|
|
|
<q-card v-if="event.status === 'approved'" class="q-pa-lg">
|
|
<q-card-section class="q-pa-none">
|
|
<h5 class="q-mt-none">Buy Ticket</h5>
|
|
<q-form @submit="createInvoice()" class="q-gutter-md">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.name"
|
|
label="Your name "
|
|
:rules="[val => nameValidation(val)]"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.email"
|
|
type="email"
|
|
label="Your email "
|
|
:rules="[
|
|
val => !!val || '* Required',
|
|
val => emailValidation(val)
|
|
]"
|
|
lazy-rules
|
|
></q-input>
|
|
<q-input
|
|
v-if="this.extra?.conditional"
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.refund"
|
|
label="Refund lnadress or LNURL "
|
|
:rules="[val => !!val || '* Required']"
|
|
lazy-rules
|
|
:hint="`If minimum tickets (${this.extra?.min_tickets}) are not met, refund will be sent.`"
|
|
></q-input>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.promo_code"
|
|
label="(optional) Promo Code "
|
|
></q-input>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
:disable="
|
|
formDialog.data.name == '' ||
|
|
formDialog.data.email == '' ||
|
|
Boolean(paymentReq)
|
|
"
|
|
type="submit"
|
|
>Submit</q-btn
|
|
>
|
|
<q-btn @click="resetForm" flat color="grey" class="q-ml-auto"
|
|
>Clear</q-btn
|
|
>
|
|
</div>
|
|
</q-form>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card v-show="ticketLink.show" class="q-pa-lg">
|
|
<div class="text-center q-mb-lg">
|
|
<q-btn
|
|
unelevated
|
|
size="xl"
|
|
:href="ticketLink.data.link"
|
|
target="_blank"
|
|
color="primary"
|
|
type="a"
|
|
>Link to your ticket!</q-btn
|
|
>
|
|
<br /><br />
|
|
<p>You'll be redirected in a few moments...</p>
|
|
</div>
|
|
</q-card>
|
|
</div>
|
|
|
|
<q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog">
|
|
<q-card
|
|
v-if="!receive.paymentReq"
|
|
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
|
>
|
|
</q-card>
|
|
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
<div class="text-center q-mb-lg">
|
|
<lnbits-qrcode
|
|
:href="'lightning:' + receive.paymentReq"
|
|
:value="'LIGHTNING:' + receive.paymentReq.toUpperCase()"
|
|
></lnbits-qrcode>
|
|
</div>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
outline
|
|
color="grey"
|
|
@click="utils.copyText(receive.paymentReq)"
|
|
>Copy invoice</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</q-card>
|
|
</q-dialog>
|
|
</div>
|
|
<div v-else class="row q-col-gutter-md justify-center">
|
|
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
|
<q-card class="q-pa-lg">
|
|
<q-card-section class="q-pa-none">
|
|
<h3 class="q-my-none q-pa-lg" v-text="eventErrorLabel"></h3>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
</template>
|