Files
cvfs/apps/webapp/src/libs/api.ts
Claude e6c29f3bd4 Redesign webapp with minimal style and full backend integration
- New monochrome design system in globals.css (no shadows, tight spacing, system-font)
- Root layout stripped to html/body; home page owns its Header/Footer
- Dashboard fully wired to FastAPI backend: upload, branch, submission, publish, download
- CVTree rebuilt as compact file-tree component driven by real version data
- DiffViewer rebuilt as minimal git-diff display using real patch data
- API client (libs/api.ts) extended with all CRUD operations
- Header redesigned to match minimal style
- Backend: added GET /documents/{id}/versions/{id}/download endpoint for DOCX export

https://claude.ai/code/session_01Xmxm2QLgFBgRJyYD6VukR6
2026-04-02 18:12:25 +00:00

134 lines
4.0 KiB
TypeScript

const API = process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:9812";
export type StructuredBlock = {
path: string;
block_type: string;
text: string;
keywords: string[];
};
export type Patch = {
id: string;
target_path: string;
operation: string;
old_value?: string | null;
new_value?: string | null;
metadata_json?: Record<string, unknown> | null;
created_at: string;
};
export type Version = {
id: string;
branch_name: string;
version_label?: string | null;
parent_version_id?: string | null;
structured_blocks?: StructuredBlock[] | null;
artifact_docx_key?: string | null;
patches: Patch[];
created_at: string;
updated_at: string;
};
export type Document = {
id: string;
title: string;
description?: string | null;
owner_id: string;
root_version_id?: string | null;
versions: Version[];
created_at: string;
updated_at: string;
};
export type Submission = {
id: string;
version_id: string;
company_name: string;
role_title: string;
job_url?: string | null;
job_description?: string | null;
status: string;
created_at: string;
};
export type PublicAsset = {
id: string;
slug: string;
artifact_key: string;
is_public: boolean;
url?: string | null;
version_id?: string | null;
submission_id?: string | null;
created_at: string;
};
async function req<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API}${path}`, {
...init,
headers: { accept: "application/json", ...init?.headers },
});
if (!res.ok) {
const detail = await res.text().catch(() => res.statusText);
throw new Error(detail || `HTTP ${res.status}`);
}
return res.json();
}
export const fetchDocuments = (): Promise<Document[]> =>
req<{ items: Document[] }>("/api/v1/documents", { cache: "no-store" }).then(r => r.items);
export const fetchDocument = (id: string): Promise<Document> =>
req<Document>(`/api/v1/documents/${id}`, { cache: "no-store" });
export async function uploadDocument(title: string, description: string | null, file: File): Promise<Document> {
const form = new FormData();
form.append("title", title);
if (description) form.append("description", description);
form.append("file", file);
return req<Document>("/api/v1/documents", { method: "POST", body: form });
}
export const downloadVersionUrl = (documentId: string, versionId: string): string =>
`${API}/api/v1/documents/${documentId}/versions/${versionId}/download`;
export async function createBranch(
parentVersionId: string,
branchName: string,
versionLabel?: string | null,
patches: Record<string, unknown>[] = [],
): Promise<Version> {
return req<Version>("/api/v1/versions/branches", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ parent_version_id: parentVersionId, branch_name: branchName, version_label: versionLabel ?? null, patches }),
});
}
export async function createSubmission(
versionId: string,
companyName: string,
roleTitle: string,
jobUrl?: string | null,
jobDescription?: string | null,
): Promise<Submission> {
return req<Submission>("/api/v1/submissions", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ version_id: versionId, company_name: companyName, role_title: roleTitle, job_url: jobUrl ?? null, job_description: jobDescription ?? null }),
});
}
export async function publishVersion(
versionId?: string | null,
submissionId?: string | null,
slug?: string | null,
): Promise<PublicAsset> {
return req<PublicAsset>("/api/v1/public/publish", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ version_id: versionId ?? null, submission_id: submissionId ?? null, slug: slug ?? null }),
});
}
export { API as API_BASE_URL };