Fill-the-Frames / frontend /src /lib /api /base-client.ts
sid385's picture
refactor: Centralize frontend API client and implement modular service layer
a8fd684
Raw
History Blame Contribute Delete
1.95 kB
import { ApiResponse } from "@/types/api";
const BASE_URL =
process.env.NEXT_PUBLIC_API_URL ||
"https://sid385-fill-the-frames.hf.space/api/v1";
export class ApiError extends Error {
status: number;
constructor(message: string, status: number) {
super(message);
this.name = "ApiError";
this.status = status;
}
}
interface RequestOptions {
params?: Record<string, string | number | boolean | undefined>;
headers?: Record<string, string>;
}
async function request<T>(
path: string,
options: RequestOptions & { method: string; body?: string | FormData } = { method: "GET" }
): Promise<T> {
const { params, ...fetchOptions } = options;
let url = `${BASE_URL}${path}`;
if (params) {
const searchParams = new URLSearchParams();
for (const [key, value] of Object.entries(params)) {
if (value !== undefined) {
searchParams.append(key, String(value));
}
}
const qs = searchParams.toString();
if (qs) url += `?${qs}`;
}
const response = await fetch(url, fetchOptions);
if (!response.ok) {
throw new ApiError(
`Request failed with status ${response.status}`,
response.status
);
}
return response.json() as Promise<T>;
}
export const api = {
get: <T>(path: string, options?: RequestOptions) =>
request<T>(path, { ...options, method: "GET" }),
post: <T>(path: string, body?: unknown, options?: RequestOptions) =>
request<T>(path, {
...options,
method: "POST",
headers: { "Content-Type": "application/json", ...options?.headers },
body: body ? JSON.stringify(body) : undefined,
}),
postFormData: <T>(path: string, formData: FormData, options?: RequestOptions) =>
request<T>(path, { ...options, method: "POST", body: formData }),
delete: <T>(path: string, options?: RequestOptions) =>
request<T>(path, { ...options, method: "DELETE" }),
};
export { BASE_URL };
export type { ApiResponse };