Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Pewiz/ulagos360/llms.txt

Use this file to discover all available pages before exploring further.

ULagos 360° is built on a strict two-tier architecture. The frontend is a fully static React 19 single-page application compiled by Vite and deployable to any static host (Vercel, Netlify, etc.). The backend is a separate Socket.IO server running on Railway. The two tiers never communicate over HTTP REST — every state change, user registration, and real-time broadcast flows through a single persistent WebSocket connection managed by the useSocketConnection hook.

Component Tree

The rendered UI is composed of a shallow, predictable hierarchy rooted at <App>:
main.jsx
└── <App>
    ├── <Header>          — user identity, connection badge, logout/recovery controls
    ├── <StatsOverview>   — live aggregate counts (available, occupied, reserved…)
    └── <SpacesGrid>
        └── <SpaceCard>   — per-space status badge + action buttons (one per space)
<App> owns no local React state (useState/useRef). It destructures spaces, loading, connected, currentUser, and all action handlers from useRealTimeSpaces, and separately calls usePersistentBackup() to obtain recoverFromBackup for the emergency recovery handler wired to <Header>.

Hook Layer

Three custom hooks form the application’s logic layer, each with a clearly bounded responsibility.

useRealTimeSpaces

Primary orchestration hook consumed by App.jsx. Composes the Zustand store with useSocketConnection, wires optimistic updates to socket emissions, and sets up the 120-second forceSync interval plus beforeunload / visibilitychange flush handlers.

useSocketConnection

Owns the Socket.IO client lifecycle: creates the socket with io(), handles connect / disconnect / connect_error, registers all server-to-client event listeners, and exposes sendSpaceUpdate, forceServerSync, loginUser, and disconnectSocket to callers.

usePersistentBackup

Runs a setInterval every 15 seconds to snapshot spaces into two localStorage keys: ulagos360_spaces_backup (raw spaces map) and ulagos360_emergency_backup (timestamped envelope). Also flushes on visibilitychange (tab hidden) and beforeunload. Exposes recoverFromBackup() for the emergency recovery action in <Header>.

Data Flow

1

Store initialisation

On mount, useRealTimeSpaces calls initializeSpaces(). If the spaces-storage localStorage key is empty (first visit or after logout), the store is seeded from the SPACES_DATA constant catalogue, setting every space to disponible. If persisted data already exists, this step is a no-op and loading is set to false immediately.
2

Socket connection and registration

useSocketConnection opens a Socket.IO connection to https://ulagos360-backend-production.up.railway.app (polling → WebSocket upgrade). On connect, if a user is found in localStorage, the hook emits register_user followed immediately by get_all_spaces. The server responds with a full state snapshot on one of its aliased state events (spaces_state, all_spaces_state, etc.), which hydrates the Zustand store via updateSpace.
3

Optimistic space update

When a tutor taps an action button on a SpaceCard, handleUpdateSpaceStatus in useRealTimeSpaces fires in two steps — first calling updateSpaceStatus() on the Zustand store (UI updates instantly), then passing the returned updates object to sendSpaceUpdate(), which emits the update_space Socket.IO event to the backend.
4

Server broadcast to other clients

The backend rebroadcasts the change as a space_updated event. Every other connected client’s useSocketConnection instance receives it and calls updateSpace(spaceId, updates), keeping all panels in sync without polling.
5

Fallback sync

If the server has not responded with full state within 5 seconds of registration, registerUserAndRecoverState emits sync_spaces to upload the client’s local state. Additionally, forceServerSync is called 1 second after a user becomes connected, and the 120-second interval in useRealTimeSpaces calls forceSync to re-write the Zustand persist snapshot.

Repository Structure

ULagos 360° ships as two independent repositories. This document covers the frontend only. The backend Socket.IO server lives in a separate repo and is deployed exclusively on Railway. To point the frontend at a different backend, update the server URL constant inside src/hooks/useSocketConnection.js.
The frontend source layout:
src/
├── App.jsx                      # Root layout; consumes useRealTimeSpaces
├── components/
│   ├── Header.jsx               # User identity, connection indicator, actions
│   ├── StatsOverview.jsx        # Aggregate space statistics bar
│   ├── SpacesGrid.jsx           # Category-grouped grid of SpaceCards
│   └── SpaceCard.jsx            # Individual space tile with status controls
├── constants/
│   └── spaces.js                # SPACES_DATA catalogue + SPACE_STATUS enum
├── hooks/
│   ├── useRealTimeSpaces.js     # Orchestration hook (consumed by App)
│   ├── useSocketConnection.js   # Socket.IO lifecycle and event wiring
│   └── usePersistentBackup.js   # 15 s localStorage backup + recovery
└── stores/
    └── spacesStore.js           # Zustand store with persist middleware

Key Dependencies

PackageVersionRole
react19.1.0UI framework
socket.io-client4.8.1WebSocket + polling transport
zustand5.0.7Global state with persist middleware
tailwindcss4.1.11Utility-first styling
vite7.0.4Build tool and dev server
lucide-react0.536.0Icon library

Build docs developers (and LLMs) love