StreetWatch is a community-driven web application that empowers residents to report and track local infrastructure issues β potholes, broken streetlights, illegal dumping, water leaks, and more β and visualize them on an interactive map in real time.
- Overview
- Features
- Tech Stack
- Project Structure
- Getting Started
- Architecture & Key Decisions
- API Routes
- Security
- Deployment
- Contributing
- License
StreetWatch bridges the gap between residents and local authorities by making it dead simple to report neighborhood issues with photos and precise locations. Reports are pinned on a live Leaflet map, filtered to a 15-mile radius around the user using the Haversine formula, and updated in real time via Pusher WebSockets β so everyone in the community sees new reports the moment they're submitted.
- π Interactive Map β Leaflet-powered map with custom markers per issue category
- πΈ Photo Uploads β Drag-and-drop image upload backed by AWS S3 with pre-signed URLs
- π΄ Live Updates β Real-time issue feed powered by Pusher WebSockets; no refresh needed
- π Proximity Filtering β Haversine formula filters the map to a 15-mile radius around the user
- π Authentication β Secure sign-up / login flows via Better Auth
- π‘οΈ Bot Protection & Rate Limiting β ArcJet shields all sensitive API endpoints
- πΊοΈ Geocoding β Forward and reverse geocoding powered by LocationIQ for address search and coordinate-to-address resolution
- π Status Tracking β Each issue carries a status:
Pending,In Progress, orResolved - π Community Impact Dashboard β Stats and charts showing reported vs. resolved issues
- π± Responsive Design β Mobile-first layout with a dedicated mobile map/list toggle
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS v4, tw-animate-css |
| UI Components | Radix UI, shadcn/ui, Lucide React |
| Animation | Motion (Framer Motion v12) |
| Map | React Leaflet + Leaflet.js |
| Charts | Recharts |
| Auth | Better Auth |
| Database | PostgreSQL via Prisma ORM + Prisma Accelerate |
| File Storage | AWS S3 (via @aws-sdk) |
| Real-time | Pusher + pusher-js |
| Form Handling | React Hook Form + Zod |
| Geocoding | LocationIQ (forward & reverse) |
| Bot Protection | ArcJet |
| Notifications | Sonner |
StreetWatch/
βββ app/
β βββ (public)/
β β βββ (auth)/
β β β βββ login/
β β β βββ sign-up/
β β βββ layout.tsx
β βββ (root)/
β βββ _components/
β β βββ landing/ # Landing page sections
β β β βββ AreaIssues.tsx
β β β βββ CommunityImpact.tsx
β β β βββ CTA.tsx
β β β βββ Hero.tsx
β β β βββ MakingDifference.tsx
β β β βββ YourAreaIssues.tsx
β β βββ CallMap.tsx
β β βββ Countup.tsx
β β βββ Footer.tsx
β β βββ IssueMap.tsx # Main interactive map
β β βββ IssueMarker.tsx
β β βββ Map.tsx
β β βββ Navbar.tsx
β β βββ UserDropdown.tsx
β βββ report-issue/ # Issue submission flow
β βββ reported-issues/ # Browse & filter issues
β βββ _components/
β β βββ header.tsx
β β βββ issue-detail.tsx
β β βββ issue-list.tsx
β β βββ LocationError.tsx
β β βββ LocationSearchBar.tsx
β β βββ map-view.tsx
β β βββ MobileViewToggle.tsx
β β βββ ReportedIssuesContainer.tsx
β βββ action.ts
β βββ page.tsx
βββ api/
β βββ auth/
β βββ geocoding/
β βββ issues/
β βββ s3/
βββ components/
β βββ file-uploader/
β βββ ui/
β βββ AsyncBoundary.tsx
β βββ ErrorBoundary.tsx
βββ contexts/
β βββ LocationContext.tsx
βββ hooks/
β βββ use-construct-url.ts
β βββ use-facebook.ts
β βββ use-github.ts
β βββ use-google.ts
β βββ use-media-query.ts
β βββ use-signout.ts
βββ lib/
β βββ animation.ts
β βββ arcjet.ts
β βββ auth-client.ts
β βββ auth.ts
β βββ geo-utils.ts # Haversine formula & distance helpers
β βββ prisma.ts
β βββ pusher.ts
β βββ S3Client.ts
β βββ types.ts
β βββ utils.ts
β βββ zodSchema.ts
βββ prisma/
β βββ migrations/
β βββ schema.prisma
βββ providers/
β βββ PusherProvider.tsx
βββ public/
- Node.js >= 18.x
- npm or pnpm
- PostgreSQL database (or a Prisma Accelerate connection string)
- AWS S3 bucket with appropriate IAM permissions
- Pusher app credentials
- ArcJet API key
git clone https://github.com/your-username/streetwatch.git
cd streetwatch
npm installCreate a .env file in the root of the project:
# Database
DATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DATABASE"
# Better Auth
BETTER_AUTH_SECRET="your-auth-secret"
BETTER_AUTH_URL="http://localhost:3000"
# AWS S3
AWS_REGION="your-region"
AWS_ACCESS_KEY_ID="your-access-key"
AWS_SECRET_ACCESS_KEY="your-secret-key"
AWS_ENDPOINT_URL_S3="your-s3-endpoin-url"
AWS_ENDPOINT_URL_IAM="your-IAM-endpoin-url"
NEXT_PUBLIC_S3_BUCKET_NAME_IMAGES=="your-bucket-name"
# Pusher
PUSHER_APP_ID="your-app-id"
PUSHER_SECRET="your-pusher-secret"
NEXT_PUBLIC_PUSHER_KEY="your-pusher-key"
NEXT_PUBLIC_PUSHER_CLUSTER="your-cluster"
# LocationIQ
LOCATIONIQ_API_KEY="your-locationiq-api-key"
# ArcJet
ARCJET_KEY="your-arcjet-key"
# OAuth (optional)
GITHUB_CLIENT_ID="..."
GITHUB_CLIENT_SECRET="..."
GOOGLE_CLIENT_ID="..."
GOOGLE_CLIENT_SECRET="..."
FACEBOOK_CLIENT_ID="..."
FACEBOOK_CLIENT_SECRET="..."# Generate Prisma client
npx prisma generate
# Run migrations
npx prisma migrate deploy
# (Optional) Seed the database
npx prisma db seed# Development
npm run dev
# Production build
npm run build
npm startOpen http://localhost:3000 in your browser.
Rather than loading every issue in the database onto the map, StreetWatch fetches only issues within a 15-mile radius of the user's current location. The Haversine formula (implemented in lib/geo-utils.ts) calculates the great-circle distance between two coordinates, keeping the map focused and performant regardless of how many total issues exist in the system.
When a user submits a new issue, the server publishes an event to a Pusher channel. All connected clients subscribed via PusherProvider.tsx receive the update instantly and append the new marker to the map without a page refresh. This keeps the community map live and accurate.
The file uploader generates a pre-signed S3 URL server-side (via the /api/s3 route), then uploads directly from the browser to S3. This keeps large binary payloads out of the Next.js server and reduces latency significantly.
All write endpoints (/api/issues, /api/s3, auth routes) are protected by ArcJet. This adds bot detection, rate limiting, and shield rules without any infrastructure overhead, protecting the platform from spam submissions and abuse.
Better Auth handles credential-based sign-up/login as well as OAuth flows (GitHub, Google, Facebook). Session management is server-side, and the auth client is exposed via lib/auth-client.ts for use in client components.
| Method | Route | Description |
|---|---|---|
GET |
/api/issues |
Fetch issues (filtered by lat/lng radius) |
POST |
/api/issues |
Submit a new issue report |
PATCH |
/api/issues/[id] |
Update issue status |
POST |
/api/s3 |
Generate a pre-signed S3 upload URL |
GET |
/api/geocoding |
Forward (coordinates β address) & Reverse geocode (address β coordinates) using LocationIQ |
GET/POST |
/api/auth/[...all] |
Better Auth handler |
- ArcJet β Bot detection and rate limiting on all sensitive routes
- Pre-signed S3 URLs β Uploads go directly to S3; secrets never exposed to the client
- Zod validation β All form inputs and API payloads are validated against strict schemas (
lib/zodSchema.ts) - Better Auth β CSRF protection and secure session management built in
- Environment variables β All secrets are kept out of source control via
.env
StreetWatch is optimized for deployment on Vercel, but works on any platform that supports Next.js.
Vercel (recommended):
- Push your repository to GitHub
- Import the project in the Vercel dashboard
- Add all environment variables from the
.envtemplate above - Deploy β Vercel handles builds, edge functions, and CDN automatically
Other platforms: Ensure NODE_ENV=production, run npm run build, then npm start. A PostgreSQL instance must be accessible from the host.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature - Commit your changes:
git commit -m "feat: add your feature" - Push to the branch:
git push origin feature/your-feature - Open a Pull Request
Please make sure your code passes npm run lint before submitting.
Built with β€οΈ for communities everywhere.