Fork of atp.tools as a universal profile for people on the ATmosphere
at main 136 lines 4.3 kB view raw
1import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 2import { 3 DropdownMenu, 4 DropdownMenuContent, 5 DropdownMenuGroup, 6 DropdownMenuItem, 7 DropdownMenuSeparator, 8 DropdownMenuTrigger, 9} from "@/components/ui/dropdown-menu"; 10import { useSidebar } from "@/components/ui/sidebar"; 11import { QtContext, resolveBskyUser } from "@/providers/qtprovider"; 12import { ChevronRight, Trash2, User, User2, Users } from "lucide-react"; 13import { useContext, useEffect, useState } from "preact/hooks"; 14import { Button } from "../ui/button"; 15import { Link } from "@tanstack/react-router"; 16 17export function UserSwitcher() { 18 const { isMobile } = useSidebar(); 19 let qt = useContext(QtContext); 20 if (!qt) return null; 21 22 const [userList, setUserList] = useState< 23 { name: string; did: string; avatar: string }[] 24 >([]); 25 26 const [isDeleting, setIsDeleting] = useState(false); 27 28 const handleRemoveAccount = async (did: string) => { 29 if (!qt?.client) return; 30 setIsDeleting(true); 31 try { 32 await qt.client.logout(did as `did:${string}:${string}`); 33 } catch (error) { 34 console.error("Failed to remove account:", error); 35 } finally { 36 setIsDeleting(false); 37 } 38 }; 39 40 const refreshUserList = async () => { 41 setUserList([]); 42 if (!qt?.client?.currentAgent?.sub) return; 43 44 try { 45 qt.accounts.forEach(async (did) => { 46 let { data } = await resolveBskyUser(did, qt); 47 if (data && data.displayName) 48 setUserList((ulist) => [ 49 ...(ulist || []), 50 { 51 name: data.displayName || "", 52 did: did, 53 avatar: data.avatar || "", 54 }, 55 ]); 56 }); 57 } catch (err) { 58 console.error("Failed to fetch user data:", err); 59 } 60 }; 61 62 useEffect(() => { 63 refreshUserList(); 64 }, [qt]); 65 66 return ( 67 <DropdownMenu> 68 <DropdownMenuTrigger asChild> 69 <div className="flex justify-between items-center w-full"> 70 <div className="flex items-center gap-2"> 71 <User /> 72 Switch Accounts 73 </div> 74 <ChevronRight /> 75 </div> 76 </DropdownMenuTrigger> 77 <DropdownMenuContent 78 className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg" 79 side={isMobile ? "bottom" : "right"} 80 align="end" 81 sideOffset={4} 82 > 83 <DropdownMenuGroup> 84 {userList.map((user) => ( 85 <DropdownMenuItem 86 key={user.did} 87 onClick={() => { 88 qt.client.switchAccount(user.did as `did:${string}:${string}`); 89 }} 90 > 91 <Avatar className="h-8 w-8 rounded-lg"> 92 <AvatarImage src={user.avatar} alt={user.name} /> 93 <AvatarFallback className="rounded-lg">CN</AvatarFallback> 94 </Avatar> 95 <div className="grid flex-1 text-left text-sm leading-tight"> 96 <span className="truncate font-semibold">{user.name}</span> 97 <span className="truncate text-muted-foreground"> 98 {user.did} 99 </span> 100 </div> 101 <Button 102 variant="ghost" 103 size="icon" 104 className="text-destructive hover:text-destructive hover:bg-destructive/10" 105 disabled={isDeleting} 106 onClick={() => handleRemoveAccount(user.did)} 107 aria-label={`Remove account ${user.name}`} 108 > 109 <Trash2 className="h-4 w-4" /> 110 </Button> 111 </DropdownMenuItem> 112 ))} 113 </DropdownMenuGroup> 114 <DropdownMenuSeparator /> 115 <DropdownMenuGroup> 116 <DropdownMenuItem 117 onClick={(event) => { 118 event.preventDefault(); 119 qt.openManagementModal(); 120 console.log("Opening mgmt modal"); 121 }} 122 > 123 <User2 /> 124 Manage Accounts 125 </DropdownMenuItem> 126 <Link to="/auth/login" className="flex items-center gap-2"> 127 <DropdownMenuItem> 128 <Users /> 129 Log in to another account 130 </DropdownMenuItem> 131 </Link> 132 </DropdownMenuGroup> 133 </DropdownMenuContent> 134 </DropdownMenu> 135 ); 136}