Simplify new project dialog: always show repo and local folder fields
Remove the workspace setup toggle menu ("Where will work be done on this
project?") and instead always display both repo URL and local folder
inputs directly. Both fields are marked as optional with help tooltips
explaining their purpose. Repo is shown first, local folder second.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -23,10 +23,13 @@ import {
|
|||||||
Calendar,
|
Calendar,
|
||||||
Plus,
|
Plus,
|
||||||
X,
|
X,
|
||||||
FolderOpen,
|
HelpCircle,
|
||||||
Github,
|
|
||||||
GitBranch,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
import { PROJECT_COLORS } from "@paperclipai/shared";
|
import { PROJECT_COLORS } from "@paperclipai/shared";
|
||||||
import { cn } from "../lib/utils";
|
import { cn } from "../lib/utils";
|
||||||
import { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor";
|
import { MarkdownEditor, type MarkdownEditorRef } from "./MarkdownEditor";
|
||||||
@@ -41,8 +44,6 @@ const projectStatuses = [
|
|||||||
{ value: "cancelled", label: "Cancelled" },
|
{ value: "cancelled", label: "Cancelled" },
|
||||||
];
|
];
|
||||||
|
|
||||||
type WorkspaceSetup = "none" | "local" | "repo" | "both";
|
|
||||||
|
|
||||||
export function NewProjectDialog() {
|
export function NewProjectDialog() {
|
||||||
const { newProjectOpen, closeNewProject } = useDialog();
|
const { newProjectOpen, closeNewProject } = useDialog();
|
||||||
const { selectedCompanyId, selectedCompany } = useCompany();
|
const { selectedCompanyId, selectedCompany } = useCompany();
|
||||||
@@ -53,7 +54,6 @@ export function NewProjectDialog() {
|
|||||||
const [goalIds, setGoalIds] = useState<string[]>([]);
|
const [goalIds, setGoalIds] = useState<string[]>([]);
|
||||||
const [targetDate, setTargetDate] = useState("");
|
const [targetDate, setTargetDate] = useState("");
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [workspaceSetup, setWorkspaceSetup] = useState<WorkspaceSetup>("none");
|
|
||||||
const [workspaceLocalPath, setWorkspaceLocalPath] = useState("");
|
const [workspaceLocalPath, setWorkspaceLocalPath] = useState("");
|
||||||
const [workspaceRepoUrl, setWorkspaceRepoUrl] = useState("");
|
const [workspaceRepoUrl, setWorkspaceRepoUrl] = useState("");
|
||||||
const [workspaceError, setWorkspaceError] = useState<string | null>(null);
|
const [workspaceError, setWorkspaceError] = useState<string | null>(null);
|
||||||
@@ -87,7 +87,6 @@ export function NewProjectDialog() {
|
|||||||
setGoalIds([]);
|
setGoalIds([]);
|
||||||
setTargetDate("");
|
setTargetDate("");
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
setWorkspaceSetup("none");
|
|
||||||
setWorkspaceLocalPath("");
|
setWorkspaceLocalPath("");
|
||||||
setWorkspaceRepoUrl("");
|
setWorkspaceRepoUrl("");
|
||||||
setWorkspaceError(null);
|
setWorkspaceError(null);
|
||||||
@@ -124,23 +123,16 @@ export function NewProjectDialog() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleWorkspaceSetup = (next: WorkspaceSetup) => {
|
|
||||||
setWorkspaceSetup((prev) => (prev === next ? "none" : next));
|
|
||||||
setWorkspaceError(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
if (!selectedCompanyId || !name.trim()) return;
|
if (!selectedCompanyId || !name.trim()) return;
|
||||||
const localRequired = workspaceSetup === "local" || workspaceSetup === "both";
|
|
||||||
const repoRequired = workspaceSetup === "repo" || workspaceSetup === "both";
|
|
||||||
const localPath = workspaceLocalPath.trim();
|
const localPath = workspaceLocalPath.trim();
|
||||||
const repoUrl = workspaceRepoUrl.trim();
|
const repoUrl = workspaceRepoUrl.trim();
|
||||||
|
|
||||||
if (localRequired && !isAbsolutePath(localPath)) {
|
if (localPath && !isAbsolutePath(localPath)) {
|
||||||
setWorkspaceError("Local folder must be a full absolute path.");
|
setWorkspaceError("Local folder must be a full absolute path.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (repoRequired && !isGitHubRepoUrl(repoUrl)) {
|
if (repoUrl && !isGitHubRepoUrl(repoUrl)) {
|
||||||
setWorkspaceError("Repo must use a valid GitHub repo URL.");
|
setWorkspaceError("Repo must use a valid GitHub repo URL.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -157,28 +149,15 @@ export function NewProjectDialog() {
|
|||||||
...(targetDate ? { targetDate } : {}),
|
...(targetDate ? { targetDate } : {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const workspacePayloads: Array<Record<string, unknown>> = [];
|
if (localPath || repoUrl) {
|
||||||
if (localRequired && repoRequired) {
|
const workspacePayload: Record<string, unknown> = {
|
||||||
workspacePayloads.push({
|
name: localPath
|
||||||
name: deriveWorkspaceNameFromPath(localPath),
|
? deriveWorkspaceNameFromPath(localPath)
|
||||||
cwd: localPath,
|
: deriveWorkspaceNameFromRepo(repoUrl),
|
||||||
repoUrl,
|
...(localPath ? { cwd: localPath } : {}),
|
||||||
});
|
...(repoUrl ? { repoUrl } : {}),
|
||||||
} else if (localRequired) {
|
};
|
||||||
workspacePayloads.push({
|
await projectsApi.createWorkspace(created.id, workspacePayload);
|
||||||
name: deriveWorkspaceNameFromPath(localPath),
|
|
||||||
cwd: localPath,
|
|
||||||
});
|
|
||||||
} else if (repoRequired) {
|
|
||||||
workspacePayloads.push({
|
|
||||||
name: deriveWorkspaceNameFromRepo(repoUrl),
|
|
||||||
repoUrl,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (const workspacePayload of workspacePayloads) {
|
|
||||||
await projectsApi.createWorkspace(created.id, {
|
|
||||||
...workspacePayload,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(selectedCompanyId) });
|
queryClient.invalidateQueries({ queryKey: queryKeys.projects.list(selectedCompanyId) });
|
||||||
@@ -280,80 +259,51 @@ export function NewProjectDialog() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="px-4 pb-3 space-y-3 border-t border-border">
|
<div className="px-4 pb-3 space-y-3 border-t border-border">
|
||||||
<div className="pt-3">
|
<div className="rounded-md border border-border p-2">
|
||||||
<p className="text-sm font-medium">Where will work be done on this project?</p>
|
<div className="mb-1 flex items-center gap-1.5">
|
||||||
<p className="text-xs text-muted-foreground">Add a repo and/or local folder for this project.</p>
|
<label className="block text-xs text-muted-foreground">Repo URL</label>
|
||||||
</div>
|
<span className="text-xs text-muted-foreground/50">optional</span>
|
||||||
<div className="grid gap-2 sm:grid-cols-3">
|
<Tooltip delayDuration={300}>
|
||||||
<button
|
<TooltipTrigger asChild>
|
||||||
type="button"
|
<HelpCircle className="h-3 w-3 text-muted-foreground/50 cursor-help" />
|
||||||
className={cn(
|
</TooltipTrigger>
|
||||||
"rounded-lg border px-3 py-3 text-left transition-colors",
|
<TooltipContent side="top" className="max-w-[240px] text-xs">
|
||||||
workspaceSetup === "local" ? "border-foreground bg-accent/40" : "border-border hover:bg-accent/30",
|
Link a GitHub repository so agents can clone, read, and push code for this project.
|
||||||
)}
|
</TooltipContent>
|
||||||
onClick={() => toggleWorkspaceSetup("local")}
|
</Tooltip>
|
||||||
>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
<input
|
||||||
<FolderOpen className="h-4 w-4" />
|
className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs outline-none"
|
||||||
A local folder
|
value={workspaceRepoUrl}
|
||||||
</div>
|
onChange={(e) => { setWorkspaceRepoUrl(e.target.value); setWorkspaceError(null); }}
|
||||||
<p className="mt-1 text-xs text-muted-foreground">Use a full path on this machine.</p>
|
placeholder="https://github.com/org/repo"
|
||||||
</button>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={cn(
|
|
||||||
"rounded-lg border px-3 py-3 text-left transition-colors",
|
|
||||||
workspaceSetup === "repo" ? "border-foreground bg-accent/40" : "border-border hover:bg-accent/30",
|
|
||||||
)}
|
|
||||||
onClick={() => toggleWorkspaceSetup("repo")}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
|
||||||
<Github className="h-4 w-4" />
|
|
||||||
A repo
|
|
||||||
</div>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">Paste a GitHub URL.</p>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={cn(
|
|
||||||
"rounded-lg border px-3 py-3 text-left transition-colors",
|
|
||||||
workspaceSetup === "both" ? "border-foreground bg-accent/40" : "border-border hover:bg-accent/30",
|
|
||||||
)}
|
|
||||||
onClick={() => toggleWorkspaceSetup("both")}
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-2 text-sm font-medium">
|
|
||||||
<GitBranch className="h-4 w-4" />
|
|
||||||
Both
|
|
||||||
</div>
|
|
||||||
<p className="mt-1 text-xs text-muted-foreground">Configure both repo and local folder.</p>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(workspaceSetup === "local" || workspaceSetup === "both") && (
|
<div className="rounded-md border border-border p-2">
|
||||||
<div className="rounded-md border border-border p-2">
|
<div className="mb-1 flex items-center gap-1.5">
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Local folder (full path)</label>
|
<label className="block text-xs text-muted-foreground">Local folder</label>
|
||||||
<div className="flex items-center gap-2">
|
<span className="text-xs text-muted-foreground/50">optional</span>
|
||||||
<input
|
<Tooltip delayDuration={300}>
|
||||||
className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
|
<TooltipTrigger asChild>
|
||||||
value={workspaceLocalPath}
|
<HelpCircle className="h-3 w-3 text-muted-foreground/50 cursor-help" />
|
||||||
onChange={(e) => setWorkspaceLocalPath(e.target.value)}
|
</TooltipTrigger>
|
||||||
placeholder="/absolute/path/to/workspace"
|
<TooltipContent side="top" className="max-w-[240px] text-xs">
|
||||||
/>
|
Set an absolute path on this machine where local agents will read and write files for this project.
|
||||||
<ChoosePathButton />
|
</TooltipContent>
|
||||||
</div>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="flex items-center gap-2">
|
||||||
{(workspaceSetup === "repo" || workspaceSetup === "both") && (
|
|
||||||
<div className="rounded-md border border-border p-2">
|
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">Repo URL</label>
|
|
||||||
<input
|
<input
|
||||||
className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs outline-none"
|
className="w-full rounded border border-border bg-transparent px-2 py-1 text-xs font-mono outline-none"
|
||||||
value={workspaceRepoUrl}
|
value={workspaceLocalPath}
|
||||||
onChange={(e) => setWorkspaceRepoUrl(e.target.value)}
|
onChange={(e) => { setWorkspaceLocalPath(e.target.value); setWorkspaceError(null); }}
|
||||||
placeholder="https://github.com/org/repo"
|
placeholder="/absolute/path/to/workspace"
|
||||||
/>
|
/>
|
||||||
|
<ChoosePathButton />
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
{workspaceError && (
|
{workspaceError && (
|
||||||
<p className="text-xs text-destructive">{workspaceError}</p>
|
<p className="text-xs text-destructive">{workspaceError}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user