Back to Projects
Professional2026

PotholeWatch - Pavement Assessment Dashboard

Frontend / Web App Lead (collaborated with API + edge-AI teams)

PotholeWatch - Pavement Assessment Dashboard screenshot

Overview

A real-time dashboard that turns raw dashcam footage into actionable pavement-defect intelligence. While the dashcam hardware (running YOLOv26-seg on edge AI) and detection API were built by a separate team, I owned the entire operator-facing web app: a live detection feed, severity analytics, work-order management, role-based user administration, and a PWA install story for field operators. The system is in production, deployed at pothole.watch, and used by road crews to triage and dispatch repairs.

Tech Stack

React 18TypeScriptVite 5React Router v7TanStack Query v5Tailwind CSS 3Supabase Auth + StorageExpress + Zod (API glue)Vercel (frontend + serverless API)vite-plugin-pwa (Workbox)AxiosReact Image Cropreact-hot-toast

Screenshots

PotholeWatch - Pavement Assessment Dashboard screenshot
PotholeWatch - Pavement Assessment Dashboard screenshot
PotholeWatch - Pavement Assessment Dashboard screenshot
PotholeWatch - Pavement Assessment Dashboard screenshot

The Problem

Road crews needed a way to monitor live pothole detections from edge-AI dashcams, prioritize repairs by severity, and dispatch work orders - all from the field. The detection pipeline (YOLOv26-seg running on edge hardware) and the upstream API were handled by another team, but the operator experience didn't exist: there was no web app, no way to manage who could log in, no offline story for spotty connections on remote routes, and no admin tooling to grant or revoke crew access.

The Solution

Built the full operator-facing web app from the ground up: a responsive dashboard with a live detection feed, severity distribution charts, a detection history table, an interactive severity-coded map, and a work-orders management screen. Wired auth through Supabase with role-based access (admin / member), implemented a user-management CRUD with avatar uploads (with image cropping) and a CSV-exportable activity log. Migrated the data layer to TanStack Query for caching, optimistic updates, and request deduplication. Made the app installable as a PWA so field operators can pin it to their phone home screen with a real install banner and an in-app update flow that only prompts when a new build is waiting. Server-side: enforced role gates on user write endpoints and stubbed an admin notification hook for the upcoming Web Push integration.

Challenges

  • Translating raw detection payloads from a separate team's API into a dashboard that helps operators prioritize at a glance - severity donuts, color-coded map markers, sortable tables, and a live feed that updates without overwhelming the UI
  • Designing role-based access that works on both sides of the wire: server-side requireAdmin middleware on write endpoints (defense in depth) plus client-side gating so members see a clean read-only view of the user list
  • Building a PWA that installs on localhost for testing, ships an update prompt only when the app is installed (not in a regular browser tab), and uses app-shell-only offline caching so live detection data never shows up stale
  • Wiring Supabase Storage avatar uploads through an Express serverless function - getting the bucket public flag and RLS policy right so service-role uploads succeed without leaking unauthenticated access
  • Migrating from ad-hoc fetch+useState to TanStack Query without rewriting every page at once - incremental adoption across detections, materials, users, logs, and dashboard widgets

Key Learnings

  • Server-side role gating is non-negotiable even when the UI hides write actions - the requireAdmin middleware in api/index.ts is the real gate, and the client-side isAdmin flag is just a UX layer on top
  • PWA update flows need a 'prompt' registration type (not 'autoUpdate') for installed users, since there are no tabs to close - combined with a standalone-only banner to avoid noise in regular browser tabs
  • TanStack Query's invalidateQueries pattern makes role-aware data refreshes trivial: after a create/update/delete, one line invalidates the ['users'] key and the list, count, and dependent widgets all rehydrate consistently
  • Stubbing the future integration point first (notifyAdminsOfNewUser logging the recipient list) gives the eventual Web Push adapter a single, predictable hook to replace - and lets you test the recipient-filter logic today