Files
2026-01-23 21:40:16 -05:00

87 lines
2.2 KiB
TypeScript

"use client";
import { useAuth } from "@/context/AuthContext";
import { useCallback, useMemo } from "react";
const DEFAULT_API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "";
export function useApi(baseUrl: string = DEFAULT_API_BASE) {
const { token } = useAuth();
const apiFetch = useCallback(
async <T = any>(path: string, options: RequestInit = {}): Promise<T> => {
// Allow either full URLs or just `/api/...`
const url = path.startsWith("http")
? path
: `${baseUrl.replace(/\/$/, "")}${path}`;
const headers: HeadersInit = {
"Content-Type": "application/json",
...(options.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
};
const res = await fetch(url, {
...options,
headers,
});
if (!res.ok) {
let bodyText = "";
try {
bodyText = await res.text();
} catch {
// ignore
}
throw new Error(
`API error ${res.status}: ${
bodyText || res.statusText || "Unknown error"
}`
);
}
// handle 204 / empty body safely
try {
return (await res.json()) as T;
} catch {
return {} as T;
}
},
[baseUrl, token] // 👈 only changes when baseUrl or token change
);
const get = useCallback(
<T = any>(path: string) => apiFetch<T>(path, { method: "GET" }),
[apiFetch]
);
const post = useCallback(
<T = any>(path: string, body?: unknown) =>
apiFetch<T>(path, {
method: "POST",
body: body !== undefined ? JSON.stringify(body) : undefined,
}),
[apiFetch]
);
const put = useCallback(
<T = any>(path: string, body?: unknown) =>
apiFetch<T>(path, {
method: "PUT",
body: body !== undefined ? JSON.stringify(body) : undefined,
}),
[apiFetch]
);
const del = useCallback(
<T = any>(path: string) => apiFetch<T>(path, { method: "DELETE" }),
[apiFetch]
);
// Optional: memoize the returned object so destructuring doesn't change refs
return useMemo(
() => ({ apiFetch, get, post, put, del }),
[apiFetch, get, post, put, del]
);
}