"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; import { JM } from "./modal-theme"; import { DEFAULT_DESIGN_KIT_ID } from "@/lib/design-kits/types"; import { SetupHeader, FieldLabel, TextInput, TextArea, AudienceSelector, DesignKitSelector, PrimaryButton, SecondaryButton, StepDots, type SetupProps, type Audience, } from "./setup-shared"; /** * "Import existing code" — three-step setup with GitHub OAuth. * * Step 0: project name + audience + repo source. * Step 1: design system starter kit. * Step 2: optional "what do you want to do with it" textarea (seeds * the AI's first message). */ export function ImportSetup({ workspace, onClose, onBack }: SetupProps) { const router = useRouter(); const [step, setStep] = useState(0); const [name, setName] = useState(""); const [audience, setAudience] = useState("customers"); const [designKitId, setDesignKitId] = useState(DEFAULT_DESIGN_KIT_ID); // GitHub OAuth state. `connected === undefined` means we haven't // checked yet; `null` means "checked, not linked". const [connected, setConnected] = useState(undefined); const [picker, setPicker] = useState<"github" | "url">("github"); const [filter, setFilter] = useState(""); const [selectedRepoId, setSelectedRepoId] = useState(null); const [manualUrl, setManualUrl] = useState(""); const [intent, setIntent] = useState(""); const [loading, setLoading] = useState(false); useEffect(() => { let cancelled = false; fetch("/api/integrations/github/repos", { credentials: "include" }) .then(r => r.json()) .then(d => { if (cancelled) return; if (d?.connected && Array.isArray(d.repos)) { setConnected({ login: d.login, repos: d.repos }); setPicker("github"); } else { setConnected(null); setPicker("url"); // no point defaulting to a picker that's empty } }) .catch(() => { if (!cancelled) setConnected(null); }); return () => { cancelled = true; }; }, []); // Surface ?gh_error / ?gh_connected toasts after returning from GitHub. useEffect(() => { if (typeof window === "undefined") return; const u = new URL(window.location.href); const err = u.searchParams.get("gh_error"); const ok = u.searchParams.get("gh_connected"); if (err) { toast.error(`GitHub: ${err}`); u.searchParams.delete("gh_error"); window.history.replaceState({}, "", u.toString()); } if (ok) { toast.success(`Connected GitHub as @${ok}`); u.searchParams.delete("gh_connected"); window.history.replaceState({}, "", u.toString()); // Refresh repo list silently. fetch("/api/integrations/github/repos", { credentials: "include" }) .then(r => r.json()) .then(d => { if (d?.connected) setConnected({ login: d.login, repos: d.repos }); }) .catch(() => {}); } }, []); const selectedRepo = connected && typeof connected === "object" ? connected.repos.find(r => r.id === selectedRepoId) ?? null : null; const isValidUrl = /^https?:\/\//i.test(manualUrl.trim()); const canContinue = name.trim().length > 0 && ((picker === "github" && !!selectedRepo) || (picker === "url" && isValidUrl)); const handleConnect = () => { const returnTo = window.location.pathname + window.location.search; window.location.href = `/api/integrations/github/connect?returnTo=${encodeURIComponent(returnTo)}`; }; const handleDisconnect = async () => { await fetch("/api/integrations/github/disconnect", { method: "POST", credentials: "include" }); setConnected(null); setPicker("url"); setSelectedRepoId(null); toast.success("Disconnected GitHub"); }; const handleCreate = async () => { if (!canContinue) return; const repoUrl = picker === "github" && selectedRepo ? selectedRepo.htmlUrl : manualUrl.trim(); 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, "-"), vision: intent.trim() || `Continue work on ${repoUrl}`, product: { name: name.trim() }, audience, designKitId, creationMode: "import", githubRepoUrl: repoUrl, githubDefaultBranch: selectedRepo?.defaultBranch ?? null, githubRepoId: selectedRepo?.id ?? null, sourceData: { audience, designKitId, repoUrl, via: picker === "github" ? "oauth" : "url", ghLogin: picker === "github" ? connected && typeof connected === "object" ? connected.login : null : null, intent: intent.trim() || null, }, }), }); 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}/product`); } catch { toast.error("Something went wrong"); } finally { setLoading(false); } }; const filteredRepos = connected && typeof connected === "object" ? connected.repos.filter(r => { const q = filter.trim().toLowerCase(); if (!q) return true; return r.fullName.toLowerCase().includes(q) || (r.description ?? "").toLowerCase().includes(q); }) : []; const totalSteps = 3; const handleHeaderBack = () => { if (step === 0) onBack(); else setStep(step - 1); }; return (
{step === 0 && ( <> Project name {/* Repo source */} {connected === undefined ? (
Checking GitHub connection…
) : connected === null ? ( ) : ( )} setStep(1)} disabled={!canContinue}> Next → } /> )} {step === 1 && ( <> setStep(0)}>← Back} primary={ setStep(2)}>Next →} /> )} {step === 2 && ( <> What do you want to do with it? (optional)