Retro Bulletin Board Systems on atproto. Web app and TUI.
lazy mirror of alyraffauf/atbbs
atbbs.xyz
forums
python
tui
atproto
bbs
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}