From e4e60aaae512c291b4964d102db1e69da70185d3 Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Mon, 4 May 2026 17:54:01 +0200 Subject: [PATCH 1/2] feat(code): add GitHub settings page to manage personal integration Adds a dedicated GitHub entry under Settings where users can connect, reconnect, and disconnect their personal GitHub integration powering cloud-task PR authoring. Generated-By: PostHog Code Task-Id: a0ecb9d8-786a-416d-9d1c-80dc690a6fdd --- apps/code/src/renderer/api/posthogClient.ts | 20 ++ .../settings/components/SettingsDialog.tsx | 5 + .../components/sections/GitHubSettings.tsx | 197 ++++++++++++++++++ .../settings/stores/settingsDialogStore.ts | 1 + 4 files changed, 223 insertions(+) create mode 100644 apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx diff --git a/apps/code/src/renderer/api/posthogClient.ts b/apps/code/src/renderer/api/posthogClient.ts index b2003f114..be78c5166 100644 --- a/apps/code/src/renderer/api/posthogClient.ts +++ b/apps/code/src/renderer/api/posthogClient.ts @@ -622,6 +622,26 @@ export class PostHogAPIClient { return data.results ?? []; } + async disconnectGithubUserIntegration(installationId: string): Promise { + const urlPath = `/api/users/@me/integrations/github/${installationId}/`; + const url = new URL(`${this.api.baseUrl}${urlPath}`); + const response = await this.api.fetcher.fetch({ + method: "delete", + url, + path: urlPath, + }); + if (!response.ok && response.status !== 204) { + const err = (await response.json().catch(() => ({}))) as { + detail?: unknown; + }; + const detail = + typeof err.detail === "string" + ? err.detail + : "Failed to disconnect GitHub"; + throw new Error(detail); + } + } + async switchOrganization(orgId: string): Promise { await this.api.patch("/api/users/{uuid}/", { path: { uuid: "@me" }, diff --git a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx index 43ccb5914..efc2c5253 100644 --- a/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx +++ b/apps/code/src/renderer/features/settings/components/SettingsDialog.tsx @@ -19,6 +19,7 @@ import { CreditCard, Folder, GearSix, + GithubLogo, HardDrives, Keyboard, Palette, @@ -37,6 +38,7 @@ import { ClaudeCodeSettings } from "./sections/ClaudeCodeSettings"; import { CloudEnvironmentsSettings } from "./sections/CloudEnvironmentsSettings"; import { EnvironmentsSettings } from "./sections/environments/EnvironmentsSettings"; import { GeneralSettings } from "./sections/GeneralSettings"; +import { GitHubSettings } from "./sections/GitHubSettings"; import { McpServersSettings } from "./sections/McpServersSettings"; import { PersonalizationSettings } from "./sections/PersonalizationSettings"; import { PlanUsageSettings } from "./sections/PlanUsageSettings"; @@ -82,6 +84,7 @@ const SIDEBAR_ITEMS: SidebarItem[] = [ fullwidth: true, }, { id: "shortcuts", label: "Shortcuts", icon: }, + { id: "github", label: "GitHub", icon: }, { id: "signals", @@ -103,6 +106,7 @@ const CATEGORY_TITLES: Record = { "claude-code": "Claude Code", "mcp-servers": "MCP Servers", shortcuts: "Shortcuts", + github: "GitHub", signals: "Signals", updates: "Updates", @@ -120,6 +124,7 @@ const CATEGORY_COMPONENTS: Record = { "claude-code": ClaudeCodeSettings, "mcp-servers": McpServersSettings, shortcuts: ShortcutsSettings, + github: GitHubSettings, signals: SignalSourcesSettings, updates: UpdatesSettings, diff --git a/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx new file mode 100644 index 000000000..f687a22d1 --- /dev/null +++ b/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx @@ -0,0 +1,197 @@ +import { useOptionalAuthenticatedClient } from "@features/auth/hooks/authClient"; +import { useGitHubIntegrationCallback } from "@features/integrations/hooks/useGitHubIntegrationCallback"; +import { useConnectUserGithub } from "@hooks/useConnectUserGithub"; +import { useUserGithubIntegrations } from "@hooks/useIntegrations"; +import { + ArrowSquareOutIcon, + CheckCircleIcon, + GithubLogoIcon, +} from "@phosphor-icons/react"; +import { + AlertDialog, + Box, + Button, + Flex, + Spinner, + Text, +} from "@radix-ui/themes"; +import { toast } from "@renderer/utils/toast"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useState } from "react"; + +export function GitHubSettings() { + const apiClient = useOptionalAuthenticatedClient(); + const queryClient = useQueryClient(); + const { data: integrations = [], isLoading } = useUserGithubIntegrations(); + const integration = integrations[0]; + + const { connect, isConnecting, canConnect } = useConnectUserGithub(); + const [confirmDisconnect, setConfirmDisconnect] = useState(false); + + useGitHubIntegrationCallback({ + onSuccess: () => { + void queryClient.invalidateQueries({ + queryKey: ["user-github-integrations"], + }); + void queryClient.invalidateQueries({ + queryKey: ["integrations", "list"], + }); + }, + onError: (message) => { + toast.error(message); + }, + }); + + const disconnect = useMutation({ + mutationFn: async (installationId: string) => { + if (!apiClient) throw new Error("Not authenticated"); + await apiClient.disconnectGithubUserIntegration(installationId); + }, + onSuccess: async () => { + setConfirmDisconnect(false); + toast.success("Disconnected personal GitHub"); + await Promise.all([ + queryClient.invalidateQueries({ + queryKey: ["user-github-integrations"], + }), + queryClient.invalidateQueries({ + queryKey: ["integrations", "list"], + }), + ]); + }, + onError: (error) => { + toast.error( + error instanceof Error ? error.message : "Failed to disconnect GitHub", + ); + }, + }); + + const accountName = integration?.account?.name?.trim() ?? null; + const isConnected = !!integration; + + return ( + + + + + + + + + Personal GitHub + + {isLoading ? ( + Loading… + ) : isConnected ? ( + + + + Connected{accountName ? ` as ${accountName}` : ""} · used for + cloud task PRs + + + ) : ( + + Connect your personal GitHub so cloud-task PRs are authored as + you. + + )} + + + + + {isConnecting ? ( + + + Waiting… + + ) : isConnected ? ( + <> + + + + ) : ( + + )} + + + + { + if (!disconnect.isPending) setConfirmDisconnect(open); + }} + > + + + Disconnect personal GitHub? + + + + Cloud task PRs will fall back to your team's integration. You can + reconnect at any time. + + + + + + + + + + + + ); +} diff --git a/apps/code/src/renderer/features/settings/stores/settingsDialogStore.ts b/apps/code/src/renderer/features/settings/stores/settingsDialogStore.ts index 69660dded..b37c77698 100644 --- a/apps/code/src/renderer/features/settings/stores/settingsDialogStore.ts +++ b/apps/code/src/renderer/features/settings/stores/settingsDialogStore.ts @@ -11,6 +11,7 @@ export type SettingsCategory = | "claude-code" | "shortcuts" | "mcp-servers" + | "github" | "signals" | "updates" | "advanced"; From b3dc71e51b2a12626713aba75c2c51edefbb0964 Mon Sep 17 00:00:00 2001 From: Vojta Bartos Date: Mon, 4 May 2026 18:23:31 +0200 Subject: [PATCH 2/2] feat(code): show project-level GitHub integration in settings Adds a read-only row beneath personal GitHub showing the team's project-level integration status, connected repos, and a shortcut to manage it under Signals. Generated-By: PostHog Code Task-Id: a0ecb9d8-786a-416d-9d1c-80dc690a6fdd --- .../components/sections/GitHubSettings.tsx | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx b/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx index f687a22d1..d8747814f 100644 --- a/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx +++ b/apps/code/src/renderer/features/settings/components/sections/GitHubSettings.tsx @@ -1,11 +1,16 @@ import { useOptionalAuthenticatedClient } from "@features/auth/hooks/authClient"; import { useGitHubIntegrationCallback } from "@features/integrations/hooks/useGitHubIntegrationCallback"; +import { useSettingsDialogStore } from "@features/settings/stores/settingsDialogStore"; import { useConnectUserGithub } from "@hooks/useConnectUserGithub"; -import { useUserGithubIntegrations } from "@hooks/useIntegrations"; +import { + useRepositoryIntegration, + useUserGithubIntegrations, +} from "@hooks/useIntegrations"; import { ArrowSquareOutIcon, CheckCircleIcon, GithubLogoIcon, + InfoIcon, } from "@phosphor-icons/react"; import { AlertDialog, @@ -14,6 +19,7 @@ import { Flex, Spinner, Text, + Tooltip, } from "@radix-ui/themes"; import { toast } from "@renderer/utils/toast"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -28,6 +34,15 @@ export function GitHubSettings() { const { connect, isConnecting, canConnect } = useConnectUserGithub(); const [confirmDisconnect, setConfirmDisconnect] = useState(false); + const setSettingsCategory = useSettingsDialogStore( + (state) => state.setCategory, + ); + const { + repositories: teamRepositories, + hasGithubIntegration: hasTeamIntegration, + isLoadingRepos: isLoadingTeam, + } = useRepositoryIntegration(); + useGitHubIntegrationCallback({ onSuccess: () => { void queryClient.invalidateQueries({ @@ -154,6 +169,81 @@ export function GitHubSettings() { + + + + + + + + Project GitHub + + {isLoadingTeam ? ( + Loading… + ) : hasTeamIntegration ? ( + teamRepositories.length > 0 ? ( + + {teamRepositories.map((repo) => ( + + {repo} + + ))} + + } + side="bottom" + > + + + + Connected · {teamRepositories.length}{" "} + {teamRepositories.length === 1 ? "repo" : "repos"} + + + + + ) : ( + + + + Connected + + + ) + ) : ( + + No project-level GitHub integration on this team. + + )} + + + + + + + + {