101 lines
3.2 KiB
TypeScript
101 lines
3.2 KiB
TypeScript
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;
|
|
});
|
|
|
|
// ─── 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<TicketResponse[]> {
|
|
const response = await apiClient.get<TicketResponse[]>("/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<LockSeatResponse>(
|
|
`/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<void> {
|
|
const idempotencyKey = "req-" + Math.random().toString(36).substring(2, 9);
|
|
await apiClient.post("/webhooks/payment", {
|
|
ticket_id: ticketId,
|
|
idempotency_key: idempotencyKey,
|
|
status: "success",
|
|
});
|
|
}
|
|
|
|
export default apiClient;
|