117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
import { parseApiError } from '@/lib/auth-api';
|
|
|
|
|
|
function getBaseUrl(): string {
|
|
return import.meta.env.VITE_API_BASE_URL ?? '';
|
|
}
|
|
|
|
let getToken: () => string | null = () => null;
|
|
let getPropertyId: () => string | null = () => null;
|
|
|
|
/** Register token + property scope for hotel API paths (call once from the auth store module). */
|
|
export function registerHotelApiContext(ctx: {
|
|
getToken: () => string | null;
|
|
getPropertyId: () => string | null;
|
|
}) {
|
|
getToken = ctx.getToken;
|
|
getPropertyId = ctx.getPropertyId;
|
|
}
|
|
|
|
/** Paths that stay unscoped (mock-only or non-hotel). */
|
|
function shouldRewriteForHotel(path: string): boolean {
|
|
const first = path.split('?')[0];
|
|
if (first.startsWith('/auth')) return false;
|
|
if (first.startsWith('/properties')) return false;
|
|
if (first.startsWith('/admin')) return false;
|
|
return true;
|
|
}
|
|
|
|
function rewriteHotelPath(path: string): string {
|
|
const pid = getPropertyId();
|
|
if (!pid || !shouldRewriteForHotel(path)) return path;
|
|
|
|
const [pathname, query] = path.split('?');
|
|
const q = query ? `?${query}` : '';
|
|
|
|
if (pathname === '/dashboard') {
|
|
return `/properties/${pid}/hotel/dashboard/summary${q}`;
|
|
}
|
|
|
|
return `/properties/${pid}/hotel${pathname}${q}`;
|
|
}
|
|
|
|
async function request(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown,
|
|
init?: RequestInit,
|
|
): Promise<Response> {
|
|
const token = getToken();
|
|
const resolved = rewriteHotelPath(path);
|
|
const headers: Record<string, string> = {
|
|
...(init?.headers as Record<string, string>),
|
|
};
|
|
const isFormData = typeof FormData !== "undefined" && body instanceof FormData;
|
|
if (body !== undefined && !isFormData) headers["Content-Type"] = "application/json";
|
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
|
|
const url = `${getBaseUrl()}${resolved}`;
|
|
return fetch(url, {
|
|
...init,
|
|
method,
|
|
headers,
|
|
body:
|
|
body === undefined
|
|
? init?.body
|
|
: isFormData
|
|
? (body as FormData)
|
|
: JSON.stringify(body),
|
|
});
|
|
}
|
|
|
|
export async function apiGet<T>(path: string, init?: RequestInit): Promise<T> {
|
|
const res = await request('GET', path, undefined, init);
|
|
if (!res.ok) throw new Error(await parseApiError(res));
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
export async function apiPost<T>(path: string, body: unknown, init?: RequestInit): Promise<T> {
|
|
const res = await request('POST', path, body, init);
|
|
if (!res.ok) throw new Error(await parseApiError(res));
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
export async function apiPatch<T>(path: string, body: unknown, init?: RequestInit): Promise<T> {
|
|
const res = await request('PATCH', path, body, init);
|
|
if (!res.ok) throw new Error(await parseApiError(res));
|
|
return res.json() as Promise<T>;
|
|
}
|
|
|
|
export async function apiDelete(path: string, init?: RequestInit): Promise<void> {
|
|
const res = await request('DELETE', path, undefined, init);
|
|
if (!res.ok) throw new Error(await parseApiError(res));
|
|
}
|
|
|
|
/** Authenticated binary download (e.g. CSV export). */
|
|
export async function apiDownloadBlob(
|
|
path: string,
|
|
init?: RequestInit,
|
|
): Promise<{ blob: Blob; filename: string | undefined }> {
|
|
const token = getToken();
|
|
const resolved = rewriteHotelPath(path);
|
|
const headers: Record<string, string> = { ...(init?.headers as Record<string, string>) };
|
|
if (token) headers.Authorization = `Bearer ${token}`;
|
|
|
|
const url = `${getBaseUrl()}${resolved}`;
|
|
const res = await fetch(url, { ...init, method: 'GET', headers });
|
|
if (!res.ok) throw new Error(await parseApiError(res));
|
|
|
|
const cd = res.headers.get('Content-Disposition');
|
|
let filename: string | undefined;
|
|
const m = cd?.match(/filename="?([^";]+)"?/);
|
|
if (m) filename = m[1];
|
|
|
|
const blob = await res.blob();
|
|
return { blob, filename };
|
|
}
|