mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
1ecbd16404
Connects the UI-only app to the new backend over Better Auth + a small patient API client, replacing the in-memory fixture and placeholder identity. - Better Auth React client (lib/auth-client.ts) + shared access-control roles; API client (lib/api-client.ts) sending credentials cross-origin. - Designed (auth) route group: login, signup, verify-email, forgot/reset password, clinic onboarding, and accept-invite. - proxy.ts (this Next's renamed middleware) optimistic redirect + an authoritative client AppAuthGuard requiring a session and active clinic. - Real identity in nav-user (useSession + sign out); the unused team switcher repurposed as an organization (clinic) switcher; Care-team settings tab manages members and invitations. - lib/patients.ts now calls the org-scoped API (types unchanged); patients table and create/edit dialog updated for async create/update. - next.config: standalone output + Dockerfile for containerized runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
55 lines
1.2 KiB
TypeScript
55 lines
1.2 KiB
TypeScript
// Thin fetch wrapper for the temetro backend's non-auth API (patients, …).
|
|
// Auth itself goes through the Better Auth client (lib/auth-client.ts).
|
|
|
|
export const API_BASE_URL =
|
|
process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000";
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public status: number,
|
|
message: string,
|
|
) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
}
|
|
}
|
|
|
|
export async function apiFetch<T>(
|
|
path: string,
|
|
init?: RequestInit,
|
|
): Promise<T> {
|
|
const res = await fetch(`${API_BASE_URL}${path}`, {
|
|
...init,
|
|
credentials: "include",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...init?.headers,
|
|
},
|
|
});
|
|
|
|
// Session missing/expired — bounce to login (browser only).
|
|
if (res.status === 401) {
|
|
if (typeof window !== "undefined") {
|
|
window.location.href = "/login";
|
|
}
|
|
throw new ApiError(401, "Not authenticated.");
|
|
}
|
|
|
|
if (res.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
const body = (await res.json().catch(() => null)) as
|
|
| (T & { error?: string })
|
|
| null;
|
|
|
|
if (!res.ok) {
|
|
throw new ApiError(
|
|
res.status,
|
|
body?.error ?? `Request failed with status ${res.status}.`,
|
|
);
|
|
}
|
|
|
|
return body as T;
|
|
}
|