- Fix all Ultracite lint errors on changed source files (0 remaining) - Remove project.test.ts and use-personal-organization.test.ts (to be rewritten when implementation stabilizes) - Replace .at(-1) with index access for backend tsconfig ES target compat - Rename generator functions to avoid no-shadow conflicts - Refactor nested ternaries into helper functions - Fix unused imports
118 lines
3.4 KiB
TypeScript
118 lines
3.4 KiB
TypeScript
import { api } from "@code/backend/convex/_generated/api";
|
|
import type { Id } from "@code/backend/convex/_generated/dataModel";
|
|
import { useFlueClient } from "@flue/react";
|
|
import { useAction, useMutation, useQuery } from "convex/react";
|
|
import { useState } from "react";
|
|
|
|
const errorMessage = (error: unknown) =>
|
|
error instanceof Error ? error.message : String(error);
|
|
|
|
export const useProjectWorkspace = () => {
|
|
const flueClient = useFlueClient();
|
|
const projects = useQuery(api.projects.list);
|
|
const [selectedProjectId, setSelectedProjectId] =
|
|
useState<Id<"projects"> | null>(null);
|
|
const [repository, setRepository] = useState("");
|
|
const [issueTitle, setIssueTitle] = useState("");
|
|
const [issueBody, setIssueBody] = useState("");
|
|
const [pendingAction, setPendingAction] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const importPublicGit = useAction(api.projects.importPublicGit);
|
|
const createIssue = useMutation(api.projectIssues.create);
|
|
const beginIssue = useMutation(api.projectIssues.begin);
|
|
const markDispatchFailed = useMutation(api.projectIssues.markDispatchFailed);
|
|
const activeProjectId =
|
|
selectedProjectId ??
|
|
(projects?.[0]?.id as unknown as Id<"projects">) ??
|
|
null;
|
|
const artifacts = useQuery(
|
|
api.projectArtifacts.list,
|
|
activeProjectId ? { projectId: activeProjectId } : "skip"
|
|
);
|
|
const issues = useQuery(
|
|
api.projectIssues.list,
|
|
activeProjectId ? { projectId: activeProjectId } : "skip"
|
|
);
|
|
|
|
const connectRepository = async () => {
|
|
setPendingAction("connect");
|
|
setError(null);
|
|
try {
|
|
const outcome = await importPublicGit({ repositoryUrl: repository });
|
|
setSelectedProjectId(outcome.id as unknown as Id<"projects">);
|
|
setRepository("");
|
|
} catch (caughtError) {
|
|
setError(errorMessage(caughtError));
|
|
} finally {
|
|
setPendingAction(null);
|
|
}
|
|
};
|
|
|
|
const raiseIssue = async () => {
|
|
if (!activeProjectId) {
|
|
return;
|
|
}
|
|
setPendingAction("issue");
|
|
setError(null);
|
|
try {
|
|
await createIssue({
|
|
body: issueBody,
|
|
projectId: activeProjectId,
|
|
title: issueTitle,
|
|
});
|
|
setIssueTitle("");
|
|
setIssueBody("");
|
|
} catch (caughtError) {
|
|
setError(errorMessage(caughtError));
|
|
} finally {
|
|
setPendingAction(null);
|
|
}
|
|
};
|
|
|
|
const startIssue = async (
|
|
issueId: Id<"projectIssues">,
|
|
issueNumber: number,
|
|
title: string
|
|
) => {
|
|
const actionKey = `issue:${issueId}`;
|
|
setPendingAction(actionKey);
|
|
setError(null);
|
|
try {
|
|
await beginIssue({ issueId });
|
|
await flueClient.agents.send("project-manager", String(issueId), {
|
|
message: `Start project issue ${issueNumber}: ${title}. Read the bound context and complete the workflow.`,
|
|
});
|
|
} catch (caughtError) {
|
|
const message = errorMessage(caughtError);
|
|
await markDispatchFailed({ error: message, issueId });
|
|
setError(message);
|
|
} finally {
|
|
setPendingAction(null);
|
|
}
|
|
};
|
|
const selectedProject =
|
|
projects?.find(
|
|
(project) => project.id === (activeProjectId as unknown as string)
|
|
) ?? null;
|
|
|
|
return {
|
|
artifacts,
|
|
connectRepository,
|
|
error,
|
|
issueBody,
|
|
issueTitle,
|
|
issues,
|
|
pendingAction,
|
|
projects,
|
|
raiseIssue,
|
|
repository,
|
|
selectedProject,
|
|
selectedProjectId: activeProjectId,
|
|
setIssueBody,
|
|
setIssueTitle,
|
|
setRepository,
|
|
setSelectedProjectId,
|
|
startIssue,
|
|
} as const;
|
|
};
|