Compare commits
No commits in common. "af3c9853c0a9968891fd32ecda26c2856d448a01" and "e5f0202a4a1a069172d47e286877bf86fd6430e3" have entirely different histories.
af3c9853c0
...
e5f0202a4a
3 changed files with 34 additions and 280 deletions
|
|
@ -32,45 +32,28 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select'
|
} from '@/components/ui/select'
|
||||||
import { Calendar, Loader2, MapPin, AlertCircle } from 'lucide-vue-next'
|
import { Calendar, Loader2, MapPin } from 'lucide-vue-next'
|
||||||
import { toastService } from '@/core/services/ToastService'
|
import { toastService } from '@/core/services/ToastService'
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import ImageUpload from '@/modules/base/components/ImageUpload.vue'
|
import ImageUpload from '@/modules/base/components/ImageUpload.vue'
|
||||||
import DatePicker from '@/modules/base/components/DatePicker.vue'
|
import DatePicker from '@/modules/base/components/DatePicker.vue'
|
||||||
import TimePicker from '@/modules/base/components/TimePicker.vue'
|
import TimePicker from '@/modules/base/components/TimePicker.vue'
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
||||||
import type { ImageUploadService, UploadedImage } from '@/modules/base/services/ImageUploadService'
|
import type { ImageUploadService, UploadedImage } from '@/modules/base/services/ImageUploadService'
|
||||||
import type { TicketApiService } from '../services/TicketApiService'
|
import type { TicketApiService } from '../services/TicketApiService'
|
||||||
import type { CreateEventRequest, TicketedEvent } from '../types/ticket'
|
import type { CreateEventRequest } from '../types/ticket'
|
||||||
import { ALL_CATEGORIES } from '../types/category'
|
import { ALL_CATEGORIES } from '../types/category'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean
|
open: boolean
|
||||||
/** When set, dialog opens in edit mode for this event. */
|
onCreateEvent: (eventData: CreateEventRequest) => Promise<void>
|
||||||
event?: TicketedEvent | null
|
|
||||||
/** Create handler. Required when not editing. */
|
|
||||||
onCreateEvent?: (eventData: CreateEventRequest) => Promise<void>
|
|
||||||
/** Update handler. Required when editing. */
|
|
||||||
onUpdateEvent?: (eventId: string, eventData: CreateEventRequest) => Promise<void>
|
|
||||||
/** Whether the current user is an LNbits admin. Drives the
|
|
||||||
* "edit will go back to pending approval" warning copy. */
|
|
||||||
isAdmin?: boolean
|
|
||||||
/** Whether the events extension has auto_approve enabled. */
|
|
||||||
autoApprove?: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<Props>()
|
const props = defineProps<Props>()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:open': [value: boolean]
|
'update:open': [value: boolean]
|
||||||
'event-created': []
|
'event-created': []
|
||||||
'event-updated': []
|
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isEditMode = computed(() => Boolean(props.event?.id))
|
|
||||||
const willGoToPending = computed(
|
|
||||||
() => isEditMode.value && !props.isAdmin && !props.autoApprove
|
|
||||||
)
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
// Fold a date input ("YYYY-MM-DD") and an optional time input ("HH:MM")
|
// Fold a date input ("YYYY-MM-DD") and an optional time input ("HH:MM")
|
||||||
|
|
@ -134,18 +117,6 @@ interface BannerImage extends UploadedImage {
|
||||||
}
|
}
|
||||||
const bannerImages = ref<BannerImage[]>([])
|
const bannerImages = ref<BannerImage[]>([])
|
||||||
|
|
||||||
// Inverse of foldDateTime: split a stored "YYYY-MM-DD[THH:MM]" back
|
|
||||||
// into separate date + time pieces for the form inputs.
|
|
||||||
function splitDateTime(value: string | null | undefined): { date: string; time: string } {
|
|
||||||
if (!value) return { date: '', time: '' }
|
|
||||||
const [date, time = ''] = value.split('T')
|
|
||||||
return { date, time: time.slice(0, 5) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// When `true`, suppress the auto-mirror watcher so we don't clobber an
|
|
||||||
// edit-mode population with start-date side effects mid-setValues.
|
|
||||||
const isPopulating = ref(false)
|
|
||||||
|
|
||||||
// Auto-mirror end date to start: when the user picks a start date,
|
// Auto-mirror end date to start: when the user picks a start date,
|
||||||
// surface that same date in the end-date picker so a one-day event
|
// surface that same date in the end-date picker so a one-day event
|
||||||
// requires no extra clicks. Don't overwrite an end date the user
|
// requires no extra clicks. Don't overwrite an end date the user
|
||||||
|
|
@ -154,7 +125,6 @@ const isPopulating = ref(false)
|
||||||
watch(
|
watch(
|
||||||
() => form.values.event_start_date,
|
() => form.values.event_start_date,
|
||||||
(start, prev) => {
|
(start, prev) => {
|
||||||
if (isPopulating.value) return
|
|
||||||
if (!start) return
|
if (!start) return
|
||||||
const end = form.values.event_end_date
|
const end = form.values.event_end_date
|
||||||
if (!end || end < start || end === prev) {
|
if (!end || end < start || end === prev) {
|
||||||
|
|
@ -171,61 +141,18 @@ const availableCurrencies = ref<string[]>(['sat'])
|
||||||
const loadingCurrencies = ref(false)
|
const loadingCurrencies = ref(false)
|
||||||
const selectedCategories = ref<string[]>([])
|
const selectedCategories = ref<string[]>([])
|
||||||
|
|
||||||
function populateFromEvent(event: TicketedEvent) {
|
|
||||||
isPopulating.value = true
|
|
||||||
const start = splitDateTime(event.event_start_date)
|
|
||||||
const end = splitDateTime(event.event_end_date)
|
|
||||||
form.setValues({
|
|
||||||
name: event.name,
|
|
||||||
info: event.info ?? '',
|
|
||||||
event_start_date: start.date,
|
|
||||||
event_start_time: start.time,
|
|
||||||
event_end_date: end.date,
|
|
||||||
event_end_time: end.time,
|
|
||||||
location: event.location ?? '',
|
|
||||||
currency: event.currency ?? 'sat',
|
|
||||||
amount_tickets: event.amount_tickets ?? 0,
|
|
||||||
price_per_ticket: event.price_per_ticket ?? 0,
|
|
||||||
})
|
|
||||||
selectedCategories.value = [...(event.categories ?? [])]
|
|
||||||
if (event.banner) {
|
|
||||||
// Mirror the URL-to-alias bridge from market's CreateProductDialog
|
|
||||||
// so the <ImageUpload> renders the existing banner via its pict-rs
|
|
||||||
// file ID. delete_token is unknown for already-uploaded images, so
|
|
||||||
// removal just clears the slot client-side.
|
|
||||||
const url = event.banner
|
|
||||||
const alias = url.includes('/image/original/')
|
|
||||||
? url.split('/image/original/')[1]
|
|
||||||
: url
|
|
||||||
bannerImages.value = [
|
|
||||||
{ alias, isPrimary: true, delete_token: '', details: {} as any },
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
bannerImages.value = []
|
|
||||||
}
|
|
||||||
// Release the watcher guard on the next tick so vee-validate's batched
|
|
||||||
// updates settle before user input can drive the auto-mirror.
|
|
||||||
setTimeout(() => {
|
|
||||||
isPopulating.value = false
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.open, async (isOpen) => {
|
watch(() => props.open, async (isOpen) => {
|
||||||
if (isOpen) {
|
if (isOpen && ticketApi && !loadingCurrencies.value) {
|
||||||
if (ticketApi && !loadingCurrencies.value) {
|
loadingCurrencies.value = true
|
||||||
loadingCurrencies.value = true
|
try {
|
||||||
try {
|
availableCurrencies.value = await ticketApi.getCurrencies()
|
||||||
availableCurrencies.value = await ticketApi.getCurrencies()
|
} catch (error) {
|
||||||
} catch (error) {
|
console.warn('Failed to load currencies:', error)
|
||||||
console.warn('Failed to load currencies:', error)
|
} finally {
|
||||||
} finally {
|
loadingCurrencies.value = false
|
||||||
loadingCurrencies.value = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (props.event) {
|
}
|
||||||
populateFromEvent(props.event)
|
if (!isOpen) {
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectedCategories.value = []
|
selectedCategories.value = []
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -268,11 +195,7 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
formValues.event_start_date,
|
formValues.event_start_date,
|
||||||
formValues.event_start_time
|
formValues.event_start_time
|
||||||
),
|
),
|
||||||
}
|
wallet: preferredWallet.id,
|
||||||
if (!isEditMode.value) {
|
|
||||||
// Wallet binds at creation. The backend ignores the field on
|
|
||||||
// update so we leave it off the edit payload for clean wire.
|
|
||||||
eventData.wallet = preferredWallet.id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional fields — only include if provided
|
// Optional fields — only include if provided
|
||||||
|
|
@ -286,49 +209,21 @@ const onSubmit = form.handleSubmit(async (formValues) => {
|
||||||
if (formValues.location) eventData.location = formValues.location
|
if (formValues.location) eventData.location = formValues.location
|
||||||
if (bannerImages.value.length > 0) {
|
if (bannerImages.value.length > 0) {
|
||||||
eventData.banner = imageService.getImageUrl(bannerImages.value[0].alias)
|
eventData.banner = imageService.getImageUrl(bannerImages.value[0].alias)
|
||||||
} else if (isEditMode.value) {
|
|
||||||
// User cleared the banner during edit — propagate the null so the
|
|
||||||
// backend wipes the field instead of keeping the old image.
|
|
||||||
eventData.banner = null
|
|
||||||
}
|
}
|
||||||
if (formValues.currency) eventData.currency = formValues.currency
|
if (formValues.currency) eventData.currency = formValues.currency
|
||||||
if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets
|
if (formValues.amount_tickets) eventData.amount_tickets = formValues.amount_tickets
|
||||||
if (formValues.price_per_ticket) eventData.price_per_ticket = formValues.price_per_ticket
|
if (formValues.price_per_ticket) eventData.price_per_ticket = formValues.price_per_ticket
|
||||||
if (selectedCategories.value.length > 0) eventData.categories = selectedCategories.value
|
if (selectedCategories.value.length > 0) eventData.categories = selectedCategories.value
|
||||||
|
|
||||||
if (isEditMode.value) {
|
await props.onCreateEvent(eventData)
|
||||||
if (!props.onUpdateEvent || !props.event?.id) {
|
toastService.success('Event submitted!')
|
||||||
toastService.error('Update handler missing')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await props.onUpdateEvent(props.event.id, eventData)
|
|
||||||
toastService.success(
|
|
||||||
willGoToPending.value
|
|
||||||
? 'Event updated — pending re-approval'
|
|
||||||
: 'Event updated!'
|
|
||||||
)
|
|
||||||
emit('event-updated')
|
|
||||||
} else {
|
|
||||||
if (!props.onCreateEvent) {
|
|
||||||
toastService.error('Create handler missing')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await props.onCreateEvent(eventData)
|
|
||||||
toastService.success('Event submitted!')
|
|
||||||
emit('event-created')
|
|
||||||
}
|
|
||||||
|
|
||||||
resetForm()
|
resetForm()
|
||||||
selectedCategories.value = []
|
selectedCategories.value = []
|
||||||
bannerImages.value = []
|
bannerImages.value = []
|
||||||
emit('update:open', false)
|
emit('update:open', false)
|
||||||
|
emit('event-created')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage =
|
const errorMessage = error instanceof Error ? error.message : 'Failed to create event'
|
||||||
error instanceof Error
|
|
||||||
? error.message
|
|
||||||
: isEditMode.value
|
|
||||||
? 'Failed to update event'
|
|
||||||
: 'Failed to create event'
|
|
||||||
toastService.error(errorMessage)
|
toastService.error(errorMessage)
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
|
|
@ -351,27 +246,15 @@ const handleOpenChange = (open: boolean) => {
|
||||||
<DialogHeader class="px-6 pt-6 pb-2">
|
<DialogHeader class="px-6 pt-6 pb-2">
|
||||||
<DialogTitle class="flex items-center gap-2">
|
<DialogTitle class="flex items-center gap-2">
|
||||||
<Calendar class="w-5 h-5" />
|
<Calendar class="w-5 h-5" />
|
||||||
{{ isEditMode ? 'Edit Event' : 'Create Event' }}
|
Create Event
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
{{
|
Only a title and start date are required.
|
||||||
isEditMode
|
|
||||||
? 'Update event details. Tickets already sold are not affected.'
|
|
||||||
: 'Only a title and start date are required.'
|
|
||||||
}}
|
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<ScrollArea class="max-h-[70vh] px-6 pb-6">
|
<ScrollArea class="max-h-[70vh] px-6 pb-6">
|
||||||
<form @submit="onSubmit" class="space-y-4">
|
<form @submit="onSubmit" class="space-y-4">
|
||||||
<Alert v-if="willGoToPending" variant="default" class="border-orange-500/40 bg-orange-500/5">
|
|
||||||
<AlertCircle class="h-4 w-4 text-orange-500" />
|
|
||||||
<AlertDescription>
|
|
||||||
Saving will resubmit for approval. The event will be removed
|
|
||||||
from public feeds until reviewed.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<!-- Title (required) -->
|
<!-- Title (required) -->
|
||||||
<FormField v-slot="{ componentField }" name="name">
|
<FormField v-slot="{ componentField }" name="name">
|
||||||
<FormItem>
|
<FormItem>
|
||||||
|
|
@ -568,11 +451,7 @@ const handleOpenChange = (open: boolean) => {
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="submit" :disabled="isLoading || !isFormValid">
|
<Button type="submit" :disabled="isLoading || !isFormValid">
|
||||||
<Loader2 v-if="isLoading" class="w-4 h-4 mr-2 animate-spin" />
|
<Loader2 v-if="isLoading" class="w-4 h-4 mr-2 animate-spin" />
|
||||||
{{
|
{{ isLoading ? 'Submitting...' : 'Submit Event' }}
|
||||||
isLoading
|
|
||||||
? (isEditMode ? 'Saving...' : 'Submitting...')
|
|
||||||
: (isEditMode ? 'Save changes' : 'Submit Event')
|
|
||||||
}}
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -148,60 +148,6 @@ export class TicketApiService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing event. Requires the event's wallet admin key.
|
|
||||||
* Status is re-derived server-side from admin/auto_approve — a non-
|
|
||||||
* admin owner editing under `auto_approve=false` lands back at
|
|
||||||
* `proposed` regardless of the current state.
|
|
||||||
*/
|
|
||||||
async updateEvent(
|
|
||||||
eventId: string,
|
|
||||||
eventData: CreateEventRequest,
|
|
||||||
adminKey: string,
|
|
||||||
): Promise<TicketedEvent> {
|
|
||||||
return this.request(`/events/api/v1/events/${eventId}`, {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-API-KEY': adminKey,
|
|
||||||
},
|
|
||||||
body: JSON.stringify(eventData),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Probe whether the current user has LNbits admin privileges. The
|
|
||||||
* `/all` endpoint is `check_admin`-gated, so a 200 means "admin",
|
|
||||||
* any other response means "not admin".
|
|
||||||
*/
|
|
||||||
async isAdmin(adminKey: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await this.request('/events/api/v1/events/all', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'X-API-KEY': adminKey },
|
|
||||||
})
|
|
||||||
return true
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read the extension's auto_approve flag. Admin-only endpoint, so
|
|
||||||
* non-admin callers see false (the safe default for UI gating).
|
|
||||||
*/
|
|
||||||
async getAutoApprove(adminKey: string): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
const settings = await this.request('/events/api/v1/events/settings', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: { 'X-API-KEY': adminKey },
|
|
||||||
})
|
|
||||||
return Boolean(settings?.auto_approve)
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch available currencies from LNbits.
|
* Fetch available currencies from LNbits.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { 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, Pencil } from 'lucide-vue-next'
|
import { User, LogIn, Plus } 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, TicketedEvent } from '../types/ticket'
|
import type { CreateEventRequest } from '../types/ticket'
|
||||||
|
|
||||||
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
|
const { upcomingEvents, pastEvents, isLoading, error, refresh } = useEvents()
|
||||||
const { isAuthenticated, userDisplay, currentUser } = useAuth()
|
const { isAuthenticated, userDisplay } = useAuth()
|
||||||
|
|
||||||
const showPurchaseDialog = ref(false)
|
const showPurchaseDialog = ref(false)
|
||||||
const selectedEvent = ref<{
|
const selectedEvent = ref<{
|
||||||
|
|
@ -27,33 +27,7 @@ const selectedEvent = ref<{
|
||||||
currency: string
|
currency: string
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
|
||||||
const showEventDialog = ref(false)
|
const showCreateDialog = 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'
|
||||||
|
|
@ -78,6 +52,7 @@ 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.')
|
||||||
|
|
@ -86,36 +61,7 @@ async function handleCreateEvent(eventData: CreateEventRequest) {
|
||||||
await ticketApi.createEvent(eventData, invoiceKey)
|
await ticketApi.createEvent(eventData, invoiceKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdateEvent(eventId: string, eventData: CreateEventRequest) {
|
function handleEventCreated() {
|
||||||
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>
|
||||||
|
|
@ -137,7 +83,7 @@ function handleEventChanged() {
|
||||||
</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="openCreateDialog" class="flex-1 sm:flex-none">
|
<Button v-if="isAuthenticated" variant="default" size="sm" @click="showCreateDialog = true" 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>
|
||||||
|
|
@ -182,9 +128,9 @@ function handleEventChanged() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardFooter class="flex gap-2">
|
<CardFooter>
|
||||||
<Button
|
<Button
|
||||||
class="flex-1"
|
class="w-full"
|
||||||
variant="default"
|
variant="default"
|
||||||
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
|
:disabled="event.amount_tickets <= event.sold || !isAuthenticated"
|
||||||
@click="handlePurchaseClick(event)"
|
@click="handlePurchaseClick(event)"
|
||||||
|
|
@ -195,15 +141,6 @@ function handleEventChanged() {
|
||||||
</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>
|
||||||
|
|
@ -252,18 +189,10 @@ function handleEventChanged() {
|
||||||
|
|
||||||
<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="showEventDialog"
|
:open="showCreateDialog"
|
||||||
:event="editingEvent"
|
@update:open="showCreateDialog = $event"
|
||||||
:is-admin="isAdmin"
|
|
||||||
:auto-approve="autoApprove"
|
|
||||||
:on-create-event="handleCreateEvent"
|
:on-create-event="handleCreateEvent"
|
||||||
:on-update-event="handleUpdateEvent"
|
@event-created="handleEventCreated"
|
||||||
@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