Scrapboard.org client
1import {
2 Dialog,
3 DialogContent,
4 DialogDescription,
5 DialogFooter,
6 DialogHeader,
7 DialogTitle,
8 DialogTrigger,
9} from "@/components/ui/dialog";
10import { useAuth } from "@/lib/hooks/useAuth";
11import { useState } from "react";
12import { Button } from "./ui/button";
13import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs";
14import { EditIcon, LoaderCircle } from "lucide-react";
15import { Board, useBoardsStore } from "@/lib/stores/boards";
16import { BoardsPicker } from "./BoardPicker";
17import { toast } from "sonner";
18import { AtUri } from "@atproto/api";
19import { LIST_COLLECTION, LIST_ITEM_COLLECTION } from "@/constants";
20import { FeedItem } from "./Feed";
21import { BoardItem, useBoardItemsStore } from "@/lib/stores/boardItems";
22import clsx from "clsx";
23import { Input } from "./ui/input";
24import { Textarea } from "./ui/textarea";
25import { DeleteButton } from "./DeleteButton";
26
27export function EditButton({
28 board,
29 rkey,
30 className,
31}: {
32 board: Board;
33 rkey: string;
34 className?: string;
35}) {
36 const { agent } = useAuth();
37 const [isLoading, setLoading] = useState(false);
38 const [isOpen, setOpen] = useState(false);
39 const [name, setName] = useState(board.name);
40 const [description, setDescription] = useState(board.description);
41 const { setBoard } = useBoardsStore();
42
43 if (agent == null) return <div>not logged in :(</div>;
44 return (
45 <Dialog open={isOpen} onOpenChange={setOpen}>
46 <DialogTrigger asChild>
47 <span
48 onClick={(e) => {
49 e.stopPropagation();
50 }}
51 className={clsx("cursor-pointer", className)}
52 >
53 <Button
54 size="sm"
55 className={clsx("cursor-pointer")}
56 variant={"ghost"}
57 >
58 <EditIcon />
59 </Button>
60 </span>
61 </DialogTrigger>
62
63 <DialogContent>
64 <DialogHeader>
65 <DialogTitle>Update board</DialogTitle>
66 <DialogDescription className="pt-5">
67 <Input
68 onChange={(e) => setName(e.target.value)}
69 value={name}
70 className="dark:text-white text-black"
71 />
72 <Textarea
73 className="mt-2 dark:text-white text-black"
74 onChange={(e) => setDescription(e.target.value)}
75 value={description}
76 placeholder="Enter a description of your board..."
77 />
78 </DialogDescription>
79 </DialogHeader>
80 <DialogFooter className="justify-between flex w-full">
81 <DeleteButton board={board} rkey={rkey} />
82 <Button
83 onClick={async (e) => {
84 e.stopPropagation(); // Optional, but safe
85
86 setLoading(true);
87 try {
88 const record: Board = {
89 name,
90 description,
91 };
92
93 const result = await agent.com.atproto.repo.applyWrites({
94 repo: agent.assertDid,
95 writes: [
96 {
97 $type: "com.atproto.repo.applyWrites#update",
98 collection: LIST_COLLECTION,
99 value: record,
100 rkey: rkey,
101 },
102 ],
103 });
104
105 const newRecord = await agent.com.atproto.repo.getRecord({
106 repo: agent.assertDid,
107 collection: LIST_COLLECTION,
108 rkey: rkey,
109 });
110
111 const newRecordData = Board.safeParse(newRecord.data.value);
112
113 if (
114 result?.success &&
115 newRecord.success &&
116 newRecordData.success
117 ) {
118 setBoard(agent.assertDid, rkey, newRecordData.data);
119 toast("Board updated");
120 setOpen(false);
121 } else {
122 toast("Failed to update board");
123 }
124 } finally {
125 setLoading(false);
126 }
127 }}
128 disabled={name.length <= 0}
129 className="cursor-pointer"
130 >
131 {isLoading && <LoaderCircle className="animate-spin ml-2" />}
132 Update
133 </Button>
134 </DialogFooter>
135 </DialogContent>
136 </Dialog>
137 );
138}