Fork of atp.tools as a universal profile for people on the ATmosphere
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}