M2: refactor all firebase write sites to storage adapter

- 37 call sites: setDoc/updateDoc/deleteDoc/addDoc/getDocs/writeBatch -> storage.*
- adapter wraps SDK, path-string interface
- storage instance app-wide (getStorage)
- firebase.js: static imports (getDoc/getDocs alias), no dynamic import

56 frontend tests green. STORAGE=firebase = identical behavior.
This commit is contained in:
david raistrick
2026-06-28 21:05:39 -04:00
parent 5bb9e5fc19
commit 812298fa73
2 changed files with 57 additions and 54 deletions
+53 -50
View File
@@ -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, getStorage } from './storage';
import { getFirestore, doc, setDoc, 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,
@@ -95,6 +95,7 @@ const PUBLIC_DATA_PATH = `artifacts/${APP_ID}/public/data`;
let app;
let db;
let auth;
let storage;
// Initialize Firebase
const initializeFirebase = () => {
@@ -110,6 +111,7 @@ const initializeFirebase = () => {
app = initializeApp(firebaseConfig);
db = getFirestore(app);
auth = getAuth(app);
storage = getStorage();
return true;
} catch (error) {
console.error("Error initializing Firebase:", error);
@@ -162,7 +164,7 @@ const logAction = async (message, context = {}, undoData = null) => {
try {
const entry = { timestamp: Date.now(), message, ...context };
if (undoData) entry.undo = undoData;
await addDoc(collection(db, getPath.logs()), entry);
await storage.addDoc(getPath.logs(), entry);
} catch (err) {
console.error('Error writing log:', err);
}
@@ -565,7 +567,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
};
try {
await updateDoc(doc(db, getPath.campaign(campaignId)), {
await storage.updateDoc(getPath.campaign(campaignId), {
players: [...campaignCharacters, newCharacter]
});
setCharacterName('');
@@ -601,7 +603,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
);
try {
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
setEditingCharacter(null);
} catch (err) {
console.error("Error updating character:", err);
@@ -620,7 +622,7 @@ function CharacterManager({ campaignId, campaignCharacters }) {
const updatedCharacters = campaignCharacters.filter(c => c.id !== itemToDelete.id);
try {
await updateDoc(doc(db, getPath.campaign(campaignId)), { players: updatedCharacters });
await storage.updateDoc(getPath.campaign(campaignId), { players: updatedCharacters });
} catch (err) {
console.error("Error deleting character:", err);
alert("Failed to delete character. Please try again.");
@@ -876,7 +878,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
};
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
participants: [...participants, newParticipant]
});
logAction(`${nameToAdd} added to encounter (Initiative: ${finalInitiative})`, { encounterName: encounter.name }, {
@@ -941,7 +943,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
}
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
participants: [...participants, ...newParticipants]
});
console.log(`Added ${newParticipants.length} characters to the encounter.`);
@@ -959,7 +961,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
);
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
setEditingParticipant(null);
} catch (err) {
console.error("Error updating participant:", err);
@@ -988,7 +990,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
};
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
participants: updatedParticipants,
...computeTurnOrderAfterRemoval(encounter, itemToDelete.id, updatedParticipants)
});
@@ -1018,7 +1020,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
: computeTurnOrderAfterRemoval(encounter, participantId, updatedParticipants);
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
logAction(`${participant.name} ${newIsActive ? 'reactivated' : 'deactivated'}`, { encounterName: encounter.name }, {
encounterPath,
updates: {
@@ -1102,7 +1104,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
};
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants, ...turnUpdates });
await storage.updateDoc(encounterPath, { participants: updatedParticipants, ...turnUpdates });
setHpChangeValues(prev => ({ ...prev, [participantId]: '' }));
const hpLine = `${participant.currentHp}${newHp} HP`;
const deathSuffix = (isDead && !wasDead) ? (participant.type === 'character' ? ' — Unconscious' : ' — Defeated') : '';
@@ -1133,13 +1135,13 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
);
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
// Wait for animation to complete on player display (2 seconds) then remove participant
setTimeout(async () => {
const finalParticipants = participants.filter(p => p.id !== participantId);
try {
await updateDoc(doc(db, encounterPath), { participants: finalParticipants });
await storage.updateDoc(encounterPath, { participants: finalParticipants });
} catch (err) {
console.error("Error removing dead participant:", err);
}
@@ -1154,7 +1156,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
);
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
} catch (err) {
console.error("Error updating death saves:", err);
}
@@ -1175,7 +1177,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
return { ...p, conditions: next };
});
try {
await updateDoc(doc(db, encounterPath), { participants: updatedParticipants });
await storage.updateDoc(encounterPath, { participants: updatedParticipants });
const cond = CONDITIONS.find(c => c.id === conditionId);
const condLabel = cond ? `${cond.label} ${cond.emoji}` : conditionId;
logAction(`${participant.name} ${wasActive ? 'lost' : 'gained'} ${condLabel}`, { encounterName: encounter.name }, {
@@ -1228,7 +1230,7 @@ function ParticipantManager({ encounter, encounterPath, campaignCharacters }) {
currentParticipants.splice(targetIndex, 0, removedItem);
try {
await updateDoc(doc(db, encounterPath), { participants: currentParticipants });
await storage.updateDoc(encounterPath, { participants: currentParticipants });
} catch (err) {
console.error("Error reordering participants:", err);
}
@@ -1600,7 +1602,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
const handleToggleHidePlayerHp = async () => {
if (!db) return;
try {
await setDoc(doc(db, getPath.activeDisplay()), { hidePlayerHp: !hidePlayerHp }, { merge: true });
await storage.setDoc(getPath.activeDisplay(), { hidePlayerHp: !hidePlayerHp }, { merge: true });
} catch (err) {
console.error("Error toggling hidePlayerHp:", err);
}
@@ -1621,7 +1623,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
const sortedParticipants = sortParticipantsByInitiative(activeParticipants, encounter.participants);
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
isStarted: true,
isPaused: false,
round: 1,
@@ -1629,7 +1631,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
turnOrderIds: sortedParticipants.map(p => p.id)
});
await setDoc(doc(db, getPath.activeDisplay()), {
await storage.setDoc(getPath.activeDisplay(), {
activeCampaignId: campaignId,
activeEncounterId: encounter.id
}, { merge: true });
@@ -1664,7 +1666,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
}
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
isPaused: newPausedState,
turnOrderIds: newTurnOrderIds
});
@@ -1690,7 +1692,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
if (activePsInOrder.length === 0) {
alert("No active participants left.");
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
isStarted: false,
isPaused: false,
currentTurnParticipantId: null,
@@ -1730,7 +1732,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
if (!nextParticipant) return;
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
currentTurnParticipantId: nextParticipant.id,
round: nextRound,
turnOrderIds: newTurnOrderIds,
@@ -1752,7 +1754,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
if (!db) return;
try {
await updateDoc(doc(db, encounterPath), {
await storage.updateDoc(encounterPath, {
isStarted: false,
isPaused: false,
currentTurnParticipantId: null,
@@ -1760,7 +1762,7 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
turnOrderIds: []
});
await setDoc(doc(db, getPath.activeDisplay()), {
await storage.setDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null
}, { merge: true });
@@ -1920,7 +1922,7 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
const newEncounterId = generateId();
try {
await setDoc(doc(db, getPath.encounters(campaignId), newEncounterId), {
await storage.setDoc(`${getPath.encounters(campaignId)}/${newEncounterId}`, {
name: name.trim(),
createdAt: new Date().toISOString(),
participants: [],
@@ -1949,14 +1951,14 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
const encounterId = itemToDelete.id;
try {
await deleteDoc(doc(db, getPath.encounter(campaignId, encounterId)));
await storage.deleteDoc(getPath.encounter(campaignId, encounterId));
if (selectedEncounterId === encounterId) {
setSelectedEncounterId(null);
}
if (activeDisplayInfo && activeDisplayInfo.activeEncounterId === encounterId) {
await updateDoc(doc(db, getPath.activeDisplay()), {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null
});
@@ -1978,12 +1980,12 @@ function EncounterManager({ campaignId, initialActiveEncounterId, campaignCharac
const currentActiveEncounter = activeDisplayInfo?.activeEncounterId;
if (currentActiveCampaign === campaignId && currentActiveEncounter === encounterId) {
await setDoc(doc(db, getPath.activeDisplay()), {
await storage.setDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null,
}, { merge: true });
} else {
await setDoc(doc(db, getPath.activeDisplay()), {
await storage.setDoc(getPath.activeDisplay(), {
activeCampaignId: campaignId,
activeEncounterId: encounterId,
}, { merge: true });
@@ -2139,8 +2141,8 @@ function AdminView({ userId }) {
let encounterCount = 0;
try {
const encountersSnapshot = await getDocs(collection(db, getPath.encounters(campaign.id)));
encounterCount = encountersSnapshot.size;
const encounters = await storage.getCollection(getPath.encounters(campaign.id));
encounterCount = encounters.length;
} catch (err) {
console.error(`Failed to fetch encounters for campaign ${campaign.id}:`, err);
}
@@ -2180,7 +2182,7 @@ function AdminView({ userId }) {
const newCampaignId = generateId();
try {
await setDoc(doc(db, getPath.campaign(newCampaignId)), {
await storage.setDoc(getPath.campaign(newCampaignId), {
name: name.trim(),
playerDisplayBackgroundUrl: backgroundUrl.trim() || '',
ownerId: userId,
@@ -2208,23 +2210,23 @@ function AdminView({ userId }) {
try {
const encountersPath = getPath.encounters(campaignId);
const encountersSnapshot = await getDocs(collection(db, encountersPath));
const batch = writeBatch(db);
const encounters = await storage.getCollection(encountersPath);
const deleteOps = encounters.map(e => {
const id = e.id || e.path?.split('/').pop();
return { type: 'delete', path: `${encountersPath}/${id}` };
});
if (deleteOps.length > 0) await storage.batchWrite(deleteOps);
encountersSnapshot.docs.forEach(encounterDoc => batch.delete(encounterDoc.ref));
await batch.commit();
await deleteDoc(doc(db, getPath.campaign(campaignId)));
await storage.deleteDoc(getPath.campaign(campaignId));
if (selectedCampaignId === campaignId) {
setSelectedCampaignId(null);
}
const activeDisplayRef = doc(db, getPath.activeDisplay());
const activeDisplaySnap = await getDoc(activeDisplayRef);
const activeDisplay = await storage.getDoc(getPath.activeDisplay());
if (activeDisplaySnap.exists() && activeDisplaySnap.data().activeCampaignId === campaignId) {
await updateDoc(activeDisplayRef, {
if (activeDisplay && activeDisplay.activeCampaignId === campaignId) {
await storage.updateDoc(getPath.activeDisplay(), {
activeCampaignId: null,
activeEncounterId: null
});
@@ -2672,13 +2674,14 @@ function LogsView() {
const [undoingId, setUndoingId] = useState(null);
const handleClearLogs = async () => {
if (!db) return;
try {
const snapshot = await getDocs(collection(db, getPath.logs()));
if (!snapshot.empty) {
const batch = writeBatch(db);
snapshot.docs.forEach(d => batch.delete(d.ref));
await batch.commit();
const logs = await storage.getCollection(getPath.logs());
if (logs.length > 0) {
const ops = logs.map(l => {
const id = l.id || l.path?.split('/').pop();
return { type: 'delete', path: `${getPath.logs()}/${id}` };
});
await storage.batchWrite(ops);
}
} catch (err) {
console.error('Error clearing logs:', err);
@@ -2690,8 +2693,8 @@ function LogsView() {
if (!db || !entry.undo) return;
setUndoingId(entry.id);
try {
await updateDoc(doc(db, entry.undo.encounterPath), entry.undo.updates);
await updateDoc(doc(db, getPath.logs(), entry.id), { undone: true });
await storage.updateDoc(entry.undo.encounterPath, entry.undo.updates);
await storage.updateDoc(`${getPath.logs()}/${entry.id}`, { undone: true });
} catch (err) {
console.error('Error undoing action:', err);
alert('Failed to roll back. The encounter may have changed or no longer exists.');