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
93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
"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>
|
||
);
|
||
}
|