mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-02 22:43:27 +00:00
fix: Improve account selector (#626)
* fix: Improve account selector * cr * cr * cr
This commit is contained in:
parent
9906dd4e2a
commit
bedd2c9b68
4 changed files with 198 additions and 47 deletions
|
|
@ -16,6 +16,7 @@ import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
import { useThreadsStatus } from "@/hooks/useThreadsStatus";
|
import { useThreadsStatus } from "@/hooks/useThreadsStatus";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { threadsToMetadata } from "@/lib/thread-utils";
|
import { threadsToMetadata } from "@/lib/thread-utils";
|
||||||
|
import { UserPopover } from "@/components/user-popover";
|
||||||
import { OpenSWELogoSVG } from "@/components/icons/openswe";
|
import { OpenSWELogoSVG } from "@/components/icons/openswe";
|
||||||
|
|
||||||
type FilterStatus =
|
type FilterStatus =
|
||||||
|
|
@ -122,8 +123,8 @@ function AllThreadsPageContent() {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<InstallationSelector />
|
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
<UserPopover />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
|
||||||
<TooltipIconButton
|
|
||||||
tooltip="Logout"
|
|
||||||
onClick={handleLogout}
|
|
||||||
disabled={isLoading}
|
|
||||||
className="text-xs"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<LogOut className="size-4" />
|
|
||||||
</TooltipIconButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
194
apps/web/src/components/user-popover.tsx
Normal file
194
apps/web/src/components/user-popover.tsx
Normal file
|
|
@ -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" ? (
|
||||||
|
<Building2 className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<User className="h-4 w-4" />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading || !currentInstallation) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
disabled
|
||||||
|
className={cn("h-8 w-8 rounded-full p-0", className)}
|
||||||
|
>
|
||||||
|
<div className="bg-muted h-6 w-6 animate-pulse rounded-full" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
disabled
|
||||||
|
className={cn("h-8 w-8 rounded-full p-0", className)}
|
||||||
|
>
|
||||||
|
<div className="bg-destructive/20 flex h-6 w-6 items-center justify-center rounded-full">
|
||||||
|
<GitHubSVG className="text-destructive h-3 w-3" />
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className={cn("hover:bg-accent h-8 w-8 rounded-full p-0", className)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={currentInstallation.avatarUrl}
|
||||||
|
alt={`${currentInstallation.accountName} avatar`}
|
||||||
|
className="h-6 w-6 rounded-full"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
className="w-80 p-0"
|
||||||
|
align="end"
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="mb-4 flex items-center gap-3">
|
||||||
|
<img
|
||||||
|
src={currentInstallation.avatarUrl}
|
||||||
|
alt={`${currentInstallation.accountName} avatar`}
|
||||||
|
className="h-10 w-10 rounded-full"
|
||||||
|
/>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="truncate font-medium">
|
||||||
|
{currentInstallation.accountName}
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground flex items-center gap-1 text-sm">
|
||||||
|
{getAccountIcon(currentInstallation.accountType)}
|
||||||
|
<span className="capitalize">
|
||||||
|
{currentInstallation.accountType.toLowerCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{installations.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 space-y-2">
|
||||||
|
<label className="text-sm font-medium">Switch Account</label>
|
||||||
|
<Select
|
||||||
|
value={currentInstallation.id.toString()}
|
||||||
|
onValueChange={handleValueChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full">
|
||||||
|
<div className="flex flex-1 items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={currentInstallation.avatarUrl}
|
||||||
|
alt={`${currentInstallation.accountName} avatar`}
|
||||||
|
className="h-4 w-4 rounded-full"
|
||||||
|
/>
|
||||||
|
<span className="truncate">
|
||||||
|
{currentInstallation.accountName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{installations.map((installation) => (
|
||||||
|
<SelectItem
|
||||||
|
key={installation.id}
|
||||||
|
value={installation.id.toString()}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={installation.avatarUrl}
|
||||||
|
alt={`${installation.accountName} avatar`}
|
||||||
|
className="h-4 w-4 rounded-full"
|
||||||
|
/>
|
||||||
|
<span>{installation.accountName}</span>
|
||||||
|
{getAccountIcon(installation.accountType)}
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Separator className="mb-4" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="w-full justify-start text-red-600 hover:bg-red-50 hover:text-red-700 dark:text-red-400 dark:hover:bg-red-950/50 dark:hover:text-red-300"
|
||||||
|
onClick={handleLogout}
|
||||||
|
disabled={isLoggingOut}
|
||||||
|
>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
{isLoggingOut ? "Signing out..." : "Sign out"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -19,10 +19,9 @@ import { GitHubInstallationBanner } from "../github/installation-banner";
|
||||||
import { ApiKeyBanner } from "../api-key-banner";
|
import { ApiKeyBanner } from "../api-key-banner";
|
||||||
import { QuickActions } from "./quick-actions";
|
import { QuickActions } from "./quick-actions";
|
||||||
import { DraftsSection } from "./drafts-section";
|
import { DraftsSection } from "./drafts-section";
|
||||||
import { GitHubLogoutButton } from "../github/github-oauth-button";
|
|
||||||
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
import { MANAGER_GRAPH_ID } from "@open-swe/shared/constants";
|
||||||
import { TooltipIconButton } from "../ui/tooltip-icon-button";
|
import { TooltipIconButton } from "../ui/tooltip-icon-button";
|
||||||
import { InstallationSelector } from "../github/installation-selector";
|
import { UserPopover } from "../user-popover";
|
||||||
|
|
||||||
import { useThreadsStatus } from "@/hooks/useThreadsStatus";
|
import { useThreadsStatus } from "@/hooks/useThreadsStatus";
|
||||||
import { Thread } from "@langchain/langgraph-sdk";
|
import { Thread } from "@langchain/langgraph-sdk";
|
||||||
|
|
@ -126,7 +125,6 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<InstallationSelector />
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-muted-foreground text-xs">ready</span>
|
<span className="text-muted-foreground text-xs">ready</span>
|
||||||
<div className="h-1 w-1 rounded-full bg-green-500 dark:bg-green-600"></div>
|
<div className="h-1 w-1 rounded-full bg-green-500 dark:bg-green-600"></div>
|
||||||
|
|
@ -134,7 +132,7 @@ export function DefaultView({ threads, threadsLoading }: DefaultViewProps) {
|
||||||
<OpenDocumentationButton />
|
<OpenDocumentationButton />
|
||||||
<OpenSettingsButton />
|
<OpenSettingsButton />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<GitHubLogoutButton />
|
<UserPopover />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue