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 (
);
}
return (
{currentPage === 'home' && (
)}
{currentPage === 'editor' && (
)}
{currentPage === 'post' && selectedPost && (
)}
);
};
// Header Component
const Header = ({ onNewPost, onSearchChange, searchTerm, userId }) => {
return (
);
};
// 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!
) : (
)}
);
};
// 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 && (
{ 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 && (

{ 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 && (
)}
);
};
// 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 && (
{ 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!
);
};
// 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;