A scalable, multi-city street sweeping alert platform. Each city gets its own branded experience — custom colors, logo, tagline, and data — all driven by a simple JSON config file.
| Layer | Choice |
|---|---|
| Frontend | React 18 (Vite, no TypeScript) |
| Animations | GSAP 3 |
| Routing | React Router v6 |
| Backend | Express.js (Node ESM) |
| Data | JSON files (swap for a real DB easily) |
notify-city/
├── server/
│ ├── index.js # Express API
│ ├── routes/
│ │ ├── cities.js # GET /api/cities, /api/cities/:id
│ │ └── streets.js # GET /api/cities/:id/streets?q=
│ └── data/
│ ├── boston/
│ │ ├── config.json ← city theme & settings
│ │ └── streets.json ← street sweeping data
│ └── chicago/
│ ├── config.json
│ └── streets.json
└── client/
└── src/
├── components/ # All UI components
├── context/ # AppContext (city config + notifications state)
└── App.jsx # Routes: / (city select), /:cityId (city app)
-
Create a data directory:
server/data/your-city/ ├── config.json └── streets.json -
Fill in
config.json— copy from an existing city and update:id,name,appName,taglinecolors— set your city's brand palette (primary, accent, secondary, etc.)hero— headline, subheadline, background gradientlogo— text, cityText, emoji iconlinks— official city sweeping page, city websitesweepingSeason— season dates and notecontact— department name, phone, email
-
Fill in
streets.json— an array of street objects:[ { "id": "main-st-downtown", "name": "Main Street", "neighborhood": "Downtown", "schedules": [ { "side": "North Side", "day": "Monday", "startTime": "8:00 AM", "endTime": "11:00 AM" }, { "side": "South Side", "day": "Thursday", "startTime": "8:00 AM", "endTime": "11:00 AM" } ] } ] -
Restart the server — your city is live at
http://localhost:5174/your-city.
That's it. No code changes required.
Start the API server:
cd server
npm install
npm run dev # runs on port 3001Start the React app:
cd client
npm install
npm run dev # runs on port 5174Then open http://localhost:5174.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/cities |
List all available cities |
| GET | /api/cities/:cityId |
Get a city's full config |
| GET | /api/cities/:cityId/streets |
List streets (supports ?q= search) |
| GET | /api/cities/:cityId/streets/:streetId |
Get a single street |
{
"colors": {
"primary": "#0C2340", // Header, footer, hero gradient start
"primaryLight": "#1a3a6b", // Hero gradient end, save button gradient
"accent": "#288BE4", // Buttons, links, active states
"accentDark": "#1565C0", // Accent hover state
"secondary": "#FF5E14", // Badge, highlights
"secondaryLight":"#FF7A3D", // Secondary hover
"background": "#F0F4F8", // Page background
"surface": "#FFFFFF", // Cards, panels
"surfaceAlt": "#E8EFF6", // Input backgrounds, secondary surfaces
"text": "#0C2340", // Body text
"textLight": "#4A6080", // Secondary text
"textMuted": "#8A9BB5", // Placeholder, labels
"border": "#C8D8E8", // Dividers, input borders
"success": "#1B7F3B", // Saved state, success messages
"warning": "#C9550A" // Warning text
}
}- Swap JSON for a database: Replace the file reads in
server/routes/with DB queries (PostgreSQL, MongoDB, etc.) - Auth + notification delivery: Add a users table and wire email (SendGrid), SMS (Twilio), or web push (web-push npm package)
- City subdomains: Serve
boston.yourapp.com→ injectcityId: "boston"from the request hostname - Admin panel: Add a
/admin/:cityIdroute to manage street data via UI instead of editing JSON files