Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 8037dc7

Browse files
authored
feat(code): cleaner GitHub integration onboarding step (#1990)
## Problem When a user is on the onboarding Git integration step and GitHub is already connected to another project, the UI only surfaced a single alternative project via a callout banner. This didn't handle the case where multiple other projects already have GitHub connected, and the callout placement made the flow feel disconnected from the primary action area. ## Changes - Replaced the standalone callout banner with inline messaging directly within the GitHub connection section - When one other project has GitHub connected, the project name and org are shown inline with options to either connect GitHub on the current project or switch to the already-connected project - When multiple other projects have GitHub connected, a dropdown menu is rendered inline, allowing the user to browse and select among all connected alternatives before deciding to switch or connect fresh - The "Switch to" action is now a ghost button and "Connect GitHub" is promoted to a solid button, clarifying the preferred action - Removed the `Callout` import in favor of `DropdownMenu` to support the multi-project selection UI
1 parent fdaaf43 commit 8037dc7

1 file changed

Lines changed: 116 additions & 51 deletions

File tree

‎apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx‎

Lines changed: 116 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ import {
1818
GearSix,
1919
GitBranch,
2020
} from "@phosphor-icons/react";
21-
import { Box, Button, Callout, Flex, Skeleton, Text } from "@radix-ui/themes";
21+
import {
22+
Box,
23+
Button,
24+
DropdownMenu,
25+
Flex,
26+
Skeleton,
27+
Text,
28+
} from "@radix-ui/themes";
2229
import builderHog from "@renderer/assets/images/hedgehogs/builder-hog-03.png";
2330
import { trpcClient } from "@renderer/trpc/client";
2431
import { IS_DEV } from "@shared/constants/environment";
@@ -95,12 +102,25 @@ export function GitIntegrationStep({
95102
const { repositories, isLoadingRepos } = useUserRepositoryIntegration();
96103
const githubIntegration = githubUserIntegrations[0] ?? null;
97104

98-
const alternativeConnectedProject = useMemo(() => {
99-
if (hasGitIntegration) return null;
100-
if (!projectsWithGithub.length) return null;
101-
return projectsWithGithub.find((p) => p.id !== selectedProjectId) ?? null;
105+
const alternativeConnectedProjects = useMemo(() => {
106+
if (hasGitIntegration) return [];
107+
if (!projectsWithGithub.length) return [];
108+
return projectsWithGithub.filter((p) => p.id !== selectedProjectId);
102109
}, [hasGitIntegration, projectsWithGithub, selectedProjectId]);
103110

111+
const [selectedAlternativeId, setSelectedAlternativeId] = useState<
112+
number | null
113+
>(null);
114+
115+
const selectedAlternative = useMemo(() => {
116+
if (!alternativeConnectedProjects.length) return null;
117+
return (
118+
alternativeConnectedProjects.find(
119+
(p) => p.id === selectedAlternativeId,
120+
) ?? alternativeConnectedProjects[0]
121+
);
122+
}, [alternativeConnectedProjects, selectedAlternativeId]);
123+
104124
const repoSummary = useMemo(() => {
105125
if (repositories.length === 0) return null;
106126
const names = repositories.map((r) => r.split("/").pop() ?? r);
@@ -364,33 +384,6 @@ export function GitIntegrationStep({
364384
</Box>
365385
</motion.div>
366386

367-
{alternativeConnectedProject && selectedProject && (
368-
<Callout.Root color="blue" variant="soft">
369-
<Callout.Text>
370-
GitHub is already connected on{" "}
371-
<Text className="font-bold">
372-
{alternativeConnectedProject.name}
373-
</Text>{" "}
374-
({alternativeConnectedProject.organization.name}). Switch to
375-
that project, or click{" "}
376-
<Text className="font-bold">Connect GitHub</Text> below to
377-
install a new integration on{" "}
378-
<Text className="font-bold">{selectedProject.name}</Text>.
379-
</Callout.Text>
380-
<Flex mt="2">
381-
<Button
382-
size="1"
383-
variant="soft"
384-
onClick={() =>
385-
setSelectedProjectId(alternativeConnectedProject.id)
386-
}
387-
>
388-
Switch to {alternativeConnectedProject.name}
389-
</Button>
390-
</Flex>
391-
</Callout.Root>
392-
)}
393-
394387
{/* GitHub integration */}
395388
<motion.div
396389
initial={{ opacity: 0, y: 8 }}
@@ -476,25 +469,97 @@ export function GitIntegrationStep({
476469
</Flex>
477470
</Flex>
478471
) : !isLoading && !githubUserIntegrationsLoading ? (
479-
<Flex direction="column" gap="3">
480-
<Text className="text-(--gray-11) text-sm">
481-
{timedOut
482-
? "We didn't hear back from GitHub. If the browser tab was closed, click Connect again."
483-
: isConnecting
484-
? "Waiting for GitHub... You'll return here automatically once the install completes."
485-
: "Optional. Unlocks cloud agents and pull request workflows."}
486-
</Text>
487-
<Button
488-
size="1"
489-
variant="soft"
490-
onClick={() => void handleConnectGitHub()}
491-
loading={isConnecting}
492-
className="self-start"
493-
>
494-
{isConnecting ? "Retry connection" : "Connect GitHub"}
495-
<ArrowSquareOut size={12} />
496-
</Button>
497-
</Flex>
472+
selectedAlternative &&
473+
selectedProject &&
474+
!isConnecting &&
475+
!timedOut ? (
476+
<Flex direction="column" gap="3">
477+
<Text className="text-(--gray-11) text-sm">
478+
GitHub is already connected on{" "}
479+
{alternativeConnectedProjects.length > 1 ? (
480+
<DropdownMenu.Root>
481+
<DropdownMenu.Trigger>
482+
<button
483+
type="button"
484+
className="cursor-pointer border-0 bg-transparent p-0 font-bold text-(--gray-12) underline"
485+
>
486+
{selectedAlternative.name} +{" "}
487+
{alternativeConnectedProjects.length - 1}{" "}
488+
more
489+
</button>
490+
</DropdownMenu.Trigger>
491+
<DropdownMenu.Content size="1" align="start">
492+
{alternativeConnectedProjects.map((p) => (
493+
<DropdownMenu.Item
494+
key={p.id}
495+
onSelect={() =>
496+
setSelectedAlternativeId(p.id)
497+
}
498+
>
499+
<Text className="text-[13px]">
500+
{p.name}
501+
</Text>
502+
<Text className="ml-2 text-(--gray-10) text-[13px]">
503+
{p.organization.name}
504+
</Text>
505+
</DropdownMenu.Item>
506+
))}
507+
</DropdownMenu.Content>
508+
</DropdownMenu.Root>
509+
) : (
510+
<>
511+
<Text className="font-bold">
512+
{selectedAlternative.name}
513+
</Text>{" "}
514+
({selectedAlternative.organization.name})
515+
</>
516+
)}
517+
.
518+
</Text>
519+
<Flex direction="column" gap="2" align="start">
520+
<Button
521+
size="1"
522+
variant="solid"
523+
onClick={() => void handleConnectGitHub()}
524+
>
525+
Connect GitHub on {selectedProject.name}
526+
<ArrowSquareOut size={12} />
527+
</Button>
528+
<Button
529+
size="1"
530+
variant="ghost"
531+
color="gray"
532+
onClick={() =>
533+
setSelectedProjectId(selectedAlternative.id)
534+
}
535+
>
536+
Switch to {selectedAlternative.name}
537+
</Button>
538+
</Flex>
539+
</Flex>
540+
) : (
541+
<Flex direction="column" gap="3">
542+
<Text className="text-(--gray-11) text-sm">
543+
{timedOut
544+
? "We didn't hear back from GitHub. If the browser tab was closed, click Connect again."
545+
: isConnecting
546+
? "Waiting for GitHub..."
547+
: "Optional. Unlocks cloud agents and pull request workflows."}
548+
</Text>
549+
<Button
550+
size="1"
551+
variant="soft"
552+
onClick={() => void handleConnectGitHub()}
553+
loading={isConnecting}
554+
className="self-start"
555+
>
556+
{isConnecting
557+
? "Retry connection"
558+
: "Connect GitHub"}
559+
<ArrowSquareOut size={12} />
560+
</Button>
561+
</Flex>
562+
)
498563
) : null}
499564
</Flex>
500565
</Box>

0 commit comments

Comments
 (0)