Features

Chat Rooms

The template supports both private (1-on-1) and group chat rooms. This section explains how rooms are created, managed, and displayed in the UI.

Room Types

Private Rooms

One-on-one conversations between two users. The template automatically finds existing private rooms or creates new ones when needed.

  • Always has exactly 2 participants
  • No group name or avatar
  • Created automatically when starting a chat
  • Display shows the other user's name and avatar

Group Rooms

Multi-user conversations that can have a custom name and avatar. Created explicitly by users.

  • Can have 2+ participants
  • Optional group name and avatar
  • Created via the "Create Group" button
  • Shows group avatar or initials of participants

Creating Rooms

Private Room Creation

When a user wants to start a private chat, the template uses ensureCreatePrivateChat from room.service.ts:

  1. Searches for an existing private room between the two users using getPrivateChatByUserIds
  2. If found, returns the existing room ID (prevents duplicate rooms)
  3. If not found, creates a new room with createPrivateChat, initializing:
    • type: "private"
    • participants: [uid1, uid2]
    • unreadCounts: { [uid1]: 0, [uid2]: 0 }

Group Room Creation

Group rooms are created via createGroupChat which:

  • Accepts creator UID, participant UIDs, optional group name and avatar URL
  • Ensures the creator is included in participants (deduplicates array)
  • Initializes unread counts: 0 for creator, 1 for others (they haven't seen the creation message)
  • Creates a system message: "created the group" as the initial lastMessage
  • Sets type: "group" and optional group metadata

Room List Display

Querying User's Rooms

The room list sidebar uses subscribeRoomsByUser from rooms.service.ts to show all rooms where the current user is a participant:

query(
  collection(db, "rooms"),
  where("participants", "array-contains", currentUid),
  orderBy("lastMessage.createdAt", "desc")
)

This query requires a Firestore composite index on participants and lastMessage.createdAt. Firebase will prompt you to create it on first use.

Room List Item Rendering

Each room in the list is rendered by ChatRoomItem which conditionally renders:

  • Private rooms: PrivateRoomItem – shows the other user's avatar and name
  • Group rooms: GroupRoomItem – shows group avatar (or initials) and group name

Both show the last message preview, unread count badge, and timestamp. The active room is highlighted based on state from chat.store.ts.

Room Data Structure

Every room document in Firestore has this structure:

rooms/{roomId} {
  type: "private" | "group";
  groupName?: string | null;        // only for groups
  groupAvatarURL?: string | null;   // only for groups
  
  participants: string[];           // array of UIDs
  participantsCount: number;        // denormalized count
  
  unreadCounts: {                   // per-user counters
    [uid: string]: number;
  };
  
  createdBy: string;                // UID of creator
  createdAt: Timestamp;
  
  lastMessage: {                    // denormalized preview
    text: string;
    senderId: string;
    createdAt: Timestamp;
    type: "text" | "system";
  } | null;
}

See features/chat/types/room.types.ts for the TypeScript definitions.

Implementation Hooks

The template provides these hooks for working with rooms:

  • useRoomList() – subscribes to all rooms for the current user, returns { rooms, isLoading, error }
  • useRoom(roomId) – subscribes to a specific room document, returns { room, isLoading, error }
  • useParticipants(roomId) – fetches user info for all participants in a room