From 5e3778cd442edf24626fa29786b8daf48b0e6301 Mon Sep 17 00:00:00 2001
From: Jacob Cable 
Date: Mon, 18 Aug 2025 10:58:23 +0100
Subject: [PATCH 1/5] refactor(examples): made kitchen-sink example
---
 examples/react/kitchen-sink/README.md         |  60 ++++
 .../index.html                                |   2 +-
 .../package.json                              |   7 +-
 .../postcss.config.js                         |   0
 examples/react/kitchen-sink/src/App.tsx       | 133 ++++++++
 .../src/components/CollectionQueryExample.tsx | 312 ++++++++++++++++++
 .../src/components/IdTokenExample.tsx         |  10 +-
 .../src/firebase.ts                           |  11 +-
 .../src/index.css                             |   0
 .../src/main.tsx                              |   7 +-
 .../tailwind.config.js                        |   0
 .../tsconfig.json                             |   7 +-
 .../react/kitchen-sink/tsconfig.node.json     |  10 +
 .../vite.config.ts                            |   6 +-
 examples/react/useGetIdTokenQuery/.gitignore  |  24 --
 examples/react/useGetIdTokenQuery/README.md   |  21 --
 .../useGetIdTokenQuery/postcss.config.mjs     |   9 -
 .../react/useGetIdTokenQuery/public/vite.svg  |   1 -
 examples/react/useGetIdTokenQuery/src/App.tsx |  49 ---
 .../useGetIdTokenQuery/src/vite-env.d.ts      |   1 -
 .../useGetIdTokenQuery/tailwind.config.ts     |  20 --
 pnpm-lock.yaml                                |  92 +++++-
 22 files changed, 634 insertions(+), 148 deletions(-)
 create mode 100644 examples/react/kitchen-sink/README.md
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/index.html (86%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/package.json (81%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/postcss.config.js (100%)
 create mode 100644 examples/react/kitchen-sink/src/App.tsx
 create mode 100644 examples/react/kitchen-sink/src/components/CollectionQueryExample.tsx
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/src/components/IdTokenExample.tsx (98%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/src/firebase.ts (52%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/src/index.css (100%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/src/main.tsx (62%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/tailwind.config.js (100%)
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/tsconfig.json (76%)
 create mode 100644 examples/react/kitchen-sink/tsconfig.node.json
 rename examples/react/{useGetIdTokenQuery => kitchen-sink}/vite.config.ts (53%)
 delete mode 100644 examples/react/useGetIdTokenQuery/.gitignore
 delete mode 100644 examples/react/useGetIdTokenQuery/README.md
 delete mode 100644 examples/react/useGetIdTokenQuery/postcss.config.mjs
 delete mode 100644 examples/react/useGetIdTokenQuery/public/vite.svg
 delete mode 100644 examples/react/useGetIdTokenQuery/src/App.tsx
 delete mode 100644 examples/react/useGetIdTokenQuery/src/vite-env.d.ts
 delete mode 100644 examples/react/useGetIdTokenQuery/tailwind.config.ts
diff --git a/examples/react/kitchen-sink/README.md b/examples/react/kitchen-sink/README.md
new file mode 100644
index 00000000..07d7fe34
--- /dev/null
+++ b/examples/react/kitchen-sink/README.md
@@ -0,0 +1,60 @@
+# TanStack Query Firebase Examples
+
+A comprehensive example application showcasing various TanStack Query Firebase hooks and patterns.
+
+## Features
+
+- **Authentication Examples**: ID token management with `useGetIdTokenQuery`
+- **Firestore Examples**: Collection querying with `useCollectionQuery`
+- **Real-time Updates**: See how the UI updates when data changes
+- **Mutation Integration**: Add/delete operations with proper error handling
+- **Loading States**: Proper loading and error state management
+- **Query Key Management**: Dynamic query keys based on filters
+
+## Running the Examples
+
+1. Start the Firebase emulators:
+   ```bash
+   cd ../../../ && firebase emulators:start
+   ```
+
+2. In another terminal, run the example app:
+   ```bash
+   pnpm dev:emulator
+   ```
+
+3. Navigate to different examples using the navigation bar:
+   - **Home**: Overview of available examples
+   - **ID Token Query**: Firebase Authentication token management
+   - **Collection Query**: Firestore collection querying with filters
+
+## Key Concepts Demonstrated
+
+- Using `useGetIdTokenQuery` for Firebase Authentication
+- Using `useCollectionQuery` with different query configurations
+- Combining queries with mutations (`useAddDocumentMutation`, `useDeleteDocumentMutation`)
+- Dynamic query keys for filtered results
+- Proper TypeScript integration with Firestore data
+- React Router for navigation between examples
+
+## File Structure
+
+```
+src/
+├── components/
+│   ├── IdTokenExample.tsx        # Authentication example
+│   └── CollectionQueryExample.tsx # Firestore example
+├── App.tsx                       # Main app with routing
+├── firebase.ts                   # Firebase initialization
+├── main.tsx                      # Entry point
+└── index.css                     # Tailwind CSS
+```
+
+## Technologies Used
+
+- **Vite**: Fast build tool and dev server
+- **React Router**: Client-side routing
+- **TanStack Query**: Data fetching and caching
+- **Firebase**: Authentication and Firestore
+- **Tailwind CSS**: Utility-first styling
+- **TypeScript**: Type safety
diff --git a/examples/react/useGetIdTokenQuery/index.html b/examples/react/kitchen-sink/index.html
similarity index 86%
rename from examples/react/useGetIdTokenQuery/index.html
rename to examples/react/kitchen-sink/index.html
index e4b78eae..55112303 100644
--- a/examples/react/useGetIdTokenQuery/index.html
+++ b/examples/react/kitchen-sink/index.html
@@ -4,7 +4,7 @@
     
     
     
-    Vite + React + TS
+    TanStack Query Firebase Examples
   
   
     
diff --git a/examples/react/useGetIdTokenQuery/package.json b/examples/react/kitchen-sink/package.json
similarity index 81%
rename from examples/react/useGetIdTokenQuery/package.json
rename to examples/react/kitchen-sink/package.json
index 39d53f05..855b5529 100644
--- a/examples/react/useGetIdTokenQuery/package.json
+++ b/examples/react/kitchen-sink/package.json
@@ -1,11 +1,11 @@
 {
-  "name": "useGetIdTokenQuery",
+  "name": "firebase-examples",
   "private": true,
   "version": "0.0.0",
   "type": "module",
   "scripts": {
     "dev": "vite",
-    "dev:emulator": "cd ../../../ && firebase emulators:exec --project test-project 'cd examples/react/useGetIdTokenQuery && vite'",
+    "dev:emulator": "cd ../../../ && firebase emulators:exec --project test-project 'cd examples/react/firebase-examples && vite'",
     "build": "npx vite build",
     "preview": "vite preview"
   },
@@ -15,7 +15,8 @@
     "@tanstack/react-query-devtools": "^5.84.2",
     "firebase": "^11.3.1",
     "react": "^19.1.1",
-    "react-dom": "^19.1.1"
+    "react-dom": "^19.1.1",
+    "react-router-dom": "^6.28.0"
   },
   "devDependencies": {
     "@types/react": "^19.1.9",
diff --git a/examples/react/useGetIdTokenQuery/postcss.config.js b/examples/react/kitchen-sink/postcss.config.js
similarity index 100%
rename from examples/react/useGetIdTokenQuery/postcss.config.js
rename to examples/react/kitchen-sink/postcss.config.js
diff --git a/examples/react/kitchen-sink/src/App.tsx b/examples/react/kitchen-sink/src/App.tsx
new file mode 100644
index 00000000..e1ae9e76
--- /dev/null
+++ b/examples/react/kitchen-sink/src/App.tsx
@@ -0,0 +1,133 @@
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
+import { useState } from "react";
+import { Routes, Route, Link, useLocation } from "react-router-dom";
+import { IdTokenExample } from "./components/IdTokenExample";
+import { CollectionQueryExample } from "./components/CollectionQueryExample";
+
+import "./firebase";
+
+function App() {
+  const [queryClient] = useState(
+    () =>
+      new QueryClient({
+        defaultOptions: {
+          queries: {
+            staleTime: 60 * 1000,
+          },
+        },
+      })
+  );
+
+  return (
+    
+      
+        
+        
+          
+            
+              } />
+              } />
+              }
+              />
+            
+          
+        
+      
+      
+        TanStack Query Firebase Examples
+      
+      
+        Explore different Firebase hooks and patterns with TanStack Query
+      
+
+      
+        
+          
+            Authentication
+          
+          
+            Examples of Firebase Authentication hooks including ID token
+            management.
+          
+          
+            View ID Token Example
+          
+        
+
+        
+          
Firestore
+          
+            Examples of Firestore hooks for querying collections and documents.
+          
+          
+            View Collection Query Example
+          
+        
+      
+
+      
+        
+          Built with Vite, TanStack Query, React Router, and Firebase
+        
+      
+    
+      
+        Task Management with useCollectionQuery
+      
+
+      {/* Add Task Form */}
+      
+        
Add New Task
+        
+          
+            
+             setNewTaskTitle(e.target.value)}
+              placeholder="Enter task title..."
+              className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
+              onKeyPress={(e) => e.key === "Enter" && handleAddTask()}
+            />
+          
+          
+            
+            
+          
+          
+        
+      
+
+      {/* Filter Controls */}
+      
+        Filter:
+        
+        
+        
+      
+
+      {/* Query Status */}
+      
+        {isLoading && (
+          
+        )}
+
+        {isError && (
+          
+            
Error loading tasks
+            
+              {error?.message || "An unknown error occurred"}
+            
+          
+        )}
+      
+
+      {/* Tasks List */}
+      {!isLoading && !isError && (
+        
+          {tasks.length === 0 ? (
+            
+              {filterCompleted === null
+                ? "No tasks found. Add your first task above!"
+                : `No ${
+                    filterCompleted ? "completed" : "pending"
+                  } tasks found.`}
+            
+          ) : (
+            tasks.map((task) => (
+              
+                
+                  
 handleToggleTask(task.id, task.completed)}
+                    className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
+                  />
+                  
+                    
+                      {task.title}
+                    
+                    
+                      Created: {task.createdAt.toLocaleDateString()}
+                    
+                  
+                  
+                    {task.priority}
+                  
+                
+                
+              
+            ))
+          )}
+        
+      )}
+
+      {/* Query Info */}
+      
+        
Query Information
+        
+          
+            Query Key:{" "}
+            {JSON.stringify(["tasks", filterCompleted])}
+          
+          
+            Total Tasks: {tasks.length}
+          
+          
+            Filter:{" "}
+            {filterCompleted === null
+              ? "All"
+              : filterCompleted
+              ? "Completed"
+              : "Pending"}
+          
+          
+            Status:{" "}
+            {isLoading ? "Loading" : isError ? "Error" : "Success"}
+          
+        
+      
+    
-        
-          
-            
-              Firebase Authentication Examples
-            
-            
-              TanStack Query Firebase Authentication hooks and patterns
-            
-          
-
-          
-            
-          
-
-          
-            
-              Built with Vite, TanStack Query, and Firebase Auth
-            
-          
-        
-      
           
             Status:{" "}
diff --git a/examples/react/kitchen-sink/src/components/IdTokenExample.tsx b/examples/react/kitchen-sink/src/components/IdTokenExample.tsx
index c18d72cc..b05beb37 100644
--- a/examples/react/kitchen-sink/src/components/IdTokenExample.tsx
+++ b/examples/react/kitchen-sink/src/components/IdTokenExample.tsx
@@ -9,7 +9,7 @@ export function IdTokenExample() {
   const [refreshCount, setRefreshCount] = useState(0);
   const [previousToken, setPreviousToken] = useState(null);
   const [lastForceRefreshTime, setLastForceRefreshTime] = useState(
-    null
+    null,
   );
 
   // Listen for auth state changes
@@ -44,7 +44,7 @@ export function IdTokenExample() {
     if (token) {
       console.log(
         "Token retrieved successfully:",
-        `${token.substring(0, 20)}...`
+        `${token.substring(0, 20)}...`,
       );
 
       // Check if token changed
@@ -129,7 +129,7 @@ export function IdTokenExample() {
           ) : (
             
               
-                
+                
                   Token hash: {token ? `${btoa(token).slice(0, 16)}...` : ""}
                 
                 {lastRefreshTime && (
@@ -170,7 +170,7 @@ export function IdTokenExample() {
             
Loading fresh token...
           ) : freshToken ? (
             
-              
+              
                 Token hash:{" "}
                 {freshToken ? `${btoa(freshToken).slice(0, 16)}...` : ""}
               
@@ -237,7 +237,7 @@ export function IdTokenExample() {
                       const result = [];
                       const maxLength = Math.max(
                         token.length,
-                        freshToken.length
+                        freshToken.length,
                       );
 
                       for (let i = 0; i < maxLength; i++) {
@@ -252,13 +252,13 @@ export function IdTokenExample() {
                               className="bg-yellow-300 text-red-600 font-bold"
                             >
                               {freshToken[i] || "∅"}
-                            
+                            ,
                           );
                         } else {
                           result.push(
                             
                               {token[i]}
-                            
+                            ,
                           );
                         }
                       }
diff --git a/examples/react/kitchen-sink/src/components/NestedCollectionsExample.tsx b/examples/react/kitchen-sink/src/components/NestedCollectionsExample.tsx
new file mode 100644
index 00000000..329f4813
--- /dev/null
+++ b/examples/react/kitchen-sink/src/components/NestedCollectionsExample.tsx
@@ -0,0 +1,606 @@
+import { useMutation, useQueryClient } from "@tanstack/react-query";
+import {
+  useAddDocumentMutation,
+  useCollectionQuery,
+} from "@tanstack-query-firebase/react/firestore";
+import {
+  addDoc,
+  collection,
+  deleteDoc,
+  doc,
+  getFirestore,
+  limit,
+  orderBy,
+  query,
+  updateDoc,
+  where,
+} from "firebase/firestore";
+import { useState } from "react";
+
+interface ChatMessage {
+  id: string;
+  text: string;
+  senderId: string;
+  senderName: string;
+  timestamp: Date;
+}
+
+interface Conversation {
+  id: string;
+  topic: string;
+  description: string;
+  members: string[];
+  isConcluded: boolean;
+  createdAt: Date;
+  lastMessageAt: Date;
+  chatMessages?: ChatMessage[];
+}
+
+export function NestedCollectionsExample() {
+  const [newConversationTopic, setNewConversationTopic] = useState("");
+  const [newConversationDescription, setNewConversationDescription] =
+    useState("");
+  const [selectedConversationId, setSelectedConversationId] = useState<
+    string | null
+  >(null);
+  const [newMessageText, setNewMessageText] = useState("");
+  const [filterConcluded, setFilterConcluded] = useState
(null);
+
+  const queryClient = useQueryClient();
+  const firestore = getFirestore();
+  const conversationsCollection = collection(firestore, "conversations");
+
+  // Query conversations with real-time updates
+  const conversationsQuery =
+    filterConcluded !== null
+      ? query(
+          conversationsCollection,
+          where("isConcluded", "==", filterConcluded),
+          orderBy("lastMessageAt", "desc"),
+        )
+      : query(conversationsCollection, orderBy("lastMessageAt", "desc"));
+
+  const {
+    data: conversationsSnapshot,
+    isLoading: conversationsLoading,
+    isError: conversationsError,
+    error: conversationsErrorData,
+  } = useCollectionQuery(conversationsQuery, {
+    queryKey: ["conversations", filterConcluded],
+    subscribed: true, // Enable real-time updates
+  });
+
+  // Query chat messages for selected conversation with real-time updates
+  const chatMessagesQuery = selectedConversationId
+    ? query(
+        collection(
+          firestore,
+          "conversations",
+          selectedConversationId,
+          "chatMessages",
+        ),
+        orderBy("timestamp", "asc"),
+        limit(50),
+      )
+    : null;
+
+  const {
+    data: messagesSnapshot,
+    isLoading: messagesLoading,
+    isError: messagesError,
+    error: messagesErrorData,
+  } = useCollectionQuery(chatMessagesQuery!, {
+    queryKey: ["chatMessages", selectedConversationId],
+    enabled: !!selectedConversationId && !!chatMessagesQuery,
+    subscribed: true, // Enable real-time updates
+  });
+
+  // Mutations
+  const addConversationMutation = useAddDocumentMutation(
+    conversationsCollection,
+    {
+      onSuccess: () => {
+        // Invalidate conversations query to refresh the list
+        queryClient.invalidateQueries({ queryKey: ["conversations"] });
+      },
+      onError: (error) => {
+        console.error("Failed to add conversation:", error);
+        // Could show a toast notification here
+      },
+    },
+  );
+
+  // Custom mutation for adding messages with proper invalidation
+  const addMessageMutation = useMutation({
+    mutationFn: async (newMessage: Omit) => {
+      if (!selectedConversationId) {
+        throw new Error("No conversation selected");
+      }
+      const messagesCollection = collection(
+        firestore,
+        "conversations",
+        selectedConversationId,
+        "chatMessages",
+      );
+      return addDoc(messagesCollection, newMessage);
+    },
+    onMutate: async (newMessage) => {
+      // Cancel in-flight queries
+      await queryClient.cancelQueries({
+        queryKey: ["chatMessages", selectedConversationId],
+      });
+
+      // Store the actual snapshot structure
+      const previousSnapshot = queryClient.getQueryData([
+        "chatMessages",
+        selectedConversationId,
+      ]);
+
+      // Create a temporary message with proper structure
+      const tempMessage = {
+        id: `temp-${Date.now()}`,
+        ...newMessage,
+        timestamp: new Date(),
+      };
+
+      // Update maintaining the snapshot structure
+      queryClient.setQueryData(
+        ["chatMessages", selectedConversationId],
+        (old: any) => {
+          if (!old) return old;
+
+          // Create a new doc-like object
+          const newDoc = {
+            id: tempMessage.id,
+            data: () => tempMessage,
+            // Include other doc methods if needed
+          };
+
+          return {
+            ...old,
+            docs: [...(old.docs || []), newDoc],
+          };
+        },
+      );
+
+      return { previousSnapshot };
+    },
+    onError: (error, variables, context) => {
+      // Show user-friendly error message
+      console.error("Failed to send message:", error);
+      // Could show a toast notification here
+
+      // Rollback optimistic update
+      if (context?.previousSnapshot) {
+        queryClient.setQueryData(
+          ["chatMessages", selectedConversationId],
+          context.previousSnapshot,
+        );
+      }
+    },
+    onSuccess: async () => {
+      // Update conversation's lastMessageAt
+      if (selectedConversationId) {
+        try {
+          await updateDoc(
+            doc(firestore, "conversations", selectedConversationId),
+            {
+              lastMessageAt: new Date(),
+            },
+          );
+        } catch (error) {
+          console.error("Failed to update conversation timestamp:", error);
+        }
+      }
+
+      // Invalidate both queries
+      queryClient.invalidateQueries({ queryKey: ["conversations"] });
+      queryClient.invalidateQueries({
+        queryKey: ["chatMessages", selectedConversationId],
+      });
+    },
+  });
+
+  // Custom mutation for deleting conversations
+  const deleteConversationMutation = useMutation({
+    mutationFn: async (conversationId: string) => {
+      const conversationRef = doc(firestore, "conversations", conversationId);
+      return deleteDoc(conversationRef);
+    },
+    onError: (error, conversationId) => {
+      console.error("Failed to delete conversation:", error);
+      // Could show a toast notification here
+    },
+    onSuccess: (_, conversationId) => {
+      // Invalidate conversations query
+      queryClient.invalidateQueries({ queryKey: ["conversations"] });
+
+      // Clear messages if this was the selected conversation
+      if (selectedConversationId === conversationId) {
+        queryClient.removeQueries({
+          queryKey: ["chatMessages", conversationId],
+        });
+        setSelectedConversationId(null);
+      }
+    },
+  });
+
+  const handleAddConversation = async () => {
+    if (!newConversationTopic.trim()) return;
+
+    const newConversation = {
+      topic: newConversationTopic.trim(),
+      description: newConversationDescription.trim(),
+      members: ["user1", "user2"], // In real app, this would be actual user IDs
+      isConcluded: false,
+      createdAt: new Date(),
+      lastMessageAt: new Date(),
+    };
+
+    try {
+      await addConversationMutation.mutateAsync(newConversation);
+      setNewConversationTopic("");
+      setNewConversationDescription("");
+    } catch (error) {
+      console.error("Failed to add conversation:", error);
+    }
+  };
+
+  const handleAddMessage = async () => {
+    if (!selectedConversationId || !newMessageText.trim()) return;
+
+    const newMessage = {
+      text: newMessageText.trim(),
+      senderId: "user1", // In real app, this would be the current user's ID
+      senderName: "Current User",
+      timestamp: new Date(),
+    };
+
+    try {
+      await addMessageMutation.mutateAsync(newMessage);
+      setNewMessageText("");
+    } catch (error) {
+      console.error("Failed to add message:", error);
+    }
+  };
+
+  const handleDeleteConversation = async (conversationId: string) => {
+    try {
+      await deleteConversationMutation.mutateAsync(conversationId);
+    } catch (error) {
+      console.error("Failed to delete conversation:", error);
+    }
+  };
+
+  // Proper date serialization
+  const conversations =
+    (conversationsSnapshot?.docs.map((doc) => ({
+      id: doc.id,
+      ...doc.data(),
+      // Convert Firestore Timestamps to Dates
+      createdAt:
+        doc.data().createdAt?.toDate?.() || doc.data().createdAt || new Date(),
+      lastMessageAt:
+        doc.data().lastMessageAt?.toDate?.() ||
+        doc.data().lastMessageAt ||
+        new Date(),
+    })) as Conversation[]) || [];
+
+  const messages =
+    (messagesSnapshot?.docs.map((doc) => ({
+      id: doc.id,
+      ...doc.data(),
+      // Convert Firestore Timestamps to Dates
+      timestamp:
+        doc.data().timestamp?.toDate?.() || doc.data().timestamp || new Date(),
+    })) as ChatMessage[]) || [];
+
+  const selectedConversation = conversations.find(
+    (conv) => conv.id === selectedConversationId,
+  );
+
+  return (
+    
+      
+        Nested Collections: Conversations & Chat Messages
+      
+
+      {/* Add Conversation Form */}
+      
+        
Add New Conversation
+        
+          
+            
+             setNewConversationTopic(e.target.value)}
+              placeholder="Enter conversation topic..."
+              className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
+              onKeyPress={(e) => e.key === "Enter" && handleAddConversation()}
+            />
+          
+          
+            
+             setNewConversationDescription(e.target.value)}
+              placeholder="Enter description..."
+              className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
+            />
+          
+          
+        
+      
+
+      {/* Filter Controls */}
+      
+        Filter:
+        
+        
+        
+      
+
+      
+        {/* Conversations List */}
+        
+          
Conversations
+
+          {conversationsLoading && (
+            
+              
+              
Loading conversations...
+            
+          )}
+
+          {conversationsError && (
+            
+              
+                Error loading conversations
+              
+              
+                {conversationsErrorData?.message || "An unknown error occurred"}
+              
+            
+          )}
+
+          {!conversationsLoading && !conversationsError && (
+            
+              {conversations.length === 0 ? (
+                
+                  No conversations found. Add your first conversation above!
+                
+              ) : (
+                conversations.map((conversation) => (
+                  
+                ))
+              )}
+            
+          )}
+        
+
+        {/* Chat Messages */}
+        
+          
+            {selectedConversation
+              ? `Chat: ${selectedConversation.topic}`
+              : "Select a conversation"}
+          
+
+          {selectedConversationId ? (
+            <>
+              {messagesLoading && (
+                
+                  
+                  
Loading messages...
+                
+              )}
+
+              {messagesError && (
+                
+                  
+                    Error loading messages
+                  
+                  
+                    {messagesErrorData?.message || "An unknown error occurred"}
+                  
+                
+              )}
+
+              {!messagesLoading && !messagesError && (
+                <>
+                  {/* Messages List */}
+                  
+                    {messages.length === 0 ? (
+                      
+                        No messages yet. Start the conversation!
+                      
+                    ) : (
+                      messages.map((message) => (
+                        
+                          
+                            
+                              {message.senderName}
+                            
+                            
+                              {message.timestamp.toLocaleTimeString()}
+                            
+                          
+                          
{message.text}
+                        
+                      ))
+                    )}
+                  
+
+                  {/* Add Message Form */}
+                  
+                     setNewMessageText(e.target.value)}
+                      placeholder="Type a message..."
+                      className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
+                      onKeyPress={(e) =>
+                        e.key === "Enter" && handleAddMessage()
+                      }
+                    />
+                    
+                      {addMessageMutation.isPending ? "Sending..." : "Send"}
+                    
+                  
+                >
+              )}
+            >
+          ) : (
+            
+              Select a conversation to view messages
+            
+          )}
+        
+      
+
+      {/* Query Info */}
+      
+        
Query Information
+        
+          
+            Conversations Query Key:{" "}
+            {JSON.stringify(["conversations", filterConcluded])}
+          
+          
+            Messages Query Key:{" "}
+            {selectedConversationId
+              ? JSON.stringify(["chatMessages", selectedConversationId])
+              : "Not selected"}
+          
+          
+            Total Conversations: {conversations.length}
+          
+          
+            Total Messages: {messages.length}
+          
+          
+            Filter:{" "}
+            {filterConcluded === null
+              ? "All"
+              : filterConcluded
+                ? "Concluded"
+                : "Active"}
+          
+          
+            Real-time Updates: Enabled for both queries
+          
+          
+            Optimistic Updates: Enabled for message additions
+          
+          
+            Query Invalidation: Automatic after mutations
+          
+          
+            Error Handling: Rollback on mutation failures
+          
+        
+      
+    
@@ -49,35 +54,63 @@ function App() {
 function Navigation() {
   const location = useLocation();
 
-  const navItems = [
-    { path: "/", label: "Home" },
-    { path: "/auth/id-token", label: "ID Token Query" },
-    { path: "/firestore/collection-query", label: "Collection Query" },
-  ];
+  const isActive = (path: string) => location.pathname === path;
 
   return (