From bf99ac0ca23819130c94fd065444e1115bb19770 Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Mon, 4 May 2026 12:29:34 +0100 Subject: [PATCH] feat(code): list all GitHub installations and allow adding another org --- .../components/GitIntegrationStep.tsx | 243 +++++++++++------- .../src/renderer/hooks/useIntegrations.ts | 8 +- 2 files changed, 164 insertions(+), 87 deletions(-) diff --git a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx index 95bbd925a3..ec01fbeb0f 100644 --- a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx +++ b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx @@ -22,6 +22,7 @@ import { FolderOpen, GearSix, GitBranch, + Plus, } from "@phosphor-icons/react"; import { AlertDialog, @@ -110,12 +111,11 @@ export function GitIntegrationStep({ isLoading: githubUserIntegrationsLoading, } = useUserGithubIntegrations(); const hasGitIntegration = githubUserIntegrations.length > 0; - const { repositories, isLoadingRepos, failedInstallationIds } = + const { repositories, failedInstallationIds, reposByInstallationId } = useUserRepositoryIntegration(); - const githubIntegration = githubUserIntegrations[0] ?? null; - const integrationIsStale = - !!githubIntegration && - failedInstallationIds.includes(githubIntegration.installation_id); + const anyIntegrationStale = githubUserIntegrations.some((i) => + failedInstallationIds.includes(i.installation_id), + ); const alternativeConnectedProjects = useMemo(() => { if (hasGitIntegration) return []; @@ -136,13 +136,6 @@ export function GitIntegrationStep({ ); }, [alternativeConnectedProjects, selectedAlternativeId]); - const repoSummary = useMemo(() => { - if (repositories.length === 0) return null; - const names = repositories.map((r) => r.split("/").pop() ?? r); - if (names.length <= 2) return names.join(" and "); - return `${names[0]}, ${names[1]} and ${names.length - 2} more`; - }, [repositories]); - const repoMatchesGitHub = useMemo(() => { if (!detectedRepo || repositories.length === 0) return false; return repositories.some( @@ -151,18 +144,23 @@ export function GitIntegrationStep({ }, [detectedRepo, repositories]); const apiClient = useOptionalAuthenticatedClient(); - const [disconnectConfirmOpen, setDisconnectConfirmOpen] = useState(false); + const [disconnectTarget, setDisconnectTarget] = useState<{ + installationId: string; + accountName: string; + } | null>(null); + const [reconnectingInstallationId, setReconnectingInstallationId] = useState< + string | null + >(null); const disconnectMutation = useMutation({ - mutationFn: async (opts: { silent?: boolean } = {}) => { - const installationId = githubIntegration?.installation_id; - if (!apiClient || !installationId) { - throw new Error("No GitHub integration to disconnect"); + mutationFn: async (opts: { installationId: string; silent?: boolean }) => { + if (!apiClient) { + throw new Error("Not authenticated"); } - await apiClient.disconnectGithubUserIntegration(installationId); + await apiClient.disconnectGithubUserIntegration(opts.installationId); return { silent: opts.silent ?? false }; }, onSuccess: ({ silent }) => { - setDisconnectConfirmOpen(false); + setDisconnectTarget(null); invalidateGithubQueries(queryClient, selectedProjectId); if (!silent) toast.success("GitHub disconnected."); }, @@ -344,7 +342,7 @@ export function GitIntegrationStep({ {isLoading || githubUserIntegrationsLoading ? ( ) : hasGitIntegration ? ( - integrationIsStale ? ( + anyIntegrationStale ? ( Reconnect needed @@ -356,7 +354,9 @@ export function GitIntegrationStep({ className="text-(--green-9)" /> - Connected + {githubUserIntegrations.length > 1 + ? `Connected (${githubUserIntegrations.length})` + : "Connected"} ) @@ -364,64 +364,126 @@ export function GitIntegrationStep({ {hasGitIntegration ? ( - - {integrationIsStale - ? "We can't reach GitHub with your saved authorization. The app may have been uninstalled or the token revoked. Reconnect to restore access." - : isLoadingRepos - ? "Loading repositories..." - : repoSummary - ? `Access to ${repoSummary}` - : "No repositories found. Check your GitHub app settings."} - - - {integrationIsStale && ( - - )} - + + + + {accountName} + + + {integration.account?.type === + "Organization" + ? "org" + : "personal"} + + + {isStale ? ( + + Reconnect needed + + ) : ( + + {isLoadingInstallRepos + ? "Loading…" + : installRepos.length === 1 + ? "1 repo" + : `${installRepos.length} repos`} + + )} + + + {isStale && ( + + )} + + + + + ); + })} + @@ -609,15 +672,18 @@ export function GitIntegrationStep({ { if (!next && !disconnectMutation.isPending) { - setDisconnectConfirmOpen(false); + setDisconnectTarget(null); } }} > - Disconnect GitHub + + Disconnect{" "} + {disconnectTarget ? disconnectTarget.accountName : "GitHub"} + This removes your personal GitHub authorization from PostHog. You can reconnect at any time. The GitHub App itself stays installed @@ -637,7 +703,12 @@ export function GitIntegrationStep({