Brings in ticket waves (per-wave price/currency/stock/fiat), the paginated ticket endpoint, the organiser ticket-image template, and the SatsPay on-chain surface. Refs #33. Resolutions that were not mechanical, and why: - set_ticket_paid debits the wave named on the ticket, keeping upstream's `> 0` guards; ours decremented unconditionally and could go negative. The purchase and free-ticket paths now stamp ticket_wave_id / ticket_wave_title, without which every sale would debit the primary wave. - Pricing moved onto the selected wave (basket_totals takes it as a required argument, the promo-validate endpoint resolves the same wave through the shared _resolve_ticket_wave). event.price_per_ticket is a roll-up of the PRIMARY wave since sync_event_ticket_waves, so pricing off the event quoted and charged the first wave's price to buyers who picked a later one. Regression test added. - Kept npub support in two places upstream removed it: the purchase endpoint's normalize_public_key path and the notification dispatcher. Upstream's replacement rejects with "Only NIP-05 Nostr identifiers are supported", which is false for this fork. The purchase-side rejection had merged in outside any conflict marker. - _ticket_image_url existed on both sides as two unrelated features. Ours (always-attached rendered QR card) is now _ticket_card_url; upstream's (organiser template, opt-in per wave) keeps the name. Both are wired into the mail, and the /qr/{ticket_id} endpoint — also duplicated on both sides, on the same route — is merged into one handler rather than registered twice, where the second copy would have been unreachable. - models._parse_date now accepts a full ISO datetime. Upstream's date-only strptime raised ValueError on any event whose closing_date carries a time, which create_event produces by defaulting it from event_end_date — it would have 500'd the purchase path, the public event gate and the promo preview. Reproduced before fixing. - Dropped upstream's inline make_qr_png (we import a superset from .qr) and its duplicate paymentMethodOptions in display.js, which re-derived payment options from per-method booleans and offered an on-chain option the backend rejects; the submit gate now matches the template's condition. - Restored imports the merge silently dropped with upstream's npub removal (normalize_public_key, normalize_private_key, DEFAULT_NOSTR_RELAYS). Event create/update stays ours: upstream's combined endpoint would have replaced the approval workflow and the explicit field allowlist that keeps `status` out of the request body. mypy error set is unchanged from HEAD; ruff, black and the 97 tests pass.
229 lines
7.6 KiB
Vue
229 lines
7.6 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">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.name"
|
|
label="Your name "
|
|
:rules="[val => nameValidation(val)]"
|
|
></q-input>
|
|
</div>
|
|
<div class="col-12 col-md-6 q-pr-sm">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.email"
|
|
type="email"
|
|
:label="
|
|
allowEmailNotifications
|
|
? 'Your email (ticket delivery) '
|
|
: 'Your email '
|
|
"
|
|
:rules="[
|
|
val => !!val || '* Required',
|
|
val => emailValidation(val)
|
|
]"
|
|
lazy-rules
|
|
></q-input>
|
|
</div>
|
|
<div v-if="allowNostrNotifications" class="col-12 col-md-6">
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.nostr_identifier"
|
|
label="(optional) Nostr NIP-05"
|
|
hint="If provided, we'll DM your ticket link after payment."
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
<q-input
|
|
v-if="event.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 (${event.extra?.min_tickets}) are not met, refund will be sent.`"
|
|
></q-input>
|
|
<q-select
|
|
v-if="showTicketWaveSelector"
|
|
filled
|
|
dense
|
|
v-model="formDialog.data.ticket_wave_id"
|
|
emit-value
|
|
map-options
|
|
label="Ticket wave"
|
|
:options="
|
|
activeTicketWaves.map(wave => ({
|
|
label: `${wave.title} - ${wave.price_per_ticket} ${wave.currency}`,
|
|
value: wave.id
|
|
}))
|
|
"
|
|
></q-select>
|
|
<div class="row q-col-gutter-md q-pt-lg items-center">
|
|
<div v-if="paymentMethods.length > 1" class="col-auto">
|
|
<q-option-group
|
|
v-model="formDialog.data.payment_method"
|
|
inline
|
|
:options="paymentMethodOptions"
|
|
></q-option-group>
|
|
</div>
|
|
<div
|
|
:class="
|
|
paymentMethods.length > 1 ? 'col-12 col-md-3' : 'col-12'
|
|
"
|
|
>
|
|
<q-input
|
|
filled
|
|
dense
|
|
v-model.trim="formDialog.data.promo_code"
|
|
label="(optional) Promo Code "
|
|
></q-input>
|
|
</div>
|
|
</div>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
:disable="
|
|
formDialog.data.name == '' ||
|
|
formDialog.data.email == '' ||
|
|
(showTicketWaveSelector && !formDialog.data.ticket_wave_id) ||
|
|
(receive.show && 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
|
|
>
|
|
</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-if="receive.isFiat"
|
|
class="q-pa-lg q-pt-xl lnbits__dialog-card"
|
|
>
|
|
<div class="text-center q-mb-lg">
|
|
<div class="text-h6 q-mb-sm">Continue to checkout</div>
|
|
<div class="text-body2 text-grey-5 q-mb-lg">
|
|
Your fiat checkout opened in a new tab. If it did not, use the
|
|
button below.
|
|
</div>
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
type="a"
|
|
:href="receive.paymentReq"
|
|
target="_blank"
|
|
rel="noopener"
|
|
>
|
|
Go to checkout
|
|
</q-btn>
|
|
</div>
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
outline
|
|
color="grey"
|
|
@click="utils.copyText(receive.paymentReq)"
|
|
>Copy payment link</q-btn
|
|
>
|
|
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
</div>
|
|
</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>
|