Token hash: {token ? `${btoa(token).slice(0, 16)}...` : ""}
{lastRefreshTime && (
@@ -170,7 +170,7 @@ export function IdTokenExample() {
Loading fresh token...
-
+
Token hash:{" "}
{freshToken ? `${btoa(freshToken).slice(0, 16)}...` : ""}
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..79fd2299
--- /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
+
+
+
+
+ );
+}
diff --git a/examples/react/kitchen-sink/src/components/WithConverterExample.tsx b/examples/react/kitchen-sink/src/components/WithConverterExample.tsx
new file mode 100644
index 00000000..454c95f7
--- /dev/null
+++ b/examples/react/kitchen-sink/src/components/WithConverterExample.tsx
@@ -0,0 +1,67 @@
+import { useCollectionQuery } from "@tanstack-query-firebase/react/firestore";
+import {
+ collection,
+ type DocumentData,
+ getFirestore,
+ type QueryDocumentSnapshot,
+ query,
+ type SnapshotOptions,
+} from "firebase/firestore";
+
+type Product = {
+ name: string;
+ price: number;
+};
+
+const productConverter = {
+ toFirestore(product: Product): DocumentData {
+ return product;
+ },
+ fromFirestore(
+ snapshot: QueryDocumentSnapshot,
+ options: SnapshotOptions,
+ ): Product {
+ const data = snapshot.data(options);
+ return {
+ name: data.name,
+ price: data.price,
+ };
+ },
+};
+
+export function WithConverterExample() {
+ const firestore = getFirestore();
+ const ref = query(
+ collection(firestore, "products").withConverter(productConverter),
+ );
+
+ const { data, isLoading, isError, error } = useCollectionQuery<
+ Product,
+ DocumentData
+ >(ref, {
+ queryKey: ["products"],
+ });
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (isError) {
+ return Error: {error?.message}
;
+ }
+
+ const products = data?.docs.map((doc) => doc.data()) ?? [];
+
+ return (
+
+
Products
+
+ {products.map((product) => (
+ -
+ {product.name} - ${product.price}
+
+ ))}
+
+
+ );
+}
diff --git a/examples/react/kitchen-sink/src/firebase.ts b/examples/react/kitchen-sink/src/firebase.ts
new file mode 100644
index 00000000..baa30227
--- /dev/null
+++ b/examples/react/kitchen-sink/src/firebase.ts
@@ -0,0 +1,27 @@
+import { getApps, initializeApp } from "firebase/app";
+import { connectAuthEmulator, getAuth } from "firebase/auth";
+import { connectFirestoreEmulator, getFirestore } from "firebase/firestore";
+
+if (getApps().length === 0) {
+ initializeApp({
+ projectId: "test-project",
+ apiKey: "demo-api-key", // Required for Firebase to initialize
+ });
+
+ // Connect to emulators if running locally
+ if (import.meta.env.DEV) {
+ try {
+ // Connect to Auth emulator
+ const auth = getAuth();
+ connectAuthEmulator(auth, "http://localhost:9099");
+ console.log("Connected to Firebase Auth emulator");
+
+ // Connect to Firestore emulator
+ const firestore = getFirestore();
+ connectFirestoreEmulator(firestore, "localhost", 8080);
+ console.log("Connected to Firebase Firestore emulator");
+ } catch (error) {
+ console.warn("Could not connect to Firebase emulators:", error);
+ }
+ }
+}
diff --git a/examples/react/useGetIdTokenQuery/src/index.css b/examples/react/kitchen-sink/src/index.css
similarity index 100%
rename from examples/react/useGetIdTokenQuery/src/index.css
rename to examples/react/kitchen-sink/src/index.css
diff --git a/examples/react/useGetIdTokenQuery/src/main.tsx b/examples/react/kitchen-sink/src/main.tsx
similarity index 67%
rename from examples/react/useGetIdTokenQuery/src/main.tsx
rename to examples/react/kitchen-sink/src/main.tsx
index eff7ccc6..b17a076d 100644
--- a/examples/react/useGetIdTokenQuery/src/main.tsx
+++ b/examples/react/kitchen-sink/src/main.tsx
@@ -1,10 +1,13 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
+import { BrowserRouter } from "react-router-dom";
import "./index.css";
import App from "./App.tsx";
createRoot(document.getElementById("root")!).render(
-
+
+
+
,
);
diff --git a/examples/react/useGetIdTokenQuery/tailwind.config.js b/examples/react/kitchen-sink/tailwind.config.js
similarity index 100%
rename from examples/react/useGetIdTokenQuery/tailwind.config.js
rename to examples/react/kitchen-sink/tailwind.config.js
diff --git a/examples/react/useGetIdTokenQuery/tsconfig.json b/examples/react/kitchen-sink/tsconfig.json
similarity index 64%
rename from examples/react/useGetIdTokenQuery/tsconfig.json
rename to examples/react/kitchen-sink/tsconfig.json
index 31cb5a0c..df2ec7d1 100644
--- a/examples/react/useGetIdTokenQuery/tsconfig.json
+++ b/examples/react/kitchen-sink/tsconfig.json
@@ -1,8 +1,8 @@
{
"compilerOptions": {
- "target": "ES2022",
+ "target": "ES2020",
"useDefineForClassFields": true,
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
@@ -18,7 +18,11 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ /* Types */
+ "types": ["vite/client"]
},
- "include": ["src", "vite.config.ts"]
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/examples/react/kitchen-sink/tsconfig.node.json b/examples/react/kitchen-sink/tsconfig.node.json
new file mode 100644
index 00000000..42872c59
--- /dev/null
+++ b/examples/react/kitchen-sink/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/react/useGetIdTokenQuery/vite.config.ts b/examples/react/kitchen-sink/vite.config.ts
similarity index 53%
rename from examples/react/useGetIdTokenQuery/vite.config.ts
rename to examples/react/kitchen-sink/vite.config.ts
index 8c136be8..6a1235bb 100644
--- a/examples/react/useGetIdTokenQuery/vite.config.ts
+++ b/examples/react/kitchen-sink/vite.config.ts
@@ -6,7 +6,11 @@ export default defineConfig({
plugins: [react()],
build: {
rollupOptions: {
- external: ["@tanstack-query-firebase/react/auth"],
+ external: [
+ "@tanstack-query-firebase/react/auth",
+ "@tanstack-query-firebase/react/firestore",
+ "@tanstack-query-firebase/react/data-connect",
+ ],
},
},
});
diff --git a/examples/react/useGetIdTokenQuery/.gitignore b/examples/react/useGetIdTokenQuery/.gitignore
deleted file mode 100644
index a547bf36..00000000
--- a/examples/react/useGetIdTokenQuery/.gitignore
+++ /dev/null
@@ -1,24 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-lerna-debug.log*
-
-node_modules
-dist
-dist-ssr
-*.local
-
-# Editor directories and files
-.vscode/*
-!.vscode/extensions.json
-.idea
-.DS_Store
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
diff --git a/examples/react/useGetIdTokenQuery/README.md b/examples/react/useGetIdTokenQuery/README.md
deleted file mode 100644
index a2d0969c..00000000
--- a/examples/react/useGetIdTokenQuery/README.md
+++ /dev/null
@@ -1,21 +0,0 @@
-# Firebase Authentication Example (Vite)
-
-Simple Vite React app demonstrating Firebase Authentication with TanStack Query.
-
-## Quick Start
-
-```bash
-# Install dependencies
-pnpm install
-
-# Run with emulators (recommended)
-pnpm dev:emulator
-
-# Or run without emulators
-pnpm dev
-```
-
-## Features
-
-- **ID Token Management** - `useGetIdTokenQuery` hook demo
-
diff --git a/examples/react/useGetIdTokenQuery/postcss.config.mjs b/examples/react/useGetIdTokenQuery/postcss.config.mjs
deleted file mode 100644
index 2ef30fcf..00000000
--- a/examples/react/useGetIdTokenQuery/postcss.config.mjs
+++ /dev/null
@@ -1,9 +0,0 @@
-/** @type {import('postcss-load-config').Config} */
-const config = {
- plugins: {
- tailwindcss: {},
- autoprefixer: {},
- },
-};
-
-export default config;
diff --git a/examples/react/useGetIdTokenQuery/public/vite.svg b/examples/react/useGetIdTokenQuery/public/vite.svg
deleted file mode 100644
index e7b8dfb1..00000000
--- a/examples/react/useGetIdTokenQuery/public/vite.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/examples/react/useGetIdTokenQuery/src/App.tsx b/examples/react/useGetIdTokenQuery/src/App.tsx
deleted file mode 100644
index 8374bbd8..00000000
--- a/examples/react/useGetIdTokenQuery/src/App.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
-import { useState } from "react";
-import { IdTokenExample } from "./components/IdTokenExample";
-
-import "./firebase";
-
-function App() {
- const [queryClient] = useState(
- () =>
- new QueryClient({
- defaultOptions: {
- queries: {
- staleTime: 60 * 1000,
- },
- },
- }),
- );
-
- return (
-
-
-
-
-
- Firebase Authentication Examples
-
-
- TanStack Query Firebase Authentication hooks and patterns
-
-
-
-
-
-
-
-
-
- Built with Vite, TanStack Query, and Firebase Auth
-
-
-
-
-
-
- );
-}
-
-export default App;
diff --git a/examples/react/useGetIdTokenQuery/src/firebase.ts b/examples/react/useGetIdTokenQuery/src/firebase.ts
deleted file mode 100644
index 25843ade..00000000
--- a/examples/react/useGetIdTokenQuery/src/firebase.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { getApps, initializeApp } from "firebase/app";
-import { connectAuthEmulator, getAuth } from "firebase/auth";
-
-if (getApps().length === 0) {
- initializeApp({
- projectId: "example",
- apiKey: "demo-api-key", // Required for Firebase to initialize
- });
-
- // Connect to Auth emulator if running locally
- if (import.meta.env.DEV) {
- try {
- const auth = getAuth();
- connectAuthEmulator(auth, "http://localhost:9099");
- console.log("Connected to Firebase Auth emulator");
- } catch (error) {
- console.warn("Could not connect to Firebase Auth emulator:", error);
- }
- }
-}
diff --git a/examples/react/useGetIdTokenQuery/src/vite-env.d.ts b/examples/react/useGetIdTokenQuery/src/vite-env.d.ts
deleted file mode 100644
index 11f02fe2..00000000
--- a/examples/react/useGetIdTokenQuery/src/vite-env.d.ts
+++ /dev/null
@@ -1 +0,0 @@
-///
diff --git a/examples/react/useGetIdTokenQuery/tailwind.config.ts b/examples/react/useGetIdTokenQuery/tailwind.config.ts
deleted file mode 100644
index e9a0944e..00000000
--- a/examples/react/useGetIdTokenQuery/tailwind.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { Config } from "tailwindcss";
-
-const config: Config = {
- content: [
- "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
- "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
- ],
- theme: {
- extend: {
- backgroundImage: {
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
- "gradient-conic":
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
- },
- },
- },
- plugins: [],
-};
-export default config;
diff --git a/firestore.rules b/firestore.rules
index f10db163..b18e9f05 100644
--- a/firestore.rules
+++ b/firestore.rules
@@ -7,6 +7,28 @@ service cloud.firestore {
allow create: if true;
allow get: if true;
}
+
+ match /products/{document=**} {
+ allow read: if true;
+ allow write: if true;
+ allow create: if true;
+ allow get: if true;
+ }
+
+ match /conversations/{document=**} {
+ allow read: if true;
+ allow write: if true;
+ allow create: if true;
+ allow get: if true;
+ }
+
+ match /tasks/{document=**} {
+ allow read: if true;
+ allow write: if true;
+ allow create: if true;
+ allow get: if true;
+ }
+
// match /noread/{document=**} {
// allow read: if false;
// }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea3da364..14edd8ec 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -64,6 +64,55 @@ importers:
specifier: ^10.14.0 || ^11.3.0
version: 11.3.1
+ examples/react/kitchen-sink:
+ dependencies:
+ '@tanstack-query-firebase/react':
+ specifier: workspace:*
+ version: link:../../../packages/react
+ '@tanstack/react-query':
+ specifier: ^5.66.9
+ version: 5.66.9(react@19.1.1)
+ '@tanstack/react-query-devtools':
+ specifier: ^5.84.2
+ version: 5.84.2(@tanstack/react-query@5.66.9(react@19.1.1))(react@19.1.1)
+ firebase:
+ specifier: ^11.3.1
+ version: 11.3.1
+ react:
+ specifier: ^19.1.1
+ version: 19.1.1
+ react-dom:
+ specifier: ^19.1.1
+ version: 19.1.1(react@19.1.1)
+ react-router-dom:
+ specifier: ^6.28.0
+ version: 6.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ devDependencies:
+ '@types/react':
+ specifier: ^19.1.9
+ version: 19.1.9
+ '@types/react-dom':
+ specifier: ^19.1.7
+ version: 19.1.7(@types/react@19.1.9)
+ '@vitejs/plugin-react':
+ specifier: ^4.7.0
+ version: 4.7.0(vite@7.1.1(@types/node@20.17.19)(jiti@1.21.7)(yaml@2.7.0))
+ autoprefixer:
+ specifier: ^10.4.21
+ version: 10.4.21(postcss@8.5.6)
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.6
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17
+ typescript:
+ specifier: ~5.8.3
+ version: 5.8.3
+ vite:
+ specifier: ^7.1.1
+ version: 7.1.1(@types/node@20.17.19)(jiti@1.21.7)(yaml@2.7.0)
+
examples/react/react-data-connect:
dependencies:
'@dataconnect/default-connector':
@@ -116,52 +165,6 @@ importers:
specifier: ^5
version: 5.8.3
- examples/react/useGetIdTokenQuery:
- dependencies:
- '@tanstack-query-firebase/react':
- specifier: workspace:*
- version: link:../../../packages/react
- '@tanstack/react-query':
- specifier: ^5.66.9
- version: 5.66.9(react@19.1.1)
- '@tanstack/react-query-devtools':
- specifier: ^5.84.2
- version: 5.84.2(@tanstack/react-query@5.66.9(react@19.1.1))(react@19.1.1)
- firebase:
- specifier: ^11.3.1
- version: 11.3.1
- react:
- specifier: ^19.1.1
- version: 19.1.1
- react-dom:
- specifier: ^19.1.1
- version: 19.1.1(react@19.1.1)
- devDependencies:
- '@types/react':
- specifier: ^19.1.9
- version: 19.1.9
- '@types/react-dom':
- specifier: ^19.1.7
- version: 19.1.7(@types/react@19.1.9)
- '@vitejs/plugin-react':
- specifier: ^4.7.0
- version: 4.7.0(vite@7.1.1(@types/node@20.17.19)(jiti@1.21.7)(yaml@2.7.0))
- autoprefixer:
- specifier: ^10.4.21
- version: 10.4.21(postcss@8.5.6)
- postcss:
- specifier: ^8.5.6
- version: 8.5.6
- tailwindcss:
- specifier: ^3.4.17
- version: 3.4.17
- typescript:
- specifier: ~5.8.3
- version: 5.8.3
- vite:
- specifier: ^7.1.1
- version: 7.1.1(@types/node@20.17.19)(jiti@1.21.7)(yaml@2.7.0)
-
packages/angular:
dependencies:
'@angular/common':
@@ -1403,6 +1406,10 @@ packages:
'@protobufjs/utf8@1.1.0':
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+ '@remix-run/router@1.23.0':
+ resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==}
+ engines: {node: '>=14.0.0'}
+
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
@@ -3437,6 +3444,19 @@ packages:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
+ react-router-dom@6.30.1:
+ resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+
+ react-router@6.30.1:
+ resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+
react@19.1.1:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
@@ -5348,6 +5368,8 @@ snapshots:
'@protobufjs/utf8@1.1.0': {}
+ '@remix-run/router@1.23.0': {}
+
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/rollup-android-arm-eabi@4.34.8':
@@ -5555,20 +5577,20 @@ snapshots:
'@types/babel__core@7.20.5':
dependencies:
- '@babel/parser': 7.26.9
- '@babel/types': 7.26.9
+ '@babel/parser': 7.28.0
+ '@babel/types': 7.28.2
'@types/babel__generator': 7.27.0
'@types/babel__template': 7.4.4
'@types/babel__traverse': 7.28.0
'@types/babel__generator@7.27.0':
dependencies:
- '@babel/types': 7.26.9
+ '@babel/types': 7.28.2
'@types/babel__template@7.4.4':
dependencies:
- '@babel/parser': 7.26.9
- '@babel/types': 7.26.9
+ '@babel/parser': 7.28.0
+ '@babel/types': 7.28.2
'@types/babel__traverse@7.28.0':
dependencies:
@@ -7535,6 +7557,18 @@ snapshots:
react-refresh@0.17.0: {}
+ react-router-dom@6.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
+ dependencies:
+ '@remix-run/router': 1.23.0
+ react: 19.1.1
+ react-dom: 19.1.1(react@19.1.1)
+ react-router: 6.30.1(react@19.1.1)
+
+ react-router@6.30.1(react@19.1.1):
+ dependencies:
+ '@remix-run/router': 1.23.0
+ react: 19.1.1
+
react@19.1.1: {}
read-cache@1.0.0: