UI never calls Firebase directly.
Layering:
- UI components/pages (
src/app/...) - Custom hooks (business logic) (
src/hooks/...) - Service layer (Firebase abstraction) (
src/services/...) - Firebase SDK instances (
src/lib/firebase.ts)
Examples:
- UI calls
useOwnerRooms().createRoom(...) useOwnerRoomscallsroomService+useUploadroomService/storageServicetalk to Firestore/Storage
src/
app/
components/
common/
ErrorBoundary.tsx
dashboard/
RoomForm.tsx
RoomList.tsx
ImageUploader.tsx
pages/
dashboard/
DashboardLayout.tsx
MyRooms.tsx
AddRoom.tsx
EditRoom.tsx
RoomDetails.tsx
context/
AuthContext.tsx
features/
rooms/
roomConstants.ts
roomPricing.ts
roomValidation.ts
hooks/
useRooms.ts
useUpload.ts
services/
authService.ts
roomService.ts
storageService.ts
types/
room.ts
Collection: rooms
Document: rooms/{roomId}
{
title: string,
description: string,
location: string,
roomType: "single" | "double" | "triple" | "dorm",
capacity: number,
pricing: {
type: "perStudent" | "perRoom",
perStudent?: { pricePerStudent: number, totalAtCapacity: number },
perRoom?: { occupancyPrices: { [occupancy: string]: number } },
priceMin: number,
priceMax: number
},
facilities: string[],
images: string[], // Firebase Storage download URLs
available: boolean,
ownerId: string,
ownerEmail: string,
ownerName: string,
createdAt: Timestamp,
updatedAt: Timestamp
}Storage:
rooms/{roomId}/{timestamp}_{index}_{fileName}
Suggested indexes (if you later move filtering server-side):
roomsbycreatedAt descroomsbyownerId asc, createdAt desc
- Auth:
AuthProviderlistens toonAuthStateChangedand exposeslogin/register/loginWithGoogle/logout. - Rooms:
useRooms()is for public listing/search.useOwnerRooms(ownerId)manages owner rooms + mutations.
- Optimistic UI:
deleteRoomremoves the card immediately and restores on failure.toggleAvailabilityflips UI immediately and restores on failure.updateRoomupdates fields immediately and restores on failure (images finalize after upload).
- Dashboard: room list + actions (
/dashboard) - Add/Edit wizard: multi-step form (
/dashboard/add-room,/dashboard/rooms/:roomId/edit) - Room details: details + history placeholder (
/dashboard/rooms/:roomId)
- Header is fixed and minimal (brand + identity + logout) with no secondary navigation.
- Primary actions live in page content (e.g. “Add Room” on the dashboard).