Retro Bulletin Board Systems on atproto. Web app and TUI. lazy mirror of alyraffauf/atbbs atbbs.xyz
forums python tui atproto bbs
3
fork

Configure Feed

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

web: disable buttons so we don't double post

+33 -22
+1 -1
web/src/components/form/ComposeForm.tsx
··· 113 113 </> 114 114 )} 115 115 </Button> 116 - {!attachmentsAtLimit && ( 116 + {!attachmentsAtLimit && !posting && ( 117 117 <label className="text-neutral-200 cursor-pointer bg-neutral-800 hover:bg-neutral-700 px-4 py-2 rounded inline-block"> 118 118 <span className="inline-flex items-center gap-1.5"> 119 119 <Paperclip size={14} /> attach
+26 -17
web/src/pages/BBS.tsx
··· 35 35 const [pendingNews, setPendingNews] = useState<NewsPost[]>([]); 36 36 const [deletedTids, setDeletedTids] = useState<Set<string>>(new Set()); 37 37 const [showAllNews, setShowAllNews] = useState(false); 38 + const [postingNews, setPostingNews] = useState(false); 38 39 39 40 useBreadcrumb( 40 41 [{ label: bbs.site.name, to: `/bbs/${handle}` }], ··· 46 47 47 48 async function postNews(e: SyntheticEvent) { 48 49 e.preventDefault(); 49 - if (!agent) return; 50 - const title = newsTitle.trim(); 51 - const body = newsBody.trim(); 52 - const siteUri = makeAtUri(bbs.identity.did, SITE, "self"); 53 - const attachments = await uploadAttachments(agent, newsFiles); 54 - const resp = await createPost(agent, siteUri, body, { 55 - title, 56 - attachments, 57 - }); 58 - const rkey = parseAtUri(resp.data.uri).rkey; 59 - setPendingNews((prev) => [ 60 - { uri: resp.data.uri, rkey, title, body, createdAt: nowIso() }, 61 - ...prev, 62 - ]); 63 - setNewsTitle(""); 64 - setNewsBody(""); 65 - setNewsFiles([]); 50 + if (!agent || postingNews) return; 51 + setPostingNews(true); 52 + try { 53 + const title = newsTitle.trim(); 54 + const body = newsBody.trim(); 55 + const siteUri = makeAtUri(bbs.identity.did, SITE, "self"); 56 + const attachments = await uploadAttachments(agent, newsFiles); 57 + const resp = await createPost(agent, siteUri, body, { 58 + title, 59 + attachments, 60 + }); 61 + const rkey = parseAtUri(resp.data.uri).rkey; 62 + setPendingNews((prev) => [ 63 + { uri: resp.data.uri, rkey, title, body, createdAt: nowIso() }, 64 + ...prev, 65 + ]); 66 + setNewsTitle(""); 67 + setNewsBody(""); 68 + setNewsFiles([]); 69 + } catch (error: unknown) { 70 + alert(`Could not post: ${error instanceof Error ? error.message : error}`); 71 + } finally { 72 + setPostingNews(false); 73 + } 66 74 } 67 75 68 76 async function removeNews(rkey: string) { ··· 153 161 files={newsFiles} 154 162 onFilesChange={setNewsFiles} 155 163 submitLabel="post" 164 + posting={postingNews} 156 165 /> 157 166 </details> 158 167 )}
+6 -4
web/src/pages/Board.tsx
··· 51 51 const [title, setTitle] = useState(""); 52 52 const [body, setBody] = useState(""); 53 53 const [files, setFiles] = useState<File[]>([]); 54 + const [posting, setPosting] = useState(false); 54 55 55 56 usePageTitle(`${board.name} — ${bbs.site.name}`); 56 57 useBreadcrumb( ··· 75 76 76 77 async function onCreate(e: SyntheticEvent) { 77 78 e.preventDefault(); 78 - if (!agent || !user) { 79 - alert("Not signed in."); 80 - return; 81 - } 79 + if (!agent || !user || posting) return; 80 + setPosting(true); 82 81 try { 83 82 const boardUri = makeAtUri(bbs.identity.did, BOARD, board.slug); 84 83 const attachments = await uploadAttachments(agent, files); ··· 95 94 } catch (err: unknown) { 96 95 console.error("createPost failed:", err); 97 96 alert(`Could not post: ${err instanceof Error ? err.message : err}`); 97 + } finally { 98 + setPosting(false); 98 99 } 99 100 } 100 101 ··· 122 123 bodyMaxLength={limits.POST_BODY} 123 124 files={files} 124 125 onFilesChange={setFiles} 126 + posting={posting} 125 127 /> 126 128 </details> 127 129 )}