Add workspace creation to settings panel

The workspace settings panel now includes an Add button that opens a
dialog with name, local path (with directory picker), and optional
project binding. Supports creating secondary workspaces beyond the
initial bootstrap workspace.
This commit is contained in:
ZacharyZhang-NY committed 2026-06-07 17:38:09 -04:00
1 parent 5f92cd0d75
commit 22568866e8
2 files changed
+76 -18

No files matched your search

@@ -123,7 +123,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) {
</aside>
<div className="main" style={{ flex: 1, minWidth: 0 }}>
<div className="content">
{tab === "workspace" && <WorkspacePanel organization={organization} workspaces={workspaces} />}
{tab === "workspace" && <WorkspacePanel organization={organization} workspaces={workspaces} projects={snapshot.projects.filter((p) => p.orgId === orgId)} orgId={orgId} actions={actions} />}
{tab === "members" && (
<MembersPanel
employees={employees}
@@ -1,13 +1,51 @@
import type { OrganizationRecord, WorkspaceRecord } from "../../../../shared/domain";
import { Pill } from "../../shared/ui";
import { useState, type FormEvent } from "react";
import type { OrganizationRecord, ProjectRecord, WorkspaceRecord } from "../../../../shared/domain";
import { Button, Field, Icon, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import type { SnapshotActions } from "../../app/useSnapshot";
function text(form: FormData, key: string): string {
return String(form.get(key) ?? "").trim();
}
function optional(form: FormData, key: string): string | null {
return text(form, key) || null;
}
export function WorkspacePanel({
organization,
workspaces,
projects,
orgId,
actions,
}: {
organization: OrganizationRecord;
workspaces: WorkspaceRecord[];
projects: ProjectRecord[];
orgId: string;
actions: SnapshotActions;
}) {
const [createOpen, setCreateOpen] = useState(false);
const chooseDirectory = async (form: HTMLFormElement) => {
const result = await window.agentCompany.pickDirectory();
if (result.ok && result.data) {
const input = form.elements.namedItem("localPath") as HTMLInputElement | null;
if (input) input.value = result.data;
}
};
const submit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = new FormData(event.currentTarget);
void actions.run(() => window.agentCompany.createWorkspace({
orgId,
name: text(form, "name"),
localPath: text(form, "localPath"),
projectId: optional(form, "projectId"),
})).then((result) => { if (result) setCreateOpen(false); });
};
return (
<div className="page">
<h1 className="page-h1">Workspace</h1>
@@ -30,22 +68,42 @@ export function WorkspacePanel({
</div>
</div>
{workspaces.length > 0 && (
<div className="card" style={{ padding: 18 }}>
<div className="section-label" style={{ marginBottom: 12 }}>Workspaces</div>
{workspaces.map((workspace) => (
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
<div style={{ marginLeft: "auto" }}>
{workspace.isPrimary ? <Pill tone="violet">Primary</Pill> : <Pill>Secondary</Pill>}
</div>
</div>
))}
<div className="card" style={{ padding: 18 }}>
<div className="row" style={{ marginBottom: 12 }}>
<div className="section-label">Workspaces</div>
<Button tone="ghost" onClick={() => setCreateOpen(true)} style={{ marginLeft: "auto" }}><Icon name="plus" size={13} /> Add</Button>
</div>
)}
{workspaces.map((workspace) => (
<div key={workspace.id} className="row" style={{ padding: "8px 0", borderBottom: "1px solid var(--border-soft)" }}>
<div style={{ minWidth: 0 }}>
<div style={{ fontWeight: 540 }}>{workspace.name}</div>
<div style={{ fontSize: 12, color: "var(--text-3)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{workspace.localPath}</div>
</div>
<div style={{ marginLeft: "auto" }}>
{workspace.isPrimary ? <Pill tone="violet">Primary</Pill> : <Pill>Secondary</Pill>}
</div>
</div>
))}
{workspaces.length === 0 && <div style={{ fontSize: 13, color: "var(--text-3)" }}>No workspaces configured</div>}
</div>
<Dialog title="Add workspace" open={createOpen} submitLabel="Create" onClose={() => setCreateOpen(false)} onSubmit={submit}>
<Field label="Name"><input className="input" name="name" required /></Field>
<Field label="Local path">
<div className="row" style={{ gap: 6 }}>
<input className="input" name="localPath" required style={{ flex: 1 }} />
<Button tone="ghost" onClick={(e) => { const form = (e.target as HTMLElement).closest("form"); if (form) void chooseDirectory(form); }}>
<Icon name="folder" size={13} /> Browse
</Button>
</div>
</Field>
<Field label="Project">
<select className="input" name="projectId" defaultValue="">
<option value="">None</option>
{projects.map((p) => <option key={p.id} value={p.id}>{p.name}</option>)}
</select>
</Field>
</Dialog>
</div>
);
}