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