add api client and auth store
apiFetch wrapper sends JSON with credentials, parses error
shapes off the backend's {error: '...'} responses, and exposes
typed helpers (apiGet, apiPost, apiDelete). Auth store now
drives the real /signup -> /confirm-email -> /login -> /me ->
/logout flow. Vite dev proxy points /api at the backend on
:8000.
This commit is contained in:
parent
7b00fa5f68
commit
ae7db07ec3
3 changed files with 146 additions and 7 deletions
68
frontend/blog_portal/src/api/client.ts
Normal file
68
frontend/blog_portal/src/api/client.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
const API_BASE = (import.meta.env.VITE_API_BASE as string | undefined) ?? "/api";
|
||||
|
||||
export interface ApiResult<T> {
|
||||
ok: boolean;
|
||||
status: number;
|
||||
data: T | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export async function apiFetch<T>(path: string, init?: RequestInit): Promise<ApiResult<T>> {
|
||||
const response = await fetch(`${API_BASE}${path}`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
...(init?.body ? { "Content-Type": "application/json" } : {}),
|
||||
...init?.headers,
|
||||
},
|
||||
...init,
|
||||
});
|
||||
|
||||
let payload: unknown = null;
|
||||
const text = await response.text();
|
||||
if (text.length > 0) {
|
||||
try {
|
||||
payload = JSON.parse(text) as unknown;
|
||||
} catch {
|
||||
payload = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorMessage =
|
||||
payload &&
|
||||
typeof payload === "object" &&
|
||||
"error" in payload &&
|
||||
typeof (payload as { error: unknown }).error === "string"
|
||||
? (payload as { error: string }).error
|
||||
: `request failed: ${response.status}`;
|
||||
return {
|
||||
ok: false,
|
||||
status: response.status,
|
||||
data: null,
|
||||
error: errorMessage,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
status: response.status,
|
||||
data: payload as T,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function apiGet<T>(path: string): Promise<ApiResult<T>> {
|
||||
return apiFetch<T>(path, { method: "GET" });
|
||||
}
|
||||
|
||||
export function apiPost<T>(path: string, body: unknown): Promise<ApiResult<T>> {
|
||||
return apiFetch<T>(path, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body ?? {}),
|
||||
});
|
||||
}
|
||||
|
||||
export function apiDelete<T>(path: string): Promise<ApiResult<T>> {
|
||||
return apiFetch<T>(path, { method: "DELETE" });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue