diff --git a/src/components/GitHubConnector.tsx b/src/components/GitHubConnector.tsx index 53fcf18..a1e349d 100644 --- a/src/components/GitHubConnector.tsx +++ b/src/components/GitHubConnector.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Github, Clipboard, Check } from "lucide-react"; import { IpcClient } from "@/ipc/ipc_client"; @@ -126,26 +126,45 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) { // TODO: After device flow, fetch and store the GitHub username/org in settings for use here const githubOrg = ""; // Use empty string for now (GitHub API will default to the authenticated user) - const handleRepoNameBlur = async () => { - setRepoCheckError(null); - setRepoAvailable(null); - if (!repoName) return; - setIsCheckingRepo(true); - try { - const result = await IpcClient.getInstance().checkGithubRepoAvailable( - githubOrg, - repoName, - ); - setRepoAvailable(result.available); - if (!result.available) { - setRepoCheckError(result.error || "Repository name is not available."); + const debounceTimeoutRef = useRef(null); + + const checkRepoAvailability = useCallback( + async (name: string) => { + setRepoCheckError(null); + setRepoAvailable(null); + if (!name) return; + setIsCheckingRepo(true); + try { + const result = await IpcClient.getInstance().checkGithubRepoAvailable( + githubOrg, + name, + ); + setRepoAvailable(result.available); + if (!result.available) { + setRepoCheckError( + result.error || "Repository name is not available.", + ); + } + } catch (err: any) { + setRepoCheckError(err.message || "Failed to check repo availability."); + } finally { + setIsCheckingRepo(false); } - } catch (err: any) { - setRepoCheckError(err.message || "Failed to check repo availability."); - } finally { - setIsCheckingRepo(false); - } - }; + }, + [githubOrg], + ); + + const debouncedCheckRepoAvailability = useCallback( + (name: string) => { + if (debounceTimeoutRef.current) { + clearTimeout(debounceTimeoutRef.current); + } + debounceTimeoutRef.current = setTimeout(() => { + checkRepoAvailability(name); + }, 500); + }, + [checkRepoAvailability], + ); const handleCreateRepo = async (e: React.FormEvent) => { e.preventDefault(); @@ -401,11 +420,12 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) { className="w-full border rounded px-2 py-1" value={repoName} onChange={(e) => { - setRepoName(e.target.value); + const newValue = e.target.value; + setRepoName(newValue); setRepoAvailable(null); setRepoCheckError(null); + debouncedCheckRepoAvailability(newValue); }} - onBlur={handleRepoNameBlur} disabled={isCreatingRepo} /> {isCheckingRepo && ( diff --git a/src/ipc/handlers/github_handlers.ts b/src/ipc/handlers/github_handlers.ts index 083f468..bd32d61 100644 --- a/src/ipc/handlers/github_handlers.ts +++ b/src/ipc/handlers/github_handlers.ts @@ -356,8 +356,44 @@ async function handleCreateRepo( }), }); if (!res.ok) { - const data = await res.json(); - throw new Error(data.message || "Failed to create repo"); + let errorMessage = `Failed to create repository (${res.status} ${res.statusText})`; + try { + const data = await res.json(); + logger.error("GitHub API error when creating repo:", { + status: res.status, + statusText: res.statusText, + response: data, + }); + + // Handle specific GitHub API error cases + if (data.message) { + errorMessage = data.message; + } + + // Handle validation errors with more details + if (data.errors && Array.isArray(data.errors)) { + const errorDetails = data.errors + .map((err: any) => { + if (typeof err === "string") return err; + if (err.message) return err.message; + if (err.code) return `${err.field || "field"}: ${err.code}`; + return JSON.stringify(err); + }) + .join(", "); + errorMessage = `${data.message || "Repository creation failed"}: ${errorDetails}`; + } + } catch (jsonError) { + // If response is not JSON, fall back to status text + logger.error("Failed to parse GitHub API error response:", { + status: res.status, + statusText: res.statusText, + jsonError: + jsonError instanceof Error ? jsonError.message : String(jsonError), + }); + errorMessage = `GitHub API error: ${res.status} ${res.statusText}`; + } + + throw new Error(errorMessage); } // Store org and repo in the app's DB row (apps table) await updateAppGithubRepo(appId, owner, repo);