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.

add HandleInput for cute atproto handle recs

+62 -16
+49
web/src/components/HandleInput.tsx
··· 1 + import { useEffect, useState, type InputHTMLAttributes } from "react"; 2 + 3 + const PLACEHOLDERS = [ 4 + "handle.blacksky.app", 5 + "handle.bsky.social", 6 + "handle.eurosky.social", 7 + "handle.northsky.social", 8 + "handle.selfhosted.social", 9 + "handle.tngl.sh", 10 + "handle.witchcraft.systems", 11 + "handle.your-domain.com", 12 + ]; 13 + 14 + // Props for HandleInput. Extends standard <input> props so callers can 15 + // pass things like `required`, `disabled`, `id`, etc. 16 + interface HandleInputProps extends InputHTMLAttributes<HTMLInputElement> { 17 + value: string; 18 + onChange: (v: string) => void; 19 + } 20 + 21 + export default function HandleInput({ 22 + value, 23 + onChange, 24 + className = "", 25 + ...rest // any extra <input> attributes (required, disabled, etc.) 26 + }: HandleInputProps) { 27 + // Cycle through placeholder examples every 3 seconds. 28 + const [index, setIndex] = useState(0); 29 + 30 + useEffect(() => { 31 + const timer = setInterval(() => { 32 + setIndex((i) => (i + 1) % PLACEHOLDERS.length); 33 + }, 3000); 34 + 35 + // Stop the timer when this component is removed from the page. 36 + return () => clearInterval(timer); 37 + }, []); 38 + 39 + return ( 40 + <input 41 + type="text" 42 + value={value} 43 + onChange={(e) => onChange(e.target.value)} 44 + placeholder={PLACEHOLDERS[index]} 45 + className={`bg-neutral-900 border border-neutral-800 rounded px-3 py-2 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-neutral-600 ${className}`} 46 + {...rest} 47 + /> 48 + ); 49 + }
+5 -6
web/src/pages/Home.tsx
··· 1 1 import { useEffect, useState, type SyntheticEvent } from "react"; 2 2 import { Link, useNavigate } from "react-router-dom"; 3 + import HandleInput from "../components/HandleInput"; 3 4 import { resolveIdentitiesBatch } from "../lib/atproto"; 4 5 import { SITE } from "../lib/lexicon"; 5 6 import { useTitle } from "../hooks/useTitle"; ··· 82 83 <div className="border-t border-neutral-800 py-4"> 83 84 <h2 className="text-neutral-300 mb-4">Dial a BBS</h2> 84 85 <form onSubmit={onSubmit} className="flex gap-2 mb-6"> 85 - <input 86 - type="text" 86 + <HandleInput 87 87 value={handle} 88 - onChange={(e) => setHandle(e.target.value)} 89 - placeholder="handle.example.com" 88 + onChange={setHandle} 90 89 required 91 - className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-3 py-2 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-neutral-600" 90 + className="flex-1" 92 91 /> 93 92 <button 94 93 type="submit" ··· 109 108 to={`/bbs/${encodeURIComponent(d.handle)}`} 110 109 className="flex items-baseline gap-3 px-3 py-2 -mx-3 rounded hover:bg-neutral-900 group" 111 110 > 112 - <span className="text-neutral-200 group-hover:text-white break-words"> 111 + <span className="text-neutral-200 group-hover:text-white wrap-break-word"> 113 112 {d.name} 114 113 </span> 115 114 <span className="text-neutral-500 hidden sm:inline">
+4 -5
web/src/pages/Login.tsx
··· 1 1 import { useState, type SyntheticEvent } from "react"; 2 2 import { useAuth } from "../lib/auth"; 3 3 import { useTitle } from "../hooks/useTitle"; 4 + import HandleInput from "../components/HandleInput"; 4 5 5 6 export default function Login() { 6 7 const { login } = useAuth(); ··· 29 30 </p> 30 31 {error && <p className="text-red-400 mb-4">{error}</p>} 31 32 <form onSubmit={onSubmit} className="flex gap-2 max-w-md"> 32 - <input 33 - type="text" 33 + <HandleInput 34 34 value={handle} 35 - onChange={(e) => setHandle(e.target.value)} 36 - placeholder="your-handle.bsky.social" 35 + onChange={setHandle} 37 36 required 38 - className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-3 py-2 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-neutral-600" 37 + className="flex-1" 39 38 /> 40 39 <button 41 40 type="submit"
+4 -5
web/src/pages/SysopModerate.tsx
··· 3 3 import { useAuth } from "../lib/auth"; 4 4 import { resolveIdentity } from "../lib/atproto"; 5 5 import { BAN, HIDE } from "../lib/lexicon"; 6 + import HandleInput from "../components/HandleInput"; 6 7 import { useTitle } from "../hooks/useTitle"; 7 8 import { createBan, createHide, deleteRecord } from "../lib/writes"; 8 9 import type { BBS } from "../lib/bbs"; ··· 107 108 ))} 108 109 </div> 109 110 <div className="flex gap-2"> 110 - <input 111 - type="text" 111 + <HandleInput 112 112 value={identifier} 113 - onChange={(e) => setIdentifier(e.target.value)} 114 - placeholder="handle to ban" 115 - className="flex-1 bg-neutral-900 border border-neutral-800 rounded px-3 py-2 text-neutral-200 placeholder-neutral-500 focus:outline-none focus:border-neutral-600" 113 + onChange={setIdentifier} 114 + className="flex-1" 116 115 /> 117 116 <button 118 117 onClick={ban}