Skip to content

fix: mobile layout for the volunteer flow - #80

Merged
ethnjs merged 57 commits into
mainfrom
fix/mobile-volunteer-flow
Sep 17, 2026
Merged

ethnjs merged 57 commits into
mainfrom
fix/mobile-volunteer-flow

Conversation

@ethnjs

@ethnjs ethnjs commented Sep 15, 2026

Copy link
Copy Markdown
Owner

Summary

Makes the volunteer half of the app usable on a phone: auth, home, join, onboarding, the form viewer, tournament overview, profile view/edit, and account settings. TD tools are deliberately untouched.

The viewport switch is a CSS media query rather than a useIsMobile() branch. These pages prerender on the server, which has no viewport, so a JS check necessarily renders desktop on the first frame and corrects itself after hydration — the rail visibly flashed before becoming a drawer. useIsMobile survives only where the two arrangements are different elements rather than different styling.

Two touch bugs surfaced along the way that were broken at any screen size, not just phone width.

What changed

Frontend

Responsive foundation

  • New lib/useNavDrawer.tsx — holds the mobile drawer's open state for one shell, so the toggle can live in the Topbar while the drawer is its sibling.
  • New stylesheets carry every viewport-dependent rule: Sidebar, Topbar, Shell, Settings, Profile, FormFlow, Overview, PageHeader, Banner, FloatingEditButton, Onboarding.
  • New CenteredCard.module.css — one centred-card stylesheet for the auth pages and /join, replacing the per-route copies.
  • 100vh100dvh on every full-height shell; on iOS 100vh is the viewport with the URL bar retracted, so the layout overran the screen.

Navigation

  • The hover-expand rail becomes an off-canvas drawer under 640px — hover has no touch equivalent, so the rail was a dead 52px strip.
  • The drawer toggle renders inside the Topbar, not as a position: fixed overlay. A fixed element anchors to the viewport and a sticky Topbar to the document, so overscroll visibly separated them.
  • The tournament switcher renders twice — Topbar on desktop, inside the drawer on mobile — with CSS choosing; a 280px trigger doesn't fit beside the toggle and the avatar.
  • /dashboard renders no rail for non-admins, whose only entry was a link to the page they were on.

Volunteer pages

  • Profile experience tables render as cards under 640px; the view-edit controls were hover-revealed at left: -34px, so editing your own experience was impossible on a phone, not just cramped.
  • Profile header is a CSS grid so the name stays beside the avatar while contact details stack.
  • SettingsRow stacks label above control; a 220px label plus a 60%-capped control needs ~370px.
  • FormUpdateFlow gained the same container as FormFillFlow — it had none at all, at any width.
  • Overview widgets stack: checklist ring, repeat(4, 1fr) status tiles, 110px 1fr legend rows.
  • Onboarding shows the avatar with a Sign-out-only menu, and marks every gating question required.
  • Profile edit gained a guarded back link, using UnsavedChangesProvider via a new route layout.

Touch behaviour (broken at any width)

  • RankedList splits PointerSensor into MouseSensor + TouchSensor with a 200ms press, and rows drop to touch-action: manipulation. A 4px activation plus touch-action: none meant every drag was a reorder and a long list could never be scrolled past.
  • Hover handlers that wrote inline styles moved to CSS :hover in Sidebar, SettingsNav, Banner and FloatingEditButton — on touch they fire but never un-fire, leaving rows stuck.

Shared UI

  • PageHeadermin-width: 0 on the text block; a one-word serif heading's min-content width and a flex-shrink: 0 action overflowed the card. Latent on desktop too.
  • Banner — same min-width: 0 fix, plus wrapping with an order swap so the dismiss stays on the message's line.
  • ButtonGroup — wraps instead of overflowing; six shirt sizes don't fit a phone.
  • Tooltip — the bubble is now position: fixed off the trigger's measured rect instead of absolute inside the wrapper, so no ancestor's overflow: hidden can clip it. (fix — in the roster's roles cell and inside Popover's scrolling list it rendered as an arrow with no bubble under it.)
  • Tooltip — clamps its width to the viewport and nudges back inside near a screen edge, with the arrow shifting the opposite way to keep pointing at its trigger.
  • AvatarCircle — renders IconUser instead of "?" when there's no name yet.
  • TopbarshowWordmark and showAvatar take "mobile-only" / "logout-only"; the extraLeftPad prop is replaced by showNavToggle.
  • IconShield — solid shield with the check knocked out via fill-rule: evenodd.

Removed

  • CompetitionExperienceCompactEditor / VolunteerExperienceCompactEditor — exported, imported nowhere, and an earlier attempt at the same card-stack idea.
  • app/settings/constants.ts and app/(auth)/auth.module.css — no remaining importers.

Out of scope

  • TD screens — members, events, assignments, shifts, the form builder, sheets, tournament settings. They inherit the working drawer, but their bodies are unchanged and stay desktop-only.
  • The 16px font floor on text inputs that suppresses iOS focus zoom: added, then reverted on request. iOS will zoom when focusing an input.
  • GridWarp still runs a requestAnimationFrame loop on the home page for a cursor effect a touchscreen can't drive.

Test plan

  • pytest passes locally — N/A, no backend change on this branch
  • Migration included for every model change — N/A, no schema change
  • Clicked through the UI change in a browser

Automated

  • None — the frontend has no test runner (package.json has lint, no test).
  • tsc --noEmit, eslint, and next build clean after every commit.

Manual — verified on an iPhone during development

  • Opened and closed the drawer on the dashboard and inside a tournament; nav rows and the tournament switcher both close it.
  • Pulled to refresh on a settings page — the Topbar and its toggle move together.
  • Home page — sign in / sign up sit above the fold without scrolling.
  • Profile view and edit — experience entries render and edit as cards.
  • Account settings — email, verification banner and the date-of-birth tooltip all stay on screen.
  • Onboarding — signed out from the avatar menu before completing setup.

Manual — still to check on a device

  • Filling in a form end to end, including a ranked-choice question: swipe scrolls past the list, press-and-hold reorders it.
  • Reviewing flagged answers on a form already submitted (FormUpdateFlow).
  • Tournament overview widgets at phone width.
  • /join from an invite link.
  • Tooltips inside the roster's roles cell and Popover's scrolling list, on desktop.

…lear the option highlight when hovering the footer
@vercel

vercel Bot commented Sep 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
nexus Ready Ready Preview Sep 17, 2026 5:20am UTC

@railway-app

railway-app Bot commented Sep 15, 2026

Copy link
Copy Markdown

🚅 Deployed to the nexus-pr-80 environment in nexus

Service Status Web Updated
nexus ✅ Success (View Logs) Sep 17, 2026 at 5:20 am UTC

@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-80 September 15, 2026 02:52 Destroyed
@ethnjs ethnjs changed the title feat(ui): mobile layout for the volunteer flow fix: mobile layout for the volunteer flow Sep 15, 2026
@railway-app
railway-app Bot temporarily deployed to nexus / nexus-pr-80 September 17, 2026 05:19 Destroyed
@ethnjs
ethnjs marked this pull request as ready for review September 17, 2026 05:24
@ethnjs
ethnjs merged commit 49c23cd into main Sep 17, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant