import { useState, useEffect, useCallback, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Globe } from "lucide-react"; import { IpcClient } from "@/ipc/ipc_client"; import { useSettings } from "@/hooks/useSettings"; import { useLoadApp } from "@/hooks/useLoadApp"; import { useVercelDeployments } from "@/hooks/useVercelDeployments"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import {} from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { App } from "@/ipc/ipc_types"; interface VercelConnectorProps { appId: number | null; folderName: string; } interface VercelProject { id: string; name: string; framework: string | null; } interface ConnectedVercelConnectorProps { appId: number; app: App; refreshApp: () => void; } interface UnconnectedVercelConnectorProps { appId: number | null; folderName: string; settings: any; refreshSettings: () => void; refreshApp: () => void; } function ConnectedVercelConnector({ appId, app, refreshApp, }: ConnectedVercelConnectorProps) { const { deployments, isLoading: isLoadingDeployments, error: deploymentsError, getDeployments: handleGetDeployments, disconnectProject, isDisconnecting, disconnectError, } = useVercelDeployments(appId); const handleDisconnectProject = async () => { await disconnectProject(); refreshApp(); }; return (

Connected to Vercel Project:

{ e.preventDefault(); IpcClient.getInstance().openExternalUrl( `https://vercel.com/${app.vercelTeamSlug}/${app.vercelProjectName}`, ); }} className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400" target="_blank" rel="noopener noreferrer" > {app.vercelProjectName} {app.vercelDeploymentUrl && (

Live URL:{" "} { e.preventDefault(); if (app.vercelDeploymentUrl) { IpcClient.getInstance().openExternalUrl( app.vercelDeploymentUrl, ); } }} className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400 font-mono" target="_blank" rel="noopener noreferrer" > {app.vercelDeploymentUrl}

)}
{deploymentsError && (

{deploymentsError}

)} {deployments.length > 0 && (

Recent Deployments:

{deployments.map((deployment) => (
{deployment.readyState} {new Date(deployment.createdAt).toLocaleString()}
{ e.preventDefault(); IpcClient.getInstance().openExternalUrl( `https://${deployment.url}`, ); }} className="cursor-pointer text-blue-600 hover:underline dark:text-blue-400 text-sm" target="_blank" rel="noopener noreferrer" > View
))}
)} {disconnectError && (

{disconnectError}

)}
); } function UnconnectedVercelConnector({ appId, folderName, settings, refreshSettings, refreshApp, }: UnconnectedVercelConnectorProps) { // --- Manual Token Entry State --- const [accessToken, setAccessToken] = useState(""); const [isSavingToken, setIsSavingToken] = useState(false); const [tokenError, setTokenError] = useState(null); const [tokenSuccess, setTokenSuccess] = useState(false); // --- Project Setup State --- const [projectSetupMode, setProjectSetupMode] = useState< "create" | "existing" >("create"); const [availableProjects, setAvailableProjects] = useState( [], ); const [isLoadingProjects, setIsLoadingProjects] = useState(false); const [selectedProject, setSelectedProject] = useState(""); // Create new project state const [projectName, setProjectName] = useState(folderName); const [projectAvailable, setProjectAvailable] = useState( null, ); const [projectCheckError, setProjectCheckError] = useState( null, ); const [isCheckingProject, setIsCheckingProject] = useState(false); const [isCreatingProject, setIsCreatingProject] = useState(false); const [createProjectError, setCreateProjectError] = useState( null, ); const [createProjectSuccess, setCreateProjectSuccess] = useState(false); const debounceTimeoutRef = useRef(null); // Load available projects when Vercel is connected useEffect(() => { if (settings?.vercelAccessToken && projectSetupMode === "existing") { loadAvailableProjects(); } }, [settings?.vercelAccessToken, projectSetupMode]); // Cleanup debounce timer on unmount useEffect(() => { return () => { if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } }; }, []); const loadAvailableProjects = async () => { setIsLoadingProjects(true); try { const projects = await IpcClient.getInstance().listVercelProjects(); setAvailableProjects(projects); } catch (error) { console.error("Failed to load Vercel projects:", error); } finally { setIsLoadingProjects(false); } }; const handleSaveAccessToken = async (e: React.FormEvent) => { e.preventDefault(); if (!accessToken.trim()) return; setIsSavingToken(true); setTokenError(null); setTokenSuccess(false); try { await IpcClient.getInstance().saveVercelAccessToken({ token: accessToken.trim(), }); setTokenSuccess(true); setAccessToken(""); refreshSettings(); } catch (err: any) { setTokenError(err.message || "Failed to save access token."); } finally { setIsSavingToken(false); } }; const checkProjectAvailability = useCallback(async (name: string) => { setProjectCheckError(null); setProjectAvailable(null); if (!name) return; setIsCheckingProject(true); try { const result = await IpcClient.getInstance().isVercelProjectAvailable({ name, }); setProjectAvailable(result.available); if (!result.available) { setProjectCheckError(result.error || "Project name is not available."); } } catch (err: any) { setProjectCheckError( err.message || "Failed to check project availability.", ); } finally { setIsCheckingProject(false); } }, []); const debouncedCheckProjectAvailability = useCallback( (name: string) => { if (debounceTimeoutRef.current) { clearTimeout(debounceTimeoutRef.current); } debounceTimeoutRef.current = setTimeout(() => { checkProjectAvailability(name); }, 500); }, [checkProjectAvailability], ); const handleSetupProject = async (e: React.FormEvent) => { e.preventDefault(); if (!appId) return; setCreateProjectError(null); setIsCreatingProject(true); setCreateProjectSuccess(false); try { if (projectSetupMode === "create") { await IpcClient.getInstance().createVercelProject({ name: projectName, appId, }); } else { await IpcClient.getInstance().connectToExistingVercelProject({ projectId: selectedProject, appId, }); } setCreateProjectSuccess(true); setProjectCheckError(null); refreshApp(); } catch (err: any) { setCreateProjectError( err.message || `Failed to ${projectSetupMode === "create" ? "create" : "connect to"} project.`, ); } finally { setIsCreatingProject(false); } }; if (!settings?.vercelAccessToken) { return (

Connect to Vercel

To connect your app to Vercel, you'll need to create an access token:

  1. If you don't have a Vercel account, sign up first
  2. Go to Vercel settings to create a token
  3. Copy the token and paste it below
setAccessToken(e.target.value)} disabled={isSavingToken} className="w-full" />
{tokenError && (

{tokenError}

)} {tokenSuccess && (

Successfully connected to Vercel! You can now set up your project below.

)}
); } return (
{/* Collapsible Header */}
Set up your Vercel project
{/* Collapsible Content */}
{/* Mode Selection */}
{projectSetupMode === "create" ? ( <>
{ const newValue = e.target.value; setProjectName(newValue); setProjectAvailable(null); setProjectCheckError(null); debouncedCheckProjectAvailability(newValue); }} disabled={isCreatingProject} /> {isCheckingProject && (

Checking availability...

)} {projectAvailable === true && (

Project name is available!

)} {projectAvailable === false && (

{projectCheckError}

)}
) : ( <>
)}
{createProjectError && (

{createProjectError}

)} {createProjectSuccess && (

{projectSetupMode === "create" ? "Project created and linked!" : "Connected to project!"}

)}
); } export function VercelConnector({ appId, folderName }: VercelConnectorProps) { const { app, refreshApp } = useLoadApp(appId); const { settings, refreshSettings } = useSettings(); if (app?.vercelProjectId && appId) { return ( ); } else { return ( ); } }