Case study · 2026

Campus Accessibility Map

A real-time campus accessibility mapping platform built with a 6-person agile team — React 19, Supabase, and Leaflet — surfacing live lift and ramp status and routing alerts to the students who need them.

Role

Team of 6 · Agile

Stack

React 19, Supabase, Leaflet.js, Vite, Vitest, Playwright

Links

Available on request
automated Vitest tests
67
RLS user roles
3
real-time subscriptions
2
PostgreSQL tables
6
WCAG 2.1 AA pages
5

Highlights

  • Built a full-stack campus accessibility mapping app with React 19, Supabase (PostgreSQL + Auth + Realtime) and Leaflet.js, as part of a 6-person agile team.
  • Designed a real-time notification system on Supabase PostgreSQL replication, delivering instant in-app alerts based on personalised accessibility preferences with no page refresh.
  • Engineered a two-path notification architecture — reporter-specific alerts vs preference-matched broadcasts — with bulk-insert optimisation to avoid duplicate messages.
  • Implemented row-level security policies enforcing role-based access across three user tiers (user, moderator, admin) with granular INSERT/SELECT/UPDATE/DELETE control.
  • Built a community moderation platform for accessibility officers to verify, reject and resolve reports, cascading status changes to map markers in real time.
  • Applied ARIA attributes and semantic HTML to meet WCAG 2.1 AA, tested with VoiceOver and axe-core across a 67-test Vitest + Playwright suite.

Context

University campuses have no centralised, real-time source of accessibility information. Students and staff with mobility, sensory or cognitive impairments must navigate without knowing whether a lift is out of service, a ramp is under repair, or a route is temporarily blocked. This disproportionately affects wheelchair users, visually impaired students, and neurodivergent individuals who rely on predictable, consistent routes — and accessibility officers have no efficient way to receive, verify and communicate infrastructure issues at scale. The project was developed for a second-year Software Engineering module at the University of Birmingham, addressing UN Sustainable Development Goal 11.7: universal access to safe, inclusive and accessible public spaces.

Solution

An interactive Leaflet.js map displays campus buildings as colour-coded markers — green for fully operational, amber for features under repair, red for inaccessible — derived from the worst-case status of each building's individual accessibility features. Users with saved preferences (lift access, ramp access, tactile paving, wide paths) get the map automatically filtered to relevant buildings on load. Any authenticated user can file a report against a specific feature — a particular lift, ramp, or set of automatic doors — which enters a pending queue and moves through a full lifecycle: pending, verified, under repair, resolved or rejected. Accessibility officers work from a real-time moderation dashboard where new reports appear instantly via Supabase Realtime change subscriptions; actioning a report atomically updates the report status, the feature status (which immediately recolours the map marker), and notifications for affected users. A key design decision was modelling accessibility features at the individual-feature level rather than per building, so a status change to one lift never affects the building's ramp and notifications can target users who care about that specific feature type. Supabase was chosen over the originally planned Django backend for its integrated auth, real-time subscriptions and row-level security, and Leaflet over the Google Maps API since routing wasn't needed and its custom-marker API gave full control over status colour-coding with no API-key overhead.

My contribution

  • The complete notification system — database schema, RLS policies, the two-path notification architecture, and the real-time subscription on the notifications page.
  • The moderation platform — report queue, approve/reject/resolve workflow, and the real-time report feed.
  • AuthContext design — session management, profile and role fetching, and the updateProfile function.
  • Database schema and RLS policy structure.
  • Test plan development and automated testing setup (Vitest + Playwright).
  • Accessibility implementation — ARIA attributes, keyboard navigation, and screen-reader compatibility.
  • Debugging and resolving merge conflicts across shared files after branch integration.

Challenges

The preference-based broadcast could match hundreds of users, so instead of looping I used Supabase's bulk insert — passing an array to .insert() — collapsing it to a single database round trip regardless of match count. Row-level security initially blocked moderators from inserting notifications for other users, since the standard auth.uid() = user_id policy fails when the IDs differ; the fix was a separate INSERT policy using auth.uid() is not null, while the existing SELECT policy still ensures users only read their own. Real-time postgres_changes payloads only carry the raw changed row with no joins, so new reports arrived without location or feature type — resolved with a follow-up fetchSingleReport call keyed on the payload ID before prepending to state. Finally, Supabase Realtime WebSocket connections drop when a Mac sleeps, so notifications sent during sleep never appeared; a visibilitychange listener re-runs fetchNotifications() when the tab reactivates, catching missed messages on wake without duplicating subscriptions.