Files
vibn-frontend/components/project-creation/FreshIdeaSetup.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

93 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { JM } from "./modal-theme";
import { FieldLabel, TextInput, PrimaryButton, ForWhomSelector, type SetupProps } from "./setup-shared";
export function FreshIdeaSetup({ workspace, onClose }: SetupProps) {
const router = useRouter();
const [name, setName] = useState("");
const [forWhom, setForWhom] = useState<"personal" | "client">("personal");
const [loading, setLoading] = useState(false);
const nameRef = useRef<HTMLInputElement>(null);
const canCreate = name.trim().length > 0;
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(), isForClient: forWhom === "client" },
creationMode: "fresh",
sourceData: {},
}),
});
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 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 22 }}>
<h3 style={{
fontFamily: JM.fontDisplay, fontSize: 18, fontWeight: 700,
color: JM.ink, margin: 0, letterSpacing: "-0.02em",
}}>
New project
</h3>
<button
type="button"
onClick={onClose}
style={{
background: "none", border: "none", cursor: "pointer",
color: JM.muted, fontSize: 20, padding: 4, lineHeight: 1,
fontFamily: JM.fontSans,
}}
onMouseEnter={e => (e.currentTarget.style.color = JM.mid)}
onMouseLeave={e => (e.currentTarget.style.color = JM.muted)}
>
×
</button>
</div>
<div style={{ marginBottom: 16 }}>
<FieldLabel>Project name</FieldLabel>
<TextInput
value={name}
onChange={setName}
placeholder="e.g. My SaaS App"
onKeyDown={e => { if (e.key === "Enter" && canCreate) void handleCreate(); }}
inputRef={nameRef}
autoFocus
/>
</div>
<ForWhomSelector value={forWhom} onChange={setForWhom} />
<PrimaryButton onClick={() => { void handleCreate(); }} disabled={!canCreate} loading={loading}>
Create project
</PrimaryButton>
</div>
);
}