Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/VasquezRivero92/HabboCafe/llms.txt

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

HADOS is a single-page application (SPA) built with React 19 and TypeScript, bundled by Vite, and backed entirely by Firebase. There is no custom API server — all application state lives in Firestore, all authentication is handled by Firebase Auth, and the front end communicates directly with Firebase through the official JavaScript SDK. The production environment is served by a lightweight Node.js static file server with SPA fallback routing.

Tech Stack

React 19 + TypeScript

The entire UI is a single App.tsx component tree. React 19’s concurrent renderer manages the leaderboard, modals, admin panels, and auth screens. TypeScript enforces strict typing across all Firestore document shapes (HabboUser, Dado) and component state.

Vite 8

Vite drives both development (HMR on localhost:5173) and production builds. The build pipeline runs the TypeScript compiler first (tsc -b) then Vite’s Rollup-based bundler, emitting optimized assets into the dist/ directory.

Firebase SDK 12

Firebase provides both Firestore (document database with real-time listeners) and Authentication (email/password sign-in). The SDK is imported modularly — only the specific functions used are bundled, keeping the output lean.

Node.js Static Server

server.js is a dependency-free Node.js HTTP server that serves the Vite dist/ output in production. It handles MIME types for all standard web assets and implements SPA fallback routing so deep links never return a 404.

Lucide React 1.23

All icons throughout the application — navigation, buttons, badges, modals — come from Lucide React. Icons are imported individually, ensuring tree-shaking keeps the bundle small.

Outfit Font

The design uses the Outfit typeface (Google Fonts) loaded via @import in index.css. CSS custom properties define the full design token system: colors, backgrounds, borders, and typography.

Firebase Services

HADOS uses two Firebase services, both initialized in src/firebase.ts:
src/firebase.ts
import { initializeApp } from 'firebase/app';
import { getFirestore } from 'firebase/firestore';
import { getAuth } from 'firebase/auth';

const firebaseConfig = {
  apiKey: "...",
  authDomain: "habbocafe-91afc.firebaseapp.com",
  projectId: "habbocafe-91afc",
  storageBucket: "habbocafe-91afc.firebasestorage.app",
  messagingSenderId: "601105669821",
  appId: "1:601105669821:web:6c0f2a6251df421d3d61d4",
  measurementId: "G-C3PP9031GD"
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);   // Firestore database instance
export const auth = getAuth(app);      // Auth instance
Both db and auth are exported as named singletons and imported throughout App.tsx.

Firestore Collections

HADOS uses exactly two top-level Firestore collections.

users

Each document represents a single player profile. The document ID is the Firebase Auth UID when the user self-registers, or an auto-generated ID when an intermediary or admin manually creates a player entry.
interface HabboUser {
  id: string;           // Firestore document ID (= Auth UID for self-registered users)
  username: string;     // Habbo Hotel in-game username
  email: string;        // Email address (dummy suffix for manually-created players)
  points: number;       // Current tournament score (floating-point supported)
  previousRank: number; // Rank before the last score update
  currentRank: number;  // Rank after the last score update
  avatarUrl: string;    // Full habbo.es imaging API URL
  createdAt: string;    // ISO 8601 timestamp
  role: 'global_admin' | 'dado_admin' | 'intermediario' | 'jugador';
  dadoId?: string;      // ID of the Dado this player belongs to (absent for global_admin)
}

dados

Each document represents a tournament group. The document ID is auto-generated by Firestore.
interface Dado {
  id: string;
  name: string;              // Display name of the Dado (e.g. "Dado VIP")
  code?: string;             // Optional join code (reserved for future use)
  adminId?: string;          // Auth UID of the assigned dado_admin
  adminEmail?: string;       // Email of the assigned dado_admin
  adminName?: string;        // Habbo username of the assigned dado_admin
  createdAt: string;         // ISO 8601 creation timestamp
  startDate?: string;        // Tournament start date (YYYY-MM-DD)
  endDate?: string;          // Tournament end date (YYYY-MM-DD)
  rules?: string[];          // Array of rule strings (one per line)
  schedule?: string[];       // Array of schedule strings (one per line)
  totalPrize?: string;       // Prize pool description (e.g. "3000 USDT")
  qualifyingCount?: number;  // Number of players that qualify for finals
}
There is no subcollection structure. All users across all Dados live in the flat users collection and are filtered by dadoId at query time using Firestore’s where clause. This makes cross-Dado queries (e.g. fetching all users by Habbo username for a Global Admin view) straightforward without any joins.

Data Flow

Real-Time Leaderboard Updates

The leaderboard is powered by Firestore’s onSnapshot listener. When the active Dado changes (or on initial load), the app sets up a live query:
const qUsers = query(
  collection(db, 'users'),
  where('dadoId', '==', targetDadoId)
);

const unsubUsers = onSnapshot(qUsers, (snapshot) => {
  const list: HabboUser[] = [];
  snapshot.forEach((docSnap) => {
    list.push({ id: docSnap.id, ...docSnap.data() } as HabboUser);
  });
  list.sort((a, b) => b.points - a.points);
  setUsers(list);
});
Firestore pushes a new snapshot to this callback every time any document matching the query changes — whether a score is updated, a player is added, or a role changes. React then re-renders the leaderboard in place with the updated sorted list. All listeners are cleaned up with their unsubscribe functions in useEffect return callbacks to prevent memory leaks when the component unmounts or the active Dado switches.

Score Updates and Rank Recalculation

When an intermediary or admin posts a point change, the app runs a batched write that updates every player’s currentRank and previousRank in a single Firestore commit:
const updateRanksAndSave = async (updatedUsers: HabboUser[]) => {
  const sorted = [...updatedUsers].sort((a, b) => b.points - a.points);
  const batch = writeBatch(db);

  sorted.forEach((user, index) => {
    const newRank = index + 1;
    const userRef = doc(db, 'users', user.id);
    batch.update(userRef, {
      points: user.points,
      previousRank: user.currentRank || user.previousRank,
      currentRank: newRank
    });
  });

  await batch.commit();
};
Using writeBatch ensures all rank fields update atomically — no intermediate state where some documents have new ranks and others have old ones.

Authentication

HADOS uses Firebase Authentication with the Email/Password sign-in method. The auth lifecycle is managed by a single onAuthStateChanged listener mounted once on app load:
onAuthStateChanged(auth, async (user) => {
  setCurrentUser(user);
  if (user) {
    // Attach a real-time listener to the user's Firestore profile document
    const userProfileRef = doc(db, 'users', user.uid);
    onSnapshot(userProfileRef, async (docSnap) => {
      if (docSnap.exists()) {
        setUserProfile({ ...docSnap.data() as HabboUser, id: docSnap.id });
      } else {
        // Auto-create a recovery profile if the Firestore doc is missing
        await setDoc(userProfileRef, recoveryProfileData);
      }
      setAuthLoading(false);
    });
  } else {
    setUserProfile(null);
    setAuthLoading(false);
  }
});
The app renders a loading spinner until authLoading is false, then either shows the login/register form (if currentUser is null) or the main dashboard.

Role Assignment at Registration

Roles are set at the moment a user document is written to Firestore during registration. The Global Admin role requires the secret code GLOBAL2026 to be entered during sign-up. All other new accounts receive role: "jugador" by default. Dado admins and intermediaries are promoted post-registration by higher-privilege users through Firestore updateDoc calls.

Habbo Avatar Integration

Every player’s avatar is fetched directly from the Habbo Spain imaging API. The URL pattern is:
https://www.habbo.es/habbo-imaging/avatarimage?user={username}&direction=3&head_direction=3&gesture=std&size=m
For head-only thumbnails used in compact leaderboard rows and the navbar, the headonly=1 parameter is appended:
https://www.habbo.es/habbo-imaging/avatarimage?user={username}&direction=3&head_direction=3&gesture=std&size=m&headonly=1
The username value is URL-encoded with encodeURIComponent before being inserted. Avatar URLs are stored in the avatarUrl field of each user document at creation time. If the habbo.es API returns an error (e.g. the username does not exist in Habbo Hotel), the <img> element’s onError handler falls back to a DiceBear avatar generated from the username as a seed.

SPA Routing

HADOS has no client-side router. The entire application is a single React tree rendered at the root route /. Navigation between views (leaderboard, intermediary panel, admin panels) is handled by in-memory state variables (activeCategoryTab, activeInfoTab) rather than URL changes. In production, the server.js static server implements a catch-all SPA fallback: any request for a path that does not match a file in dist/ returns dist/index.html with a 200 status, allowing the React app to boot and manage the UI state:
server.js
fs.readFile(filePath, (error, content) => {
  if (error && error.code === 'ENOENT') {
    // File not found — serve index.html for SPA fallback
    fs.readFile(path.join(DIST_DIR, 'index.html'), (err, indexContent) => {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.end(indexContent, 'utf-8');
    });
  } else {
    res.writeHead(200, { 'Content-Type': contentType });
    res.end(content, 'utf-8');
  }
});
The server listens on the port defined by the PORT or SERVER_PORT environment variables, defaulting to 8080.

Design Tokens

The visual design is driven by CSS custom properties defined in src/index.css:
TokenValueUsage
--color-accent#ffd700Habbo gold — primary CTA buttons, borders, rank #1
--color-purple#9d4eddIntermediary actions, points modal, focus rings
--color-success#10b981Positive rank movement indicators
--color-danger#ef4444Negative rank movement, warnings, reset actions
--bg-primary#070709Page background
--bg-card#15151bPanel / card background
--border-color#22222eDefault border for all panels and inputs

Build docs developers (and LLMs) love