f34c733706
- workspaceUrl: no trailing slash on /{workspaceId} (backend route has none)
- deleteWorkspace: /workspaces/{id}?force= (was /{id}/?force= with slash before ?)
Quality gates: tsc --noEmit clean, pytest workspaces API (9 passed, 1 skipped)
147 lines
3.2 KiB
TypeScript
147 lines
3.2 KiB
TypeScript
/** Hook for workspace CRUD actions with confirmation handling. */
|
|
|
|
import { useState, useCallback } from "react";
|
|
import {
|
|
createWorkspace,
|
|
deleteWorkspace,
|
|
syncWorkspace,
|
|
updateWorkspace,
|
|
} from "../api/workspaces";
|
|
import type { Workspace, CreateWorkspaceRequest } from "../types/workspace";
|
|
|
|
export interface UseWorkspaceActionsResult {
|
|
loadingId: string | null;
|
|
create: (
|
|
projectId: string,
|
|
repoId: string,
|
|
data: CreateWorkspaceRequest,
|
|
) => Promise<Workspace>;
|
|
delete: (
|
|
workspace: Workspace,
|
|
onRefresh: () => Promise<void>,
|
|
) => Promise<void>;
|
|
sync: (
|
|
projectId: string,
|
|
repoId: string,
|
|
workspace: Workspace,
|
|
onRefresh: () => Promise<void>,
|
|
) => Promise<void>;
|
|
update: (
|
|
projectId: string,
|
|
repoId: string,
|
|
workspaceId: string,
|
|
data: Partial<CreateWorkspaceRequest>,
|
|
) => Promise<Workspace>;
|
|
}
|
|
|
|
interface ApiError {
|
|
response?: {
|
|
status?: number;
|
|
data?: {
|
|
detail?: {
|
|
message?: string;
|
|
instances?: Array<{ id: string; name: string }>;
|
|
branch_deleted?: boolean;
|
|
};
|
|
};
|
|
};
|
|
}
|
|
|
|
export function useWorkspaceActions(): UseWorkspaceActionsResult {
|
|
const [loadingId, setLoadingId] = useState<string | null>(null);
|
|
|
|
const create = useCallback(
|
|
async (projectId: string, repoId: string, data: CreateWorkspaceRequest) => {
|
|
return createWorkspace(projectId, repoId, data);
|
|
},
|
|
[],
|
|
);
|
|
|
|
const deleteAction = useCallback(
|
|
async (workspace: Workspace, onRefresh: () => Promise<void>) => {
|
|
setLoadingId(workspace.id);
|
|
try {
|
|
await deleteWorkspace(workspace.id);
|
|
await onRefresh();
|
|
} catch (err) {
|
|
const error = err as ApiError;
|
|
if (error.response?.status === 409) {
|
|
const detail = error.response.data?.detail;
|
|
const instances = detail?.instances || [];
|
|
const confirmed = window.confirm(
|
|
`This workspace has ${instances.length} running tool instance(s):\n` +
|
|
instances.map((i) => `- ${i.name}`).join("\n") +
|
|
`\n\nDelete workspace and all instances?`,
|
|
);
|
|
if (confirmed) {
|
|
await deleteWorkspace(workspace.id, true);
|
|
await onRefresh();
|
|
}
|
|
} else {
|
|
throw err;
|
|
}
|
|
} finally {
|
|
setLoadingId(null);
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
const sync = useCallback(
|
|
async (
|
|
projectId: string,
|
|
repoId: string,
|
|
workspace: Workspace,
|
|
onRefresh: () => Promise<void>,
|
|
) => {
|
|
setLoadingId(workspace.id);
|
|
try {
|
|
await syncWorkspace(projectId, repoId, workspace.id);
|
|
await onRefresh();
|
|
} catch (err) {
|
|
const error = err as ApiError;
|
|
if (
|
|
error.response?.status === 409 &&
|
|
error.response.data?.detail?.branch_deleted
|
|
) {
|
|
const message =
|
|
error.response.data.detail.message ||
|
|
"Branch was deleted from remote";
|
|
const confirmed = window.confirm(
|
|
`${message}\n\nDelete this workspace?`,
|
|
);
|
|
if (confirmed) {
|
|
await deleteWorkspace(workspace.id, true);
|
|
await onRefresh();
|
|
}
|
|
} else {
|
|
throw err;
|
|
}
|
|
} finally {
|
|
setLoadingId(null);
|
|
}
|
|
},
|
|
[],
|
|
);
|
|
|
|
const update = useCallback(
|
|
async (
|
|
projectId: string,
|
|
repoId: string,
|
|
workspaceId: string,
|
|
data: Partial<CreateWorkspaceRequest>,
|
|
) => {
|
|
return updateWorkspace(projectId, repoId, workspaceId, data);
|
|
},
|
|
[],
|
|
);
|
|
|
|
return {
|
|
loadingId,
|
|
create,
|
|
delete: deleteAction,
|
|
sync,
|
|
update,
|
|
};
|
|
}
|