import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; import { AlertTriangle, CheckCircle, ExternalLink, GitBranch, Github, Import, Link, RefreshCw, Unlink, XCircle, } from "lucide-react"; import React from "react"; import { useForm } from "react-hook-form"; import { z } from "zod/v4"; import { RepositoryBrowserModal } from "@/components/project/repository-browser-modal"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Separator } from "@/components/ui/separator"; import type { VerifyGithubInstallationResponse } from "@/fetchers/github-integration/verify-github-installation"; import { useCreateGithubIntegration, useDeleteGithubIntegration, useVerifyGithubInstallation, } from "@/hooks/mutations/github-integration/use-create-github-integration"; import useImportGithubIssues from "@/hooks/mutations/github-integration/use-import-github-issues"; import useGetGithubIntegration from "@/hooks/queries/github-integration/use-get-github-integration"; import { cn } from "@/lib/cn"; import { toast } from "@/lib/toast"; const githubIntegrationSchema = z.object({ repositoryOwner: z .string() .min(1, "Repository owner is required") .regex(/^[a-zA-Z0-9-]+$/, "Invalid repository owner format"), repositoryName: z .string() .min(1, "Repository name is required") .regex(/^[a-zA-Z0-9._-]+$/, "Invalid repository name format"), }); type GithubIntegrationFormValues = z.infer; export function GitHubIntegrationSettings({ projectId, }: { projectId: string; }) { const { data: integration, isLoading } = useGetGithubIntegration(projectId); const { mutateAsync: createIntegration, isPending: isCreating } = useCreateGithubIntegration(); const { mutateAsync: deleteIntegration, isPending: isDeleting } = useDeleteGithubIntegration(); const { mutateAsync: verifyInstallation, isPending: isVerifying } = useVerifyGithubInstallation(); const { mutateAsync: importIssues, isPending: isImporting } = useImportGithubIssues(); const [verificationResult, setVerificationResult] = React.useState(null); const [showRepositoryBrowser, setShowRepositoryBrowser] = React.useState(false); const form = useForm({ resolver: standardSchemaResolver(githubIntegrationSchema), defaultValues: { repositoryOwner: integration?.repositoryOwner || "", repositoryName: integration?.repositoryName || "", }, }); React.useEffect(() => { if (integration) { form.reset({ repositoryOwner: integration.repositoryOwner, repositoryName: integration.repositoryName, }); } }, [integration, form]); const repositoryOwner = form.watch("repositoryOwner"); const repositoryName = form.watch("repositoryName"); const handleVerifyInstallation = React.useCallback( async (data: GithubIntegrationFormValues, showToast = true) => { try { const result = await verifyInstallation(data); setVerificationResult(result); if (showToast) { if (result.isInstalled && result.hasRequiredPermissions) { toast.success("GitHub App is properly installed!"); } else if (result.isInstalled) { toast.warning( "GitHub App is installed but missing required permissions", ); } else if (result.repositoryExists) { toast.warning( "GitHub App needs to be installed on this repository", ); } else { toast.error("Repository not found or not accessible"); } } } catch (error) { if (showToast) { toast.error( error instanceof Error ? error.message : "Failed to verify GitHub installation", ); } setVerificationResult(null); } }, [verifyInstallation], ); React.useEffect(() => { if (repositoryOwner && repositoryName && form.formState.isValid) { handleVerifyInstallation({ repositoryOwner, repositoryName }, false); } }, [ repositoryOwner, repositoryName, form.formState.isValid, handleVerifyInstallation, ]); const handleRepositorySelect = (repository: { owner: string; name: string; }) => { form.setValue("repositoryOwner", repository.owner, { shouldValidate: true, shouldDirty: true, shouldTouch: true, }); form.setValue("repositoryName", repository.name, { shouldValidate: true, shouldDirty: true, shouldTouch: true, }); setShowRepositoryBrowser(false); setVerificationResult(null); }; const onSubmit = async (data: GithubIntegrationFormValues) => { try { const verification = await verifyInstallation(data); if (!verification.isInstalled) { toast.error("Please install the GitHub App on this repository first"); return; } if (!verification.hasRequiredPermissions) { toast.error( `GitHub App is missing required permissions: ${verification.missingPermissions?.join(", ") || "issues"}. Please update the app permissions.`, ); return; } await createIntegration({ projectId, data, }); toast.success("GitHub integration updated successfully"); } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to update GitHub integration", ); } }; const handleDelete = async () => { try { await deleteIntegration(projectId); form.reset({ repositoryOwner: "", repositoryName: "" }); setVerificationResult(null); toast.success("GitHub integration removed successfully"); } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to remove GitHub integration", ); } }; const handleImportIssues = async () => { try { await importIssues({ projectId }); toast.success("Issues imported successfully"); } catch (error) { toast.error( error instanceof Error ? error.message : "Failed to import issues", ); } }; if (isLoading) { return (
); } const isConnected = !!integration && integration.isActive; const canImport = isConnected && verificationResult?.isInstalled && verificationResult?.hasRequiredPermissions; return (

Connection Status

{isConnected ? (

Repository connected and active

) : (

No repository connected

)}
{isConnected ? (
Connected
) : ( Not Connected )}
{isConnected && ( <>

Repository

Connected GitHub repository

{integration.repositoryOwner}/{integration.repositoryName}
)} {isConnected && verificationResult && ( <>

GitHub App Status

Installation and permissions status

{verificationResult.isInstalled && verificationResult.hasRequiredPermissions ? ( <> Properly configured ) : verificationResult.isInstalled ? ( <> Missing permissions ) : ( <> Not installed )}
)}
(
Repository Owner

GitHub username or organization

)} /> (
Repository Name

The repository name

)} />

Actions

Manage your repository connection

{isConnected && ( )}
{verificationResult && ( <>
{verificationResult.isInstalled && verificationResult.hasRequiredPermissions ? ( ) : verificationResult.isInstalled || verificationResult.repositoryExists ? ( ) : ( )}

{verificationResult.message}

{verificationResult.isInstalled && !verificationResult.hasRequiredPermissions && verificationResult.missingPermissions && (

Missing permissions:{" "} {verificationResult.missingPermissions.join(", ")}

{verificationResult.settingsUrl && ( )}
)} {!verificationResult.isInstalled && verificationResult.repositoryExists && (
{verificationResult.installationUrl && ( )}
)}
)}
{isConnected && (

Import GitHub Issues

Import existing issues from your GitHub repository as tasks

{!canImport && ( <>

Complete the repository configuration above to enable importing

)}
)}
); }