Intelligent landslide monitoring & early-warning dashboard. React + TypeScript + Vite, styled with Tailwind CSS v4, animated with Framer Motion, and wired to Firebase Realtime Database for live field-sensor data.
The app runs out of the box in simulation mode — a clearly labeled banner and generated data — until you supply real Firebase credentials. No code changes are required to go live; only environment variables.
npm install
cp .env.example .env # fill in your Firebase web config
npm run devOpen the printed local URL. With no .env values set, the dashboard runs entirely on simulated data so you can preview every section immediately.
- Create a Realtime Database in your Firebase project (production mode).
- Copy the six values from Project settings → General → Your apps → Web app into
.env. - Restart
npm run dev. Once all required variables are present, Terra Sentinel switches out of simulation mode automatically — thesimulatedbanner disappears and every listener connects to your real project.
All paths are declared in one place — src/lib/firebase/paths.ts — so you can repoint the dashboard at a different schema without touching any component:
stations/primary/live → { moisture, temperature, humidity, tilt, rainfall, battery, signal, timestamp }
stations/primary/history → { <pushId>: { ...same shape as live } }
stations/primary/alerts → { <pushId>: { timestamp, severity, reason, values, dismissed? } }
stations/primary/health → { firebaseLatencyMs, lastSyncTimestamp, sensorUptimePct, espOnline, batteryPct, apiStatus }
stations/primary/info → { name, latitude, longitude, elevationMeters }
If your ESP32 firmware writes to different paths, edit dbPaths in src/lib/firebase/paths.ts only.
The dashboard never writes to the database — it only calls onValue. Lock writes down at the database level so no client, including this one, can write without explicit future authentication:
{
"rules": {
"stations": {
".read": "auth != null",
".write": false
}
}
}Swap auth != null for true only for early local testing on a non-production project — never in a deployed app. When you add Firebase Authentication later, gate .read per user/role instead.
src/
components/
layout/ Navbar, Footer, loading screen, empty/error states
hero/ Landing hero with parallax
status/ Live status bar
sensors/ Sensor cards + sparkline
chart/ Multi-dataset realtime graph
history/ Searchable, sortable, exportable history table
map/ Station location + map placeholder
alerts/ Animated alert timeline
risk/ Signature contour-ring risk gauge
health/ System diagnostics grid
ui/ Shared primitives (Section, RevealOnScroll, Counter)
hooks/ useTerraData (data layer), useDarkMode
lib/
firebase/ config.ts (init), paths.ts (schema), listeners.ts (subscribe/unsubscribe helpers)
mockData.ts Simulation data generator
utils.ts Formatting, CSV export, class merging
types/ Shared TypeScript types + risk-scoring logic
| Token | Value |
|---|---|
| Primary | #1E8E3E |
| Accent | #4CAF50 |
| Danger | #D32F2F |
| Warning | #F9A825 |
| Background | #F7F9FA |
| Type | Inter (SF Pro Display fallback) |
The signature visual element is the risk gauge: concentric contour-line arcs, styled after a topographic survey map, that fill outward as the composite risk score rises — a direct visual echo of the terrain the system is watching, rather than a generic circular progress ring.
Dark mode is class-based (:root.dark), remembers the user's choice in localStorage, and follows the OS preference when set to "system." All animation respects prefers-reduced-motion.
- Every Firebase listener is registered through
subscribePathinsrc/lib/firebase/listeners.ts, which always returns an unsubscribe function; every consuming hook cleans up on unmount. - Connection state is derived from Firebase's own
.info/connectedpath, not guessed from listener activity. - On disconnect, the hook backs off 4 seconds before re-attaching listeners rather than looping immediately, so it never spams the database.
- The dashboard is strictly read-only:
src/lib/firebaseexposes no write helpers at all.
npm run build # type-checks with tsc -b, then builds with Vite
npm run preview # serve the production build locallydist/ is a static bundle — deploy it to Firebase Hosting, Vercel, Netlify, or any static host.
Firebase Hosting:
npm install -g firebase-tools
firebase login
firebase init hosting # point the public directory at "dist"
npm run build
firebase deployRemember to add your production domain to the Firebase project's Authorized domains list once Authentication is enabled.
This app is a client-rendered Vite SPA rather than server-rendered. Terra Sentinel's entire reason for existing is a live onValue subscription to Realtime Database — that connection only exists in the browser, so a server-rendered shell would still ship a client bundle to open it and would add build complexity (a Node runtime, hydration, streaming) without reducing time-to-first-live-reading. If you later add public marketing pages that benefit from SSR/SEO independent of the live dashboard, the cleanest path is a separate Next.js (or similar) marketing site that links into this dashboard, rather than converting the realtime app itself.
npm run lint # eslint . (TypeScript + React Hooks rules)
npm run format # prettier --write .