Skip to content

# TASK_PHASE_CITY_ROOMS_FINISH_v2

## Title

TASK_PHASE_CITY_ROOMS_FINISH_v2 — MicroDAO Rooms + City Lobby CTA + Online Stats Fix

## 1. Overview

Finish the DAARION City chat layer:

1. City Rooms page:
2. Map + List already work.

3. Fix `NaN` for online users count.

4. MicroDAO Rooms:

5. Each MicroDAO must have its own rooms section.

6. Ability to create new rooms from MicroDAO cabinet.

7. City Lobby CTA:

8. Main public chat with DAARWIZZ should be accessible directly from the homepage and top navigation.

All changes must use existing architecture: `city-service` (FastAPI + Postgres) and `apps/web` (Next.js / React).

## 2. Current State (as of 2025-12-01)

- Backend:
- `scripts/seed_city_rooms.py` creates ~31 rooms (district rooms, lobbies, city lobby, etc.).
- `city-service` exposes `/api/v1/city/rooms` (already used by UI).

- Rooms table already contains:

  - `id`
  - `slug`
  - `name` (title)
  - `description`
  - `zone` (district key)
  - `room_type` (e.g. `city`, `district`, `microdao`, etc.)
  - `is_public` (bool)
  - `microdao_id` (nullable, for MicroDAO rooms)

- Frontend:

- `/city` (City Rooms):
  - Map tab: grid of districts, shows total rooms and online counters.
  - List tab: renders all rooms with description.
  - At the bottom: summary cards: `Kімнат: 31`, `Онлайн: NaN`.
- MicroDAO pages (`/microdao/[slug]`):
  - Currently have no dedicated "MicroDAO Rooms" section.
- Homepage (`/`):
  - No direct CTA to "DAARION City Lobby".

## 3. Goals

1. Fix online counter:

2. Never show `NaN` on the City Rooms page.

3. MicroDAO Rooms UI:

4. On `/microdao/[slug]` display:

   - List of rooms belonging to this MicroDAO.
   - Button "Створити кімнату".

5. City Lobby CTA:

6. On homepage and/or main menu:
   - Button "Поспілкуватися з DAARWIZZ" that opens `DAARION City Lobby`.

## 4. Backend Tasks

### 4.1. Room model & repository

Files: * `services/city-service/models_city.py` * `services/city-service/repo_city.py` * `services/city-service/routes_city.py`

1. Ensure Room model exposes needed fields:

    class CityRoomSummary(BaseModel):
        id: str
        slug: str
        name: str
        description: Optional[str]
        zone: Optional[str]
        room_type: str
        microdao_id: Optional[str]
        is_public: bool
        members_online: int = 0  # ← must always be integer (0 by default)

1. Repo method for city rooms:

2. `get_all_rooms()` must:

   - Return `members_online` as integer (use `COALESCE(..., 0)`).

3. Repo method for MicroDAO rooms: Add/verify method:

    async def get_microdao_rooms(microdao_id: str) -> List[dict]:
        """Get rooms for specific MicroDAO"""
        # WHERE microdao_id = $1 AND is_public = true OR owner_type = 'microdao'

1. API routes:
2. Verify endpoint: `GET /api/v1/city/rooms` returns `members_online: int`.
3. Verify endpoint: `GET /city/microdao/{slug}/rooms` returns MicroDAO rooms.
4. Add endpoint for room creation:

    class CreateRoomRequest(BaseModel):
        title: str
        description: Optional[str]
        room_type: str = "microdao"
        is_public: bool = False

    @router.post("/city/microdao/{slug}/rooms")
    async def create_microdao_room(slug: str, body: CreateRoomRequest):
        # Create room for MicroDAO

1. Online stats summary:
2. Add/verify endpoint: `GET /api/v1/city/rooms/summary`
3. Returns: `{"rooms_total": int, "online_total": int}`

## 5. Frontend Tasks

Files: * `apps/web/src/app/city/page.tsx` * `apps/web/src/app/microdao/[slug]/page.tsx` * `apps/web/src/app/page.tsx` (homepage) * `apps/web/src/components/microdao/MicrodaoRoomsSection.tsx` (new)

### 5.1. Fix `NaN` online stats

1. In `city/page.tsx`:
2. Ensure online count uses fallback:

    const onlineTotal = summary?.online_total ?? 0;
    // or
    const onlineTotal = Number(summary?.online_total) || 0;

1. If API returns `null` or `undefined`, display `0`.

### 5.2. MicroDAO Rooms section

In `apps/web/src/app/microdao/[slug]/page.tsx`:

1. Add API call:

    const rooms = await fetchMicrodaoRooms(slug); // GET /city/microdao/{slug}/rooms

1. Render new section:

    <section className="space-y-4">
      <div className="flex items-center justify-between">
        <h2 className="text-xl font-semibold">Кімнати MicroDAO</h2>
        <Button onClick={openCreateRoomModal}>
          <Plus className="w-4 h-4 mr-2" />
          Створити кімнату
        </Button>
      </div>

      {rooms.length === 0 ? (
        <p className="text-muted-foreground">
          Ще немає кімнат. Створіть першу кімнату для цієї платформи.
        </p>
      ) : (
        <div className="grid gap-3">
          {rooms.map((room) => (
            <MicrodaoRoomCard key={room.slug} room={room} />
          ))}
        </div>
      )}
    </section>

1. `MicrodaoRoomCard` component:
2. title
3. description
4. room_type (badge)
5. visibility (Public/Private badge)

6. members_online

7. "Create Room" modal:

8. Назва кімнати (title, required)
9. Опис (optional)
10. Тип (select: Lobby, Governance, Project, Crew)
11. Видимість (switch: Public / Private, default = Private)
12. On submit → POST `/city/microdao/{slug}/rooms`

### 5.3. City Lobby CTA

1. On homepage (`apps/web/src/app/page.tsx`):

    <section className="py-12 text-center">
      <h2 className="text-2xl font-bold mb-4">
        Головний публічний чат DAARION City
      </h2>
      <p className="text-muted-foreground mb-6">
        Поспілкуватися з DAARWIZZ та іншими громадянами міста.
      </p>
      <Link href="/city?room=city-lobby">
        <Button size="lg" className="gap-2">
          <MessageCircle className="w-5 h-5" />
          Поспілкуватися з DAARWIZZ
        </Button>
      </Link>
    </section>

1. Optionally add to navigation header:
2. New item: "City Lobby" → `/city?room=city-lobby`

## 6. Acceptance Criteria

1. City Rooms page shows:
2. Correct number of rooms.

3. Online count is integer (0 allowed), never `NaN`.

4. MicroDAO page (`/microdao/daarion`, etc.) shows:

5. "Кімнати MicroDAO" section.
6. Ability to create a room.

7. Newly created room appears in list.

8. Homepage shows:

9. CTA "Поспілкуватися з DAARWIZZ".

10. Clicking the button opens City Lobby room.

11. All routes work both on NODE1 and NODE2.