"use client"; import { useCallback, useState } from "react"; import { IconBrandGithub, IconCheck, IconExternalLink, IconRefresh, IconTrash, IconX, } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { Spinner } from "@kandev/ui/spinner"; import { SettingsSection } from "@/components/settings/settings-section"; import { useToast } from "@/components/toast-provider"; import { useGitHubStatus } from "@/hooks/domains/github/use-github-status"; import { useGitHubAppRegistrations } from "@/hooks/domains/github/use-github-app-registrations"; import { disconnectGitHubPersonal, disconnectGitHubWorkspace, startGitHubPersonalConnect, } from "@/lib/api/domains/github-api"; import { getGitHubPersonalIdentityState } from "@/lib/github-auth"; import type { GitHubConnectionSource, GitHubConnectionState, GitHubStatus, GitHubAppRegistrationCatalogItem, } from "@/lib/types/github"; import { GitHubConnectionDialog } from "./github-connection-dialog"; import { GitHubPermissionsDialog } from "./github-permissions-dialog"; const sourceLabels: Record = { pat: "Personal access token", gh_cli: "GitHub CLI", github_app_installation: "GitHub App", legacy_shared: "Legacy shared connection", }; function connectionLabel(status: GitHubStatus): string { const connection = status.automation; if (!connection) return ""; return ( connection.actor?.login ?? connection.login ?? connection.installation_account_login ?? "GitHub App" ); } function automationActor(status: GitHubStatus): string | null { if (!status.authenticated) return null; return status.automation?.actor?.login ?? null; } function StatusLine({ status }: { status: GitHubStatus }) { const connection = status.automation; if (!connection) { return (
No automation connection
); } const actor = automationActor(status); const active = connection.status === "active" && actor !== null; return (
{active ? ( ) : ( )} {actor ?? (connection.status === "active" ? "Authentication unavailable" : connectionLabel(status))} {sourceLabels[connection.source]} {connection.status !== "active" && {connection.status}}
); } async function redirectFrom(start: () => Promise<{ url?: string; URL?: string }>) { const response = await start(); const url = response.url ?? response.URL; if (!url) throw new Error("GitHub did not return a redirect URL"); window.location.assign(url); } function errorMessage(error: unknown, fallback: string) { return error instanceof Error ? error.message : fallback; } function AutomationStatusSummary({ status, app, }: { status: GitHubStatus; app?: GitHubAppRegistrationCatalogItem; }) { const appAutomation = status.automation?.source === "github_app_installation"; return (
{appAutomation && } {!appAutomation && }
); } function AutomationActorExplanation({ status, appAutomation, }: { status: GitHubStatus; appAutomation: boolean; }) { const actor = status.automation?.actor?.login; if (!status.authenticated || !actor) return null; return (

{appAutomation ? `Kandev-managed operations use the GitHub App installed for ${actor}.` : `Kandev-managed operations act as ${actor}.`}

); } function AppRegistrationDetails({ app }: { app?: GitHubAppRegistrationCatalogItem }) { if (!app) return null; return ( <>
{app.display_name} {app.visibility} Webhook {app.webhook_status} {app.source === "managed" ? "Created by Kandev" : "Imported"}
{app.shared && (

This App registration is shared by {app.workspace_binding_count} workspaces. Registration changes affect every workspace using it; installation access remains workspace-specific.

)} ); } function HumanIdentityExplanation({ status }: { status: GitHubStatus }) { if (status.effective_personal_actor?.kind !== "human") return null; return (

This account also powers My GitHub and user-triggered actions.

); } function AutomationError({ status }: { status: GitHubStatus }) { if (!status.automation?.last_error) return null; return

{status.automation.last_error}

; } function AutomationActions({ status, workspaceId, busy, onDisconnect, onRefresh, }: { status: GitHubStatus; workspaceId: string; busy: boolean; onDisconnect: () => void; onRefresh: () => void; }) { return (
{status.automation && ( )}
); } export function GitHubAutomationSettings({ workspaceId }: { workspaceId: string }) { const { status, loaded, loading, refresh } = useGitHubStatus(workspaceId); const appRegistrations = useGitHubAppRegistrations(workspaceId); const [busy, setBusy] = useState(false); const { toast } = useToast(); const disconnect = useCallback(async () => { setBusy(true); try { await disconnectGitHubWorkspace(workspaceId); toast({ description: "Workspace GitHub connection removed", variant: "success" }); refresh(); } catch (error) { toast({ description: error instanceof Error ? error.message : "Disconnect failed", variant: "error", }); } finally { setBusy(false); } }, [refresh, toast, workspaceId]); if (!loaded || loading || !status) return ; const activeRegistrationId = status.app_registration?.id ?? status.automation?.app_registration_id; const activeApp = appRegistrations.registrations.find((item) => item.id === activeRegistrationId); return (
); } type PersonalIdentityView = { active: boolean; actor: string; personalActive: boolean; status: GitHubConnectionState | null; }; function personalIdentityView(status: GitHubStatus): PersonalIdentityView { const identity = getGitHubPersonalIdentityState(status); return { active: identity.active, actor: identity.actor, personalActive: identity.personalOAuthActive, status: status.personal?.status ?? null, }; } function PersonalIdentityStatus({ view }: { view: PersonalIdentityView }) { return (
{view.active ? ( ) : ( )} {view.actor} {view.personalActive && Personal OAuth} {view.status && view.status !== "active" && ( {view.status} )}
); } function PersonalIdentityActions({ status, busy, onConnect, onDisconnect, }: { status: GitHubStatus; busy: boolean; onConnect: () => void; onDisconnect: () => void; }) { return (
{status.app_available && status.automation?.source === "github_app_installation" && ( )} {status.personal && ( )}
); } export function GitHubPersonalSettings({ workspaceId }: { workspaceId: string }) { const { status, loaded, loading, refresh } = useGitHubStatus(workspaceId); const [busy, setBusy] = useState(false); const { toast } = useToast(); if (!loaded || loading || !status) { return ( ); } const view = personalIdentityView(status); const appAutomation = status.automation?.source === "github_app_installation"; if (!status.automation) return null; if (!appAutomation) { return (

A separate personal identity is only needed when workspace automation uses a GitHub App.

); } const disconnect = async () => { setBusy(true); try { await disconnectGitHubPersonal(workspaceId); toast({ description: "Personal GitHub identity disconnected", variant: "success" }); refresh(); } catch (error) { toast({ description: error instanceof Error ? error.message : "Disconnect failed", variant: "error", }); } finally { setBusy(false); } }; const connect = () => { void redirectFrom(() => startGitHubPersonalConnect(workspaceId)).catch((error: unknown) => toast({ description: errorMessage(error, "Identity connection failed"), variant: "error", }), ); }; return (
{status.personal?.last_error && (

{status.personal.last_error}

)}
); } function LoadingStatus() { return (
Checking GitHub connection...
); } /** Compatibility export used by older settings entrypoints. */ export function GitHubStatusCard({ workspaceId }: { workspaceId: string }) { return ; }