import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, doc, addDoc, setDoc, deleteDoc, onSnapshot, query, where, getDocs, writeBatch } from 'firebase/firestore'; // --- Helper Functions & Constants --- const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-phd-notes'; // --- Main App Component --- export default function App() { const [auth, setAuth] = useState(null); const [db, setDb] = useState(null); const [userId, setUserId] = useState(null); const [isAuthReady, setIsAuthReady] = useState(false); const [notes, setNotes] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [newNoteContent, setNewNoteContent] = useState(''); // --- Firebase Initialization --- useEffect(() => { try { const app = initializeApp(firebaseConfig); const authInstance = getAuth(app); const dbInstance = getFirestore(app); setAuth(authInstance); setDb(dbInstance); const unsubscribe = onAuthStateChanged(authInstance, async (user) => { if (user) { setUserId(user.uid); } else { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(authInstance, __initial_auth_token); } else { await signInAnonymously(authInstance); } } catch (authError) { console.error("Authentication Error:", authError); setError("Failed to authenticate. Please refresh the page."); } } setIsAuthReady(true); }); return () => unsubscribe(); } catch (e) { console.error("Firebase initialization failed:", e); setError("Could not connect to the database. Please check your connection or configuration."); setLoading(false); } }, []); // --- Data Fetching (Notes) --- useEffect(() => { if (!isAuthReady || !db || !userId) return; setLoading(true); const notesCollectionPath = `artifacts/${appId}/users/${userId}/notes`; const q = query(collection(db, notesCollectionPath)); const unsubscribe = onSnapshot(q, (snapshot) => { const notesData = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setNotes(notesData); setLoading(false); }, (err) => { console.error("Firestore Snapshot Error:", err); setError("Failed to load notes. Please try again later."); setLoading(false); }); return () => unsubscribe(); }, [isAuthReady, db, userId]); // --- Data Transformation (Build Tree) --- const noteTree = useMemo(() => { const noteMap = new Map(notes.map(note => [note.id, { ...note, children: [] }])); const tree = []; noteMap.forEach(note => { if (note.parentId && noteMap.has(note.parentId)) { const parent = noteMap.get(note.parentId); parent.children.push(note); } else { tree.push(note); } }); return tree; }, [notes]); // --- Core Firestore Actions --- const handleAddNote = useCallback(async (content, parentId = null) => { if (!db || !userId || !content.trim()) return; try { const notesCollectionPath = `artifacts/${appId}/users/${userId}/notes`; await addDoc(collection(db, notesCollectionPath), { content: content.trim(), parentId, createdAt: new Date(), userId, }); if (!parentId) { setNewNoteContent(''); } } catch (err) { console.error("Error adding note:", err); setError("Could not add note."); } }, [db, userId]); const handleUpdateNote = useCallback(async (id, newContent) => { if (!db || !userId || !newContent.trim()) return; try { const noteDocPath = `artifacts/${appId}/users/${userId}/notes/${id}`; await setDoc(doc(db, noteDocPath), { content: newContent.trim() }, { merge: true }); } catch (err) { console.error("Error updating note:", err); setError("Could not update note."); } }, [db, userId]); const handleDeleteNote = useCallback(async (noteIdToDelete) => { if (!db || !userId) return; try { const batch = writeBatch(db); const notesCollectionPath = `artifacts/${appId}/users/${userId}/notes`; // Find all descendants const descendants = new Set([noteIdToDelete]); let searchQueue = [noteIdToDelete]; while(searchQueue.length > 0){ const currentId = searchQueue.shift(); const q = query(collection(db, notesCollectionPath), where("parentId", "==", currentId)); const snapshot = await getDocs(q); snapshot.forEach(doc => { if (!descendants.has(doc.id)) { descendants.add(doc.id); searchQueue.push(doc.id); } }); } // Delete the note and all its descendants descendants.forEach(id => { const noteDocPath = `artifacts/${appId}/users/${userId}/notes/${id}`; batch.delete(doc(db, noteDocPath)); }); await batch.commit(); } catch (err) { console.error("Error deleting note and its children:", err); setError("Could not delete the note branch."); } }, [db, userId]); // --- Render Logic --- if (error) { return

Error

{error}

; } return (

PhD Research Notes

{isAuthReady && userId && UserID: {userId}}

Add a New Research Idea

setNewNoteContent(e.target.value)} placeholder="Start a new research branch..." className="flex-grow p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition" onKeyPress={(e) => e.key === 'Enter' && handleAddNote(newNoteContent)} />
{loading && (

Loading your research...

)} {!loading && notes.length === 0 && (

No research notes yet

Get started by adding a new research idea above.

)} {!loading && noteTree.length > 0 && (
{noteTree.map(note => ( ))}
)}
); } // --- NoteNode Component --- function NoteNode({ note, onAdd, onUpdate, onDelete, level }) { const [isEditing, setIsEditing] = useState(false); const [content, setContent] = useState(note.content); const [newChildContent, setNewChildContent] = useState(''); const [isAdding, setIsAdding] = useState(false); const handleUpdate = () => { if (content !== note.content) { onUpdate(note.id, content); } setIsEditing(false); }; const handleAddChild = () => { if (newChildContent.trim()) { onAdd(newChildContent, note.id); setNewChildContent(''); setIsAdding(false); } } const branchColorClasses = [ 'border-blue-500', 'border-purple-500', 'border-green-500', 'border-yellow-500', 'border-red-500', 'border-indigo-500' ]; const borderClass = branchColorClasses[level % branchColorClasses.length]; return (
{isEditing ? ( setContent(e.target.value)} onBlur={handleUpdate} onKeyPress={(e) => e.key === 'Enter' && handleUpdate()} className="flex-grow p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" autoFocus /> ) : (

{note.content}

)}
{isAdding && (
setNewChildContent(e.target.value)} placeholder="New finding or sub-topic..." className="flex-grow p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500" autoFocus onKeyPress={(e) => e.key === 'Enter' && handleAddChild()} />
)} {note.children && note.children.length > 0 && (
{note.children.map(child => ( ))}
)}
); }