Files
Spanglish/frontend/src/lib/api/tickets.ts
T
MichilisandClaude Opus 5 a0161a67d2 Add "paid at the door" ticket type to the Add Ticket modal.
Door walk-ins were only expressible as an unpaid ticket, which left the
cash out of revenue. The new type records the cash payment as paid and
makes every field optional, since a walk-in often gives no details:
a blank name is logged as "Walk-in", and a confirmation email only goes
out when an email is entered.

Door tickets reuse paymentStatus 'paid' (the column enum is capped at
paid/unpaid/comp) so badges and revenue totals pick them up with no
migration; the cash payment row is referenced "Paid at door" to keep
them distinguishable from emailed manual tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 04:39:54 +00:00

143 lines
4.7 KiB
TypeScript

import { fetchApi, API_BASE } from './client';
import type {
Ticket,
Payment,
BookingData,
TicketValidationResult,
TicketSearchResult,
LiveSearchResult,
LightningInvoice,
} from './types';
export const ticketsApi = {
book: (data: BookingData) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets', {
method: 'POST',
body: JSON.stringify(data),
}),
getById: (id: string) => fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`),
getAll: (params?: { eventId?: string; status?: string }) => {
const query = new URLSearchParams();
if (params?.eventId) query.set('eventId', params.eventId);
if (params?.status) query.set('status', params.status);
return fetchApi<{ tickets: Ticket[] }>(`/api/tickets?${query}`);
},
// Validate ticket by QR code (for scanner)
validate: (code: string, eventId?: string) =>
fetchApi<TicketValidationResult>('/api/tickets/validate', {
method: 'POST',
body: JSON.stringify({ code, eventId }),
}),
// Search tickets by name/email (for scanner manual search)
search: (query: string, eventId?: string) =>
fetchApi<{ tickets: TicketSearchResult[] }>('/api/tickets/search', {
method: 'POST',
body: JSON.stringify({ query, eventId }),
}),
// Get event check-in stats (for scanner header counter)
getCheckinStats: (eventId: string) =>
fetchApi<{ eventId: string; capacity: number; checkedIn: number; totalActive: number }>(
`/api/tickets/stats/checkin?eventId=${eventId}`
),
// Live search tickets (GET - for scanner live search with debounce)
searchLive: (q: string, eventId?: string) => {
const params = new URLSearchParams();
params.set('q', q);
if (eventId) params.set('eventId', eventId);
return fetchApi<{ tickets: LiveSearchResult[] }>(`/api/tickets/search?${params}`);
},
checkin: (id: string) =>
fetchApi<{ ticket: Ticket & { attendeeName?: string }; event?: { id: string; title: string }; message: string }>(`/api/tickets/${id}/checkin`, {
method: 'POST',
}),
removeCheckin: (id: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/remove-checkin`, {
method: 'POST',
}),
cancel: (id: string) =>
fetchApi<{ message: string }>(`/api/tickets/${id}/cancel`, { method: 'POST' }),
updateStatus: (id: string, status: string) =>
fetchApi<{ ticket: Ticket }>(`/api/tickets/${id}`, {
method: 'PUT',
body: JSON.stringify({ status }),
}),
updateNote: (id: string, note: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/note`, {
method: 'POST',
body: JSON.stringify({ note }),
}),
markPaid: (id: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/mark-paid`, {
method: 'POST',
}),
// For manual payment methods (bank_transfer, tpago) - user marks payment as sent
markPaymentSent: (id: string, payerName?: string) =>
fetchApi<{ payment: Payment; message: string }>(`/api/tickets/${id}/mark-payment-sent`, {
method: 'POST',
body: JSON.stringify({ payerName }),
}),
adminCreate: (data: {
eventId: string;
firstName: string;
lastName?: string;
email?: string;
phone?: string;
preferredLanguage?: 'en' | 'es';
autoCheckin?: boolean;
adminNote?: string;
}) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/create', {
method: 'POST',
body: JSON.stringify(data),
}),
// Unified add-attendee endpoint behind the single Add Ticket modal
// (paid = confirmation + QR, door = cash taken at the door, unpaid = pay link +
// door collection, guest = free comp)
adminAdd: (data: {
eventId: string;
type: 'paid' | 'door' | 'unpaid' | 'guest';
firstName?: string;
lastName?: string;
email?: string;
phone?: string;
preferredLanguage?: 'en' | 'es';
checkinNow?: boolean;
adminNote?: string;
}) =>
fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/add', {
method: 'POST',
body: JSON.stringify(data),
}),
checkPaymentStatus: (ticketId: string) =>
fetchApi<{ ticketStatus: string; paymentStatus: string; lnbitsStatus?: string; isPaid: boolean }>(
`/api/lnbits/status/${ticketId}`
),
// Get a Lightning invoice to pay/re-pay a ticket - reuses the stored invoice
// if it's still valid, otherwise generates a fresh one.
getLightningInvoice: (ticketId: string) =>
fetchApi<{ invoice?: LightningInvoice; reused?: boolean; alreadyPaid?: boolean }>(
`/api/lnbits/invoice/${ticketId}`,
{ method: 'POST' }
),
// Get PDF download URL (returns the URL, not the PDF itself)
getPdfUrl: (id: string) => `${API_BASE}/api/tickets/${id}/pdf`,
};