"use client"; import { useEffect, useState } from "react"; import { useRouter, useParams } from "next/navigation"; interface MigrateMainProps { projectId: string; projectName: string; sourceData?: { repoUrl?: string; liveUrl?: string; hosting?: string }; analysisResult?: Record; migrationPlan?: string; creationStage?: string; } type Stage = "input" | "auditing" | "review" | "planning" | "plan"; const HOSTING_OPTIONS = [ { value: "", label: "Select hosting provider" }, { value: "vercel", label: "Vercel" }, { value: "aws", label: "AWS" }, { value: "heroku", label: "Heroku" }, { value: "digitalocean", label: "DigitalOcean" }, { value: "gcp", label: "Google Cloud Platform" }, { value: "azure", label: "Microsoft Azure" }, { value: "railway", label: "Railway" }, { value: "render", label: "Render" }, { value: "netlify", label: "Netlify" }, { value: "self-hosted", label: "Self-hosted / VPS" }, { value: "other", label: "Other" }, ]; function MarkdownRenderer({ md }: { md: string }) { const lines = md.split('\n'); return (
{lines.map((line, i) => { if (line.startsWith('## ')) return

{line.slice(3)}

; if (line.startsWith('### ')) return

{line.slice(4)}

; if (line.startsWith('# ')) return

{line.slice(2)}

; if (line.match(/^- \[ \] /)) return (
{line.slice(6)}
); if (line.match(/^- \[x\] /i)) return (
{line.slice(6)}
); if (line.startsWith('- ') || line.startsWith('* ')) return
• {line.slice(2)}
; if (line.startsWith('---')) return
; if (!line.trim()) return
; // Bold inline const parts = line.split(/(\*\*.*?\*\*)/g).map((seg, j) => seg.startsWith("**") && seg.endsWith("**") ? {seg.slice(2, -2)} : {seg} ); return

{parts}

; })}
); } export function MigrateMain({ projectId, projectName, sourceData, analysisResult: initialAnalysis, migrationPlan: initialPlan, creationStage, }: MigrateMainProps) { const router = useRouter(); const params = useParams(); const workspace = params?.workspace as string; const getInitialStage = (): Stage => { if (initialPlan) return "plan"; if (creationStage === "planning") return "planning"; if (creationStage === "review" || initialAnalysis) return "review"; if (sourceData?.repoUrl || sourceData?.liveUrl) return "auditing"; return "input"; }; const [stage, setStage] = useState(getInitialStage); const [repoUrl, setRepoUrl] = useState(sourceData?.repoUrl ?? ""); const [liveUrl, setLiveUrl] = useState(sourceData?.liveUrl ?? ""); const [hosting, setHosting] = useState(sourceData?.hosting ?? ""); const [analysisResult, setAnalysisResult] = useState | null>(initialAnalysis ?? null); const [migrationPlan, setMigrationPlan] = useState(initialPlan ?? ""); const [progressStep, setProgressStep] = useState("cloning"); const [error, setError] = useState(null); // Poll during audit useEffect(() => { if (stage !== "auditing") return; const interval = setInterval(async () => { try { const res = await fetch(`/api/projects/${projectId}/analysis-status`); const data = await res.json(); setProgressStep(data.stage ?? "cloning"); if (data.stage === "done" && data.analysisResult) { setAnalysisResult(data.analysisResult); clearInterval(interval); setStage("review"); } } catch { /* keep polling */ } }, 2500); return () => clearInterval(interval); // eslint-disable-next-line react-hooks/exhaustive-deps }, [stage]); const startAudit = async () => { setError(null); setStage("auditing"); if (repoUrl) { try { await fetch(`/api/projects/${projectId}/analyze-repo`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ repoUrl, liveUrl, hosting }), }); } catch (e) { setError(e instanceof Error ? e.message : "Failed to start audit"); setStage("input"); } } else { // No repo — just use live URL fingerprinting via generate-migration-plan directly setStage("review"); setAnalysisResult({ summary: `Live product at ${liveUrl}`, rows: [], suggestedSurfaces: [] }); } }; const startPlanning = async () => { setStage("planning"); setError(null); try { const res = await fetch(`/api/projects/${projectId}/generate-migration-plan`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ analysisResult, sourceData: { repoUrl, liveUrl, hosting } }), }); const data = await res.json(); if (!res.ok) throw new Error(data.error || "Planning failed"); setMigrationPlan(data.migrationPlan); setStage("plan"); } catch (e) { setError(e instanceof Error ? e.message : "Planning failed"); setStage("review"); } }; // ── Stage: input ────────────────────────────────────────────────────────── if (stage === "input") { const canProceed = repoUrl.trim().startsWith("http") || liveUrl.trim().startsWith("http"); return (

Tell us about your product

{projectName} — Atlas will audit your current setup and build a safe migration plan.

{error && (
{error}
)} setRepoUrl(e.target.value)} placeholder="https://github.com/yourorg/your-repo" style={{ width: "100%", padding: "11px 14px", marginBottom: 16, borderRadius: 8, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.9rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", color: "#1a1a1a", outline: "none", boxSizing: "border-box" }} onFocus={e => (e.currentTarget.style.borderColor = "#1a1a1a")} onBlur={e => (e.currentTarget.style.borderColor = "#e0dcd4")} autoFocus /> setLiveUrl(e.target.value)} placeholder="https://yourproduct.com" style={{ width: "100%", padding: "11px 14px", marginBottom: 16, borderRadius: 8, border: "1px solid #e0dcd4", background: "#faf8f5", fontSize: "0.9rem", fontFamily: "var(--font-inter), ui-sans-serif, sans-serif", color: "#1a1a1a", outline: "none", boxSizing: "border-box" }} onFocus={e => (e.currentTarget.style.borderColor = "#1a1a1a")} onBlur={e => (e.currentTarget.style.borderColor = "#e0dcd4")} />
Non-destructive. Your existing product stays live throughout. Atlas duplicates, never deletes.
); } // ── Stage: auditing ─────────────────────────────────────────────────────── if (stage === "auditing") { const steps = [ { key: "cloning", label: "Cloning repository" }, { key: "reading", label: "Reading configuration" }, { key: "analyzing", label: "Auditing infrastructure" }, { key: "done", label: "Audit complete" }, ]; const currentIdx = steps.findIndex(s => s.key === progressStep); return (

Auditing your product

This is non-destructive — your live product is untouched

{steps.map((step, i) => { const done = i < currentIdx; const active = i === currentIdx; return (
{done ? "✓" : active ? : ""}
{step.label}
); })}
); } // ── Stage: review ───────────────────────────────────────────────────────── if (stage === "review") { const rows = (analysisResult?.rows as Array<{ category: string; item: string; status: string; detail?: string }>) ?? []; const summary = (analysisResult?.summary as string) ?? ''; return (

Audit complete

{summary || `${projectName} — review your current infrastructure below.`}

{rows.length > 0 && (
{rows.map((row, i) => { const colorMap = { found: { bg: "#f0fdf4", text: "#15803d", label: "Found" }, partial: { bg: "#fffbeb", text: "#b45309", label: "Partial" }, missing: { bg: "#fff1f2", text: "#be123c", label: "Missing" } }; const sc = colorMap[row.status as keyof typeof colorMap] ?? colorMap.found; return (
0 ? "1px solid #f6f4f0" : "none" }}>
{row.category}
{row.item}
{row.detail &&
{row.detail}
}
{sc.label}
); })}
)} {error && (
{error}
)}
Ready to build the migration plan?
Atlas will generate a phased migration doc with Mirror, Validate, Cutover, and Decommission phases.
); } // ── Stage: planning ─────────────────────────────────────────────────────── if (stage === "planning") { return (

Generating migration plan…

Atlas is designing a safe, phased migration strategy

); } // ── Stage: plan ─────────────────────────────────────────────────────────── return (
{/* Non-destructive banner */}
🛡️
Non-destructive migration — your existing product stays live throughout every phase. Atlas duplicates, never deletes.

Migration Plan

{projectName} — four phased migration with rollback plan

); }