import React, { useState, useEffect, useRef } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, collection, addDoc, getDocs, doc, getDoc, updateDoc, onSnapshot, query, where } from 'firebase/firestore'; import { marked } from 'marked'; import DOMPurify from 'dompurify'; // Ensure __app_id and __firebase_config are defined in the environment const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; // Initialize Firebase App and Services let app; let db; let auth; // Set up marked options for safe rendering marked.setOptions({ breaks: true, gfm: true, }); const App = () => { const [currentPage, setCurrentPage] = useState('home'); // 'home', 'editor', 'post' const [posts, setPosts] = useState([]); const [selectedPost, setSelectedPost] = useState(null); const [userId, setUserId] = useState(null); const [isAuthReady, setIsAuthReady] = useState(false); const [currentFilter, setCurrentFilter] = useState('All'); const [viewMode, setViewMode] = useState('grid'); // 'grid' or 'list' const [searchTerm, setSearchTerm] = useState(''); const [showLoading, setShowLoading] = useState(true); // Initialize Firebase and set up auth listener useEffect(() => { try { if (!app) { app = initializeApp(firebaseConfig); db = getFirestore(app); auth = getAuth(app); } const unsubscribe = onAuthStateChanged(auth, async (user) => { if (user) { setUserId(user.uid); console.log("Firebase Auth Ready. User ID:", user.uid); } else { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); console.log("Signed in with custom token."); } else { await signInAnonymously(auth); console.log("Signed in anonymously."); } } catch (error) { console.error("Firebase Auth Error:", error); } } setIsAuthReady(true); // Auth state checked }); return () => unsubscribe(); } catch (error) { console.error("Firebase Initialization Error:", error); } }, []); // Fetch posts when auth is ready or when userId changes useEffect(() => { if (!isAuthReady || !db) return; // Use a public collection for posts const postsColRef = collection(db, `artifacts/${appId}/public/data/posts`); const unsubscribe = onSnapshot(postsColRef, (snapshot) => { const fetchedPosts = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), date: doc.data().date?.toDate ? doc.data().date.toDate() : new Date(doc.data().date), // Convert Firebase Timestamp to Date })).sort((a, b) => b.date - a.date); // Sort by date descending setPosts(fetchedPosts); setShowLoading(false); }, (error) => { console.error("Error fetching posts:", error); setShowLoading(false); }); return () => unsubscribe(); }, [isAuthReady, userId]); // Depend on isAuthReady and userId const handleNewPostClick = () => { setSelectedPost(null); // Clear selected post for new entry setCurrentPage('editor'); }; const handlePostClick = (post) => { setSelectedPost(post); setCurrentPage('post'); }; const handleBackToHome = () => { setCurrentPage('home'); setSelectedPost(null); }; // Get unique categories for filtering const categories = ['All', ...new Set(posts.flatMap(post => post.categories || []))]; const filteredAndSearchedPosts = posts.filter(post => { const matchesCategory = currentFilter === 'All' || (post.categories && post.categories.includes(currentFilter)); const matchesSearch = searchTerm === '' || post.title.toLowerCase().includes(searchTerm.toLowerCase()) || post.content.toLowerCase().includes(searchTerm.toLowerCase()); return matchesCategory && matchesSearch; }); if (showLoading) { return (
Loading blog...
); } return (
{currentPage === 'home' && ( )} {currentPage === 'editor' && ( )} {currentPage === 'post' && selectedPost && ( )}
); }; // Header Component const Header = ({ onNewPost, onSearchChange, searchTerm, userId }) => { return (

My Blog

User ID: {userId || 'N/A'}
onSearchChange(e.target.value)} />
); }; // HomePage Component const HomePage = ({ posts, onPostClick, categories, onFilterChange, currentFilter, viewMode, onToggleViewMode }) => { return (
{/* Advertisement Banner on Home Page */}
ADVERTISEMENT: Discover great deals on web hosting!
{categories.map(category => ( ))}
{posts.length === 0 ? (

No posts found. Be the first to write one!

) : (
{posts.map(post => ( ))}
)}
); }; // PostCard Component const PostCard = ({ post, onPostClick, viewMode }) => { const excerpt = post.content ? marked.parse(post.content).replace(/<[^>]*>/g, '').substring(0, 150) + '...' : ''; const dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }; const formattedDate = post.date ? new Date(post.date).toLocaleDateString(undefined, dateOptions) : 'No date'; return (
onPostClick(post)} > {post.featuredImage && ( {post.title} { e.target.onerror = null; e.target.src="https://placehold.co/600x400/CCE5FF/000000?text=No+Image"; }} /> )}

{post.title}

{formattedDate}

{post.categories && post.categories.map(category => ( {category} ))}
); }; // PostEditor Component const PostEditor = ({ db, userId, onPostSaved, initialPost }) => { const [title, setTitle] = useState(initialPost?.title || ''); const [content, setContent] = useState(initialPost?.content || ''); const [featuredImage, setFeaturedImage] = useState(initialPost?.featuredImage || ''); const [categories, setCategories] = useState(initialPost?.categories?.join(', ') || ''); const [showImageModal, setShowImageModal] = useState(false); const [currentImageUrl, setCurrentImageUrl] = useState(''); const [saving, setSaving] = useState(false); const [message, setMessage] = useState(''); const markdownPreview = DOMPurify.sanitize(marked.parse(content)); const handleSavePost = async () => { if (!title || !content) { setMessage("Title and Content cannot be empty!"); return; } if (!userId) { setMessage("User not authenticated. Please wait or try again."); return; } setSaving(true); setMessage(''); try { const postData = { title, content, featuredImage, categories: categories.split(',').map(c => c.trim()).filter(c => c !== ''), date: new Date(), comments: initialPost?.comments || [], // Preserve existing comments if editing }; const postsColRef = collection(db, `artifacts/${appId}/public/data/posts`); if (initialPost) { // Update existing post const postDocRef = doc(db, postsColRef.path, initialPost.id); await updateDoc(postDocRef, postData); setMessage("Post updated successfully!"); console.log("Post updated with ID: ", initialPost.id); } else { // Add new post const docRef = await addDoc(postsColRef, postData); setMessage("Post published successfully!"); console.log("Post written with ID: ", docRef.id); } setTimeout(onPostSaved, 1500); // Go back to home after a delay } catch (e) { console.error("Error adding/updating document: ", e); setMessage(`Error: ${e.message}`); } finally { setSaving(false); } }; const handleInsertImage = () => { setShowImageModal(true); setCurrentImageUrl(''); // Clear previous URL }; const handleImageModalSubmit = () => { setFeaturedImage(currentImageUrl); setShowImageModal(false); }; const handleKeyDown = (e) => { if (e.key === 'Tab') { e.preventDefault(); const { selectionStart, selectionEnd, value } = e.target; setContent(value.substring(0, selectionStart) + ' ' + value.substring(selectionEnd)); setTimeout(() => { e.target.selectionStart = selectionStart + 2; e.target.selectionEnd = selectionStart + 2; }, 0); } }; return (

{initialPost ? 'Edit Post' : 'Create New Post'}

setTitle(e.target.value)} />
setCategories(e.target.value)} />
setFeaturedImage(e.target.value)} />
{featuredImage && ( Featured { e.target.onerror = null; e.target.src="https://placehold.co/300x200/CCE5FF/000000?text=Image+Load+Error"; }} /> )}
Supports **bold**, *italics*, `code`, # headings, etc.
{message && (

{message}

)}
{/* Image Upload Modal */} {showImageModal && (

Insert Image URL

setCurrentImageUrl(e.target.value)} />
)}
); }; // PostPage Component const PostPage = ({ db, userId, post, onBack }) => { const [comments, setComments] = useState(post.comments || []); // Update comments if post prop changes (e.g., coming from editing) useEffect(() => { setComments(post.comments || []); }, [post]); const addComment = async (commentText) => { if (!commentText.trim()) return; if (!userId) { console.error("User not authenticated to add comment."); return; } const newComment = { id: crypto.randomUUID(), // Unique ID for the comment authorId: userId, author: `User-${userId.substring(0, 6)}`, // Simplified author name text: commentText, date: new Date().toISOString(), }; const updatedComments = [...comments, newComment]; setComments(updatedComments); try { const postDocRef = doc(db, `artifacts/${appId}/public/data/posts`, post.id); await updateDoc(postDocRef, { comments: updatedComments, }); console.log("Comment added and post updated successfully."); } catch (error) { console.error("Error adding comment to post:", error); // Revert comments if update fails setComments(comments); } }; const shareOnTwitter = () => { const text = encodeURIComponent(`Check out this blog post: ${post.title}`); const url = encodeURIComponent(window.location.href); window.open(`https://twitter.com/intent/tweet?text=${text}&url=${url}`, '_blank'); }; const shareOnFacebook = () => { const url = encodeURIComponent(window.location.href); window.open(`https://www.facebook.com/sharer/sharer.php?u=${url}`, '_blank'); }; const shareOnLinkedIn = () => { const title = encodeURIComponent(post.title); const summary = encodeURIComponent(post.content.substring(0, 200) + '...'); const url = encodeURIComponent(window.location.href); window.open(`https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${title}&summary=${summary}&source=MyBlog`, '_blank'); }; const postContentHtml = DOMPurify.sanitize(marked.parse(post.content)); const dateOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }; const formattedDate = post.date ? new Date(post.date).toLocaleDateString(undefined, dateOptions) : 'No date'; return (
{post.featuredImage && ( {post.title} { e.target.onerror = null; e.target.src="https://placehold.co/800x400/CCE5FF/000000?text=No+Image"; }} /> )}

{post.title}

{formattedDate}

{post.categories && post.categories.map(category => ( {category} ))}
{/* Advertisement Banner within Post Page */}
ADVERTISEMENT: Explore new technologies and gadgets!

Share this Post

); }; // CommentsSection Component const CommentsSection = ({ comments, onAddComment }) => { const [newComment, setNewComment] = useState(''); const handleSubmitComment = (e) => { e.preventDefault(); if (newComment.trim()) { onAddComment(newComment); setNewComment(''); } }; const formatDate = (dateString) => { const date = new Date(dateString); const options = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return date.toLocaleDateString(undefined, options); }; return (

Comments ({comments.length})

{comments.length === 0 ? (

No comments yet. Be the first to leave one!

) : (
{comments.map((comment) => (
{comment.author ? comment.author[0].toUpperCase() : 'U'}

{comment.author || 'Anonymous'}

{formatDate(comment.date)}

{comment.text}

))}
)}
); }; export default App;