Add React UI with Vite

Dashboard, agents, goals, issues, and projects pages with sidebar
navigation. API client layer, custom hooks, and shared layout
components. Built with Vite and TypeScript.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-16 13:32:04 -06:00
parent c9d7cbfe44
commit c3d82ed857
25 changed files with 482 additions and 0 deletions

10
ui/src/api/agents.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { Agent } from "@paperclip/shared";
import { api } from "./client";
export const agentsApi = {
list: () => api.get<Agent[]>("/agents"),
get: (id: string) => api.get<Agent>(`/agents/${id}`),
create: (data: Partial<Agent>) => api.post<Agent>("/agents", data),
update: (id: string, data: Partial<Agent>) => api.patch<Agent>(`/agents/${id}`, data),
remove: (id: string) => api.delete<Agent>(`/agents/${id}`),
};

22
ui/src/api/client.ts Normal file
View File

@@ -0,0 +1,22 @@
const BASE = "/api";
async function request<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${BASE}${path}`, {
headers: { "Content-Type": "application/json" },
...init,
});
if (!res.ok) {
const body = await res.json().catch(() => null);
throw new Error(body?.error ?? `Request failed: ${res.status}`);
}
return res.json();
}
export const api = {
get: <T>(path: string) => request<T>(path),
post: <T>(path: string, body: unknown) =>
request<T>(path, { method: "POST", body: JSON.stringify(body) }),
patch: <T>(path: string, body: unknown) =>
request<T>(path, { method: "PATCH", body: JSON.stringify(body) }),
delete: <T>(path: string) => request<T>(path, { method: "DELETE" }),
};

10
ui/src/api/goals.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { Goal } from "@paperclip/shared";
import { api } from "./client";
export const goalsApi = {
list: () => api.get<Goal[]>("/goals"),
get: (id: string) => api.get<Goal>(`/goals/${id}`),
create: (data: Partial<Goal>) => api.post<Goal>("/goals", data),
update: (id: string, data: Partial<Goal>) => api.patch<Goal>(`/goals/${id}`, data),
remove: (id: string) => api.delete<Goal>(`/goals/${id}`),
};

5
ui/src/api/index.ts Normal file
View File

@@ -0,0 +1,5 @@
export { api } from "./client";
export { agentsApi } from "./agents";
export { projectsApi } from "./projects";
export { issuesApi } from "./issues";
export { goalsApi } from "./goals";

10
ui/src/api/issues.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { Issue } from "@paperclip/shared";
import { api } from "./client";
export const issuesApi = {
list: () => api.get<Issue[]>("/issues"),
get: (id: string) => api.get<Issue>(`/issues/${id}`),
create: (data: Partial<Issue>) => api.post<Issue>("/issues", data),
update: (id: string, data: Partial<Issue>) => api.patch<Issue>(`/issues/${id}`, data),
remove: (id: string) => api.delete<Issue>(`/issues/${id}`),
};

10
ui/src/api/projects.ts Normal file
View File

@@ -0,0 +1,10 @@
import type { Project } from "@paperclip/shared";
import { api } from "./client";
export const projectsApi = {
list: () => api.get<Project[]>("/projects"),
get: (id: string) => api.get<Project>(`/projects/${id}`),
create: (data: Partial<Project>) => api.post<Project>("/projects", data),
update: (id: string, data: Partial<Project>) => api.patch<Project>(`/projects/${id}`, data),
remove: (id: string) => api.delete<Project>(`/projects/${id}`),
};