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:
parent
cd35fae674
commit
af3c9853c0
1 changed files with 84 additions and 13 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue