import type { CreateEventDto, CreateEventFaqDto, CreateEventMediaDto, EventCategoryResponseDto, EventResponseDto, OrganizerGuestListResponseDto, } from '@/api/generated/models' import { getAdminEvents } from '@/api/generated/admin-events/admin-events' import { getEventCategories } from '@/api/generated/event-categories/event-categories' import { getEventExtras } from '@/api/generated/event-extras/event-extras' import { getMyEvents } from '@/api/generated/my-events/my-events' import { unwrapApiPayload } from '@/helpers/listResponse' import axiosInstance from '@/config/axios' import { API_ROUTES } from '@/services/config' import { texts } from '@/texts' import { ANALYTICS_EVENTS, trackAnalyticsEventOnce } from '@/lib/analytics' export type EventCategory = EventCategoryResponseDto export type CreateEventPayload = CreateEventDto export type CreatedEvent = EventResponseDto export type CreateEventMediaPayload = CreateEventMediaDto export type CreateEventFaqPayload = CreateEventFaqDto export type OrganizerGuestList = OrganizerGuestListResponseDto export interface PreviousAttendee { userId: string firstName: string | null lastName: string | null } interface MyHostedEventSummary { id: string title: string slug: string status: string startsAt: string bookedCount: number reservedCapacity?: number capacity: number isFree: boolean price: number createdAt: string adminApprovedAt?: string | null rejectionReason?: string | null } /** The endpoint returns the full event today while this intersection keeps older cached summaries valid. */ export type MyHostedEvent = MyHostedEventSummary & Partial> const myEventsApi = getMyEvents() const adminEventsApi = getAdminEvents() const eventCategoriesApi = getEventCategories() const eventExtrasApi = getEventExtras() export async function fetchEventCategories(): Promise { const response = await eventCategoriesApi.eventCategoriesControllerList() const payload = unwrapApiPayload(response.data) return Array.isArray(payload) ? (payload as EventCategory[]) : [] } export async function createEvent(payload: CreateEventPayload): Promise { const response = await myEventsApi.myEventsControllerCreate(payload) const data = unwrapApiPayload(response.data) if (!data?.id) { throw new Error(texts.common.invalidServerResponse) } trackAnalyticsEventOnce(ANALYTICS_EVENTS.EVENT_CREATED, data.id, { event_id: data.id, event_slug: data.slug, category_id: payload.categoryId, is_free: payload.isFree, }) return data } // Self-service endpoints (events/:id, events/:id/publish) — ownership- // checked server-side against the logged-in user. Only works if the // currently logged-in user is the event's own organizer_id. For an admin // acting on any organizer's event, use the *AsAdmin variants below instead. export async function publishEvent(eventId: string): Promise { const response = await myEventsApi.myEventsControllerPublish(eventId) const event = unwrapApiPayload(response.data) trackAnalyticsEventOnce(ANALYTICS_EVENTS.EVENT_PUBLISHED, event.id, { event_id: event.id, event_slug: event.slug, }) return event } export async function cancelEvent(eventId: string): Promise { const response = await myEventsApi.myEventsControllerCancel(eventId) return unwrapApiPayload(response.data) } export async function completeEvent(eventId: string): Promise { const response = await myEventsApi.myEventsControllerComplete(eventId) return unwrapApiPayload(response.data) } /** Soft-delete when booked_count = 0. */ export async function deleteEvent(eventId: string): Promise { const response = await axiosInstance.delete(API_ROUTES.EVENTS.DELETE(eventId)) return unwrapApiPayload(response.data) } /** Admin approval is one half of the two-key publication workflow. */ export async function approveEventAsAdmin(eventId: string): Promise { const response = await adminEventsApi.adminEventsControllerApprove(eventId) return unwrapApiPayload(response.data) } /** Force-publish bypass: skips the request/approve flow entirely. */ export async function publishEventAsAdmin(eventId: string): Promise { const response = await adminEventsApi.adminEventsControllerPublish(eventId) return unwrapApiPayload(response.data) } export async function rejectEventAsAdmin(eventId: string, rejectionReason: string): Promise { const response = await adminEventsApi.adminEventsControllerReject(eventId, { rejectionReason }) return unwrapApiPayload(response.data) } export async function cancelEventAsAdmin(eventId: string): Promise { const response = await adminEventsApi.adminEventsControllerCancel(eventId) return unwrapApiPayload(response.data) } export async function completeEventAsAdmin(eventId: string): Promise { const response = await adminEventsApi.adminEventsControllerComplete(eventId) return unwrapApiPayload(response.data) } export async function deleteEventAsAdmin(eventId: string): Promise { const response = await axiosInstance.delete(API_ROUTES.EVENTS.ADMIN_DELETE(eventId)) return unwrapApiPayload(response.data) } export type EventEditAccessMode = 'admin' | 'owner' export async function updateEvent(eventId: string, payload: Partial): Promise { const response = await myEventsApi.myEventsControllerUpdate(eventId, payload) return unwrapApiPayload(response.data) } export async function updateEventReservedCapacity( eventId: string, reservedCapacity: number, accessMode: EventEditAccessMode = 'owner' ): Promise { const path = accessMode === 'admin' ? API_ROUTES.EVENTS.ADMIN_RESERVED_CAPACITY(eventId) : API_ROUTES.EVENTS.RESERVED_CAPACITY(eventId) const response = await axiosInstance.patch(path, { reservedCapacity }) return unwrapApiPayload(response.data) } /** Admin can edit any organizer's event (same field locks as host). */ export async function updateEventAsAdmin(eventId: string, payload: Partial): Promise { const response = await axiosInstance.patch(API_ROUTES.EVENTS.ADMIN_DETAIL(eventId), payload) return unwrapApiPayload(response.data) } /** Admin-only: set (or, with null, clear) this event's platform commission override. */ export async function updateEventCommissionAsAdmin(eventId: string, commissionPercent: number | null): Promise { const response = await axiosInstance.patch(API_ROUTES.EVENTS.ADMIN_COMMISSION(eventId), { commissionPercent }) return unwrapApiPayload(response.data) } /** * Full event for edit/clone forms. Backend authorizes owner OR admin; * callers do not pass access mode. */ export async function fetchEventForEdit(eventId: string): Promise { const response = await axiosInstance.get(API_ROUTES.EVENTS.FOR_EDIT(eventId)) return unwrapApiPayload(response.data) } /** Admin detail including bookmarkCount + organizer contact. */ export async function fetchAdminEventDetail(eventId: string): Promise { const response = await axiosInstance.get(API_ROUTES.EVENTS.ADMIN_DETAIL(eventId)) return unwrapApiPayload(response.data) } export async function createEventMedia(eventId: string, payload: CreateEventMediaPayload): Promise { await eventExtrasApi.eventMediaControllerCreate(eventId, payload) } export async function updateEventMedia(eventId: string, mediaId: string, payload: Partial): Promise { await eventExtrasApi.eventMediaControllerUpdate(eventId, mediaId, payload) } export async function deleteEventMedia(eventId: string, mediaId: string): Promise { await eventExtrasApi.eventMediaControllerRemove(eventId, mediaId) } export async function createEventFaq(eventId: string, payload: CreateEventFaqPayload): Promise { await eventExtrasApi.eventFaqsControllerCreate(eventId, payload) } export async function updateEventFaq(eventId: string, faqId: string, payload: Partial): Promise { await eventExtrasApi.eventFaqsControllerUpdate(eventId, faqId, payload) } export async function deleteEventFaq(eventId: string, faqId: string): Promise { await eventExtrasApi.eventFaqsControllerRemove(eventId, faqId) } export interface EventRevisionPayload { title?: string slug?: string shortDescription?: string description?: string categoryId?: number startsAt?: string endsAt?: string provinceId?: number cityId?: number address?: string lat?: number lng?: number isFree?: boolean price?: number capacity?: number genderRestriction?: string ageRestriction?: string cancellationFeePercent?: number settings?: CreateEventPayload['settings'] media: CreateEventMediaPayload[] faqs?: CreateEventFaqPayload[] } export interface EventRevision { id: string eventId: string status: string payload: EventRevisionPayload & Record createdBy: string reviewedBy: string | null rejectionReason: string | null submittedAt: string decidedAt: string | null } export async function submitEventRevision(eventId: string, payload: EventRevisionPayload): Promise { const response = await axiosInstance.post(API_ROUTES.EVENTS.REVISION_SUBMIT(eventId), payload) return unwrapApiPayload(response.data) } export async function fetchPendingEventRevision(eventId: string): Promise { try { const response = await axiosInstance.get(API_ROUTES.EVENTS.REVISION_PENDING(eventId)) return unwrapApiPayload(response.data) } catch (error) { const status = (error as { response?: { status?: number } })?.response?.status if (status === 404) return null throw error } } export async function withdrawPendingEventRevision(eventId: string): Promise { await axiosInstance.delete(API_ROUTES.EVENTS.REVISION_WITHDRAW(eventId)) } export async function fetchAdminPendingEventRevision(eventId: string): Promise { try { const response = await axiosInstance.get(API_ROUTES.EVENTS.ADMIN_REVISION_PENDING(eventId)) return unwrapApiPayload(response.data) } catch (error) { const status = (error as { response?: { status?: number } })?.response?.status if (status === 404) return null throw error } } export async function approveEventRevisionAsAdmin(eventId: string, revisionId: string): Promise { const response = await axiosInstance.patch(API_ROUTES.EVENTS.ADMIN_REVISION_APPROVE(eventId, revisionId)) return unwrapApiPayload(response.data) } export async function rejectEventRevisionAsAdmin(eventId: string, revisionId: string, rejectionReason: string): Promise { const response = await axiosInstance.patch(API_ROUTES.EVENTS.ADMIN_REVISION_REJECT(eventId, revisionId), { rejectionReason, }) return unwrapApiPayload(response.data) } export async function linkGuestListToEvent(eventId: string, listId: string): Promise { await eventExtrasApi.eventGuestListLinksControllerLink(eventId, { listId }) } export async function fetchOrganizerGuestLists(): Promise { const response = await eventExtrasApi.organizerGuestListsControllerList({ page: 1, pageSize: 100 }) const payload = unwrapApiPayload(response.data) const items = Array.isArray(payload.items) ? payload.items : Array.isArray(payload) ? payload : [] return items as OrganizerGuestList[] } export async function fetchPreviousAttendees(): Promise { const response = await axiosInstance.get('/organizer-guest-lists/previous-attendees') const payload = unwrapApiPayload(response.data) return Array.isArray(payload) ? (payload as PreviousAttendee[]) : [] } export async function setPreviousAttendeeGuests(eventId: string, userIds: string[]): Promise { // Persists the invite set. SMS is sent automatically when the event is published // (or immediately if it is already published and has not been notified yet). await axiosInstance.put(`/events/${eventId}/guest-list-links/invited-guests`, { userIds }) }