feat(activities): edit button on user-owned events

Pencil button in the card footer of upcoming events the current user
owns (event.wallet ∈ currentUser.wallets). Clicking opens the same
CreateEventDialog in edit mode, pre-populated with the event.

Probe `is_admin` and `auto_approve` once at mount so the dialog can
render the "going back to pending" warning copy accurately for
non-admin owners.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-21 12:30:00 +02:00
commit af3c9853c0

View file

@ -1,5 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useEvents } from '../composables/useEvents'
import { useAuth } from '@/composables/useAuthService'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
@ -10,14 +10,14 @@ import { Badge } from '@/components/ui/badge'
import { format } from 'date-fns'
import PurchaseTicketDialog from '../components/PurchaseTicketDialog.vue'
import CreateEventDialog from '../components/CreateEventDialog.vue'
import { User, LogIn, Plus } from 'lucide-vue-next'
import { User, LogIn, Plus, Pencil } from 'lucide-vue-next'
import { formatEventPrice } from '@/lib/utils/formatting'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { TicketApiService } from '../services/TicketApiService'
import type { CreateEventRequest } from '../types/ticket'
import type { CreateEventRequest, TicketedEvent } from '../types/ticket'
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
const { isAuthenticated, userDisplay } = useAuth()
const { isAuthenticated, userDisplay, currentUser } = useAuth()
const showPurchaseDialog = ref(false)
const selectedEvent = ref<{
@ -27,7 +27,33 @@ const selectedEvent = ref<{
currency: string
} | null>(null)
const showCreateDialog = ref(false)
const showEventDialog = ref(false)
// `null` create mode; populated edit mode.
const editingEvent = ref<TicketedEvent | null>(null)
// Probe once at mount so the dialog can render the "going to pending"
// warning accurately. Both probes degrade to safe defaults on failure
// (not admin, not auto-approved), which biases the warning toward
// being shown when in doubt.
const isAdmin = ref(false)
const autoApprove = ref(false)
const myWalletIds = computed(
() => new Set((currentUser.value?.wallets ?? []).map((w) => w.id))
)
function canEdit(event: TicketedEvent): boolean {
return isAuthenticated.value && myWalletIds.value.has(event.wallet)
}
onMounted(async () => {
if (!isAuthenticated.value) return
const adminKey = currentUser.value?.wallets?.[0]?.adminkey
if (!adminKey) return
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
isAdmin.value = await ticketApi.isAdmin(adminKey)
autoApprove.value = await ticketApi.getAutoApprove(adminKey)
})
function formatDate(dateStr: string | null | undefined) {
if (!dateStr) return 'Date not available'
@ -52,7 +78,6 @@ function handlePurchaseClick(event: {
async function handleCreateEvent(eventData: CreateEventRequest) {
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
const { currentUser } = useAuth()
const invoiceKey = currentUser.value?.wallets?.[0]?.inkey
if (!invoiceKey) {
throw new Error('No wallet available. Please log in first.')
@ -61,7 +86,36 @@ async function handleCreateEvent(eventData: CreateEventRequest) {
await ticketApi.createEvent(eventData, invoiceKey)
}
function handleEventCreated() {
async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) {
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
// PUT /events/{id} requires the event's wallet admin key.
const wallet = (currentUser.value?.wallets ?? []).find(
(w) => w.id === editingEvent.value?.wallet,
)
const adminKey = wallet?.adminkey
if (!adminKey) {
throw new Error("Can't find the admin key for this event's wallet.")
}
await ticketApi.updateEvent(eventId, eventData, adminKey)
}
function openCreateDialog() {
editingEvent.value = null
showEventDialog.value = true
}
function openEditDialog(event: TicketedEvent) {
editingEvent.value = event
showEventDialog.value = true
}
function handleDialogClosed() {
// Reset the edit selection so a subsequent "New Event" click opens
// clean instead of inheriting the last-edited event.
if (!showEventDialog.value) editingEvent.value = null
}
function handleEventChanged() {
refresh?.()
}
</script>
@ -83,7 +137,7 @@ function handleEventCreated() {
</div>
</div>
<div class="flex gap-2 sm:flex-shrink-0">
<Button v-if="isAuthenticated" variant="default" size="sm" @click="showCreateDialog = true" class="flex-1 sm:flex-none">
<Button v-if="isAuthenticated" variant="default" size="sm" @click="openCreateDialog" class="flex-1 sm:flex-none">
<Plus class="w-4 h-4" />
<span class="ml-2">Create Event</span>
</Button>
@ -128,9 +182,9 @@ function handleEventCreated() {
</div>
</div>
</CardContent>
<CardFooter>
<CardFooter class="flex gap-2">
<Button
class="w-full"
class="flex-1"
variant="default"
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
@click="handlePurchaseClick(event)"
@ -141,6 +195,15 @@ function handleEventCreated() {
</span>
<span v-else>Buy Ticket</span>
</Button>
<Button
v-if="canEdit(event)"
variant="outline"
size="icon"
aria-label="Edit event"
@click="openEditDialog(event)"
>
<Pencil class="w-4 h-4" />
</Button>
</CardFooter>
</Card>
</div>
@ -189,10 +252,18 @@ function handleEventCreated() {
<PurchaseTicketDialog v-if="selectedEvent" :event="selectedEvent" v-model:is-open="showPurchaseDialog" />
<CreateEventDialog
:open="showCreateDialog"
@update:open="showCreateDialog = $event"
:open="showEventDialog"
:event="editingEvent"
:is-admin="isAdmin"
:auto-approve="autoApprove"
:on-create-event="handleCreateEvent"
@event-created="handleEventCreated"
:on-update-event="handleUpdateEvent"
@update:open="
showEventDialog = $event
handleDialogClosed()
"
@event-created="handleEventChanged"
@event-updated="handleEventChanged"
/>
</div>
</template>