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.

at master 125 lines 3.9 kB view raw
1import { useState, type SyntheticEvent } from "react"; 2import { useNavigate } from "react-router-dom"; 3import { useAuth } from "../lib/auth"; 4import { putBoard, putSite } from "../lib/writes"; 5import { BOARD } from "../lib/lexicon"; 6import { DEFAULT_BOARD } from "../lib/shared"; 7import { makeAtUri, nowIso } from "../lib/util"; 8import * as limits from "../lib/limits"; 9import { usePageTitle } from "../hooks/usePageTitle"; 10import { bbsUrl } from "../lib/routes"; 11import { Input, Textarea, Button } from "../components/form/Form"; 12import BoardRowEditor, { 13 type BoardRow, 14} from "../components/form/BoardRowEditor"; 15 16export default function SysopCreate() { 17 const { user, agent } = useAuth(); 18 const navigate = useNavigate(); 19 20 const [name, setName] = useState(""); 21 const [description, setDescription] = useState(""); 22 const [intro, setIntro] = useState(""); 23 const [boards, setBoards] = useState<BoardRow[]>([ 24 { 25 slug: DEFAULT_BOARD.slug, 26 name: DEFAULT_BOARD.name, 27 description: DEFAULT_BOARD.description, 28 }, 29 ]); 30 const [error, setError] = useState<string | null>(null); 31 32 usePageTitle("Create community — atbbs"); 33 34 async function onSubmit(e: SyntheticEvent) { 35 e.preventDefault(); 36 if (!agent || !user) return; 37 const cleanBoards = boards 38 .map((board) => ({ 39 slug: board.slug.trim(), 40 name: board.name.trim(), 41 description: board.description.trim(), 42 })) 43 .filter((board) => board.slug); 44 if (!name.trim() || !cleanBoards.length) { 45 setError("Name and at least one board are required."); 46 return; 47 } 48 const now = nowIso(); 49 try { 50 for (const board of cleanBoards) { 51 await putBoard( 52 agent, 53 board.slug, 54 board.name || board.slug, 55 board.description, 56 now, 57 ); 58 } 59 await putSite(agent, { 60 name: name.trim(), 61 description: description.trim(), 62 intro, 63 boards: cleanBoards.map((board) => 64 makeAtUri(user.did, BOARD, board.slug), 65 ), 66 createdAt: now, 67 }); 68 navigate(bbsUrl(user.handle)); 69 } catch { 70 setError("Could not create community."); 71 } 72 } 73 74 return ( 75 <> 76 <h1 className="text-lg text-neutral-200 mb-1">Create a community</h1> 77 <p className="text-neutral-400 mb-6"> 78 Set up your community. Your handle becomes the address. 79 </p> 80 {error && <p className="text-red-500 mb-4">{error}</p>} 81 <form onSubmit={onSubmit} className="space-y-6"> 82 <div> 83 <label className="text-xs text-neutral-400 uppercase tracking-wide"> 84 Community Name 85 </label> 86 <Input 87 name="name" 88 required 89 value={name} 90 onChange={(e) => setName(e.target.value)} 91 placeholder="My Cool Community" 92 maxLength={limits.SITE_NAME} 93 /> 94 </div> 95 <div> 96 <label className="text-xs text-neutral-400 uppercase tracking-wide"> 97 Description 98 </label> 99 <Input 100 name="description" 101 value={description} 102 onChange={(e) => setDescription(e.target.value)} 103 placeholder="A short description of your community" 104 maxLength={limits.SITE_DESCRIPTION} 105 /> 106 </div> 107 <div> 108 <label className="text-xs text-neutral-400 uppercase tracking-wide"> 109 Welcome Message 110 </label> 111 <Textarea 112 name="intro" 113 rows={6} 114 value={intro} 115 onChange={(e) => setIntro(e.target.value)} 116 placeholder="ASCII art, rules, welcome message..." 117 maxLength={limits.SITE_INTRO} 118 /> 119 </div> 120 <BoardRowEditor boards={boards} onChange={setBoards} /> 121 <Button type="submit">create bbs</Button> 122 </form> 123 </> 124 ); 125}