Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions apps/code/src/renderer/api/posthogClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,21 @@ export class PostHogAPIClient {
return data.results ?? [];
}

async disconnectGithubUserIntegration(installationId: string): Promise<void> {
const urlPath = `/api/users/@me/integrations/github/${encodeURIComponent(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 !== 404) {
throw new Error(
`Failed to disconnect GitHub integration: ${response.statusText}`,
);
}
}

async switchOrganization(orgId: string): Promise<void> {
await this.api.patch("/api/users/{uuid}/", {
path: { uuid: "@me" },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useAuthStateValue } from "@features/auth/hooks/authQueries";
import { useGitHubIntegrationCallback } from "@features/integrations/hooks/useGitHubIntegrationCallback";
import { trpcClient } from "@renderer/trpc/client";
import { IS_DEV } from "@shared/constants/environment";
import { useQueryClient } from "@tanstack/react-query";
import { type QueryClient, useQueryClient } from "@tanstack/react-query";
import { useCallback, useEffect, useRef, useState } from "react";

const POLL_INTERVAL_MS = 3_000;
Expand Down Expand Up @@ -68,6 +68,24 @@ interface Result {
reset: () => void;
}

export function invalidateGithubQueries(
queryClient: QueryClient,
projectId: number | null = null,
): void {
if (projectId !== null) {
void queryClient.invalidateQueries({
queryKey: ["integrations", projectId],
});
}
void queryClient.invalidateQueries({
queryKey: ["integrations", "list"],
});
void queryClient.invalidateQueries({
queryKey: ["user-github-integrations"],
});
void queryClient.invalidateQueries({ queryKey: ["github_login"] });
}

async function openUrlInBrowser(url: string): Promise<void> {
try {
await trpcClient.os.openExternal.mutate({ url });
Expand Down Expand Up @@ -99,20 +117,7 @@ export function useGithubUserConnect({ projectId }: Options): Result {
}, []);

const invalidate = useCallback(
(pid: number | null) => {
if (pid !== null) {
void queryClient.invalidateQueries({
queryKey: ["integrations", pid],
});
}
void queryClient.invalidateQueries({
queryKey: ["integrations", "list"],
});
void queryClient.invalidateQueries({
queryKey: ["user-github-integrations"],
});
void queryClient.invalidateQueries({ queryKey: ["github_login"] });
},
(pid: number | null) => invalidateGithubQueries(queryClient, pid),
[queryClient],
);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useOptionalAuthenticatedClient } from "@features/auth/hooks/authClient";
import { useSelectProjectMutation } from "@features/auth/hooks/authMutations";
import { useAuthStateValue } from "@features/auth/hooks/authQueries";
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
import {
describeGithubConnectError,
invalidateGithubQueries,
useGithubUserConnect,
} from "@features/integrations/hooks/useGithubUserConnect";
import { useOnboardingStore } from "@features/onboarding/stores/onboardingStore";
Expand All @@ -22,18 +24,21 @@ import {
GitBranch,
} from "@phosphor-icons/react";
import {
AlertDialog,
Box,
Button,
DropdownMenu,
Flex,
Skeleton,
Spinner,
Text,
} from "@radix-ui/themes";
import builderHog from "@renderer/assets/images/hedgehogs/builder-hog-03.png";
import { trpcClient } from "@renderer/trpc/client";
import { useQueryClient } from "@tanstack/react-query";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { AnimatePresence, motion } from "framer-motion";
import { useMemo, useState } from "react";
import { toast } from "sonner";
import type { DetectedRepo } from "../hooks/useOnboardingFlow";
import { useProjectsWithIntegrations } from "../hooks/useProjectsWithIntegrations";
import { OnboardingHogTip } from "./OnboardingHogTip";
Expand Down Expand Up @@ -141,6 +146,28 @@ export function GitIntegrationStep({
);
}, [detectedRepo, repositories]);

const apiClient = useOptionalAuthenticatedClient();
const [disconnectConfirmOpen, setDisconnectConfirmOpen] = useState(false);
const disconnectMutation = useMutation({
mutationFn: async () => {
const installationId = githubIntegration?.installation_id;
if (!apiClient || !installationId) {
throw new Error("No GitHub integration to disconnect");
}
await apiClient.disconnectGithubUserIntegration(installationId);
},
onSuccess: () => {
setDisconnectConfirmOpen(false);
invalidateGithubQueries(queryClient, selectedProjectId);
toast.success("GitHub disconnected.");
},
onError: (e) => {
toast.error(
e instanceof Error ? e.message : "Failed to disconnect GitHub.",
);
},
});

const handleContinue = () => {
if (selectedProjectId && selectedProjectId !== currentProjectId) {
selectProjectMutation.mutate(selectedProjectId);
Expand Down Expand Up @@ -369,6 +396,15 @@ export function GitIntegrationStep({
<ArrowsClockwise size={12} />
Refresh
</Button>
<Button
size="1"
variant="ghost"
color="red"
disabled={!githubIntegration?.installation_id}
onClick={() => setDisconnectConfirmOpen(true)}
>
Disconnect
</Button>
</Flex>
</Flex>
) : !isLoading && !githubUserIntegrationsLoading ? (
Expand Down Expand Up @@ -529,6 +565,45 @@ export function GitIntegrationStep({
<ArrowRight size={16} weight="bold" />
</Button>
</StepActions>

<AlertDialog.Root
open={disconnectConfirmOpen}
onOpenChange={(next) => {
if (!next && !disconnectMutation.isPending) {
setDisconnectConfirmOpen(false);
}
}}
>
<AlertDialog.Content maxWidth="450px">
<AlertDialog.Title>Disconnect GitHub</AlertDialog.Title>
<AlertDialog.Description className="text-sm">
This removes your personal GitHub authorization from PostHog. You
can reconnect at any time. The GitHub App itself stays installed
in your org — uninstall it on GitHub if you want to remove that
too.
</AlertDialog.Description>
<Flex gap="3" mt="4" justify="end">
<AlertDialog.Cancel>
<Button
variant="soft"
color="gray"
disabled={disconnectMutation.isPending}
>
Cancel
</Button>
</AlertDialog.Cancel>
<Button
variant="solid"
color="red"
onClick={() => disconnectMutation.mutate()}
disabled={disconnectMutation.isPending}
>
{disconnectMutation.isPending ? <Spinner size="1" /> : null}
Disconnect
</Button>
</Flex>
</AlertDialog.Content>
</AlertDialog.Root>
</Flex>
</Flex>
);
Expand Down
Loading