Real-time emergency response platform — coordinators report incidents, volunteers get push-notified on their phones in under 5 seconds.
Live: surge-aid.vercel.app
- A coordinator submits an emergency at
/report - Claude AI (Haiku) classifies severity (CRITICAL / HIGH / MEDIUM / LOW) and recommends volunteer skill sets — P50 latency ~1600ms
- All volunteers receive a push notification on their phone within seconds via ntfy — works on Android and iOS, no browser required
- Open-browser volunteers also get a live toast alert via Supabase Realtime WebSocket
- The coordinator dashboard tracks incident trends, resolution rates, and AI latency metrics
Homepage — live community impact stats

Emergency Report — AI-classified incident feed with skill badges

Coordinator Dashboard — severity distribution, resolution rate, AI latency KPIs

| Feature | Detail |
|---|---|
| AI triage | Claude Haiku classifies severity + recommends volunteer skills; classify latency tracked (P50/P95) on dashboard |
| ntfy push notifications | Delivered to phones via the ntfy app — no browser permissions, works on every platform including iOS |
| Realtime WebSocket | Supabase broadcast sends severity-coded toasts to open browser tabs in <1s |
| Incident lifecycle | ACTIVE → RESOLVED / FALSE_ALARM; live status on feed and map |
| Full-text search | GIN index on PostgreSQL; paginated history at /history |
| Coordinator dashboard | Severity distribution, status breakdown, 14-day trends, hour-of-day heatmap, AI latency KPI |
| Interactive map | Leaflet with USGS earthquake overlay + community incidents |
| Auth | Clerk protects the coordinator write path; volunteer alert-receive path is auth-free |
| CI | GitHub Actions runs npm run build + Playwright E2E on every push |
| Layer | Technology | Signal |
|---|---|---|
| Framework | Next.js 16 (App Router), TypeScript | Current meta-framework conventions |
| Styling | Tailwind CSS v4 | — |
| Database | Supabase (PostgreSQL + RLS + GIN index) | Schema design, indexing, row-level security |
| Real-time | Supabase Realtime WebSocket broadcast | Pub/sub beyond polling |
| Push notifications | ntfy (HTTP pub/sub) | Cross-platform phone delivery without browser APIs |
| AI classification | Anthropic Claude Haiku (structured JSON output + fallback) | LLM integration with validation |
| Auth | Clerk (Google + email) | Production-grade identity, not DIY |
| Maps | Leaflet + React-Leaflet | — |
| External data | USGS Earthquake GeoJSON API | — |
| CI/CD | GitHub Actions → Vercel | End-to-end ownership |
Coordinator submits incident at /report
→ POST /api/classify Claude: severity + action + recommended skills (~1600ms P50)
→ Supabase INSERT disasters table, status = ACTIVE
→ POST /api/trigger-alert
├── Supabase Realtime broadcast → AlertToast on open browser tabs (<1s)
└── ntfy HTTP POST → Phone notification via ntfy app (<3s)
- Node.js 20+
- Supabase project
- Clerk application
- Anthropic API key
- A free ntfy topic name (no account needed)
git clone https://github.com/Kush1601/SurgeAid.git
cd SurgeAid
npm install
cp .env.example .env.local# Supabase
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=
# Anthropic
ANTHROPIC_API_KEY=
# Clerk
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
CLERK_SECRET_KEY=
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/report
# ntfy — pick a hard-to-guess topic name (e.g. surgeaid-abc123)
# Volunteers subscribe to this topic in the free ntfy app (ntfy.sh)
NTFY_TOPIC=surgeaid-alerts
NEXT_PUBLIC_NTFY_TOPIC=surgeaid-alerts- Choose a topic name — use a hard-to-guess string like
surgeaid-k9x2m7(topics are public on ntfy.sh) - Set
NTFY_TOPICandNEXT_PUBLIC_NTFY_TOPICin.env.localto that topic - Volunteers open
/volunteer, register, then install the ntfy app and subscribe to the same topic - That's it — no account, no browser permissions, works on Android and iOS
Run all migrations in the Supabase SQL Editor in order:
supabase/migrations/20260602000000_initial_schema.sql
supabase/migrations/20260602000001_add_status_and_skills.sql
supabase/migrations/20260602000002_usgs_unique_constraint.sql
supabase/migrations/20260603000000_dashboard_rpc_functions.sql
supabase/migrations/20260603000001_push_subscriptions.sql
supabase/migrations/20260603000002_classify_ms.sql
npm run devOpen http://localhost:3000.
SurgeAid/
├── app/
│ ├── api/
│ │ ├── classify/ # POST — AI severity + skill classification (Anthropic)
│ │ ├── dashboard/ # GET — Analytics: GROUP BY severity/status via RPC
│ │ ├── disasters/ # GET — USGS earthquake feed proxy
│ │ ├── search/ # GET — Full-text search (GIN index, paginated)
│ │ ├── stats/ # GET — Live volunteer + incident counts
│ │ ├── trigger-alert/ # POST — Supabase Realtime broadcast + ntfy publish
│ │ └── seed-usgs/ # POST — Admin: upsert 24h USGS earthquakes
│ ├── components/
│ │ ├── AlertToast.tsx # Severity-coded realtime toast overlay
│ │ ├── MapDisasters.tsx # Leaflet map (USGS + Supabase markers)
│ │ └── NavBar.tsx
│ ├── hooks/
│ │ └── useSupabaseAlerts.ts # Supabase Realtime broadcast subscription
│ ├── dashboard/ # /dashboard — Coordinator analytics (auth required)
│ ├── history/ # /history — Incident search
│ ├── map/ # /map — Live response map
│ ├── report/ # /report — Submit emergency + live feed (auth required)
│ ├── sign-in/ # /sign-in — Clerk auth
│ ├── volunteer/ # /volunteer — Signup + ntfy subscription instructions
│ └── page.tsx # Homepage with live stats
├── lib/
│ ├── supabase.ts
│ └── types.ts
├── supabase/migrations/ # 6 SQL migration files
├── .github/workflows/ci.yml # GitHub Actions CI
└── Dockerfile # Multi-stage Next.js build
volunteers
id uuid, name text, phone text, skills text, subscribed boolean, created_at timestamptzdisasters
id uuid, title text, description text, lat float8, lng float8,
severity text, -- CRITICAL | HIGH | MEDIUM | LOW | UNKNOWN
action text, -- AI-generated first-responder action brief
recommended_skills text, -- AI-generated comma-separated skill sets
classify_ms integer, -- Claude API latency in ms (P50/P95 shown on dashboard)
status text, -- ACTIVE | RESOLVED | FALSE_ALARM
created_at timestamptzFull-text search: GIN index on to_tsvector('english', title || ' ' || description)
| Method | Route | Description |
|---|---|---|
GET |
/api/stats |
Live volunteer + incident counts |
GET |
/api/disasters |
USGS earthquake GeoJSON proxy (24h) |
GET |
/api/search?q=&page=0 |
Full-text incident search, paginated |
GET |
/api/dashboard |
Analytics: severity/status GROUP BY, 14-day trends, AI latency |
POST |
/api/classify |
Claude severity + action + skills; stores classify_ms |
POST |
/api/trigger-alert |
Supabase Realtime broadcast + ntfy push publish |
| Token | Value | Usage |
|---|---|---|
| Primary Red | #c1121f |
CRITICAL severity, alerts, CTAs |
| Deep Navy | #003049 |
Volunteer actions |
| Warning Amber | #fbbf24 |
ACTIVE status, MEDIUM severity |
| Success Green | #22c55e |
RESOLVED status, LOW severity |
| Blue | #3b82f6 |
Skill badges, volunteer charts |
| Warm Cream | #fefbf3 |
Page background |
MIT