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">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useEvents } from '../composables/useEvents'
|
import { useEvents } from '../composables/useEvents'
|
||||||
import { useAuth } from '@/composables/useAuthService'
|
import { useAuth } from '@/composables/useAuthService'
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
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 { format } from 'date-fns'
|
||||||
import PurchaseTicketDialog from '../components/PurchaseTicketDialog.vue'
|
import PurchaseTicketDialog from '../components/PurchaseTicketDialog.vue'
|
||||||
import CreateEventDialog from '../components/CreateEventDialog.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 { formatEventPrice } from '@/lib/utils/formatting'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import type { TicketApiService } from '../services/TicketApiService'
|
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 { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
|
||||||
const { isAuthenticated, userDisplay } = useAuth()
|
const { isAuthenticated, userDisplay, currentUser } = useAuth()
|
||||||
|
|
||||||
const showPurchaseDialog = ref(false)
|
const showPurchaseDialog = ref(false)
|
||||||
const selectedEvent = ref<{
|
const selectedEvent = ref<{
|
||||||
|
|
@ -27,7 +27,33 @@ const selectedEvent = ref<{
|
||||||
currency: string
|
currency: string
|
||||||
} | null>(null)
|
} | 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) {
|
function formatDate(dateStr: string | null | undefined) {
|
||||||
if (!dateStr) return 'Date not available'
|
if (!dateStr) return 'Date not available'
|
||||||
|
|
@ -52,7 +78,6 @@ function handlePurchaseClick(event: {
|
||||||
|
|
||||||
async function handleCreateEvent(eventData: CreateEventRequest) {
|
async function handleCreateEvent(eventData: CreateEventRequest) {
|
||||||
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
|
const ticketApi = injectService(SERVICE_TOKENS.TICKET_API) as TicketApiService
|
||||||
const { currentUser } = useAuth()
|
|
||||||
const invoiceKey = currentUser.value?.wallets?.[0]?.inkey
|
const invoiceKey = currentUser.value?.wallets?.[0]?.inkey
|
||||||
if (!invoiceKey) {
|
if (!invoiceKey) {
|
||||||
throw new Error('No wallet available. Please log in first.')
|
throw new Error('No wallet available. Please log in first.')
|
||||||
|
|
@ -61,7 +86,36 @@ async function handleCreateEvent(eventData: CreateEventRequest) {
|
||||||
await ticketApi.createEvent(eventData, invoiceKey)
|
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?.()
|
refresh?.()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -83,7 +137,7 @@ function handleEventCreated() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 sm:flex-shrink-0">
|
<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" />
|
<Plus class="w-4 h-4" />
|
||||||
<span class="ml-2">Create Event</span>
|
<span class="ml-2">Create Event</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -128,9 +182,9 @@ function handleEventCreated() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter>
|
<CardFooter class="flex gap-2">
|
||||||
<Button
|
<Button
|
||||||
class="w-full"
|
class="flex-1"
|
||||||
variant="default"
|
variant="default"
|
||||||
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
|
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
|
||||||
@click="handlePurchaseClick(event)"
|
@click="handlePurchaseClick(event)"
|
||||||
|
|
@ -141,6 +195,15 @@ function handleEventCreated() {
|
||||||
</span>
|
</span>
|
||||||
<span v-else>Buy Ticket</span>
|
<span v-else>Buy Ticket</span>
|
||||||
</Button>
|
</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>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -189,10 +252,18 @@ function handleEventCreated() {
|
||||||
|
|
||||||
<PurchaseTicketDialog v-if="selectedEvent" :event="selectedEvent" v-model:is-open="showPurchaseDialog" />
|
<PurchaseTicketDialog v-if="selectedEvent" :event="selectedEvent" v-model:is-open="showPurchaseDialog" />
|
||||||
<CreateEventDialog
|
<CreateEventDialog
|
||||||
:open="showCreateDialog"
|
:open="showEventDialog"
|
||||||
@update:open="showCreateDialog = $event"
|
:event="editingEvent"
|
||||||
|
:is-admin="isAdmin"
|
||||||
|
:auto-approve="autoApprove"
|
||||||
:on-create-event="handleCreateEvent"
|
:on-create-event="handleCreateEvent"
|
||||||
@event-created="handleEventCreated"
|
:on-update-event="handleUpdateEvent"
|
||||||
|
@update:open="
|
||||||
|
showEventDialog = $event
|
||||||
|
handleDialogClosed()
|
||||||
|
"
|
||||||
|
@event-created="handleEventChanged"
|
||||||
|
@event-updated="handleEventChanged"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue