diff --git a/web/src/components/layout/WorkspaceShell.tsx b/web/src/components/layout/WorkspaceShell.tsx index a8b698c..160f92e 100644 --- a/web/src/components/layout/WorkspaceShell.tsx +++ b/web/src/components/layout/WorkspaceShell.tsx @@ -7,6 +7,7 @@ import { EditorPanel } from "@/components/editor/EditorPanel"; import { TerminalPanel } from "@/components/terminal/TerminalPanel"; import { FileExplorerPanel } from "@/components/workspace/FileExplorerPanel"; import { StatusBar } from "@/components/workspace/StatusBar"; +import { WorkspaceSelector } from "@/components/workspace/WorkspaceSelector"; import { useWorkspaceUiStore } from "@/lib/store/workspace-ui-store"; import { findFileByPath, @@ -28,9 +29,11 @@ export function WorkspaceShell({ files }: WorkspaceShellProps) { return (
{/* Toolbar */} -
+

codespace

- + + +
+ + + + {(createMutation.isError || deleteMutation.isError) && ( + + op failed + + )} +
+ ); +} diff --git a/web/src/lib/api/workspaces.ts b/web/src/lib/api/workspaces.ts new file mode 100644 index 0000000..8133842 --- /dev/null +++ b/web/src/lib/api/workspaces.ts @@ -0,0 +1,86 @@ +import { + useMutation, + useQuery, + useQueryClient, + type UseMutationResult, + type UseQueryResult, +} from "@tanstack/react-query"; + +import { apiClient } from "@/lib/api/client"; + +export interface Workspace { + id: string; +} + +interface WorkspaceListResponse { + workspaces: Workspace[]; +} + +const queryKey = ["workspaces"] as const; + +async function fetchWorkspaces(): Promise { + const res = await apiClient("/api/workspaces"); + if (!res.ok) { + throw new Error(`Failed to list workspaces: ${res.status}`); + } + const body = (await res.json()) as WorkspaceListResponse; + return body.workspaces; +} + +async function postWorkspace(id: string): Promise { + const res = await apiClient("/api/workspaces", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ id }), + }); + if (!res.ok) { + throw new Error(`Failed to create workspace: ${res.status}`); + } + return (await res.json()) as Workspace; +} + +async function deleteWorkspace(id: string): Promise { + const res = await apiClient( + `/api/workspaces/${encodeURIComponent(id)}`, + { method: "DELETE" }, + ); + if (!res.ok) { + throw new Error(`Failed to delete workspace: ${res.status}`); + } +} + +export function useWorkspaces(): UseQueryResult { + return useQuery({ queryKey, queryFn: fetchWorkspaces }); +} + +export function useCreateWorkspace(): UseMutationResult< + Workspace, + Error, + string +> { + const qc = useQueryClient(); + return useMutation({ + mutationFn: postWorkspace, + onSuccess: (created) => { + qc.setQueryData(queryKey, (prev) => + prev ? [...prev, created] : [created], + ); + }, + }); +} + +export function useDeleteWorkspace(): UseMutationResult< + void, + Error, + string +> { + const qc = useQueryClient(); + return useMutation({ + mutationFn: deleteWorkspace, + onSuccess: (_void, id) => { + qc.setQueryData(queryKey, (prev) => + prev ? prev.filter((w) => w.id !== id) : prev, + ); + }, + }); +} diff --git a/web/src/lib/store/workspace-ui-store.ts b/web/src/lib/store/workspace-ui-store.ts index 969fa02..6a8f2a2 100644 --- a/web/src/lib/store/workspace-ui-store.ts +++ b/web/src/lib/store/workspace-ui-store.ts @@ -4,16 +4,20 @@ interface WorkspaceUiState { activeFilePath: string; terminalVisible: boolean; sidebarCollapsed: boolean; + currentWorkspaceId: string | null; setActiveFilePath: (path: string) => void; toggleTerminal: () => void; toggleSidebar: () => void; + setCurrentWorkspaceId: (id: string | null) => void; } export const useWorkspaceUiStore = create((set) => ({ activeFilePath: "src/main.tsx", terminalVisible: true, sidebarCollapsed: false, + currentWorkspaceId: null, setActiveFilePath: (path) => set({ activeFilePath: path }), toggleTerminal: () => set((s) => ({ terminalVisible: !s.terminalVisible })), toggleSidebar: () => set((s) => ({ sidebarCollapsed: !s.sidebarCollapsed })), + setCurrentWorkspaceId: (id) => set({ currentWorkspaceId: id }), })); diff --git a/web/vite.config.ts b/web/vite.config.ts index 4c7daa6..f3908f8 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -3,6 +3,12 @@ import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; +// In dev the frontend runs on its own port; proxy API calls to the Go backend +// so the browser sees same-origin requests. In production the Go binary serves +// the built assets directly and no proxy is involved. +const backendTarget = + process.env.VITE_BACKEND_URL ?? "http://localhost:8080"; + export default defineConfig({ plugins: [react(), tailwindcss()], resolve: { @@ -10,4 +16,10 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, + server: { + proxy: { + "/api": { target: backendTarget, changeOrigin: true }, + "/healthz": { target: backendTarget, changeOrigin: true }, + }, + }, });