this repo has no description
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: board deletes

TurtlePaw 7a195099 5a52fc8b

+141 -1
+129
src/components/DeleteButton.tsx
··· 1 + import { 2 + Dialog, 3 + DialogClose, 4 + DialogContent, 5 + DialogDescription, 6 + DialogFooter, 7 + DialogHeader, 8 + DialogTitle, 9 + DialogTrigger, 10 + } from "@/components/ui/dialog"; 11 + import { useAuth } from "@/lib/hooks/useAuth"; 12 + import { useState } from "react"; 13 + import { Button } from "./ui/button"; 14 + import { PostView } from "@atproto/api/dist/client/types/app/bsky/feed/defs"; 15 + import { DeleteIcon, EditIcon, LoaderCircle, TrashIcon } from "lucide-react"; 16 + import { Board, useBoardsStore } from "@/lib/stores/boards"; 17 + import { BoardsPicker } from "./BoardPicker"; 18 + import { toast } from "sonner"; 19 + import { AtUri } from "@atproto/api"; 20 + import { LIST_COLLECTION, LIST_ITEM_COLLECTION } from "@/constants"; 21 + import { FeedItem } from "./Feed"; 22 + import { BoardItem, useBoardItemsStore } from "@/lib/stores/boardItems"; 23 + import clsx from "clsx"; 24 + import { Input } from "./ui/input"; 25 + import { Textarea } from "./ui/textarea"; 26 + 27 + export function DeleteButton({ board, rkey }: { board: Board; rkey: string }) { 28 + const { agent } = useAuth(); 29 + const [isLoading, setLoading] = useState(false); 30 + const [isOpen, setOpen] = useState(false); 31 + const [name, setName] = useState(board.name); 32 + const [description, setDescription] = useState(board.description); 33 + const { setBoard, removeBoard } = useBoardsStore(); 34 + const { boardItems } = useBoardItemsStore(); 35 + 36 + if (agent == null) return <div>not logged in :(</div>; 37 + return ( 38 + <Dialog open={isOpen} onOpenChange={setOpen}> 39 + <DialogTrigger asChild> 40 + <span 41 + onClick={(e) => { 42 + e.stopPropagation(); 43 + }} 44 + className={clsx("cursor-pointer")} 45 + > 46 + <Button 47 + className={clsx( 48 + "cursor-pointer", 49 + "text-red-400 hover:text-red-400" 50 + )} 51 + variant={"ghost"} 52 + > 53 + <TrashIcon /> Delete Board 54 + </Button> 55 + </span> 56 + </DialogTrigger> 57 + 58 + <DialogContent> 59 + <DialogHeader> 60 + <DialogTitle>Delete board?</DialogTitle> 61 + <DialogDescription className="pt-5"> 62 + Are you sure you want to delete the board and all items in it? 63 + Looked like it was a pretty good board you had going there. 64 + </DialogDescription> 65 + </DialogHeader> 66 + <DialogFooter> 67 + <DialogClose> 68 + <Button className="cursor-pointer" variant={"secondary"}> 69 + Cancel 70 + </Button> 71 + </DialogClose> 72 + <Button 73 + onClick={async (e) => { 74 + e.stopPropagation(); // Optional, but safe 75 + 76 + setLoading(true); 77 + try { 78 + const listUri = AtUri.make( 79 + agent.assertDid, 80 + LIST_COLLECTION, 81 + rkey 82 + ); 83 + const items = boardItems 84 + .entries() 85 + .filter((e) => AtUri.make(e[1].list).rkey == listUri.rkey); 86 + 87 + for (const item of items) { 88 + const itemDeleteRes = 89 + await agent.com.atproto.repo.deleteRecord({ 90 + repo: agent.assertDid, 91 + collection: LIST_ITEM_COLLECTION, 92 + rkey: item[0], 93 + }); 94 + 95 + if (!itemDeleteRes.success) { 96 + toast(`Failed to delete ${item[0]}`); 97 + } 98 + } 99 + 100 + const listDeleteRes = await agent.com.atproto.repo.deleteRecord( 101 + { 102 + repo: agent.assertDid, 103 + collection: LIST_COLLECTION, 104 + rkey: rkey, 105 + } 106 + ); 107 + 108 + if (listDeleteRes.success) { 109 + removeBoard(rkey); 110 + toast("Board deleted"); 111 + setOpen(false); 112 + } else { 113 + toast("Failed to delete board"); 114 + } 115 + } finally { 116 + setLoading(false); 117 + } 118 + }} 119 + disabled={name.length <= 0} 120 + className="cursor-pointer" 121 + > 122 + {isLoading && <LoaderCircle className="animate-spin ml-2" />} 123 + Confirm 124 + </Button> 125 + </DialogFooter> 126 + </DialogContent> 127 + </Dialog> 128 + ); 129 + }
+3 -1
src/components/EditButton.tsx
··· 22 22 import clsx from "clsx"; 23 23 import { Input } from "./ui/input"; 24 24 import { Textarea } from "./ui/textarea"; 25 + import { DeleteButton } from "./DeleteButton"; 25 26 26 27 export function EditButton({ 27 28 board, ··· 76 77 /> 77 78 </DialogDescription> 78 79 </DialogHeader> 79 - <DialogFooter> 80 + <DialogFooter className="justify-between flex w-full"> 81 + <DeleteButton board={board} rkey={rkey} /> 80 82 <Button 81 83 onClick={async (e) => { 82 84 e.stopPropagation(); // Optional, but safe
+9
src/lib/stores/boards.tsx
··· 13 13 type FeedDefsState = { 14 14 boards: Map<string, Board>; 15 15 setBoard: (rkey: string, board: Board) => void; 16 + removeBoard: (rkey: string) => void; 16 17 isLoading: boolean; 17 18 setLoading: (value: boolean) => void; 18 19 }; ··· 25 26 set((state) => ({ 26 27 boards: new Map(state.boards).set(rkey, board), 27 28 })), 29 + removeBoard: (rkey) => 30 + set((state) => { 31 + const newMap = new Map(state.boards); 32 + newMap.delete(rkey); 33 + return { 34 + boards: newMap, 35 + }; 36 + }), 28 37 isLoading: true, 29 38 setLoading(value) { 30 39 set(() => ({