const API_BASE = process.env.NEXT_PUBLIC_API_URL || "";
export class ApiError extends Error {
constructor(
public status: number,
message: string
) {
super(message);
}
}
export async function apiFetch<T>(
path: string,
opts?: RequestInit
): Promise<T> {
const url = `${API_BASE}/api${path}`;
const res = await fetch(url, {
credentials: "include",
...opts,
headers: {
"Content-Type": "application/json",
...opts?.headers,
},
});
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
throw new ApiError(res.status, body.error || res.statusText);
}
return res.json();
}
function getCsrfToken(): string {
if (typeof document === "undefined") return "";
const match = document.cookie.match(/csrf_token=([^;]+)/);
return match?.[1] || "";
}
export async function apiMutate<T>(
path: string,
method: "POST" | "PUT" | "DELETE" = "POST",
body?: unknown
): Promise<T> {
return apiFetch<T>(path, {
method,
headers: {
"X-CSRF-Token": getCsrfToken(),
},
body: body ? JSON.stringify(body) : undefined,
});
}