From 96b4e975f347e7d583d80d83669af2fe9f227f29 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Oct 2026 23:48:03 +0000 Subject: [PATCH] Board: UCLA events editor sign-in - BOARD_UCLA_EVENTS_PASSWORD signs in to /board/ucla as the events editor (the socials role Berkeley already has): the member view plus adding, editing and deleting events. Event writes accept only that chapter's admin or events-editor session; everything else stays admin-only, and admins-only content stays hidden. - The header says "Signed in as events editor". - The change log labels each entry by the role that made it ("UCLA events editor", "UCLA admin"). The optional "Your name" box also shows on the events tab for the events editor. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QzNDQSmhjgKE63rwUnwcMq --- .env.example | 4 +++- BOARD_SETUP.md | 7 +++++- components/board/ChangesTab.tsx | 39 ++++++++++++++++++++------------- components/board/SocialsTab.tsx | 2 ++ lib/board/chapter-server.ts | 1 + lib/board/chapters.ts | 2 +- lib/board/editable.ts | 4 ++-- lib/board/roles.ts | 4 +++- 8 files changed, 42 insertions(+), 21 deletions(-) diff --git a/.env.example b/.env.example index 7859710..26ff070 100644 --- a/.env.example +++ b/.env.example @@ -15,8 +15,10 @@ BOARD_SOCIALS_PASSWORD= # Passwords for /board/ucla (UCLA's hub). Fiona and Will get # BOARD_UCLA_ADMIN_PASSWORD (admin view); everyone else on the UCLA board # gets BOARD_UCLA_PASSWORD. Use values different from Berkeley's; neither -# chapter's passwords open the other's hub. +# chapter's passwords open the other's hub. Whoever runs UCLA's events gets +# BOARD_UCLA_EVENTS_PASSWORD (member view plus editing events). BOARD_UCLA_ADMIN_PASSWORD= +BOARD_UCLA_EVENTS_PASSWORD= BOARD_UCLA_PASSWORD= # Signs the board hub login cookies (both chapters). Any random string of 32+ characters, # e.g. the output of: openssl rand -base64 32 diff --git a/BOARD_SETUP.md b/BOARD_SETUP.md index 37cd6a8..c721410 100644 --- a/BOARD_SETUP.md +++ b/BOARD_SETUP.md @@ -31,8 +31,13 @@ signs out everyone who used it, and you share the new one with the rest. | Railway variable | Who gets it | What it unlocks | | --------------------------- | ----------------------- | ------------------------------------- | | `BOARD_UCLA_PASSWORD` | everyone on the UCLA board | view everything, submit deliverables | +| `BOARD_UCLA_EVENTS_PASSWORD` | the events editor | also add, edit and delete events | | `BOARD_UCLA_ADMIN_PASSWORD` | Fiona and Will | edit everything on the UCLA hub | +The events password sees what members see and can change events (dates, +times, locations, leads, status), nothing else. Its edits go in the change +log as "UCLA events editor". + Use passwords that differ from Berkeley's. Ten wrong tries from the same connection lock a hub's form for 15 minutes. @@ -119,7 +124,7 @@ board. Postgres service's `DATABASE_URL` (Railway offers it as a reference). 2. **Passwords and secret.** In the same Variables screen, add: - `BOARD_PASSWORD`, `BOARD_SOCIALS_PASSWORD`, `BOARD_ADMIN_PASSWORD` - - `BOARD_UCLA_PASSWORD`, `BOARD_UCLA_ADMIN_PASSWORD` + - `BOARD_UCLA_PASSWORD`, `BOARD_UCLA_EVENTS_PASSWORD`, `BOARD_UCLA_ADMIN_PASSWORD` - `BOARD_SESSION_SECRET`: a random string of at least 32 characters. `openssl rand -base64 32` makes one. 3. Redeploy, open `https://www.getvindicated.org/board`, and sign in with diff --git a/components/board/ChangesTab.tsx b/components/board/ChangesTab.tsx index 6a7efe7..bdddfbc 100644 --- a/components/board/ChangesTab.tsx +++ b/components/board/ChangesTab.tsx @@ -30,24 +30,41 @@ const when = (iso: string) => // "Restore previous version". export function ChangesTab() { const { chapter, data, user } = useBoard(); - const [name, setName] = useState(""); - const [saved, setSaved] = useState(false); - useEffect(() => setName(getActor()), []); if (user.role !== "admin") return null; - const who = chapter.id === "ucla" ? "UCLA admin" : "Berkeley admin"; + const place = chapter.id === "ucla" ? "UCLA" : "Berkeley"; + const who = (role: string) => `${place} ${chapter.roleLabels[role] ?? role}`; return (

Changes

-

Every change made with the {chapter.id === "ucla" ? "UCLA" : "Berkeley"} admin password, newest first. Only admins see this tab.

+

Every change made on the {place} hub, newest first. Only admins see this tab.

+ + + {data.changelog.length === 0 &&

No changes yet.

} +
    + {data.changelog.map((e, i) => ( + + ))} +
+
+ ); +} + +// "Your name for the log": optional, saved in this browser, sent with each +// change. Everyone with a password shares it, so the log calls it self-reported. +export function ActorName() { + const [name, setName] = useState(""); + const [saved, setSaved] = useState(false); + useEffect(() => setName(getActor()), []); + return (
- - {data.changelog.length === 0 &&

No changes yet.

} - - ); } diff --git a/components/board/SocialsTab.tsx b/components/board/SocialsTab.tsx index c8b7c8f..1a14f27 100644 --- a/components/board/SocialsTab.tsx +++ b/components/board/SocialsTab.tsx @@ -5,6 +5,7 @@ import type { Social } from "@/lib/board/types"; import { SOCIAL_MONTHS, SOCIAL_STATUSES } from "@/lib/board/static"; import type { ChapterConfig } from "@/lib/board/chapters"; import { canEditSocials } from "@/lib/board/roles"; +import { ActorName } from "./ChangesTab"; import { useBoard } from "./BoardApp"; import { RowEditor, type FieldDef } from "./RowEditor"; import { D, fmt, isoDate } from "./util"; @@ -66,6 +67,7 @@ export function SocialsTab() { return (

{cfg.heading}

+ {editor && user.role !== "admin" && }
{FILTERS.map((f) => (