template tutoring-admin-dashboard v1
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* The HTTP client. One place that knows the base URL.
|
||||
*
|
||||
* Deliberately dependency-free: the scaffold installs no axios, so an `api.ts`
|
||||
* that imported it would fail to build. The shape (`.get(...).then(r => r.data)`)
|
||||
* matches what the fullstack skill's examples use.
|
||||
*
|
||||
* VITE_API_URL is the backend BASE, wired at deploy time. Endpoints live under
|
||||
* `/api`. Never hardcode localhost — it works in dev and breaks on deploy.
|
||||
*/
|
||||
const BASE = (import.meta.env.VITE_API_URL ?? '').replace(/\/$/, '');
|
||||
|
||||
export type Res<T> = { data: T; status: number };
|
||||
|
||||
// Set by an auth store (the `auth` block) — every request then carries it as a
|
||||
// Bearer header. Nothing else in the app needs to know how auth travels.
|
||||
let authToken: string | null = null;
|
||||
export const setAuthToken = (token: string | null): void => {
|
||||
authToken = token;
|
||||
};
|
||||
|
||||
async function request<T>(method: string, path: string, body?: unknown): Promise<Res<T>> {
|
||||
const headers: Record<string, string> = {};
|
||||
if (body !== undefined) headers['Content-Type'] = 'application/json';
|
||||
if (authToken) headers.Authorization = `Bearer ${authToken}`;
|
||||
const res = await fetch(`${BASE}${path}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body === undefined ? undefined : JSON.stringify(body),
|
||||
});
|
||||
if (!res.ok) {
|
||||
let detail = `${res.status} ${res.statusText}`;
|
||||
try {
|
||||
const j = (await res.json()) as { detail?: unknown };
|
||||
if (typeof j.detail === 'string') detail = j.detail;
|
||||
} catch {
|
||||
/* keep the status line */
|
||||
}
|
||||
throw new Error(detail);
|
||||
}
|
||||
// 204 has no body — reading it as JSON throws, and a delete that "fails"
|
||||
// because it succeeded is a confusing bug to chase.
|
||||
const data = res.status === 204 ? (undefined as T) : ((await res.json()) as T);
|
||||
return { data, status: res.status };
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>('GET', path),
|
||||
post: <T>(path: string, body?: unknown) => request<T>('POST', path, body ?? {}),
|
||||
put: <T>(path: string, body?: unknown) => request<T>('PUT', path, body ?? {}),
|
||||
patch: <T>(path: string, body?: unknown) => request<T>('PATCH', path, body ?? {}),
|
||||
delete: <T>(path: string) => request<T>('DELETE', path),
|
||||
};
|
||||
|
||||
export const apiBaseUrl = () => BASE;
|
||||
Reference in New Issue
Block a user