Skip to content

Application portal: merge hacker, mentor/judge application and staff portal - #9

Merged
TC960 merged 74 commits into
mainfrom
merge-final
Oct 23, 2026
Merged

TC960 merged 74 commits into
mainfrom
merge-final

Conversation

@TC960

@TC960 TC960 commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Brings the application portal together in one branch: Elisa's hacker portal update (#8), Satyash's mentor/judge application (#7), and Satyash's mentor/judge/hybrid portal (Mentor/Judge/Mentor+Judge-pages, which had no PR). Merging this closes #7 and #8.

What changed on top of the three branches

  • Everyone signs in with Google before applying, mentors and judges included. A mentor/judge application is tied to the account and uses its email, so the portal reads it directly. There's no more matching a typed email to a Google account later.
  • Roles come from the live event model. The Supabase project already has events, per-event event_roles, an attendees roster with check-in codes, and triggers that grant roles when an application is accepted. The portal now reads roles from event_roles and sends the current event (EVENT_CODE in src/portal/event.ts) with every application, so accepted people reach the check-in roster. Before this, the sign-in link's ?role= was saved on the profile; now the browser can't set a role or a status.
  • After sign-in: anyone with a mentor/judge application goes to /portal. Someone coming from the mentor/judge sign-in pages without one goes to the application form. Everyone else goes to the hacker dashboard.
  • One small migration, supabase/migrations/20261021000000_application_portal.sql, replaces the two earlier SQL files. It lets signed-in mentors and judges apply, closes the anonymous route, and stops the browser setting profile role or email. It has already been applied to the live project, after a dry run that was rolled back.
  • Portal paths are defined in one place (src/portal/paths.ts), and the deploy workflow writes entry pages for the new routes.
  • The tests set jsdom's localStorage again, which Node 25+ hides. 24 staff-portal tests were failing on newer Node because of this.

Before this works on the live site

Add https://datahacks.ds3ucsd.com/** under Supabase → Authentication → URL Configuration → Redirect URLs. The list is empty, so Google sign-in on the real site can't return to /auth/callback.

Testing

  • Typecheck, build, and 62/62 tests pass.
  • I ran the migration twice against a local Postgres set up like the live project, and once against an empty one. Checks: users can't give themselves a role, accept their own application, submit as someone else, read others' applications, or upload into another user's resume folder. Anonymous inserts are rejected, and accepting or rejecting an application updates the role immediately.
  • In the browser, signed out: every portal route redirects to the right sign-in page, the staff portals render in ?preview, and "Create an Account" opens the hacker sign-in.
  • Live, against the real project (local dev server): a hacker signs in with Google, applies with a resume, and sees "submitted". A mentor signs in, is sent to the form, applies with their Google email, and sees "Application pending" in the mentor portal. Not yet checked: accepting an application and seeing the role and check-in code appear.

Remaining loose ends are in docs/application-portal-todo.md: a single entrance and one portal for hackers too, DocuSign credentials, real contacts, waiver records in Supabase, and Evan's branch.

🤖 Generated with Claude Code

Yuuki131313 and others added 30 commits October 14, 2026 22:01
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ly blurb

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Validate that the resume URL is a drive.google.com file link and update
the label and placeholder to say it must be a .pdf Google Drive link.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign-in goes to /portal, which looks up the person's application and opens
/portal/mentor, /portal/judge or /portal/hybrid. Adds role-specific run of show
and handbook names, a Judging Portal placeholder, DocuSign embedded waiver
signing (edge function), and routing tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Run of show from the DataHacks main venue spreadsheet (public + judging rows),
  with required mentor office hours and judge events, and the venue under the heading
- Demo handbook content from the DataHacks 2026 Hacker Handbook
- Status pill beside the greeting; labels without trailing periods
- Dev ?preview mode gets a simulated DocuSign so the waiver lock/unlock flow can be
  tried locally; waiver page shows submission status; keep close/decline messages
- public/dev/ is git-ignored for local test files

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n can be accessed through 'The End' section of sidebar or 'Join the adventure' button
…abel

Mentor/judge submissions now insert into public.applications with
applicant_type ('mentor' | 'judge' | 'hybrid'), status and submitted_at.
Hacker applications set applicant_type 'hacker'. Removes the
"APPLICATION" label under the mentor/judge heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The callback used to save whatever ?role= was in the link, so anyone could
make themselves a judge. Roles now come back from the profile row, which a
database trigger sets, and the migration locks profiles, applications and
resumes to their owners. Portal paths live in one file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into merge-final

# Conflicts:
#	src/App.tsx
… into merge-final

# Conflicts:
#	CLAUDE.md
#	src/App.tsx
#	src/pages/AuthCallback.tsx
… accepted applications

Staff applications were sent without signing in, so the portal had to find
them by matching the typed email to the Google account later. Now everyone
signs in first: the application is tied to the account, the staff portal
reads it directly, and the lookup function is gone.

An accepted mentor/judge/hybrid application now sets the person's role, kept
in sync by the database. All the SQL is one migration that builds the
database from scratch or brings the live one up to date, replacing the two
earlier files.

Also restores jsdom's localStorage in tests, which Node 25+ hides.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…agram

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The live project already has events, per-event roles (event_roles), an
attendee roster with check-in codes, and triggers that grant roles when an
application is accepted. None of it is in this repo, and the earlier
migration would have dropped its organizer access.

Now the portal reads roles from event_roles, sends the current event with
every application (so accepted people get onto the check-in roster), and
the migration only opens applications to signed-in mentors/judges, closes
the anonymous route, and stops the browser setting profile role or email.
Applied to the live project on 2026-10-21 after a rolled-back dry run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hema dump

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Everyone lands in /portal after Google sign-in. Before acceptance the sidebar
has only Status (the default) and Application; acceptance opens the handbook,
run of show and liability waiver, plus Judging for judges and, on the day, a
Mentors tab for hackers showing who's checked in. Contacts sit in the sidebar
footer. People who haven't applied pick hacker or mentor/judge and fill the
form inside the portal; both forms now ask for a phone number. The Minecraft
hacker pages are gone and their old addresses forward here.

/portal/organizer lists hacker and mentor/judge applications by status, with
search, CSV export and the full application. Accept/waitlist/reject only
stages a decision; releasing them takes typing the event code, applies them
in one database call (roles and check-in codes follow from the existing
triggers) and emails everyone through the send-email function. Organizers can
also email any group from the console. Dev ?preview runs it on sample data.

The new migration adds phone and the staged-decision columns,
release_decisions and mentor_roster, and lets organizers read resumes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
TC960 and others added 29 commits October 22, 2026 20:46
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
…ion dates

Two migrations were dated tomorrow (2026-10-23), which would make the CLI
refuse any migration created today; they're renamed to today, keeping their
order. Neither is recorded in the CLI's migration history yet, so the rename
is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
…uction

- Baseline: the shared schema captured from production (public schema with
  the check-in tables, functions, triggers, policies and grants, plus our
  storage policies and Realtime membership) in one migration; the four
  hand-applied migrations move to supabase/archive/. A second migration adds
  the resumes bucket and the DH26 event row if missing. Both applied to the
  new staging project, which then matched production exactly: schema, grants,
  storage policies and Realtime.
- Grants are reset per object before production's exact grants, because a
  fresh project's default privileges would otherwise give the API roles more.
- supabase-ci (PRs): migration rules, new migrations tried on staging in a
  rolled-back transaction, edge functions type-checked.
- supabase-deploy (main): staging push, lint and functions, then production
  dry run, push and functions; production stays locked until
  SUPABASE_PROD_BOOTSTRAPPED is set after the one-time history repair.
- supabase-drift and supabase-backup run nightly; the backup is encrypted with
  BACKUP_PASSPHRASE before it leaves the runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
…et already ran

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Database changes through GitHub Actions: staging first, then production
A week of testing runs against a separate event, so the real DH27 starts
clean. Event codes must be 2-10 letters or digits, hence DH27BETA rather than
DH27-betaTest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
The descent now runs landing, last year, tracks, challenges, prizes,
sponsors, speakers, FAQ, with the footer as bedrock underneath. Each section
is its own layer of the world: Overworld, sulfur caves, lush caves, the
mineshaft, the deep dark, the stronghold, the End city, the End.

Mentors & judges, the run of show and the application leave the scroll.
They still open as their own pages for old links, and /datacraft/trials and
/stronghold redirect to them, but the public site links to neither: the run
of show lives in the portal now, and the nav and hero have Apply as a hacker
and Apply to mentor or judge buttons that go straight to Google.

Challenges is a new placeholder section for the design team. Lush caves and
the End city borrow the closest existing backdrops until they have their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Site shell: reorder sections, one layer per section, apply buttons in the nav
Two simple pixel-block backdrops (moss, clay and glow berries; end stone,
purpur and end rods) made by scripts/generate-basic-layer-art.py to the same
rules as the other reliefs, replacing the borrowed tiles until the design
team draws the real ones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
The school suggestions now come from the open Hipo university-domains-list
(MIT licensed), filtered to the US and Canada: 2,494 schools, A-Z. Typing
still accepts a school that isn't listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
The browser's datalist popup can't be styled, so the school field now uses
its own searchable list: it opens after two letters, puts matches that start
with what was typed first, and works with the arrow keys, Enter and Escape.
Schools not on the list can still be typed in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
"UCSD", "uc san diego" and "san diego uc" all find University of
California, San Diego; "standford" finds Stanford. Common nicknames (UCLA,
SDSU, Cal Poly, UBC, U of T, ...) jump to the right school, UC / CSU / Cal
State expand to the full system name, filler words are ignored, and a typo
or two is forgiven.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
scripts/dev/create-dummy-applicant.mjs makes an @example.com test account and
submits its application signed in as that person, so every database rule
applies. /dev/test-accounts (npm run dev only, left out of builds) opens a
dummy applicant's portal in a tab that keeps its own login, so several can be
open next to the organizer console. The shared test password lives in
.env.local.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Test tabs shared one storage key, and Supabase syncs logins between tabs that
share a key, so each new test tab pushed its login into the others. Their
requests then went out as the wrong person, found no application, and showed
the empty form. Each dummy now has its own key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Portal pages showed the main site's title. Now the tab says where you are:
"Status · DataHacks Application Portal", "Hacker Application · ...",
"Organizer Console · DataHacks" and so on, and the site's title comes back
when you leave the portal. Also lists the six new dummy mentors and judges on
the dev test-accounts page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Picking accept, waitlist or reject now moves the row into that tab at once,
marked "Not released" (and what the applicant still sees, if they'd already
heard). Releasing stays the separate step that tells people.

Also takes the portal role away when a released acceptance is changed, so
someone moved from accepted to rejected loses the accepted-only pages. Their
check-in code already went.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
docs/testing/portal-test-plan.md is the checklist for teammates and test
agents: both application forms (including bad input), the organizer console,
check-in, and phone width, with a bug report format. The dummy-applicant script
can now make an account with no application, and ?go= on the dev test-accounts
page drops a tester straight onto a form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Hackers now get a page per topic instead of one handbook: Resources,
Transportation, Travel, Reimbursement, Run of show, Food, Mentorship,
Prizes, Tracks & challenges, Hardware, Devpost, then Mentors (open now
for testing) and the waiver. Content stays in handbookContent; Travel,
Reimbursement and Hardware have marked drafts. Mentors and judges keep
their handbook, and old hacker handbook links open Resources.

Pages load inside the portal frame with a dark fallback, the page behind
the portal is dark, and the other pages are fetched in the background.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Pasting "+1 858 555 0100" saved (185) 855-5010. A leading country code 1 is
now dropped once there are 11 digits. Found while checking a test agent's
report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Signing out clears every saved login in the browser so the next person starts
clean. In a dev test tab that also signed the organizer out of the console in
the next tab. Test tabs now clear only their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
"contact_email" puts a real address on a dummy's application, so a release or
blast can be checked end to end in an inbox you own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
- ci: type-check, test and build on every pull request. It already caught a
  tab-title test that only passed with a local .env, so the titles now live in
  their own module.
- deploy: give every portal page its own entry (the organizer console, the
  apply forms and the hacker pages were missing, so they were served as 404s).
  Hacker pages come from the same list the sidebar uses.
- DH26 gets its dates (April 18-19, 2026). With none, the check-in app sorted
  it first and checked people in to last year's event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
DH27BETA test data stays next to the real DH27, so:
- Resumes in storage were readable by an organizer of any event; now only
  by organizers of an event the resume was submitted to (migration).
- An application can no longer move to another event, by its applicant or
  an organizer of both events (migration, trigger).
- The signed-waiver flag in localStorage was one key for every event and
  every account on the browser; it is now per event and per person.
- Staging a decision also filters on the current event.
- docs/event-isolation.md: how each piece is scoped, and the checklist for
  switching EVENT_CODE to DH27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
scripts/backup/restore-drill.sh restores a backup (a file, the newest
nightly artifact, or a fresh dump taken exactly like supabase-backup) into
a throwaway Postgres 17 container, compares row counts and row checksums
with production, spot-checks a DH27BETA application, and deletes
everything. supabase-restore-drill runs it every Sunday. docs/backups.md
covers what is and isn't backed up and how to restore in an emergency.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
None in production lacked one (0 of 24 on 2026-10-23), so make it a rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgBz2EXk65565W9vj1xQ75
Keep each event's data to itself (DH27BETA vs DH27)
@TC960
TC960 merged commit e245e41 into main Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants