const API_BASE = process.env.NEXT_PUBLIC_API_URL || ''; export interface ApiError { error: string; } async function fetchApi( endpoint: string, options: RequestInit = {} ): Promise { const token = typeof window !== 'undefined' ? localStorage.getItem('spanglish-token') : null; const headers: HeadersInit = { 'Content-Type': 'application/json', ...options.headers, }; if (token) { (headers as Record)['Authorization'] = `Bearer ${token}`; } const res = await fetch(`${API_BASE}${endpoint}`, { ...options, headers, }); if (!res.ok) { const errorData = await res.json().catch(() => ({ error: 'Request failed' })); const errorMessage = typeof errorData.error === 'string' ? errorData.error : (errorData.message || JSON.stringify(errorData) || 'Request failed'); throw new Error(errorMessage); } return res.json(); } // Events API export const eventsApi = { getAll: (params?: { status?: string; upcoming?: boolean }) => { const query = new URLSearchParams(); if (params?.status) query.set('status', params.status); if (params?.upcoming) query.set('upcoming', 'true'); return fetchApi<{ events: Event[] }>(`/api/events?${query}`); }, getById: (id: string) => fetchApi<{ event: Event }>(`/api/events/${id}`), getNextUpcoming: () => fetchApi<{ event: Event | null }>('/api/events/next/upcoming'), create: (data: Partial) => fetchApi<{ event: Event }>('/api/events', { method: 'POST', body: JSON.stringify(data), }), update: (id: string, data: Partial) => fetchApi<{ event: Event }>(`/api/events/${id}`, { method: 'PUT', body: JSON.stringify(data), }), delete: (id: string) => fetchApi<{ message: string }>(`/api/events/${id}`, { method: 'DELETE' }), duplicate: (id: string) => fetchApi<{ event: Event; message: string }>(`/api/events/${id}/duplicate`, { method: 'POST' }), }; // Tickets API 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('/api/tickets/validate', { method: 'POST', body: JSON.stringify({ code, eventId }), }), 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), }), manualCreate: (data: { eventId: string; firstName: string; lastName?: string; email: string; phone?: string; preferredLanguage?: 'en' | 'es'; adminNote?: string; }) => fetchApi<{ ticket: Ticket; payment: Payment; message: string }>('/api/tickets/admin/manual', { method: 'POST', body: JSON.stringify(data), }), checkPaymentStatus: (ticketId: string) => 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 export const contactsApi = { submit: (data: { name: string; email: string; message: string }) => fetchApi<{ message: string }>('/api/contacts', { method: 'POST', body: JSON.stringify(data), }), subscribe: (email: string, name?: string) => fetchApi<{ message: string }>('/api/contacts/subscribe', { method: 'POST', body: JSON.stringify({ email, name }), }), getAll: (status?: string) => { const query = status ? `?status=${status}` : ''; return fetchApi<{ contacts: Contact[] }>(`/api/contacts${query}`); }, updateStatus: (id: string, status: string) => fetchApi<{ contact: Contact }>(`/api/contacts/${id}`, { method: 'PUT', body: JSON.stringify({ status }), }), }; // Users API export const usersApi = { getAll: (role?: string) => { const query = role ? `?role=${role}` : ''; return fetchApi<{ users: User[] }>(`/api/users${query}`); }, getById: (id: string) => fetchApi<{ user: User }>(`/api/users/${id}`), update: (id: string, data: Partial) => fetchApi<{ user: User }>(`/api/users/${id}`, { method: 'PUT', body: JSON.stringify(data), }), delete: (id: string) => fetchApi<{ message: string }>(`/api/users/${id}`, { method: 'DELETE' }), }; // Payments API export const paymentsApi = { getAll: (params?: { status?: string; provider?: string; pendingApproval?: boolean }) => { const query = new URLSearchParams(); if (params?.status) query.set('status', params.status); if (params?.provider) query.set('provider', params.provider); if (params?.pendingApproval) query.set('pendingApproval', 'true'); return fetchApi<{ payments: PaymentWithDetails[] }>(`/api/payments?${query}`); }, getPendingApproval: () => fetchApi<{ payments: PaymentWithDetails[] }>('/api/payments/pending-approval'), update: (id: string, data: { status: string; reference?: string; adminNote?: string }) => fetchApi<{ payment: Payment }>(`/api/payments/${id}`, { method: 'PUT', body: JSON.stringify(data), }), approve: (id: string, adminNote?: string, sendEmail: boolean = true) => fetchApi<{ payment: Payment; message: string }>(`/api/payments/${id}/approve`, { method: 'POST', body: JSON.stringify({ adminNote, sendEmail }), }), reject: (id: string, adminNote?: string, sendEmail: boolean = true) => fetchApi<{ payment: Payment; message: string }>(`/api/payments/${id}/reject`, { method: 'POST', body: JSON.stringify({ adminNote, sendEmail }), }), sendReminder: (id: string) => fetchApi<{ message: string; reminderSentAt?: string }>(`/api/payments/${id}/send-reminder`, { method: 'POST', }), updateNote: (id: string, adminNote: string) => fetchApi<{ payment: Payment; message: string }>(`/api/payments/${id}/note`, { method: 'POST', body: JSON.stringify({ adminNote }), }), refund: (id: string) => fetchApi<{ message: string }>(`/api/payments/${id}/refund`, { method: 'POST' }), }; // Payment Options API export const paymentOptionsApi = { // Global payment options getGlobal: () => fetchApi<{ paymentOptions: PaymentOptionsConfig }>('/api/payment-options'), updateGlobal: (data: Partial) => fetchApi<{ paymentOptions: PaymentOptionsConfig; message: string }>('/api/payment-options', { method: 'PUT', body: JSON.stringify(data), }), // Event-specific options (merged with global) getForEvent: (eventId: string) => fetchApi<{ paymentOptions: PaymentOptionsConfig; hasOverrides: boolean }>( `/api/payment-options/event/${eventId}` ), // Event overrides (admin only) getEventOverrides: (eventId: string) => fetchApi<{ overrides: Partial | null }>( `/api/payment-options/event/${eventId}/overrides` ), updateEventOverrides: (eventId: string, data: Partial) => fetchApi<{ overrides: Partial; message: string }>( `/api/payment-options/event/${eventId}/overrides`, { method: 'PUT', body: JSON.stringify(data), } ), deleteEventOverrides: (eventId: string) => fetchApi<{ message: string }>(`/api/payment-options/event/${eventId}/overrides`, { method: 'DELETE', }), }; // Media API export const mediaApi = { getAll: (relatedType?: string, relatedId?: string) => { const params = new URLSearchParams(); if (relatedType) params.set('relatedType', relatedType); if (relatedId) params.set('relatedId', relatedId); const query = params.toString(); return fetchApi<{ media: Media[] }>(`/api/media${query ? `?${query}` : ''}`); }, upload: async (file: File, relatedId?: string, relatedType?: string) => { const token = typeof window !== 'undefined' ? localStorage.getItem('spanglish-token') : null; const formData = new FormData(); formData.append('file', file); if (relatedId) formData.append('relatedId', relatedId); if (relatedType) formData.append('relatedType', relatedType); const res = await fetch(`${API_BASE}/api/media/upload`, { method: 'POST', headers: token ? { 'Authorization': `Bearer ${token}` } : {}, body: formData, }); if (!res.ok) { const errorData = await res.json().catch(() => ({ error: 'Upload failed' })); throw new Error(errorData.error || 'Upload failed'); } return res.json() as Promise<{ media: Media; url: string }>; }, delete: (id: string) => fetchApi<{ message: string }>(`/api/media/${id}`, { method: 'DELETE' }), }; // Admin API export const adminApi = { getDashboard: () => fetchApi<{ dashboard: DashboardData }>('/api/admin/dashboard'), getAnalytics: () => fetchApi<{ analytics: AnalyticsData }>('/api/admin/analytics'), exportTickets: (eventId?: string) => { const query = eventId ? `?eventId=${eventId}` : ''; return fetchApi<{ tickets: ExportedTicket[] }>(`/api/admin/export/tickets${query}`); }, exportFinancial: (params?: { startDate?: string; endDate?: string; eventId?: string }) => { const query = new URLSearchParams(); if (params?.startDate) query.set('startDate', params.startDate); if (params?.endDate) query.set('endDate', params.endDate); if (params?.eventId) query.set('eventId', params.eventId); return fetchApi<{ payments: ExportedPayment[]; summary: FinancialSummary }>(`/api/admin/export/financial?${query}`); }, }; // Emails API export const emailsApi = { // Templates getTemplates: () => fetchApi<{ templates: EmailTemplate[] }>('/api/emails/templates'), getTemplate: (id: string) => fetchApi<{ template: EmailTemplate }>(`/api/emails/templates/${id}`), createTemplate: (data: Partial) => fetchApi<{ template: EmailTemplate; message: string }>('/api/emails/templates', { method: 'POST', body: JSON.stringify(data), }), updateTemplate: (id: string, data: Partial) => fetchApi<{ template: EmailTemplate; message: string }>(`/api/emails/templates/${id}`, { method: 'PUT', body: JSON.stringify(data), }), deleteTemplate: (id: string) => fetchApi<{ message: string }>(`/api/emails/templates/${id}`, { method: 'DELETE' }), getTemplateVariables: (slug: string) => fetchApi<{ variables: EmailVariable[] }>(`/api/emails/templates/${slug}/variables`), // Sending sendToEvent: (eventId: string, data: { templateSlug: string; customVariables?: Record; recipientFilter?: 'all' | 'confirmed' | 'pending' | 'checked_in'; }) => fetchApi<{ success: boolean; sentCount: number; failedCount: number; errors: string[] }>( `/api/emails/send/event/${eventId}`, { method: 'POST', body: JSON.stringify(data), } ), sendCustom: (data: { to: string; toName?: string; subject: string; bodyHtml: string; bodyText?: string; eventId?: string; }) => fetchApi<{ success: boolean; logId?: string; error?: string }>('/api/emails/send/custom', { method: 'POST', body: JSON.stringify(data), }), preview: (data: { templateSlug: string; variables?: Record; locale?: string; }) => fetchApi<{ subject: string; bodyHtml: string }>('/api/emails/preview', { method: 'POST', body: JSON.stringify(data), }), // Logs getLogs: (params?: { eventId?: string; status?: string; limit?: number; offset?: number }) => { const query = new URLSearchParams(); if (params?.eventId) query.set('eventId', params.eventId); if (params?.status) query.set('status', params.status); if (params?.limit) query.set('limit', params.limit.toString()); if (params?.offset) query.set('offset', params.offset.toString()); return fetchApi<{ logs: EmailLog[]; pagination: Pagination }>(`/api/emails/logs?${query}`); }, getLog: (id: string) => fetchApi<{ log: EmailLog }>(`/api/emails/logs/${id}`), getStats: (eventId?: string) => { const query = eventId ? `?eventId=${eventId}` : ''; return fetchApi<{ stats: EmailStats }>(`/api/emails/stats${query}`); }, seedTemplates: () => fetchApi<{ message: string }>('/api/emails/seed-templates', { method: 'POST' }), }; // Types export interface Event { id: string; title: string; titleEs?: string; description: string; descriptionEs?: string; shortDescription?: string; shortDescriptionEs?: string; startDatetime: string; endDatetime?: string; location: string; locationUrl?: string; price: number; currency: string; capacity: number; status: 'draft' | 'published' | 'cancelled' | 'completed' | 'archived'; bannerUrl?: string; externalBookingEnabled?: boolean; externalBookingUrl?: string; bookedCount?: number; availableSeats?: number; isFeatured?: boolean; createdAt: string; updatedAt: string; } export interface Ticket { id: string; bookingId?: string; // Groups multiple tickets from same booking userId: string; eventId: string; attendeeFirstName: string; attendeeLastName?: string; attendeeEmail?: string; attendeePhone?: string; attendeeRuc?: string; preferredLanguage?: string; status: 'pending' | 'confirmed' | 'cancelled' | 'checked_in'; checkinAt?: string; checkedInByAdminId?: string; qrCode: string; adminNote?: string; createdAt: string; event?: Event; payment?: Payment; 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; provider: 'bancard' | 'lightning' | 'cash' | 'bank_transfer' | 'tpago'; amount: number; currency: string; status: 'pending' | 'pending_approval' | 'paid' | 'refunded' | 'failed'; reference?: string; userMarkedPaidAt?: string; payerName?: string; // Name of payer if different from attendee paidAt?: string; paidByAdminId?: string; adminNote?: string; reminderSentAt?: string; // When payment reminder email was sent createdAt: string; updatedAt: string; } export interface PaymentWithDetails extends Payment { ticket: { id: string; bookingId?: string; attendeeFirstName: string; attendeeLastName?: string; attendeeEmail?: string; attendeePhone?: string; status: string; } | null; event: { id: string; title: string; startDatetime: string; } | null; } export interface PaymentOptionsConfig { tpagoEnabled: boolean; tpagoLink?: string | null; tpagoInstructions?: string | null; tpagoInstructionsEs?: string | null; bankTransferEnabled: boolean; bankName?: string | null; bankAccountHolder?: string | null; bankAccountNumber?: string | null; bankAlias?: string | null; bankPhone?: string | null; bankNotes?: string | null; bankNotesEs?: string | null; lightningEnabled: boolean; cashEnabled: boolean; cashInstructions?: string | null; cashInstructionsEs?: string | null; // Booking settings allowDuplicateBookings?: boolean; } export interface User { id: string; email: string; name: string; phone?: string; role: 'admin' | 'organizer' | 'staff' | 'marketing' | 'user'; languagePreference?: string; isClaimed?: boolean; rucNumber?: string; accountStatus?: string; createdAt: string; } export interface Contact { id: string; name: string; email: string; message: string; status: 'new' | 'read' | 'replied'; createdAt: string; } export interface AttendeeData { firstName: string; lastName?: string; } export interface BookingData { eventId: string; firstName: string; lastName: string; email: string; phone: string; preferredLanguage?: 'en' | 'es'; paymentMethod: 'bancard' | 'lightning' | 'cash' | 'bank_transfer' | 'tpago'; ruc?: string; // For multi-ticket bookings attendees?: AttendeeData[]; } export interface DashboardData { stats: { totalUsers: number; totalEvents: number; totalTickets: number; confirmedTickets: number; pendingPayments: number; totalRevenue: number; newContacts: number; totalSubscribers: number; }; upcomingEvents: Event[]; recentTickets: Ticket[]; } export interface AnalyticsData { events: { id: string; title: string; date: string; capacity: number; totalBookings: number; confirmedBookings: number; checkedIn: number; revenue: number; }[]; } export interface Media { id: string; fileUrl: string; type: 'image' | 'video' | 'document'; relatedId?: string; relatedType?: string; createdAt: string; } export interface ExportedTicket { ticketId: string; ticketStatus: string; qrCode: string; checkinAt?: string; userName: string; userEmail: string; userPhone?: string; eventTitle: string; eventDate: string; paymentStatus: string; paymentAmount: number; createdAt: string; } export interface EmailTemplate { id: string; name: string; slug: string; subject: string; subjectEs?: string; bodyHtml: string; bodyHtmlEs?: string; bodyText?: string; bodyTextEs?: string; description?: string; variables: EmailVariable[]; isSystem: boolean; isActive: boolean; createdAt: string; updatedAt: string; } export interface EmailVariable { name: string; description: string; example: string; } export interface EmailLog { id: string; templateId?: string; eventId?: string; recipientEmail: string; recipientName?: string; subject: string; bodyHtml?: string; status: 'pending' | 'sent' | 'failed' | 'bounced'; errorMessage?: string; sentAt?: string; sentBy?: string; createdAt: string; } export interface EmailStats { total: number; sent: number; failed: number; pending: number; } export interface Pagination { total: number; limit: number; offset: number; hasMore: boolean; } export interface ExportedPayment { paymentId: string; amount: number; currency: string; provider: string; status: string; reference?: string; paidAt?: string; createdAt: string; ticketId: string; attendeeFirstName: string; attendeeLastName?: string; attendeeEmail?: string; eventId: string; eventTitle: string; eventDate: string; } export interface FinancialSummary { totalPayments: number; totalPaid: number; totalPending: number; totalRefunded: number; byProvider: { bancard: number; lightning: number; cash: number; bank_transfer: number; tpago: number; }; paidCount: number; pendingCount: number; pendingApprovalCount: number; refundedCount: number; failedCount: number; } // ==================== User Dashboard Types ==================== export interface UserProfile { id: string; email: string; name: string; phone?: string; languagePreference?: string; rucNumber?: string; isClaimed: boolean; accountStatus: string; hasPassword: boolean; hasGoogleLinked: boolean; memberSince: string; membershipDays: number; createdAt: string; } export interface UserTicket extends Ticket { invoice?: { id: string; invoiceNumber: string; pdfUrl?: string; createdAt: string; } | null; } export interface UserPayment extends Payment { ticket: { id: string; attendeeFirstName: string; attendeeLastName?: string; status: string; } | null; event: { id: string; title: string; titleEs?: string; startDatetime: string; } | null; invoice?: { id: string; invoiceNumber: string; pdfUrl?: string; } | null; } export interface UserInvoice { id: string; paymentId: string; invoiceNumber: string; rucNumber?: string; legalName?: string; amount: number; currency: string; pdfUrl?: string; status: string; createdAt: string; event?: { id: string; title: string; titleEs?: string; startDatetime: string; } | null; } export interface UserSession { id: string; userAgent?: string; ipAddress?: string; lastActiveAt: string; createdAt: string; } export interface DashboardSummary { user: { name: string; email: string; accountStatus: string; memberSince: string; membershipDays: number; }; stats: { totalTickets: number; confirmedTickets: number; upcomingEvents: number; pendingPayments: number; }; } export interface NextEventInfo { event: Event; ticket: Ticket; payment: Payment | null; } // ==================== Auth API (new methods) ==================== export const authApi = { // Magic link requestMagicLink: (email: string) => fetchApi<{ message: string }>('/api/auth/magic-link/request', { method: 'POST', body: JSON.stringify({ email }), }), verifyMagicLink: (token: string) => fetchApi<{ user: User; token: string; refreshToken: string }>('/api/auth/magic-link/verify', { method: 'POST', body: JSON.stringify({ token }), }), // Password reset requestPasswordReset: (email: string) => fetchApi<{ message: string }>('/api/auth/password-reset/request', { method: 'POST', body: JSON.stringify({ email }), }), confirmPasswordReset: (token: string, password: string) => fetchApi<{ message: string }>('/api/auth/password-reset/confirm', { method: 'POST', body: JSON.stringify({ token, password }), }), // Account claiming requestClaimAccount: (email: string) => fetchApi<{ message: string }>('/api/auth/claim-account/request', { method: 'POST', body: JSON.stringify({ email }), }), confirmClaimAccount: (token: string, data: { password?: string; googleId?: string }) => fetchApi<{ user: User; token: string; refreshToken: string; message: string }>( '/api/auth/claim-account/confirm', { method: 'POST', body: JSON.stringify({ token, ...data }), } ), // Google OAuth googleAuth: (credential: string) => fetchApi<{ user: User; token: string; refreshToken: string }>('/api/auth/google', { method: 'POST', body: JSON.stringify({ credential }), }), // Change password changePassword: (currentPassword: string, newPassword: string) => fetchApi<{ message: string }>('/api/auth/change-password', { method: 'POST', body: JSON.stringify({ currentPassword, newPassword }), }), // Get current user me: () => fetchApi<{ user: User }>('/api/auth/me'), }; // ==================== User Dashboard API ==================== export const dashboardApi = { // Summary getSummary: () => fetchApi<{ summary: DashboardSummary }>('/api/dashboard/summary'), // Profile getProfile: () => fetchApi<{ profile: UserProfile }>('/api/dashboard/profile'), updateProfile: (data: { name?: string; phone?: string; languagePreference?: string; rucNumber?: string }) => fetchApi<{ profile: UserProfile; message: string }>('/api/dashboard/profile', { method: 'PUT', body: JSON.stringify(data), }), // Tickets getTickets: () => fetchApi<{ tickets: UserTicket[] }>('/api/dashboard/tickets'), getTicket: (id: string) => fetchApi<{ ticket: UserTicket }>(`/api/dashboard/tickets/${id}`), // Next event getNextEvent: () => fetchApi<{ nextEvent: NextEventInfo | null }>('/api/dashboard/next-event'), // Payments getPayments: () => fetchApi<{ payments: UserPayment[] }>('/api/dashboard/payments'), // Invoices getInvoices: () => fetchApi<{ invoices: UserInvoice[] }>('/api/dashboard/invoices'), // Sessions getSessions: () => fetchApi<{ sessions: UserSession[] }>('/api/dashboard/sessions'), revokeSession: (id: string) => fetchApi<{ message: string }>(`/api/dashboard/sessions/${id}`, { method: 'DELETE' }), revokeAllSessions: () => fetchApi<{ message: string }>('/api/dashboard/sessions/revoke-all', { method: 'POST' }), // Security setPassword: (password: string) => fetchApi<{ message: string }>('/api/dashboard/set-password', { method: 'POST', body: JSON.stringify({ password }), }), 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; featuredEventId?: 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) => fetchApi<{ settings: SiteSettings; message: string }>('/api/site-settings', { method: 'PUT', body: JSON.stringify(data), }), getTimezones: () => fetchApi<{ timezones: TimezoneOption[] }>('/api/site-settings/timezones'), setFeaturedEvent: (eventId: string | null) => fetchApi<{ featuredEventId: string | null; message: string }>('/api/site-settings/featured-event', { method: 'PUT', body: JSON.stringify({ eventId }), }), }; // ==================== Legal Pages Types ==================== export interface LegalPage { id: string; slug: string; title: string; titleEs?: string | null; contentText: string; contentTextEs?: string | null; contentMarkdown: string; contentMarkdownEs?: string | null; updatedAt: string; updatedBy?: string | null; createdAt: string; source?: 'database' | 'filesystem'; hasEnglish?: boolean; hasSpanish?: boolean; } export interface LegalPagePublic { id?: string; slug: string; title: string; contentMarkdown: string; updatedAt?: string; source?: 'database' | 'filesystem'; } export interface LegalPageListItem { id: string; slug: string; title: string; updatedAt: string; hasEnglish?: boolean; hasSpanish?: boolean; } // ==================== Legal Pages API ==================== export const legalPagesApi = { // Public endpoints getAll: (locale?: string) => fetchApi<{ pages: LegalPageListItem[] }>(`/api/legal-pages${locale ? `?locale=${locale}` : ''}`), getBySlug: (slug: string, locale?: string) => fetchApi<{ page: LegalPagePublic }>(`/api/legal-pages/${slug}${locale ? `?locale=${locale}` : ''}`), // Admin endpoints getAdminList: () => fetchApi<{ pages: LegalPage[] }>('/api/legal-pages/admin/list'), getAdminPage: (slug: string) => fetchApi<{ page: LegalPage }>(`/api/legal-pages/admin/${slug}`), update: (slug: string, data: { contentMarkdown?: string; contentMarkdownEs?: string; title?: string; titleEs?: string; }) => fetchApi<{ page: LegalPage; message: string }>(`/api/legal-pages/admin/${slug}`, { method: 'PUT', body: JSON.stringify(data), }), seed: () => fetchApi<{ message: string; seeded: number; pages?: string[] }>('/api/legal-pages/admin/seed', { method: 'POST', }), };