Scrapboard.org client
1import {
2 Dialog,
3 DialogClose,
4 DialogContent,
5 DialogDescription,
6 DialogFooter,
7 DialogHeader,
8 DialogTitle,
9 DialogTrigger,
10} from "@/components/ui/dialog";
11import { useAuth } from "@/lib/hooks/useAuth";
12import { useState } from "react";
13import { Button } from "./ui/button";
14import { LoaderCircle, TrashIcon } from "lucide-react";
15import { Board, useBoardsStore } from "@/lib/stores/boards";
16import { toast } from "sonner";
17import { AtUri } from "@atproto/api";
18import { LIST_COLLECTION, LIST_ITEM_COLLECTION } from "@/constants";
19import { useBoardItemsStore } from "@/lib/stores/boardItems";
20import clsx from "clsx";
21import { Progress } from "./ui/progress";
22import { redirect } from "next/navigation";
23
24export function DeleteButton({ board, rkey }: { board: Board; rkey: string }) {
25 const { agent } = useAuth();
26 const [isLoading, setLoading] = useState(false);
27 const [isOpen, setOpen] = useState(false);
28 const [name, setName] = useState(board.name);
29 const [description, setDescription] = useState(board.description);
30 const { setBoard, removeBoard } = useBoardsStore();
31 const { boardItems } = useBoardItemsStore();
32 const [progress, setProgress] = useState(0);
33 const [totalItems, setTotalItems] = useState(0);
34
35 if (agent == null) return <div>not logged in :(</div>;
36 return (
37 <Dialog open={isOpen} onOpenChange={setOpen}>
38 <DialogTrigger asChild>
39 <span
40 onClick={(e) => {
41 e.stopPropagation();
42 }}
43 className={clsx("cursor-pointer")}
44 >
45 <Button
46 className={clsx(
47 "cursor-pointer",
48 "text-red-400 hover:text-red-400"
49 )}
50 variant={"ghost"}
51 >
52 <TrashIcon /> Delete Board
53 </Button>
54 </span>
55 </DialogTrigger>
56
57 <DialogContent>
58 <DialogHeader>
59 <DialogTitle>Delete board?</DialogTitle>
60 <DialogDescription className="pt-5">
61 Are you sure you want to delete the board and all items in it?
62 Looked like it was a pretty good board you had going there.
63 </DialogDescription>
64 </DialogHeader>
65
66 {isLoading && (
67 <div className="space-y-2 my-4">
68 <div className="text-sm text-muted-foreground">
69 Deleting items... ({progress}/{totalItems})
70 </div>
71 <Progress value={(progress / Math.max(totalItems, 1)) * 100} />
72 </div>
73 )}
74
75 <DialogFooter>
76 {!isLoading && (
77 <DialogClose>
78 <Button className="cursor-pointer" variant={"secondary"}>
79 Cancel
80 </Button>
81 </DialogClose>
82 )}
83
84 {!isLoading ? (
85 <Button
86 onClick={async (e) => {
87 e.stopPropagation();
88
89 setLoading(true);
90 try {
91 const listUri = AtUri.make(
92 agent.assertDid,
93 LIST_COLLECTION,
94 rkey
95 );
96 const items = Array.from(
97 boardItems
98 .entries()
99 .filter((e) => AtUri.make(e[1].list).rkey == listUri.rkey)
100 );
101
102 setTotalItems(items.length + 1); // +1 for the board itself
103 setProgress(0);
104
105 for (let i = 0; i < items.length; i++) {
106 const item = items[i];
107 const itemDeleteRes =
108 await agent.com.atproto.repo.deleteRecord({
109 repo: agent.assertDid,
110 collection: LIST_ITEM_COLLECTION,
111 rkey: item[0],
112 });
113
114 if (!itemDeleteRes.success) {
115 toast(`Failed to delete ${item[0]}`);
116 }
117
118 setProgress(i + 1);
119 }
120
121 const listDeleteRes =
122 await agent.com.atproto.repo.deleteRecord({
123 repo: agent.assertDid,
124 collection: LIST_COLLECTION,
125 rkey: rkey,
126 });
127
128 setProgress(totalItems);
129
130 if (listDeleteRes.success) {
131 removeBoard(agent.assertDid, rkey);
132 toast("Board deleted");
133 setOpen(false);
134 redirect("/boards");
135 } else {
136 toast("Failed to delete board");
137 }
138 } finally {
139 setLoading(false);
140 }
141 }}
142 disabled={name.length <= 0}
143 className="cursor-pointer"
144 >
145 Confirm
146 </Button>
147 ) : (
148 <Button disabled className="cursor-not-allowed">
149 <LoaderCircle className="animate-spin mr-2" />
150 Deleting...
151 </Button>
152 )}
153 </DialogFooter>
154 </DialogContent>
155 </Dialog>
156 );
157}