Files
vibn-frontend/components/project-creation/CodeImportSetup.tsx
Mark Henderson 6f79a88abd fix(gitea-bot): add write:organization scope so bot can create repos
Without this the bot PAT 403s on POST /orgs/{org}/repos, which is
the single most important operation — creating new project repos
inside the workspace's Gitea org.

Made-with: Cursor
2026-04-21 11:05:55 -07:00

106 lines
3.5 KiB
TypeScript

"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { JM } from "./modal-theme";
import { SetupHeader, FieldLabel, TextInput, PrimaryButton, type SetupProps } from "./setup-shared";
export function CodeImportSetup({ workspace, onClose, onBack }: SetupProps) {
const router = useRouter();
const [name, setName] = useState("");
const [repoUrl, setRepoUrl] = useState("");
const [pat, setPat] = useState("");
const [loading, setLoading] = useState(false);
const isValidUrl = repoUrl.trim().startsWith("http");
const canCreate = name.trim().length > 0 && isValidUrl;
const handleCreate = async () => {
if (!canCreate) return;
setLoading(true);
try {
const res = await fetch("/api/projects/create", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
projectName: name.trim(),
projectType: "web-app",
slug: name.toLowerCase().replace(/[^a-z0-9]+/g, "-"),
product: { name: name.trim() },
creationMode: "code-import",
sourceData: { repoUrl: repoUrl.trim(), pat: pat.trim() || undefined },
}),
});
if (!res.ok) {
const err = await res.json();
toast.error(err.error || "Failed to create project");
return;
}
const data = await res.json();
onClose();
router.push(`/${workspace}/project/${data.projectId}/overview`);
} catch {
toast.error("Something went wrong");
} finally {
setLoading(false);
}
};
return (
<div style={{ padding: 28 }}>
<SetupHeader
icon="⌘" label="Import Code" tagline="Already have a repo"
accent="#1D4ED8" onBack={onBack} onClose={onClose}
/>
<FieldLabel>Project name</FieldLabel>
<TextInput
value={name}
onChange={setName}
placeholder="What is this project called?"
autoFocus
/>
<FieldLabel>Repository URL</FieldLabel>
<TextInput
value={repoUrl}
onChange={setRepoUrl}
placeholder="https://github.com/yourorg/your-repo"
/>
<FieldLabel>
Personal Access Token{" "}
<span style={{ color: JM.muted, fontWeight: 400 }}>(required for private repos)</span>
</FieldLabel>
<input
type="password"
value={pat}
onChange={e => setPat(e.target.value)}
placeholder="ghp_… or similar"
style={{
width: "100%", padding: "10px 13px", marginBottom: 20,
borderRadius: 8, border: `1px solid ${JM.border}`,
background: JM.inputBg, fontSize: 14,
fontFamily: JM.fontSans, color: JM.ink,
outline: "none", boxSizing: "border-box",
}}
onFocus={e => (e.currentTarget.style.borderColor = JM.indigo)}
onBlur={e => (e.currentTarget.style.borderColor = JM.border)}
/>
<div style={{
fontSize: 12, color: JM.mid, marginBottom: 20, lineHeight: 1.5,
padding: "12px 14px", background: JM.cream, borderRadius: 8,
border: `1px solid ${JM.border}`, fontFamily: JM.fontSans,
}}>
Vibn will clone your repo, read key files, and build a full architecture map tech stack, routes, database, auth, and third-party integrations. Tokens are used only for cloning and are not stored.
</div>
<PrimaryButton onClick={handleCreate} disabled={!canCreate} loading={loading}>
Import & map
</PrimaryButton>
</div>
);
}