debounce GitHub repo name check & show create repo error details (#418)

Fixes #366
This commit is contained in:
Will Chen
2025-06-16 23:18:11 -07:00
committed by GitHub
parent d6d6918d1b
commit f25f6c7631
2 changed files with 80 additions and 24 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState, useEffect, useCallback, useRef } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Github, Clipboard, Check } from "lucide-react"; import { Github, Clipboard, Check } from "lucide-react";
import { IpcClient } from "@/ipc/ipc_client"; 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 // 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 githubOrg = ""; // Use empty string for now (GitHub API will default to the authenticated user)
const handleRepoNameBlur = async () => { const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
setRepoCheckError(null);
setRepoAvailable(null); const checkRepoAvailability = useCallback(
if (!repoName) return; async (name: string) => {
setIsCheckingRepo(true); setRepoCheckError(null);
try { setRepoAvailable(null);
const result = await IpcClient.getInstance().checkGithubRepoAvailable( if (!name) return;
githubOrg, setIsCheckingRepo(true);
repoName, try {
); const result = await IpcClient.getInstance().checkGithubRepoAvailable(
setRepoAvailable(result.available); githubOrg,
if (!result.available) { name,
setRepoCheckError(result.error || "Repository name is not available."); );
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."); [githubOrg],
} finally { );
setIsCheckingRepo(false);
} const debouncedCheckRepoAvailability = useCallback(
}; (name: string) => {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
checkRepoAvailability(name);
}, 500);
},
[checkRepoAvailability],
);
const handleCreateRepo = async (e: React.FormEvent) => { const handleCreateRepo = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
@@ -401,11 +420,12 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
className="w-full border rounded px-2 py-1" className="w-full border rounded px-2 py-1"
value={repoName} value={repoName}
onChange={(e) => { onChange={(e) => {
setRepoName(e.target.value); const newValue = e.target.value;
setRepoName(newValue);
setRepoAvailable(null); setRepoAvailable(null);
setRepoCheckError(null); setRepoCheckError(null);
debouncedCheckRepoAvailability(newValue);
}} }}
onBlur={handleRepoNameBlur}
disabled={isCreatingRepo} disabled={isCreatingRepo}
/> />
{isCheckingRepo && ( {isCheckingRepo && (

View File

@@ -356,8 +356,44 @@ async function handleCreateRepo(
}), }),
}); });
if (!res.ok) { if (!res.ok) {
const data = await res.json(); let errorMessage = `Failed to create repository (${res.status} ${res.statusText})`;
throw new Error(data.message || "Failed to create repo"); 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) // Store org and repo in the app's DB row (apps table)
await updateAppGithubRepo(appId, owner, repo); await updateAppGithubRepo(appId, owner, repo);