From bedd2c9b68ebd26b3705205be5dbdd7bb6e53b4c Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 31 Jul 2025 12:25:24 -0700 Subject: [PATCH] fix: Improve account selector (#626) * fix: Improve account selector * cr * cr * cr --- apps/web/src/app/(v2)/chat/threads/page.tsx | 3 +- .../components/github/github-oauth-button.tsx | 42 ---- apps/web/src/components/user-popover.tsx | 194 ++++++++++++++++++ apps/web/src/components/v2/default-view.tsx | 6 +- 4 files changed, 198 insertions(+), 47 deletions(-) delete mode 100644 apps/web/src/components/github/github-oauth-button.tsx create mode 100644 apps/web/src/components/user-popover.tsx diff --git a/apps/web/src/app/(v2)/chat/threads/page.tsx b/apps/web/src/app/(v2)/chat/threads/page.tsx index 84566c10..4079194b 100644 --- a/apps/web/src/app/(v2)/chat/threads/page.tsx +++ b/apps/web/src/app/(v2)/chat/threads/page.tsx @@ -16,6 +16,7 @@ import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants"; import { useThreadsStatus } from "@/hooks/useThreadsStatus"; import { cn } from "@/lib/utils"; import { threadsToMetadata } from "@/lib/thread-utils"; +import { UserPopover } from "@/components/user-popover"; import { OpenSWELogoSVG } from "@/components/icons/openswe"; type FilterStatus = @@ -122,8 +123,8 @@ function AllThreadsPageContent() {
- +
diff --git a/apps/web/src/components/github/github-oauth-button.tsx b/apps/web/src/components/github/github-oauth-button.tsx deleted file mode 100644 index fe1a1947..00000000 --- a/apps/web/src/components/github/github-oauth-button.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { LogOut } from "lucide-react"; -import { TooltipIconButton } from "../ui/tooltip-icon-button"; - -const GITHUB_APP_INSTALLED_KEY = "github_app_installed"; - -export function GitHubLogoutButton() { - const [isLoading, setIsLoading] = useState(false); - - const handleLogout = async () => { - setIsLoading(true); - try { - const response = await fetch("/api/auth/logout", { - method: "POST", - }); - if (response.ok) { - localStorage.removeItem(GITHUB_APP_INSTALLED_KEY); - window.location.href = "/"; - } else { - console.error("Logout failed"); - } - } catch (error) { - console.error("Error during logout:", error); - } finally { - setIsLoading(false); - } - }; - - return ( - - - - ); -} diff --git a/apps/web/src/components/user-popover.tsx b/apps/web/src/components/user-popover.tsx new file mode 100644 index 00000000..85f92dd7 --- /dev/null +++ b/apps/web/src/components/user-popover.tsx @@ -0,0 +1,194 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "@/components/ui/select"; +import { Separator } from "@/components/ui/separator"; +import { useGitHubAppProvider } from "@/providers/GitHubApp"; +import { Building2, LogOut, User } from "lucide-react"; +import { GitHubSVG } from "@/components/icons/github"; +import { cn } from "@/lib/utils"; +import { useState } from "react"; + +interface UserPopoverProps { + className?: string; +} + +export function UserPopover({ className }: UserPopoverProps) { + const { + installations, + currentInstallation, + installationsLoading: isLoading, + installationsError: error, + switchInstallation, + } = useGitHubAppProvider(); + + const [isLoggingOut, setIsLoggingOut] = useState(false); + + const GITHUB_APP_INSTALLED_KEY = "github_app_installed"; + + const handleLogout = async () => { + setIsLoggingOut(true); + try { + const response = await fetch("/api/auth/logout", { + method: "POST", + }); + if (response.ok) { + localStorage.removeItem(GITHUB_APP_INSTALLED_KEY); + window.location.href = "/"; + } else { + console.error("Logout failed"); + } + } catch (error) { + console.error("Error during logout:", error); + } finally { + setIsLoggingOut(false); + } + }; + + const handleValueChange = async (value: string) => { + await switchInstallation(value); + }; + + const getAccountIcon = (accountType: "User" | "Organization") => { + return accountType === "Organization" ? ( + + ) : ( + + ); + }; + + if (isLoading || !currentInstallation) { + return ( + + ); + } + + if (error) { + return ( + + ); + } + + return ( + + + + + +
+
+ {`${currentInstallation.accountName} +
+
+ {currentInstallation.accountName} +
+
+ {getAccountIcon(currentInstallation.accountType)} + + {currentInstallation.accountType.toLowerCase()} + +
+
+
+ + {installations.length > 1 && ( + <> +
+ + +
+ + + )} + + +
+
+
+ ); +} diff --git a/apps/web/src/components/v2/default-view.tsx b/apps/web/src/components/v2/default-view.tsx index f05f36a1..9942d14d 100644 --- a/apps/web/src/components/v2/default-view.tsx +++ b/apps/web/src/components/v2/default-view.tsx @@ -19,10 +19,9 @@ import { GitHubInstallationBanner } from "../github/installation-banner"; import { ApiKeyBanner } from "../api-key-banner"; import { QuickActions } from "./quick-actions"; import { DraftsSection } from "./drafts-section"; -import { GitHubLogoutButton } from "../github/github-oauth-button"; import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants"; import { TooltipIconButton } from "../ui/tooltip-icon-button"; -import { InstallationSelector } from "../github/installation-selector"; +import { UserPopover } from "../user-popover"; import { useThreadsStatus } from "@/hooks/useThreadsStatus"; import { Thread } from "@langchain/langgraph-sdk"; @@ -126,7 +125,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) { />
-
ready
@@ -134,7 +132,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) { - +