chore: initial commit for phase05

This commit is contained in:
hibna
2026-02-21 16:59:21 +03:00
parent 218452706c
commit 0941a9ba46
43 changed files with 4431 additions and 17 deletions
+99
View File
@@ -0,0 +1,99 @@
const API_BASE = '/api';
interface RequestOptions extends RequestInit {
params?: Record<string, string>;
}
class ApiError extends Error {
constructor(
public status: number,
public data: unknown,
) {
super(`API Error ${status}`);
this.name = 'ApiError';
}
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const { params, ...fetchOptions } = options;
let url = `${API_BASE}${path}`;
if (params) {
const searchParams = new URLSearchParams(params);
url += `?${searchParams.toString()}`;
}
const token = localStorage.getItem('access_token');
const headers: Record<string, string> = {
...(fetchOptions.headers as Record<string, string>),
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
if (fetchOptions.body && typeof fetchOptions.body === 'string') {
headers['Content-Type'] = 'application/json';
}
const res = await fetch(url, { ...fetchOptions, headers });
if (res.status === 401) {
// Try refresh
const refreshed = await refreshToken();
if (refreshed) {
headers['Authorization'] = `Bearer ${localStorage.getItem('access_token')}`;
const retry = await fetch(url, { ...fetchOptions, headers });
if (!retry.ok) throw new ApiError(retry.status, await retry.json().catch(() => null));
if (retry.status === 204) return undefined as T;
return retry.json();
}
localStorage.removeItem('access_token');
window.location.href = '/login';
throw new ApiError(401, null);
}
if (!res.ok) {
throw new ApiError(res.status, await res.json().catch(() => null));
}
if (res.status === 204) return undefined as T;
return res.json();
}
async function refreshToken(): Promise<boolean> {
try {
const res = await fetch(`${API_BASE}/auth/refresh`, {
method: 'POST',
credentials: 'include',
});
if (!res.ok) return false;
const data = await res.json();
localStorage.setItem('access_token', data.accessToken);
return true;
} catch {
return false;
}
}
export const api = {
get: <T>(path: string, params?: Record<string, string>) =>
request<T>(path, { params }),
post: <T>(path: string, body?: unknown) =>
request<T>(path, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
}),
patch: <T>(path: string, body?: unknown) =>
request<T>(path, {
method: 'PATCH',
body: body ? JSON.stringify(body) : undefined,
}),
delete: <T>(path: string) =>
request<T>(path, { method: 'DELETE' }),
};
export { ApiError };