diff --git a/src/App.js b/src/App.js index 1dad3e0..2a5c949 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import { initializeApp } from './storage'; import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from './storage'; -import { getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch } from './storage'; +import { getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, getStorage } from './storage'; import { PlusCircle, Users, Swords, Trash2, Eye, Edit3, Save, XCircle, ChevronsUpDown, UserCheck, UserX, HeartCrack, HeartPulse, Zap, EyeOff, ExternalLink, AlertTriangle, @@ -201,32 +201,22 @@ function useFirestoreDocument(docPath) { const [error, setError] = useState(null); useEffect(() => { - if (!db || !docPath) { + if (!docPath) { setData(null); setIsLoading(false); - setError(docPath ? "Firestore not available." : "Document path not provided."); + setError("Document path not provided."); return; } setIsLoading(true); setError(null); - const docRef = doc(db, docPath); - const unsubscribe = onSnapshot( - docRef, - (docSnap) => { - setData(docSnap.exists() ? { id: docSnap.id, ...docSnap.data() } : null); - setIsLoading(false); - }, - (err) => { - console.error(`Error fetching document ${docPath}:`, err); - setError(err.message || "Failed to fetch document."); - setIsLoading(false); - setData(null); - } - ); - - return () => unsubscribe(); + const storage = getStorage(); + const unsubscribe = storage.subscribeDoc(docPath, (doc) => { + setData(doc); + setIsLoading(false); + }); + return () => { if (typeof unsubscribe === 'function') unsubscribe(); }; }, [docPath]); return { data, isLoading, error }; @@ -239,34 +229,23 @@ function useFirestoreCollection(collectionPath, queryConstraints = []) { const queryString = useMemo(() => JSON.stringify(queryConstraints), [queryConstraints]); useEffect(() => { - if (!db || !collectionPath) { + if (!collectionPath) { setData([]); setIsLoading(false); - setError(collectionPath ? "Firestore not available." : "Collection path not provided."); + setError("Collection path not provided."); return; } setIsLoading(true); setError(null); - const q = query(collection(db, collectionPath), ...queryConstraints); - const unsubscribe = onSnapshot( - q, - (snapshot) => { - const items = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); - setData(items); - setIsLoading(false); - }, - (err) => { - console.error(`Error fetching collection ${collectionPath}:`, err); - setError(err.message || "Failed to fetch collection."); - setIsLoading(false); - setData([]); - } - ); - - return () => unsubscribe(); - // We use queryString instead of queryConstraints to avoid re-renders on array reference changes + const storage = getStorage(); + const unsubscribe = storage.subscribeCollection(collectionPath, (items) => { + setData(items); + setIsLoading(false); + }); + return () => { if (typeof unsubscribe === 'function') unsubscribe(); }; + // queryString, not array ref // eslint-disable-next-line react-hooks/exhaustive-deps }, [collectionPath, queryString]); diff --git a/src/storage/index.js b/src/storage/index.js index 3b25acc..74ffdbe 100644 --- a/src/storage/index.js +++ b/src/storage/index.js @@ -1,13 +1,47 @@ -// src/storage/index.js — barrel re-export of Firebase SDK. -// Phase C: App.js swaps imports from 'firebase/*' to here. -// STORAGE=firebase = identical behavior. Zero risk. -// Later: factory picks impl based on REACT_APP_STORAGE env. +// src/storage/index.js — storage factory + SDK re-exports. +// STORAGE=firebase (default): adapter wraps SDK. STORAGE=ws: backend. +// App.js imports getStorage() for subscribe; still imports SDK pieces for writes (per-group refactor pending). -export { initializeApp } from 'firebase/app'; -export { +import { initializeApp } from 'firebase/app'; +import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken, } from 'firebase/auth'; -export { +import { getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, } from 'firebase/firestore'; +import { initFirebase, createFirebaseStorage } from './firebase'; + +let storageInstance = null; + +// Returns adapter instance implementing interface (getDoc/setDoc/subscribeDoc/etc). +export function getStorage() { + if (storageInstance) return storageInstance; + const mode = process.env.REACT_APP_STORAGE || 'firebase'; + if (mode === 'firebase') { + const ok = initFirebase(); + if (!ok) throw new Error('Firebase config missing. Check REACT_APP_FIREBASE_* env.'); + storageInstance = createFirebaseStorage(); + } else if (mode === 'ws') { + const { createWsStorage } = require('./ws'); + storageInstance = createWsStorage({ + baseUrl: process.env.REACT_APP_BACKEND_URL || 'http://127.0.0.1:4001', + wsUrl: process.env.REACT_APP_BACKEND_WS || 'ws://127.0.0.1:4001/ws', + }); + } else { + const { createMemoryStorage } = require('./memory'); + storageInstance = createMemoryStorage(); + } + return storageInstance; +} + +export function getStorageMode() { + return process.env.REACT_APP_STORAGE || 'firebase'; +} + +export { + initializeApp, + getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken, + getFirestore, doc, setDoc, getDoc, getDocs, addDoc, collection, + onSnapshot, updateDoc, deleteDoc, query, orderBy, limit, writeBatch, +};