Add ticket system with QR scanner and PDF generation

- Add ticket validation and check-in API endpoints
- Add PDF ticket generation with QR codes (pdfkit)
- Add admin QR scanner page with camera support
- Add admin site settings page
- Update email templates with PDF ticket download link
- Add checked_in_by_admin_id field for audit tracking
- Update booking success page with ticket download
- Various UI improvements to events and booking pages
This commit is contained in:
Michilis
2026-02-02 00:45:12 +00:00
parent b0cbaa60f0
commit 9410e83b89
28 changed files with 1930 additions and 85 deletions

View File

@@ -86,8 +86,15 @@ export const ticketsApi = {
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 }),
}),
checkin: (id: string) =>
fetchApi<{ ticket: Ticket; message: string }>(`/api/tickets/${id}/checkin`, {
fetchApi<{ ticket: Ticket & { attendeeName?: string }; event?: { id: string; title: string }; message: string }>(`/api/tickets/${id}/checkin`, {
method: 'POST',
}),
@@ -141,6 +148,9 @@ export const ticketsApi = {
fetchApi<{ ticketStatus: string; paymentStatus: string; lnbitsStatus?: string; isPaid: boolean }>(
`/api/lnbits/status/${ticketId}`
),
// Get PDF download URL (returns the URL, not the PDF itself)
getPdfUrl: (id: string) => `${API_BASE}/api/tickets/${id}/pdf`,
};
// Contacts API
@@ -413,6 +423,8 @@ export interface Event {
titleEs?: string;
description: string;
descriptionEs?: string;
shortDescription?: string;
shortDescriptionEs?: string;
startDatetime: string;
endDatetime?: string;
location: string;
@@ -441,6 +453,7 @@ export interface Ticket {
preferredLanguage?: string;
status: 'pending' | 'confirmed' | 'cancelled' | 'checked_in';
checkinAt?: string;
checkedInByAdminId?: string;
qrCode: string;
adminNote?: string;
createdAt: string;
@@ -449,6 +462,29 @@ export interface Ticket {
user?: User;
}
export interface TicketValidationResult {
valid: boolean;
status: 'valid' | 'already_checked_in' | 'pending_payment' | 'cancelled' | 'invalid' | 'wrong_event';
canCheckIn: boolean;
ticket?: {
id: string;
qrCode: string;
attendeeName: string;
attendeeEmail?: string;
attendeePhone?: string;
status: string;
checkinAt?: string;
checkedInBy?: string;
};
event?: {
id: string;
title: string;
startDatetime: string;
location: string;
};
error?: string;
}
export interface Payment {
id: string;
ticketId: string;
@@ -892,3 +928,42 @@ export const dashboardApi = {
unlinkGoogle: () =>
fetchApi<{ message: string }>('/api/dashboard/unlink-google', { method: 'POST' }),
};
// ==================== Site Settings API ====================
export interface SiteSettings {
id?: string;
timezone: string;
siteName: string;
siteDescription?: string | null;
siteDescriptionEs?: string | null;
contactEmail?: string | null;
contactPhone?: string | null;
facebookUrl?: string | null;
instagramUrl?: string | null;
twitterUrl?: string | null;
linkedinUrl?: string | null;
maintenanceMode: boolean;
maintenanceMessage?: string | null;
maintenanceMessageEs?: string | null;
updatedAt?: string;
updatedBy?: string;
}
export interface TimezoneOption {
value: string;
label: string;
}
export const siteSettingsApi = {
get: () => fetchApi<{ settings: SiteSettings }>('/api/site-settings'),
update: (data: Partial<SiteSettings>) =>
fetchApi<{ settings: SiteSettings; message: string }>('/api/site-settings', {
method: 'PUT',
body: JSON.stringify(data),
}),
getTimezones: () =>
fetchApi<{ timezones: TimezoneOption[] }>('/api/site-settings/timezones'),
};