import axios from "axios"; import { useAuthStore } from "@/store/authStore"; const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8081/api", headers: { "Content-Type": "application/json" }, }); apiClient.interceptors.request.use((config) => { const token = useAuthStore.getState().token; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // ─── Auth ───────────────────────────────────────────────────────────────────── interface LoginResponse { access_token: string; token_type: string; } /** * POST /auth/login * Returns a Bearer access token on success. * Throws AxiosError 401 for invalid credentials, 422 for validation errors. */ export async function loginApi( email: string, password: string ): Promise<{ access_token: string }> { const response = await apiClient.post("/auth/login", { email, password, }); return { access_token: response.data.access_token }; } // ─── Domain types (mirror backend schemas) ──────────────────────────────────── export interface TournamentInfo { id: number; title: string; event_date: string; // ISO-8601 } export interface SeatInfo { id: number; sector: string; row: number; number: number; price: number; tournament: TournamentInfo; } export type TicketStatus = "AVAILABLE" | "LOCKED" | "PAID" | "SCANNED" | "REFUNDED"; export interface TicketResponse { id: number; status: TicketStatus; pdf_url: string | null; created_at: string; // ISO-8601 seat: SeatInfo; } // ─── Tickets API ────────────────────────────────────────────────────────────── /** * GET /api/tickets/me * Returns all PAID tickets for the authenticated user. * Throws AxiosError 401 if the token is missing or expired. */ export async function getMyTicketsApi(): Promise { const response = await apiClient.get("/tickets/me"); return response.data; } // ─── Seat locking ───────────────────────────────────────────────────────────── interface LockSeatResponse { message: string; seat_id: number; status: string; /** Returned by backend when the Ticket row is created. Falls back to seat_id. */ ticket_id?: number; } /** * POST /api/seats/{seat_id}/lock?user_id={user_id} * Returns the ticketId for the locked seat (falls back to seatId if backend * does not yet expose ticket_id in the response body). * Throws AxiosError with status 409 if the seat is already locked. */ export async function lockSeatApi( seatId: number, userId: number ): Promise<{ ticketId: number }> { const response = await apiClient.post( `/seats/${seatId}/lock`, null, { params: { user_id: userId } } ); return { ticketId: response.data.ticket_id ?? seatId }; } // ─── Payment webhook ────────────────────────────────────────────────────────── /** * POST /api/webhooks/payment * Simulates a payment gateway callback for the given ticket. * Uses a random idempotency key to satisfy the backend's idempotency check. */ export async function processPaymentWebhook(ticketId: number): Promise { const idempotencyKey = "req-" + Math.random().toString(36).substring(2, 9); await apiClient.post("/webhooks/payment", { ticket_id: ticketId, idempotency_key: idempotencyKey, status: "success", }); } // ─── Admin: Tournaments ─────────────────────────────────────────────────────── export interface TournamentCreate { title: string; description?: string; event_date: string; // ISO-8601 } export interface TournamentAdminResponse { id: number; title: string; description: string | null; event_date: string; } export interface SectorConfigInput { sector_name: string; rows: number; seats_per_row: number; price: number; } /** * POST /api/tournaments * Creates a new tournament. Requires a superuser token (403 otherwise). */ export async function createTournamentApi( data: TournamentCreate ): Promise { const response = await apiClient.post("/tournaments", data); return response.data; } /** * POST /api/tournaments/{tournament_id}/generate-seats * Bulk-generates seats for a tournament. Requires a superuser token. */ export async function generateSeatsApi( tournamentId: number, sectors: SectorConfigInput[] ): Promise<{ message: string }> { const response = await apiClient.post<{ message: string }>( `/tournaments/${tournamentId}/generate-seats`, { sectors } ); return response.data; } export default apiClient;