feat: event proposal and approval workflow #9

Closed
padreug wants to merge 38 commits from feat/event-approval-workflow into main
2 changed files with 131 additions and 2 deletions
Showing only changes of commit 702ab70559 - Show all commits

feat: add pending approvals UI to admin panel
Some checks failed
lint.yml / feat: add pending approvals UI to admin panel (pull_request) Failing after 0s

- Separate "Pending Approvals" card with approve/reject buttons
  (appears only when proposed events exist)
- Status badge column in events table (green/orange/red)
- Inline approve/reject buttons on proposed events in table
- Following castle extension's approval UI pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Padreug 2026-04-27 10:37:48 +02:00

View file

@ -73,7 +73,8 @@ window.app = Vue.createApp({
field: 'sold' field: 'sold'
}, },
{name: 'info', align: 'left', label: 'Info', field: 'info'}, {name: 'info', align: 'left', label: 'Info', field: 'info'},
{name: 'banner', align: 'left', label: 'Banner', field: 'banner'} {name: 'banner', align: 'left', label: 'Banner', field: 'banner'},
{name: 'status', align: 'left', label: 'Status', field: 'status'}
], ],
pagination: { pagination: {
rowsPerPage: 10 rowsPerPage: 10
@ -112,7 +113,56 @@ window.app = Vue.createApp({
} }
} }
}, },
computed: {
pendingEvents() {
return this.events.filter(e => e.status === 'proposed')
}
},
methods: { methods: {
approveEvent(eventId) {
LNbits.utils
.confirmDialog('Approve this event?')
.onOk(() => {
LNbits.api
.request(
'PUT',
'/events/api/v1/events/' + eventId + '/approve',
this.g.user.wallets[0].adminkey
)
.then(() => {
this.$q.notify({
type: 'positive',
message: 'Event approved'
})
this.getEvents()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
},
rejectEvent(eventId) {
LNbits.utils
.confirmDialog('Reject this event?')
.onOk(() => {
LNbits.api
.request(
'PUT',
'/events/api/v1/events/' + eventId + '/reject',
this.g.user.wallets[0].adminkey
)
.then(() => {
this.$q.notify({
type: 'positive',
message: 'Event rejected'
})
this.getEvents()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
})
},
getTickets() { getTickets() {
LNbits.api LNbits.api
.request( .request(

View file

@ -10,6 +10,58 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
<!-- Pending Event Approvals -->
<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" />
Pending Approvals
<q-badge color="orange" :label="pendingEvents.length" class="q-ml-sm" />
</h5>
</div>
</div>
<q-list separator>
<q-item v-for="event in pendingEvents" :key="event.id">
<q-item-section>
<q-item-label>{% raw %}{{ event.name }}{% endraw %}</q-item-label>
<q-item-label caption>
{% raw %}{{ event.event_start_date }}{% endraw %}
&mdash;
{% raw %}{{ event.info.substring(0, 80) }}{% endraw %}{% raw %}{{ event.info.length > 80 ? '...' : '' }}{% endraw %}
</q-item-label>
<q-item-label caption>
{% raw %}{{ event.amount_tickets }}{% endraw %} tickets &bull;
{% raw %}{{ event.price_per_ticket }}{% endraw %} {% raw %}{{ event.currency }}{% endraw %}
</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
dense
outline
color="red"
icon="block"
label="Reject"
size="sm"
@click="rejectEvent(event.id)"
/>
</div>
</q-item-section>
</q-item>
</q-list>
</q-card-section>
</q-card>
<q-card> <q-card>
<q-card-section> <q-card-section>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
@ -76,9 +128,36 @@
></q-btn> ></q-btn>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props"> <q-td v-for="col in props.cols" :key="col.name" :props="props">
<span v-text="col.value"></span> <q-badge
v-if="col.name === 'status'"
:color="col.value === 'approved' ? 'green' : col.value === 'proposed' ? 'orange' : 'red'"
:label="col.value"
/>
<span v-else v-text="col.value"></span>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn
v-if="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="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 <q-btn
flat flat
dense dense