From cc7cdee60b1973fd1b9f3e4f63720b7d4ce5b4ae Mon Sep 17 00:00:00 2001 From: Rob Colbert Date: Fri, 15 May 2026 19:34:08 -0400 Subject: [PATCH] Pull Project initial implementation --- gadget-code/frontend/src/lib/api.ts | 2 + .../frontend/src/pages/ProjectManager.tsx | 118 ++++++++++++- gadget-code/src/controllers/api/v1/project.ts | 60 +++++++ gadget-code/src/services/project.ts | 63 +++++++ gadget-code/tests/project-api.test.ts | 118 ++++++++++++- gadget-drone/package.json | 1 + pnpm-lock.yaml | 161 ++++++++++++++++++ 7 files changed, 517 insertions(+), 6 deletions(-) diff --git a/gadget-code/frontend/src/lib/api.ts b/gadget-code/frontend/src/lib/api.ts index 99a4a72..a07291d 100644 --- a/gadget-code/frontend/src/lib/api.ts +++ b/gadget-code/frontend/src/lib/api.ts @@ -246,6 +246,8 @@ export const projectApi = { get: (id: string) => api.get(`/api/v1/projects/${id}`), create: (data: { name: string; slug: string; gitUrl?: string }) => api.post("/api/v1/projects", data), + pull: (gitUrl: string) => + api.post("/api/v1/projects/pull", { gitUrl }), update: ( id: string, data: Partial<{ diff --git a/gadget-code/frontend/src/pages/ProjectManager.tsx b/gadget-code/frontend/src/pages/ProjectManager.tsx index 84a7d94..250cf6f 100644 --- a/gadget-code/frontend/src/pages/ProjectManager.tsx +++ b/gadget-code/frontend/src/pages/ProjectManager.tsx @@ -116,6 +116,83 @@ function NewProjectForm({ ); } +function PullProjectForm({ + onCancel, + onSuccess, +}: { + onCancel: () => void; + onSuccess: (project: Project) => void; +}) { + const [gitUrl, setGitUrl] = useState(""); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(""); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!gitUrl.trim()) { + setError("Git Repository URL is required"); + return; + } + + setSubmitting(true); + setError(""); + + try { + const project = await projectApi.pull(gitUrl.trim()); + onSuccess(project); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to pull project"); + } finally { + setSubmitting(false); + } + }; + + return ( +
+

Pull New Project

+

+ Import an existing project from a git repository. The project name and + slug will be derived automatically from the repository URL. +

+
+
+ + setGitUrl(e.target.value)} + className="w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none" + placeholder="https://github.com/user/repo.git" + autoFocus + /> +

+ HTTPS or SSH format (e.g., git@github.com:user/repo.git) +

+
+ {error &&

{error}

} +
+ + +
+
+
+ ); +} + interface EditProjectFormProps { project: Project; onCancel: () => void; @@ -820,6 +897,7 @@ export default function ProjectManager({ user }: ProjectManagerProps) { ); const [loading, setLoading] = useState(true); const [showNewForm, setShowNewForm] = useState(false); + const [showPullForm, setShowPullForm] = useState(false); useEffect(() => { loadProjects(); @@ -854,6 +932,12 @@ export default function ProjectManager({ user }: ProjectManagerProps) { loadProjects(); }; + const handleProjectPulled = (project: Project) => { + setShowPullForm(false); + loadProjects(); + navigate(`/projects/${project.slug}`); + }; + const handleProjectDeleted = () => { setSelectedProject(null); loadProjects(); @@ -908,26 +992,43 @@ export default function ProjectManager({ user }: ProjectManagerProps) { ); } + const showForm = showNewForm || showPullForm; + return (
{/* Left Sidebar - Project List */}