Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion BOARD_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
39 changes: 24 additions & 15 deletions components/board/ChangesTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<section>
<h2>Changes</h2>
<p>Every change made with the {chapter.id === "ucla" ? "UCLA" : "Berkeley"} admin password, newest first. Only admins see this tab.</p>
<p>Every change made on the {place} hub, newest first. Only admins see this tab.</p>
<ActorName />

{data.changelog.length === 0 && <p>No changes yet.</p>}
<ul className="log">
{data.changelog.map((e, i) => (
<Entry key={e.id} e={e} who={who(e.role)} later={data.changelog.slice(0, i)} />
))}
</ul>
</section>
);
}

// "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 (
<div className="note">
<label htmlFor="actor" className="inline">
Your name for the log
<input
id="actor"
value={name}
placeholder="Fiona"
placeholder="First name"
onChange={(e) => {
setName(e.target.value);
setSaved(false);
Expand All @@ -66,16 +83,8 @@ export function ChangesTab() {
Save
</button>
{saved && <span className="msg ok"> Saved in this browser.</span>}
<p style={{ margin: "8px 0 0" }}>Optional. Everyone shares the admin password, so this name is self-reported; the log marks it that way.</p>
<p style={{ margin: "8px 0 0" }}>Optional. Everyone shares the password, so this name is self-reported; the change log marks it that way.</p>
</div>

{data.changelog.length === 0 && <p>No changes yet.</p>}
<ul className="log">
{data.changelog.map((e, i) => (
<Entry key={e.id} e={e} who={who} later={data.changelog.slice(0, i)} />
))}
</ul>
</section>
);
}

Expand Down
2 changes: 2 additions & 0 deletions components/board/SocialsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -66,6 +67,7 @@ export function SocialsTab() {
return (
<section>
<h2>{cfg.heading}</h2>
{editor && user.role !== "admin" && <ActorName />}
<div className="bar" role="group" aria-label="Filter socials">
{FILTERS.map((f) => (
<button
Expand Down
1 change: 1 addition & 0 deletions lib/board/chapter-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const CHAPTER_SERVER: Record<ChapterId, ChapterServer> = {
organizer: { email: "fionarhine@ucla.edu", name: "Fiona Wangsawidjaja", short: "Fiona" },
passwords: [
["admin", "BOARD_UCLA_ADMIN_PASSWORD"],
["socials", "BOARD_UCLA_EVENTS_PASSWORD"],
["member", "BOARD_UCLA_PASSWORD"],
],
submissions: {
Expand Down
2 changes: 1 addition & 1 deletion lib/board/chapters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const UCLA: ChapterConfig = {
loginSub: "UCLA chapter board hub",
contact: "Fiona",
otherHub: { prompt: "Why am I on this chuz school's page?", label: "Take me to Berkeley's hub", path: "/board" },
roleLabels: { admin: "admin", member: "member" },
roleLabels: { admin: "admin", socials: "events editor", member: "member" },
teams: ["Data", "Research", "App Dev", "Design", "Technical Writing", "High School"],
rule: null,
proofRequired: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/board/editable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import "server-only";
import { TABLES, oneOf, type TableDef } from "./tables";
import type { BoardRole } from "./roles";

// Which roles may change each table through /api/board/[table]. UCLA has no
// socials role, so only its admins edit events.
// Which roles may change each table through /api/board/[table]. The socials
// role (Berkeley's socials editor, UCLA's events editor) edits events only.
const EDITORS: Record<keyof typeof TABLES, BoardRole[]> = {
socials: ["admin", "socials"],
assignments: ["admin"],
Expand Down
4 changes: 3 additions & 1 deletion lib/board/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// set per chapter in chapter-server.ts:
// Berkeley BOARD_ADMIN_PASSWORD (Rana), BOARD_SOCIALS_PASSWORD (socials
// editor; no one yet, leave unset), BOARD_PASSWORD (everyone)
// UCLA BOARD_UCLA_ADMIN_PASSWORD (Fiona and Will), BOARD_UCLA_PASSWORD
// UCLA BOARD_UCLA_ADMIN_PASSWORD (Fiona and Will),
// BOARD_UCLA_EVENTS_PASSWORD (events editor), BOARD_UCLA_PASSWORD
// The "socials" role is called the events editor on UCLA's hub.

export type BoardRole = "admin" | "socials" | "member";

Expand Down