From 23d5bd266297b3ed6d3165e71ea5ba311a00af91 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Wed, 15 Apr 2026 21:54:51 -0400 Subject: [PATCH] web: add icons --- web/package-lock.json | 34 +++++++++++- web/package.json | 1 + web/src/components/ActivityList.tsx | 5 +- web/src/components/BBSPanel.tsx | 11 ++-- web/src/components/DialBBS.tsx | 5 +- web/src/components/MyThreadList.tsx | 5 +- web/src/components/PinButton.tsx | 5 +- web/src/components/PinnedList.tsx | 5 +- web/src/components/form/ComposeForm.tsx | 5 +- web/src/components/nav/ActionButton.tsx | 10 +++- web/src/components/post/AttachmentLink.tsx | 6 +- web/src/components/profile/EditProfile.tsx | 7 ++- web/src/components/profile/ViewProfile.tsx | 11 ++-- web/src/pages/BBS.tsx | 25 +++++---- web/src/pages/Board.tsx | 5 +- web/src/pages/LoggedOutHome.tsx | 64 +++++++++++----------- web/src/pages/Login.tsx | 13 +++-- web/src/pages/Profile.tsx | 5 +- 18 files changed, 137 insertions(+), 85 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 15224ea..20f09a7 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -10,15 +10,16 @@ "dependencies": { "@atcute/atproto": "^3.1.11", "@atcute/client": "^4.2.1", - "@atcute/lex-cli": "^2.7.0", "@atcute/oauth-browser-client": "^3.0.0", - "@tailwindcss/typography": "^0.5.19", + "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", "react-markdown": "^10.1.0", "react-router-dom": "^7.14.0" }, "devDependencies": { + "@atcute/lex-cli": "^2.7.0", + "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.2.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", @@ -42,6 +43,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/@atcute/car/-/car-5.1.1.tgz", "integrity": "sha512-MeRUJNXYgAHrJZw7mMoZJb9xIqv3LZLQw90rRRAVAo8SGNdICwyqe6Bf2LGesX73QM04MBuYO6Kqhvold3TFfg==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/cbor": "^2.3.2", @@ -54,6 +56,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/@atcute/cbor/-/cbor-2.3.2.tgz", "integrity": "sha512-xP2SORSau/VVI00x2V4BjwIkHr6EQ7l/MXEOPaa4LGYtePFc4gnD4L1yN10dT5NEuUnvGEuCh6arLB7gz1smVQ==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/cid": "^2.4.1", @@ -65,6 +68,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@atcute/cid/-/cid-2.4.1.tgz", "integrity": "sha512-bwhna69RCv7yetXudtj+2qrMPYvhhIQqvJz6YUpUS98v7OdF3X2dnye9Nig2NDrklZcuyOsu7sQo7GOykJXRLQ==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/multibase": "^1.1.8", @@ -85,6 +89,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@atcute/crypto/-/crypto-2.4.1.tgz", "integrity": "sha512-tJ3Pi/XYcAsABKtqSlSOTKfO5YiQ4XdqlTuPS8HiRZSezOPcXBFFzAFWpSIJPURbVPFQL3LLrrK0Ea24wl5qeQ==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/multibase": "^1.2.0", @@ -120,6 +125,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/@atcute/lex-cli/-/lex-cli-2.7.0.tgz", "integrity": "sha512-U/M2+KGYtxe5pXleDgGsmrByMg0i7QpNJQRsoP4zPp1sEtPeLK6+7I0axJA2wkRiw+426B9IuPygVcS2FHLaDQ==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/identity": "^1.1.4", @@ -141,6 +147,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@atcute/lexicon-doc/-/lexicon-doc-2.2.0.tgz", "integrity": "sha512-6l4lDlL6KPLDGknRh6HlfGbv98haUgQ0DFaAr1yA4vA95b8YYZUZ4/370ENpiq+d6Lv0tdDAMvOon2mynrp3pQ==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/identity": "^1.1.4", @@ -154,6 +161,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/@atcute/lexicon-resolver/-/lexicon-resolver-0.1.6.tgz", "integrity": "sha512-wJC/ChmpP7k+ywpOd07CMvioXjIGaFpF3bDwXLi/086LYjSWHOvtW6pyC+mqP5wLhjyH2hn4wmi77Buew1l1aw==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/crypto": "^2.3.0", @@ -184,6 +192,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@atcute/mst/-/mst-1.0.0.tgz", "integrity": "sha512-pMce2efib+dmKtnGnIvJZitVncJkpr3AmhyfgfYllni8KzsaDGsJmuGavSVpuojAhQe+6jYwHFtpm/beiiH4uw==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/cbor": "^2.3.2", @@ -252,6 +261,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/@atcute/repo/-/repo-0.1.4.tgz", "integrity": "sha512-uzbGJkE+1A8UFviosJrtw7HW87u8nCCH1V3yOQ79FPrRhS67EvEHF6GTg4aMkP21ze/pRtttJ1k9pFfDmyTlTg==", + "dev": true, "license": "0BSD", "dependencies": { "@atcute/car": "^5.1.1", @@ -291,6 +301,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@atcute/varint/-/varint-2.0.0.tgz", "integrity": "sha512-CEY/oVK/nVpL4e5y3sdenLETDL6/Xu5xsE/0TupK+f0Yv8jcD60t2gD8SHROWSvUwYLdkjczLCSA7YrtnjCzWw==", + "dev": true, "license": "0BSD" }, "node_modules/@babel/code-frame": { @@ -691,6 +702,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-3.0.0.tgz", "integrity": "sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==", + "dev": true, "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" @@ -700,6 +712,7 @@ "version": "0.10.7", "resolved": "https://registry.npmjs.org/@optique/core/-/core-0.10.7.tgz", "integrity": "sha512-FwSX8ILFqzcCqZi6Xetsa4flJp/yyqFG4d4eFD98BtqdzxxuylzdrKvsXj/ow8mcoVjYkTuaIkqHSBxonqMcQg==", + "dev": true, "funding": [ "https://github.com/sponsors/dahlia" ], @@ -714,6 +727,7 @@ "version": "0.10.7", "resolved": "https://registry.npmjs.org/@optique/run/-/run-0.10.7.tgz", "integrity": "sha512-1CVdH8uyptj1nFGS2MLacSmZceRClez4LD/G/Gm38wrAVnJq6I+9Fvyh2bVHErsZLQzR0a12CYMUWIgDKY3X1w==", + "dev": true, "funding": [ "https://github.com/sponsors/dahlia" ], @@ -1298,6 +1312,7 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "dev": true, "license": "MIT", "dependencies": { "postcss-selector-parser": "6.0.10" @@ -1639,6 +1654,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -2266,6 +2282,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.8.0.tgz", + "integrity": "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -2931,6 +2956,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -2979,6 +3005,7 @@ "version": "6.0.10", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -3011,6 +3038,7 @@ "version": "3.8.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", + "dev": true, "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" @@ -3280,6 +3308,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, "license": "MIT" }, "node_modules/tapable": { @@ -3483,6 +3512,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, "node_modules/vfile": { diff --git a/web/package.json b/web/package.json index 32d3d50..f1cf196 100644 --- a/web/package.json +++ b/web/package.json @@ -12,6 +12,7 @@ "@atcute/atproto": "^3.1.11", "@atcute/client": "^4.2.1", "@atcute/oauth-browser-client": "^3.0.0", + "lucide-react": "^1.8.0", "react": "^19.2.5", "react-dom": "^19.2.5", "react-markdown": "^10.1.0", diff --git a/web/src/components/ActivityList.tsx b/web/src/components/ActivityList.tsx index 8de2995..c7562ed 100644 --- a/web/src/components/ActivityList.tsx +++ b/web/src/components/ActivityList.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { ChevronDown } from "lucide-react"; import { Link } from "react-router-dom"; import { parseAtUri } from "../lib/util"; import PostBody from "./post/PostBody"; @@ -46,9 +47,9 @@ export default function ActivityList({ items, userHandle }: ActivityListProps) {
)} diff --git a/web/src/components/BBSPanel.tsx b/web/src/components/BBSPanel.tsx index 437d01f..b26b164 100644 --- a/web/src/components/BBSPanel.tsx +++ b/web/src/components/BBSPanel.tsx @@ -1,4 +1,5 @@ import { Link } from "react-router-dom"; +import { Monitor, Pencil, Shield, Trash2, Plus } from "lucide-react"; import { ActionLink } from "./nav/ActionButton"; const cardStyle = @@ -17,7 +18,7 @@ export default function BBSPanel({ hasBBS, userHandle, onDelete }: BBSPanelProps

No BBS yet.

- create a bbs + create a bbs ); } @@ -25,22 +26,22 @@ export default function BBSPanel({ hasBBS, userHandle, onDelete }: BBSPanelProps return (
-
Browse
+
Browse
View your BBS.
-
Edit
+
Edit
Name, boards, intro.
-
Moderate
+
Moderate
Bans and hidden posts.
diff --git a/web/src/components/DialBBS.tsx b/web/src/components/DialBBS.tsx index b4ff0fa..e408f1c 100644 --- a/web/src/components/DialBBS.tsx +++ b/web/src/components/DialBBS.tsx @@ -1,4 +1,5 @@ import { useState, type SyntheticEvent } from "react"; +import { ArrowRight, Dices } from "lucide-react"; import { Link, useNavigate } from "react-router-dom"; import HandleInput from "./form/HandleInput"; import { Button } from "./form/Form"; @@ -92,8 +93,8 @@ export default function DialBBS({ discovered, suggestions }: DialBBSProps) { } aria-label="Dial a BBS by handle" /> - - + + {dropdownOpen && (
diff --git a/web/src/components/MyThreadList.tsx b/web/src/components/MyThreadList.tsx index b79277e..c25fb05 100644 --- a/web/src/components/MyThreadList.tsx +++ b/web/src/components/MyThreadList.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { ChevronDown } from "lucide-react"; import { Link } from "react-router-dom"; import { parseAtUri, formatFullDate, relativeDate } from "../lib/util"; import type { MyThread } from "../router/loaders"; @@ -49,9 +50,9 @@ export default function MyThreadList({ threads }: MyThreadListProps) {
)} diff --git a/web/src/components/PinButton.tsx b/web/src/components/PinButton.tsx index f363b9f..eba11a5 100644 --- a/web/src/components/PinButton.tsx +++ b/web/src/components/PinButton.tsx @@ -4,6 +4,7 @@ import { PIN } from "../lib/lexicon"; import { parseAtUri } from "../lib/util"; import { createPin, deleteRecord } from "../lib/writes"; import { ActionButton } from "./nav/ActionButton"; +import { Pin, PinOff } from "lucide-react"; interface PinButtonProps { bbsDid: string; @@ -29,8 +30,8 @@ export default function PinButton({ bbsDid, initialRkey }: PinButtonProps) { if (!user) return null; return ( - - {pinRkey ? "✕ unpin" : "+ pin"} + + {pinRkey ? "unpin" : "pin"} ); } diff --git a/web/src/components/PinnedList.tsx b/web/src/components/PinnedList.tsx index 5a3d64f..d2c4c8c 100644 --- a/web/src/components/PinnedList.tsx +++ b/web/src/components/PinnedList.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { ChevronDown } from "lucide-react"; import ListLink from "./nav/ListLink"; import type { PinnedBBS } from "../router/loaders"; @@ -31,9 +32,9 @@ export default function PinnedList({ pins }: PinnedListProps) { {shown < pins.length && ( )}
diff --git a/web/src/components/form/ComposeForm.tsx b/web/src/components/form/ComposeForm.tsx index e781589..76578ca 100644 --- a/web/src/components/form/ComposeForm.tsx +++ b/web/src/components/form/ComposeForm.tsx @@ -1,4 +1,5 @@ import type { SyntheticEvent } from "react"; +import { Send, Paperclip } from "lucide-react"; import { Input, Textarea, Button } from "./Form"; import FileChips from "./FileChips"; import { MAX_ATTACHMENTS } from "../../lib/limits"; @@ -103,11 +104,11 @@ export default function ComposeForm({
{!attachmentsAtLimit && (
- +
diff --git a/web/src/components/profile/ViewProfile.tsx b/web/src/components/profile/ViewProfile.tsx index 174c76e..49fb0c1 100644 --- a/web/src/components/profile/ViewProfile.tsx +++ b/web/src/components/profile/ViewProfile.tsx @@ -1,4 +1,5 @@ import { Link } from "react-router-dom"; +import { Pencil, ChevronRight, Monitor } from "lucide-react"; import PostBody from "../post/PostBody"; import { ActionButton } from "../nav/ActionButton"; import type { Profile } from "../../lib/profile"; @@ -23,7 +24,7 @@ export default function ViewProfile({ {profile?.name ?? handle} {isOwner && ( - edit profile + edit )}

@@ -42,8 +43,8 @@ export default function ViewProfile({ )} {profile?.bbsName && (

-

- BBS +

+ BBS

)}
- - → - + )} diff --git a/web/src/pages/BBS.tsx b/web/src/pages/BBS.tsx index 659e70f..36f6cde 100644 --- a/web/src/pages/BBS.tsx +++ b/web/src/pages/BBS.tsx @@ -13,6 +13,7 @@ import ListLink from "../components/nav/ListLink"; import ActionBar from "../components/nav/ActionBar"; import { ActionLink } from "../components/nav/ActionButton"; import PinButton from "../components/PinButton"; +import { User, Pencil, Shield, LayoutGrid, Newspaper, Megaphone, ChevronDown } from "lucide-react"; import type { News } from "../lib/bbs"; import type { BBSLoaderData } from "../router/loaders"; import PostBody from "../components/post/PostBody"; @@ -83,11 +84,11 @@ export default function BBSPage() {

{bbs.site.description}

- - operator + + owner - {isSysop && edit} - {isSysop && moderate} + {isSysop && edit} + {isSysop && moderate} @@ -98,8 +99,8 @@ export default function BBSPage() { )}
-

- Boards +

+ Boards

{bbs.site.boards.map((board) => ( @@ -114,14 +115,14 @@ export default function BBSPage() {
-

- News +

+ News

{isSysop && (
- - post news + + post news 3 && ( )} diff --git a/web/src/pages/Board.tsx b/web/src/pages/Board.tsx index fe5ef20..f41886e 100644 --- a/web/src/pages/Board.tsx +++ b/web/src/pages/Board.tsx @@ -1,4 +1,5 @@ import { useEffect, useState, type SyntheticEvent } from "react"; +import { PenLine } from "lucide-react"; import { useLoaderData, useNavigate, @@ -111,8 +112,8 @@ export default function BoardPage() { {user && (
- - new thread + + new thread ("pip"); + const [copied, setCopied] = useState(false); usePageTitle("atbbs"); + const installCommands: Record = { + pip: "pip install atbbs\natbbs", + uv: "uv tool install atbbs\natbbs", + brew: "brew install alyraffauf/tap/atbbs\natbbs", + telnet: "telnet tel.atbbs.xyz", + }; + + function handleCopy() { + navigator.clipboard.writeText(installCommands[tab]); + setCopied(true); + setTimeout(() => setCopied(false), 1500); + } + const activeTab = "py-2 border-b-2 text-neutral-200 border-neutral-200"; const inactiveTab = "py-2 border-b-2 text-neutral-400 hover:text-neutral-300 border-transparent"; return ( -
+
-

Dial a BBS

+

Dial a BBS

@@ -74,36 +89,23 @@ export default function LoggedOutHome() { ))}
- {tab === "pip" && ( -
-            $ pip install
-            atbbs
-            {"\n"}
-            $ atbbs
-          
- )} - {tab === "uv" && ( -
-            $ uv tool
-            install atbbs
-            {"\n"}
-            $ atbbs
-          
- )} - {tab === "brew" && ( -
-            $ brew install
-            alyraffauf/tap/atbbs
-            {"\n"}
-            $ atbbs
+        
+
+            {installCommands[tab].split("\n").map((line, i) => (
+              
+                {i > 0 && "\n"}
+                $ {line}
+              
+            ))}
           
- )} - {tab === "telnet" && ( -
-            $ telnet
-            tel.atbbs.xyz
-          
- )} + +
); diff --git a/web/src/pages/Login.tsx b/web/src/pages/Login.tsx index 9c87de5..3162ec4 100644 --- a/web/src/pages/Login.tsx +++ b/web/src/pages/Login.tsx @@ -1,4 +1,5 @@ import { useState, type SyntheticEvent } from "react"; +import { LogIn, MessageSquare, Pin, User, Monitor } from "lucide-react"; import { useAuth } from "../lib/auth"; import { usePageTitle } from "../hooks/usePageTitle"; import { useHandleSearch } from "../hooks/useHandleSearch"; @@ -88,7 +89,7 @@ export default function Login() { aria-label="Enter your handle" /> {dropdownOpen && ( @@ -132,11 +133,11 @@ export default function Login() {

Once signed in, you can:

-
    -
  • Post threads and replies
  • -
  • Pin boards you like
  • -
  • Set up a profile
  • -
  • Start your own community
  • +
      +
    • Post threads and replies
    • +
    • Pin boards you like
    • +
    • Set up a profile
    • +
    • Start your own community

    We'll redirect you to your provider to continue. diff --git a/web/src/pages/Profile.tsx b/web/src/pages/Profile.tsx index fd08f09..8ad0b79 100644 --- a/web/src/pages/Profile.tsx +++ b/web/src/pages/Profile.tsx @@ -1,4 +1,5 @@ import { Suspense, useState } from "react"; +import { MessageSquare } from "lucide-react"; import { Await, useLoaderData, useRevalidator } from "react-router-dom"; import { useAuth } from "../lib/auth"; import { usePageTitle } from "../hooks/usePageTitle"; @@ -45,8 +46,8 @@ export default function Profile() { onEdit={() => setEditing(true)} />

    -

    - Recent Threads +

    + Recent Threads

    loading...

    } -- 2.51.2