Initial Planlink implementation

This commit is contained in:
2026-03-05 14:15:58 -05:00
commit ad6c63b699
39 changed files with 7510 additions and 0 deletions
+1569
View File
File diff suppressed because it is too large Load Diff
+238
View File
@@ -0,0 +1,238 @@
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:4000/api";
async function request(path, options = {}) {
const res = await fetch(`${API_URL}${path}`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
...(options.headers || {})
},
...options
});
const data = await res.json();
if (!res.ok) {
const message = data?.message || "request failed";
throw new Error(message);
}
return data;
}
export function login(payload) {
return request("/auth/login", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function register(payload) {
return request("/auth/register", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function logout() {
return request("/auth/logout", { method: "POST" });
}
export function me() {
return request("/me");
}
export function listProjects() {
return request("/projects");
}
export function createProject(payload) {
return request("/projects", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function listProjectMembers(projectId) {
return request(`/projects/${projectId}/members`);
}
export function inviteProjectMember(projectId, payload) {
return request(`/projects/${projectId}/invites`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export function listProjectInvites(projectId) {
return request(`/projects/${projectId}/invites`);
}
export function acceptProjectInvite(projectId, token) {
return request(`/projects/${projectId}/invites/accept`, {
method: "POST",
body: JSON.stringify({ token })
});
}
export function revokeProjectInvite(projectId, inviteId) {
return request(`/projects/${projectId}/invites/revoke`, {
method: "POST",
body: JSON.stringify({ inviteId })
});
}
export function listBoards(projectId) {
return request(`/boards?projectId=${projectId}`);
}
export function createBoard(payload) {
return request("/boards", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function listBoardActivity(boardId) {
return request(`/boards/${boardId}/activity`);
}
export function listLists(boardId) {
return request(`/lists?boardId=${boardId}`);
}
export function createList(payload) {
return request("/lists", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function updateList(id, payload) {
return request(`/lists/${id}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function listCards(listId) {
return request(`/cards?listId=${listId}`);
}
export function createCard(payload) {
return request("/cards", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function updateCard(id, payload) {
return request(`/cards/${id}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function listComments(cardId) {
return request(`/cards/${cardId}/comments`);
}
export function addComment(cardId, body) {
return request(`/cards/${cardId}/comments`, {
method: "POST",
body: JSON.stringify({ body })
});
}
export function listAttachments(cardId) {
return request(`/cards/${cardId}/attachments`);
}
export function addAttachment(cardId, payload) {
return request(`/cards/${cardId}/attachments`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export function listCardMembers(cardId) {
return request(`/cards/${cardId}/members`);
}
export function addCardMember(cardId, userId) {
return request(`/cards/${cardId}/members`, {
method: "POST",
body: JSON.stringify({ userId })
});
}
export function listCardLabels(cardId) {
return request(`/cards/${cardId}/labels`);
}
export function addCardLabel(cardId, labelId) {
return request(`/cards/${cardId}/labels`, {
method: "POST",
body: JSON.stringify({ labelId })
});
}
export function listLabels(projectId) {
return request(`/labels?projectId=${projectId}`);
}
export function createLabel(payload) {
return request("/labels", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function createPlannerRequest(payload) {
return request("/planner/requests", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function runPlannerRequest(id) {
return request(`/planner/requests/${id}/run`, { method: "POST" });
}
export function streamPlannerRequest(id, onDelta, onDone, onError) {
const url = `${API_URL}/planner/requests/${id}/stream`;
const source = new EventSource(url, { withCredentials: true });
source.addEventListener("delta", (event) => {
const data = JSON.parse(event.data);
onDelta?.(data.delta || "");
});
source.addEventListener("done", () => {
onDone?.();
source.close();
});
source.addEventListener("error", () => {
onError?.();
source.close();
});
return source;
}
export function saveManualPlanner(payload) {
return request("/planner/manual", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function listPlanners() {
return request("/planner");
}
export function updatePlanner(id, outputText) {
return request(`/planner/requests/${id}`, {
method: "PATCH",
body: JSON.stringify({ outputText })
});
}
+10
View File
@@ -0,0 +1,10 @@
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App.jsx";
import "./styles.css";
createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
+12
View File
@@ -0,0 +1,12 @@
import { io } from "socket.io-client";
const SOCKET_URL = import.meta.env.VITE_SOCKET_URL || "http://localhost:4000";
let socket;
export function getSocket() {
if (!socket) {
socket = io(SOCKET_URL, { withCredentials: true });
}
return socket;
}
+7
View File
@@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
color-scheme: light dark;
}