say it here

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Public Message Board</title>
    <!-- Load Tailwind CSS from CDN -->
    <script src="https://cdn.tailwindcss.com"></script>
    <!-- Use Inter font family -->
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
        body {
            font-family: 'Inter', sans-serif;
            background-color: #0d1117; /* Dark background for a calm feel */
            color: #c9d1d9; /* Light text */
            min-height: 100vh;
        }
        .note-card {
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.06);
        }
    </style>
    <!-- Firebase SDK Imports -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, collection, addDoc, onSnapshot, query, serverTimestamp, setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // IMPORTANT: Global variables provided by the Canvas environment
        const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
        const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : null;
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        let db;
        let auth;
        let userId = null;
        let notesCollectionRef = null;

        // Utility function to safely parse a date and format it
        function formatTimestamp(timestamp) {
            if (timestamp && timestamp.toDate) {
                return timestamp.toDate().toLocaleString('en-US', {
                    year: 'numeric',
                    month: 'short',
                    day: 'numeric',
                    hour: '2-digit',
                    minute: '2-digit'
                });
            }
            return 'Unknown Time';
        }

        // --- 1. FIREBASE INITIALIZATION AND AUTHENTICATION ---
        async function initializeFirebase() {
            if (!firebaseConfig) {
                console.error("Firebase configuration is missing.");
                return;
            }

            setLogLevel('debug'); // Enable debug logging for Firestore

            const app = initializeApp(firebaseConfig);
            db = getFirestore(app);
            auth = getAuth(app);

            // Authentication setup
            try {
                if (initialAuthToken) {
                    await signInWithCustomToken(auth, initialAuthToken);
                } else {
                    await signInAnonymously(auth);
                }
            } catch (error) {
                console.error("Firebase Auth Error:", error);
            }

            // Listen for auth state changes to get the user ID
            onAuthStateChanged(auth, (user) => {
                if (user) {
                    userId = user.uid;
                } else {
                    // Fallback to a random ID if anonymous sign-in failed (shouldn't happen)
                    userId = crypto.randomUUID();
                }

                // Initialize Firestore references after getting the userId
                // Public data path: /artifacts/{appId}/public/data/notes
                notesCollectionRef = collection(db, 'artifacts', appId, 'public', 'data', 'notes');

                // Start listeners once authenticated and references are set
                setupNoteListener();
                document.getElementById('userIdDisplay').textContent = userId;
                document.getElementById('postNoteForm').classList.remove('hidden');
                document.getElementById('loadingIndicator').classList.add('hidden');
            });
        }

        // --- 2. REAL-TIME NOTE LISTENER ---
        function setupNoteListener() {
            if (!notesCollectionRef) return;

            const q = query(notesCollectionRef); // Fetching all notes in the public collection

            onSnapshot(q, (snapshot) => {
                const notesContainer = document.getElementById('notesContainer');
                const noteDocs = [];

                snapshot.forEach((doc) => {
                    noteDocs.push({ id: doc.id, ...doc.data() });
                });

                // IMPORTANT: Sort in memory since Firestore orderBy() is discouraged.
                // Sort by timestamp descending (newest first)
                noteDocs.sort((a, b) => {
                    const timeA = a.timestamp ? a.timestamp.toMillis() : 0;
                    const timeB = b.timestamp ? b.timestamp.toMillis() : 0;
                    return timeB - timeA;
                });

                // Clear and render new list
                notesContainer.innerHTML = '';
                if (noteDocs.length === 0) {
                    notesContainer.innerHTML = '<p class="text-center text-gray-500 py-8">Be the first to leave a message.</p>';
                } else {
                    noteDocs.forEach(note => {
                        const noteCard = document.createElement('div');
                        noteCard.className = 'note-card p-4 bg-gray-800 rounded-lg border border-gray-700 hover:border-blue-500 transition-colors';
                        
                        // Sanitize and display message content
                        const messageText = note.message ? note.message.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;') : 'Empty Message';

                        noteCard.innerHTML = `
                            <p class="text-lg mb-2 whitespace-pre-wrap">${messageText}</p>
                            <p class="text-xs text-gray-500 mt-3 border-t border-gray-700 pt-2">
                                Posted on ${formatTimestamp(note.timestamp)}
                            </p>
                        `;
                        notesContainer.appendChild(noteCard);
                    });
                }
            }, (error) => {
                console.error("Firestore Snapshot Error:", error);
                document.getElementById('notesContainer').innerHTML = '<p class="text-center text-red-500 py-8">Error loading messages. Please check the console.</p>';
            });
        }

        // --- 3. NOTE SUBMISSION ---
        async function postNote(message) {
            if (!notesCollectionRef || !userId) {
                console.error("Firestore not initialized or user not authenticated.");
                return;
            }

            try {
                await addDoc(notesCollectionRef, {
                    message: message,
                    timestamp: serverTimestamp(), // Use server timestamp for accurate ordering
                    authorId: userId,
                });
            } catch (e) {
                console.error("Error adding document: ", e);
                // Display error message to user
                showToast("Failed to post message. Please try again.", 'error');
            }
        }

        // --- 4. UI INTERACTION ---
        document.addEventListener('DOMContentLoaded', () => {
            const form = document.getElementById('postNoteForm');
            const messageInput = document.getElementById('messageInput');
            const submitButton = form.querySelector('button[type="submit"]');

            form.addEventListener('submit', async (e) => {
                e.preventDefault();
                const message = messageInput.value.trim();

                if (message.length === 0) {
                    showToast("Your message can't be empty.", 'warning');
                    return;
                }

                // Disable button and show loading state
                submitButton.disabled = true;
                submitButton.innerHTML = `<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> Posting...`;

                await postNote(message);
                
                // Re-enable button and clear input
                submitButton.disabled = false;
                submitButton.innerHTML = 'Post Message';
                messageInput.value = '';
                showToast("Message posted!", 'success');
            });
            
            // Start Firebase initialization
            initializeFirebase();
        });

        // Simple toast notification function (instead of alert)
        function showToast(message, type = 'info') {
            const toastContainer = document.getElementById('toastContainer');
            const toast = document.createElement('div');
            
            let baseClasses = 'fixed top-4 right-4 z-50 p-3 rounded-lg shadow-xl text-white transition-opacity duration-300';
            let colorClass = '';

            switch(type) {
                case 'success':
                    colorClass = 'bg-green-600';
                    break;
                case 'error':
                    colorClass = 'bg-red-600';
                    break;
                case 'warning':
                    colorClass = 'bg-yellow-600';
                    break;
                default:
                    colorClass = 'bg-blue-600';
            }

            toast.className = `${baseClasses} ${colorClass}`;
            toast.textContent = message;

            toastContainer.appendChild(toast);

            // Auto-hide the toast
            setTimeout(() => {
                toast.style.opacity = '0';
                setTimeout(() => toast.remove(), 300);
            }, 3000);
        }

    </script>
</head>
<body class="p-4 md:p-8">

    <!-- Toast Container -->
    <div id="toastContainer"></div>

    <div class="max-w-2xl mx-auto">
        <header class="text-center mb-10 p-6 bg-gray-900 rounded-xl shadow-2xl border-b border-blue-600">
            <h1 class="text-4xl font-extrabold text-white mb-2">The Public Canvas</h1>
            <p class="text-blue-400">Leave a note for anyone. See what others have shared.</p>
            <p class="text-xs mt-3 text-gray-600">Your User ID: <span id="userIdDisplay" class="font-mono text-gray-500">Loading...</span></p>
        </header>

        <!-- Loading Indicator -->
        <div id="loadingIndicator" class="text-center py-10">
            <svg class="animate-spin mx-auto h-8 w-8 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            <p class="mt-2 text-gray-400">Connecting to the board...</p>
        </div>

        <!-- Note Submission Form -->
        <form id="postNoteForm" class="hidden p-6 mb-10 bg-gray-800 rounded-xl border border-gray-700 shadow-xl">
            <label for="messageInput" class="block text-sm font-medium mb-2 text-gray-300">Your Message (Visible to All):</label>
            <textarea 
                id="messageInput" 
                rows="4" 
                placeholder="Type your message here..." 
                class="w-full p-3 mb-4 bg-gray-900 border border-gray-600 rounded-lg focus:ring-blue-500 focus:border-blue-500 text-white resize-none"
                required
            ></textarea>
            <button 
                type="submit" 
                class="w-full flex justify-center items-center py-3 px-4 border border-transparent rounded-lg shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 focus:ring-offset-gray-800 transition duration-150 ease-in-out"
            >
                Post Message
            </button>
        </form>

        <!-- Notes Display Section -->
        <section>
            <h2 class="text-2xl font-semibold mb-6 text-gray-200 border-b border-gray-700 pb-2">Recent Messages</h2>
            <div id="notesContainer" class="space-y-4">
                <!-- Notes will be dynamically inserted here by JavaScript -->
            </div>
        </section>
    </div>
</body>
</html>


Comments

Popular posts from this blog