Forum https://cdn.tailwindcss.com
// Firebase imports import { initializeApp } from ‘https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js’; import { getAuth, onAuthStateChanged, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile } from ‘https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js’; import { getFirestore, collection, doc, addDoc, getDocs, onSnapshot, query, orderBy, serverTimestamp, setDoc, getDoc } from ‘https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js’; // Make Firebase available globally window.Firebase = { initializeApp, getAuth, onAuthStateChanged, signOut, createUserWithEmailAndPassword, signInWithEmailAndPassword, updateProfile, getFirestore, collection, doc, addDoc, getDocs, onSnapshot, query, orderBy, serverTimestamp, setDoc, getDoc }; // Firebase Configuration – REPLACE WITH YOUR ACTUAL CONFIG const firebaseConfig = { apiKey: “YOUR_API_KEY”, authDomain: “YOUR_AUTH_DOMAIN”, projectId: “YOUR_PROJECT_ID”, storageBucket: “YOUR_STORAGE_BUCKET”, messagingSenderId: “YOUR_MESSAGING_SENDER_ID”, appId: “YOUR_APP_ID” }; // Make config available globally window.__firebase_config = JSON.stringify(firebaseConfig); window.__app_id = ‘aotm-forum-app’; // Forum Application Code const appId = typeof __app_id !== ‘undefined’ ? __app_id : ‘aotm-forum-app’; // Global state let db = null; let auth = null; let user = null; let threads = []; let selectedThread = null; let posts = []; let isLogin = true; let initialThreadTitle = ”; // DOM elements const loadingScreen = document.getElementById(‘loading-screen’); const authScreen = document.getElementById(‘auth-screen’); const mainApp = document.getElementById(‘main-app’); const authTitle = document.getElementById(‘auth-title’); const authSubtitle = document.getElementById(‘auth-subtitle’); const authForm = document.getElementById(‘auth-form’); const authSubmit = document.getElementById(‘auth-submit’); const authToggle = document.getElementById(‘auth-toggle’); const authError = document.getElementById(‘auth-error’); const fullnameField = document.getElementById(‘fullname-field’); const userName = document.getElementById(‘user-name’); const signOutBtn = document.getElementById(‘sign-out-btn’); const mainTitle = document.getElementById(‘main-title’); const newThreadBtn = document.getElementById(‘new-thread-btn’); const contentLoading = document.getElementById(‘content-loading’); const threadList = document.getElementById(‘thread-list’); const threadView = document.getElementById(‘thread-view’); const backToThreads = document.getElementById(‘back-to-threads’); const threadDetails = document.getElementById(‘thread-details’); const postsList = document.getElementById(‘posts-list’); const postForm = document.getElementById(‘post-form’); const createThreadForm = document.getElementById(‘create-thread-form’); const threadForm = document.getElementById(‘thread-form’); const cancelThread = document.getElementById(‘cancel-thread’); const emptyState = document.getElementById(’empty-state’); const currentYear = document.getElementById(‘current-year’); // Initialize app async function initializeApp() { try { // Set current year currentYear.textContent = new Date().getFullYear(); // Wait for Firebase to be available while (!window.Firebase) { await new Promise(resolve => setTimeout(resolve, 100)); } const firebaseConfig = JSON.parse(window.__firebase_config); const app = window.Firebase.initializeApp(firebaseConfig); db = window.Firebase.getFirestore(app); auth = window.Firebase.getAuth(app); // Check URL for thread subject const queryParams = new URLSearchParams(window.location.search); const subject = queryParams.get(‘thread_subject’); if (subject) { initialThreadTitle = subject; } // Listen for auth state changes window.Firebase.onAuthStateChanged(auth, (currentUser) => { user = currentUser; updateUI(); if (user) { loadThreads(); } }); } catch (error) { console.error(“Firebase initialization error:”, error); loadingScreen.style.display = ‘none’; authScreen.style.display = ‘flex’; } } // Update UI based on auth state function updateUI() { loadingScreen.style.display = ‘none’; if (!user) { authScreen.style.display = ‘flex’; mainApp.style.display = ‘none’; } else { authScreen.style.display = ‘none’; mainApp.style.display = ‘block’; userName.textContent = user.displayName || ‘User’; // Reset view state showThreadList(); } } // Auth functions function toggleAuthMode() { isLogin = !isLogin; authError.style.display = ‘none’; if (isLogin) { authTitle.textContent = ‘Welcome Back’; authSubtitle.textContent = ‘Sign in to continue’; authSubmit.textContent = ‘Sign In’; authToggle.textContent = ‘Need an account? Sign Up’; fullnameField.style.display = ‘none’; } else { authTitle.textContent = ‘Join the Discussion’; authSubtitle.textContent = ‘Create an account to get started’; authSubmit.textContent = ‘Sign Up’; authToggle.textContent = ‘Already have an account? Sign In’; fullnameField.style.display = ‘block’; } } async function handleAuth(e) { e.preventDefault(); authError.style.display = ‘none’; const email = document.getElementById(’email’).value; const password = document.getElementById(‘password’).value; const fullName = document.getElementById(‘fullName’).value; try { if (isLogin) { await window.Firebase.signInWithEmailAndPassword(auth, email, password); } else { if (!fullName) { showAuthError(‘Full name is required to sign up.’); return; } const userCredential = await window.Firebase.createUserWithEmailAndPassword(auth, email, password); await window.Firebase.updateProfile(userCredential.user, { displayName: fullName }); } } catch (err) { showAuthError(err.message); } } function showAuthError(message) { authError.textContent = message; authError.style.display = ‘block’; } async function handleSignOut() { try { await window.Firebase.signOut(auth); threads = []; selectedThread = null; posts = []; } catch (error) { console.error(‘Sign out error:’, error); } } // Thread functions async function loadThreads() { if (!user || !db) { threads = []; renderThreadList(); return; } contentLoading.style.display = ‘flex’; const threadsCollectionPath = `artifacts/${appId}/public/data/threads`; const q = window.Firebase.query(window.Firebase.collection(db, threadsCollectionPath)); window.Firebase.onSnapshot(q, (querySnapshot) => { threads = []; querySnapshot.forEach((doc) => { threads.push({ id: doc.id, …doc.data() }); }); threads.sort((a, b) => (b.createdAt?.toMillis() || 0) – (a.createdAt?.toMillis() || 0)); contentLoading.style.display = ‘none’; renderThreadList(); }, (error) => { console.error(“Error fetching threads:”, error); contentLoading.style.display = ‘none’; renderThreadList(); }); } function renderThreadList() { threadList.innerHTML = ”; if (threads.length === 0) { emptyState.style.display = ‘block’; return; } emptyState.style.display = ‘none’; threads.forEach(thread => { const threadElement = document.createElement(‘div’); threadElement.className = ‘bg-white p-5 rounded-lg shadow-sm hover:shadow-lg transition-shadow duration-300 cursor-pointer border border-gray-200’; threadElement.onclick = () => selectThread(thread); threadElement.innerHTML = `

${escapeHtml(thread.title)}

By ${escapeHtml(thread.authorDisplayName)} ${formatTimestamp(thread.createdAt)} ${thread.replyCount || 0} replies
`; threadList.appendChild(threadElement); }); } function selectThread(thread) { selectedThread = thread; loadPosts(); showThreadView(); } async function loadPosts() { if (!selectedThread || !db) return; const postsCollectionPath = `artifacts/${appId}/public/data/threads/${selectedThread.id}/posts`; const q = window.Firebase.query( window.Firebase.collection(db, postsCollectionPath), window.Firebase.orderBy(“createdAt”, “asc”) ); window.Firebase.onSnapshot(q, (querySnapshot) => { posts = []; querySnapshot.forEach((doc) => { posts.push({ id: doc.id, …doc.data() }); }); renderPosts(); }, (error) => console.error(“Error fetching posts:”, error)); } function renderPosts() { postsList.innerHTML = ”; posts.forEach(post => { const postElement = document.createElement(‘div’); postElement.className = ‘bg-white p-4 rounded-lg shadow-sm border border-gray-200’; postElement.innerHTML = `
${escapeHtml(post.authorDisplayName)} ${formatTimestamp(post.createdAt)}

${escapeHtml(post.content)}

`; postsList.appendChild(postElement); }); // Scroll to bottom setTimeout(() => { postsList.scrollIntoView({ behavior: ‘smooth’, block: ‘end’ }); }, 100); } async function createThread(title, content) { if (!db || !user) return; const threadsCollectionPath = `artifacts/${appId}/public/data/threads`; try { await window.Firebase.addDoc(window.Firebase.collection(db, threadsCollectionPath), { title: title, content: content, authorId: user.uid, authorDisplayName: user.displayName || “Anonymous”, createdAt: window.Firebase.serverTimestamp(), replyCount: 0, }); showThreadList(); initialThreadTitle = ”; } catch (error) { console.error(“Error creating thread:”, error); } } async function createPost(content) { if (!db || !user || !selectedThread) return; const postsCollectionPath = `artifacts/${appId}/public/data/threads/${selectedThread.id}/posts`; const threadDocRef = window.Firebase.doc(db, `artifacts/${appId}/public/data/threads`, selectedThread.id); try { await window.Firebase.addDoc(window.Firebase.collection(db, postsCollectionPath), { content: content, authorId: user.uid, authorDisplayName: user.displayName || “Anonymous”, createdAt: window.Firebase.serverTimestamp(), }); const threadDoc = await window.Firebase.getDoc(threadDocRef); const currentReplyCount = threadDoc.data().replyCount || 0; await window.Firebase.setDoc(threadDocRef, { replyCount: currentReplyCount + 1 }, { merge: true }); // Clear the form document.getElementById(‘post-content’).value = ”; } catch (error) { console.error(“Error creating post:”, error); } } // UI Navigation function showThreadList() { mainTitle.textContent = ‘Forum Threads’; newThreadBtn.style.display = ‘flex’; threadList.style.display = ‘block’; threadView.style.display = ‘none’; createThreadForm.style.display = ‘none’; if (initialThreadTitle) { showCreateThreadForm(); } } function showThreadView() { mainTitle.textContent = ‘Discussion’; newThreadBtn.style.display = ‘none’; threadList.style.display = ‘none’; threadView.style.display = ‘block’; createThreadForm.style.display = ‘none’; emptyState.style.display = ‘none’; // Render thread details threadDetails.innerHTML = `

${escapeHtml(selectedThread.title)}

By ${escapeHtml(selectedThread.authorDisplayName)} ${formatTimestamp(selectedThread.createdAt)}

${escapeHtml(selectedThread.content)}

`; } function showCreateThreadForm() { mainTitle.textContent = ‘Forum Threads’; newThreadBtn.style.display = ‘none’; threadList.style.display = ‘none’; threadView.style.display = ‘none’; createThreadForm.style.display = ‘block’; emptyState.style.display = ‘none’; // Set initial title if provided document.getElementById(‘thread-title’).value = initialThreadTitle; } // Event Listeners authToggle.addEventListener(‘click’, toggleAuthMode); authForm.addEventListener(‘submit’, handleAuth); signOutBtn.addEventListener(‘click’, handleSignOut); newThreadBtn.addEventListener(‘click’, showCreateThreadForm); backToThreads.addEventListener(‘click’, () => { selectedThread = null; posts = []; showThreadList(); }); cancelThread.addEventListener(‘click’, () => { showThreadList(); initialThreadTitle = ”; }); threadForm.addEventListener(‘submit’, (e) => { e.preventDefault(); const title = document.getElementById(‘thread-title’).value.trim(); const content = document.getElementById(‘thread-content’).value.trim(); if (title && content) { createThread(title, content); document.getElementById(‘thread-title’).value = ”; document.getElementById(‘thread-content’).value = ”; } }); postForm.addEventListener(‘submit’, (e) => { e.preventDefault(); const content = document.getElementById(‘post-content’).value.trim(); if (content) { createPost(content); } }); // Utility Functions function formatTimestamp(timestamp) { if (!timestamp) return ‘Just now’; const date = timestamp.toDate(); const now = new Date(); const secondsPast = (now.getTime() – date.getTime()) / 1000; if (secondsPast < 60) return `${Math.round(secondsPast)}s ago`; if (secondsPast < 3600) return `${Math.round(secondsPast / 60)}m ago`; if (secondsPast <= 86400) return `${Math.round(secondsPast / 3600)}h ago`; const day = date.getDate(); const month = date.toLocaleString('default', { month: 'short' }); const year = date.getFullYear(); if(year === now.getFullYear()) return `${month} ${day}`; return `${month} ${day}, ${year}`; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Initialize the app initializeApp();

Subscribe to be informed about all upcoming topics.

Go back

Your message has been sent

Warning
Warning
Warning!