debounce GitHub repo name check & show create repo error details (#418)
Fixes #366
This commit is contained in:
@@ -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 && (
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user