Initial commit - BraceIQMed platform with frontend, API, and brace generator
This commit is contained in:
107
frontend/src/lib/api.ts
Normal file
107
frontend/src/lib/api.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
const API_BASE = 'https://cfx9z50wj2.execute-api.ca-central-1.amazonaws.com/prod';
|
||||
|
||||
async function http<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const base = API_BASE ? API_BASE.replace(/\/+$/, '') : '';
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
const url = `${base}${normalizedPath}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
...init,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...(init?.headers || {})
|
||||
}
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text().catch(() => "");
|
||||
throw new Error(`HTTP ${res.status} ${res.statusText}: ${text}`);
|
||||
}
|
||||
|
||||
return (await res.json()) as T;
|
||||
}
|
||||
|
||||
export type CaseStatus = {
|
||||
case: {
|
||||
case_id: string;
|
||||
status: string;
|
||||
current_step: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
steps: Array<{
|
||||
step_name: string;
|
||||
step_order: number;
|
||||
status: string;
|
||||
started_at: string | null;
|
||||
finished_at: string | null;
|
||||
error_message: string | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type CaseAssets = {
|
||||
caseId: string;
|
||||
apImageUrl: string;
|
||||
bucket?: string;
|
||||
key?: string;
|
||||
};
|
||||
|
||||
export type SubmitLandmarksRequest = {
|
||||
// Backend Lambda requires caseId in body (even though it's also in the URL)
|
||||
caseId?: string;
|
||||
view: "ap";
|
||||
landmarks: Record<string, { x: number; y: number }>;
|
||||
};
|
||||
|
||||
export type SubmitLandmarksResponse = {
|
||||
ok: boolean;
|
||||
caseId: string;
|
||||
resumedPipeline: boolean;
|
||||
values: {
|
||||
pelvis_offset_px: number;
|
||||
t1_offset_px: number;
|
||||
tp_offset_px: number;
|
||||
dominant_curve: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type UploadUrlResponse = {
|
||||
uploadUrl: string;
|
||||
key?: string;
|
||||
s3Key?: string;
|
||||
};
|
||||
|
||||
export const api = {
|
||||
createCase: (body: { notes?: string } = {}) =>
|
||||
http<{ caseId: string }>(`/cases`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
startCase: (caseId: string) =>
|
||||
http<{ caseId: string; executionArn?: string; status?: string }>(
|
||||
`/cases/${encodeURIComponent(caseId)}/start`,
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({}),
|
||||
}
|
||||
),
|
||||
|
||||
getUploadUrl: (caseId: string, body: { view: string; contentType?: string; filename?: string }) =>
|
||||
http<UploadUrlResponse>(`/cases/${encodeURIComponent(caseId)}/upload-url`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
|
||||
getCaseStatus: (caseId: string) => http<CaseStatus>(`/cases/${encodeURIComponent(caseId)}`),
|
||||
getCaseAssets: (caseId: string) => http<CaseAssets>(`/cases/${encodeURIComponent(caseId)}/assets`),
|
||||
|
||||
// FIX: include caseId in JSON body to satisfy backend Lambda contract
|
||||
submitLandmarks: (caseId: string, body: SubmitLandmarksRequest) =>
|
||||
http<SubmitLandmarksResponse>(`/cases/${encodeURIComponent(caseId)}/landmarks`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
...body,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
Reference in New Issue
Block a user