Fork of atp.tools as a universal profile for people on the ATmosphere
at main 135 lines 3.9 kB view raw
1import { QtContext, resolveBskyUser } from "@/providers/qtprovider"; 2import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; 3import { Trash2 } from "lucide-react"; 4import { useContext, useState, useEffect } from "preact/hooks"; 5import { Button } from "@/components/ui/button"; 6import { 7 DialogFooter, 8 DialogHeader, 9 Dialog, 10 DialogContent, 11 DialogTitle, 12 DialogDescription, 13} from "../ui/dialog"; 14export function AccountsManagementModal({ 15 isOpen, 16 onClose, 17}: { 18 isOpen: boolean; 19 onClose: () => void; 20}) { 21 const qt = useContext(QtContext); 22 const [userList, setUserList] = useState< 23 Array<{ 24 name: string; 25 did: string; 26 avatar: string; 27 }> 28 >([]); 29 const [isDeleting, setIsDeleting] = useState(false); 30 31 // Handle user list fetching 32 useEffect(() => { 33 async function fetchUsers() { 34 if (!qt?.accounts.length) return; 35 36 setUserList([]); // Reset list before fetching 37 38 for (const did of qt.accounts) { 39 try { 40 const { data } = await resolveBskyUser(did, qt); 41 if (data && data.displayName) { 42 setUserList((prev) => [ 43 ...prev, 44 { 45 name: data.displayName || "", 46 did: did, 47 avatar: data.avatar || "", 48 }, 49 ]); 50 } 51 } catch (err) { 52 console.error(`Failed to fetch user data for ${did}:`, err); 53 } 54 } 55 } 56 57 if (isOpen) { 58 fetchUsers(); 59 } 60 }, [isOpen, qt?.accounts]); 61 62 const handleRemoveAccount = async (did: string) => { 63 if (!qt?.client) return; 64 setIsDeleting(true); 65 try { 66 await qt.client.logout(did as `did:${string}:${string}`); 67 } catch (error) { 68 console.error("Failed to remove account:", error); 69 } finally { 70 setIsDeleting(false); 71 } 72 }; 73 74 return ( 75 <Dialog 76 open={isOpen} 77 onOpenChange={(open) => { 78 if (!isDeleting && !open) { 79 onClose(); 80 } 81 }} 82 > 83 <DialogContent 84 onInteractOutside={(e) => { 85 if (isDeleting) { 86 e.preventDefault(); 87 } 88 }} 89 > 90 <DialogHeader> 91 <DialogTitle>Manage Accounts</DialogTitle> 92 <DialogDescription> 93 View and manage your connected accounts 94 </DialogDescription> 95 </DialogHeader> 96 <div className="flex flex-col gap-4 py-4"> 97 {userList.map((user) => ( 98 <div 99 key={user.did} 100 className="flex items-center justify-between p-2 rounded-lg hover:bg-accent" 101 > 102 <div className="flex items-center gap-3"> 103 <Avatar className="h-10 w-10 rounded-lg"> 104 <AvatarImage src={user.avatar} alt={user.name} /> 105 <AvatarFallback className="rounded-lg"> 106 {user.name.slice(0, 2)} 107 </AvatarFallback> 108 </Avatar> 109 <div className="grid flex-1 text-left text-sm leading-tight"> 110 <span className="font-semibold">{user.name}</span> 111 <span className="text-muted-foreground">{user.did}</span> 112 </div> 113 </div> 114 <Button 115 variant="ghost" 116 size="icon" 117 className="text-destructive hover:text-destructive hover:bg-destructive/10" 118 disabled={isDeleting} 119 onClick={() => handleRemoveAccount(user.did)} 120 aria-label={`Remove account ${user.name}`} 121 > 122 <Trash2 className="h-4 w-4" /> 123 </Button> 124 </div> 125 ))} 126 </div> 127 <DialogFooter> 128 <Button variant="outline" disabled={isDeleting} onClick={onClose}> 129 Close 130 </Button> 131 </DialogFooter> 132 </DialogContent> 133 </Dialog> 134 ); 135}