Real-time campus room availability tracker. Built with React + Vite + Firebase (Firestore + Auth).
| Layer | Tech |
|---|---|
| Frontend | React 18, Vite |
| Styling | Tailwind CSS v3 |
| Routing | React Router v6 |
| State | Zustand (single store) |
| Charts | Recharts |
| Notifications | react-hot-toast |
| QR Codes | qrcode |
| Backend | Firebase Firestore + Auth |
campussync/
├── src/
│ ├── firebase/
│ │ ├── config.js # Firebase init (replace with your config)
│ │ ├── auth.js # Login / register / auth listener
│ │ └── rooms.js # Firestore queries + onSnapshot
│ ├── store/
│ │ └── useStore.js # Single Zustand store (authUser + rooms)
│ ├── hooks/
│ │ ├── useAuth.js # Auth bootstrap hook
│ │ └── useRooms.js # Single Firestore listener hook
│ ├── utils/
│ │ └── helpers.js # effectiveStatus, timeAgo, roomUrl
│ ├── components/
│ │ ├── Navbar.jsx
│ │ ├── RoomCard.jsx
│ │ ├── ProtectedRoute.jsx
│ │ └── QRModal.jsx
│ ├── pages/
│ │ ├── Login.jsx
│ │ ├── Dashboard.jsx
│ │ ├── RoomPage.jsx # /room/:roomId — also QR scan target
│ │ └── Admin.jsx
│ ├── App.jsx # Routes + global hooks
│ ├── main.jsx
│ └── index.css
├── scripts/
│ └── seed.js # One-time Firestore room seeder
├── firestore.rules # Security rules (deploy to Firebase)
├── firestore.indexes.json
├── firebase.json # Hosting + Firestore config
├── vite.config.js
├── tailwind.config.js
├── postcss.config.js
└── package.json
- Go to console.firebase.google.com
- Create a new project
- Enable Authentication → Email/Password
- Create a Firestore Database (start in test mode, then apply rules)
Edit src/firebase/config.js and replace the placeholder values with your project's config:
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
...
};Find these values in: Firebase Console → Project Settings → Your Apps → Web App.
npm install
npm run dev# Install admin SDK separately (not included in app deps)
npm install firebase-admin
# Set your service account key
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/serviceAccountKey.json"
# Edit scripts/seed.js and set your projectId, then:
node scripts/seed.jsnpm run firebase:login
npm run deploy:rules
npm run deploy:indexesnpm run deploy:hostingnpm run deploy:allnpm run deploy:preview| Role | Permissions |
|---|---|
student |
View room status (read-only) |
faculty |
View + toggle room status (free ↔ occupied) |
admin |
View + toggle + add/delete rooms + analytics |
Roles are set in the Firestore users collection. During registration via the UI, users can self-select a role (suitable for demo/MVP). For production, restrict role assignment to admin-only writes.
When faculty marks a room as occupied:
autoResetAtis set tonow + 2 hours- The UI computes
effectiveStatus(room)client-side - If
autoResetAt < now, the room displays as free without a DB write - This avoids any backend/cloud function requirement for the MVP
Firestore /rooms ──onSnapshot──▶ Zustand rooms[] ──▶ All UI components
(1 listener) (auto re-render)
- One listener, attached in
useRoomsListenerafter auth resolves - Zero polling, zero manual refresh
- Listener detaches automatically on auth logout
| Path | Component | Access |
|---|---|---|
/login |
Login | Public |
/dashboard |
Dashboard | All roles |
/room/:roomId |
RoomPage | All roles (toggle if faculty/admin) |
/admin |
Admin | Admin only |
{
"id": "auto-generated",
"name": "Computer Lab 101",
"building": "Engineering",
"floor": "3",
"type": "lab",
"capacity": 40,
"status": "free",
"updatedAt": "<Timestamp>",
"updatedBy": "<userId | null>",
"note": "Projector under maintenance",
"autoResetAt": "<Timestamp | null>",
"reservedBy": "<userId | null>",
"reservedUntil": "<Timestamp | null>",
"features": ["projector", "whiteboard"],
"coordinates": { "x": 120, "y": 45 },
"createdAt": "<Timestamp>"
}{
"id": "<Firebase Auth UID>",
"name": "Jane Doe",
"email": "jane@university.edu",
"role": "faculty",
"createdAt": "<Timestamp>"
}{
"roomId": "<roomId>",
"userId": "<userId>",
"action": "occupied",
"timestamp": "<Timestamp>",
"note": "Optional action note"
}- Single Firestore listener for all rooms (not per-room)
- Flat collections — no nested subcollections
- All computed values (effectiveStatus, timeAgo) derived in UI, not stored
- 1 action = 1 Firestore write
- Zustand store has no selectors overhead — components subscribe to minimal slices
- Replace
src/firebase/config.jswith real credentials - Deploy
firestore.rules(remove test mode) - Restrict role self-selection in registration (admin-only role assignment)
- Add Firebase App Check for abuse prevention
- Consider Cloud Functions for reliable auto-reset (vs client-side)
- Add
logscollection writes for full analytics