Full Stack To-Do App Project code in Python

← Back to Projects

Full Stack To-Do App in Python

About the project: This is Creating a "Full Stack" application typically involves a backend (often Python, like Flask or Django) and a frontend (HTML/CSS/JS) with a database.

The goal is to provide a single, fully runnable file in this environment, We will create a powerful HTML, Tailwind CSS, and JavaScript application that uses Google Firestore as the persistent, real-time database. This structure fulfills the "Full Stack" requirement by including the necessary client-side logic and database connectivity, making the list persistent and collaborative.

Project Level: Advance


This single HTML file provides a responsive and persistent to-do list experience. Below is the single file for your To-Do Assistant.


Key Features of this "Full Stack" App:

  1. Persistence (Database): It uses Firebase Firestore to save tasks, ensuring they are available across sessions and devices.
  2. Real-time Updates: It uses Firestore's onSnapshot listener, meaning if you open this app on multiple screens (or if a collaborator modifies the list), the changes update instantly without needing a manual refresh.
  3. Authentication: It handles user authentication automatically using the environment's provided security token (__initial_auth_token), or falls back to anonymous sign-in, and then displays the unique User ID in the header.
  4. Security Compliant: It saves data to the required public collection path (/artifacts/{appId}/public/data/todos,), which is correctly structured for collaborative apps in this environment.

Project Structure

This project consists of a single Python script. You can name it fullStackToDoList.py.

Copy the following code into your file. The code is well-commented to help you understand each part of the voice assistant's functionality.


  	<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Full Stack To-Do Assistant</title>
    
    <script src="https://cdn.tailwindcss.com"></script>
    
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
        body {
            font-family: 'Inter', sans-serif;
            background-color: #f7f7f7;
        }
        /* Custom scrollbar for task list */
        #taskList::-webkit-scrollbar {
            width: 8px;
        }
        #taskList::-webkit-scrollbar-thumb {
            background-color: #a78bfa;
            border-radius: 4px;
        }
    </style>
</head>
<body class="min-h-screen flex items-start justify-center p-4 sm:p-8">

    
    <div id="appContainer" class="w-full max-w-lg bg-white shadow-2xl rounded-xl p-6 sm:p-8 space-y-6">
        <header class="text-center">
            <h1 class="text-3xl font-extrabold text-purple-700">Real-Time To-Do List</h1>
            <p id="userIdDisplay" class="text-sm text-gray-500 mt-1 truncate">User ID: Loading...</p>
        </header>

        
        <div class="flex flex-col sm:flex-row space-y-3 sm:space-y-0 sm:space-x-3">
            <input 
                type="text" 
                id="newTaskInput" 
                placeholder="What needs to be done?"
                class="flex-grow p-3 border-2 border-purple-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 transition duration-150"
                onkeypress="if(event.key === 'Enter') document.getElementById('addTaskBtn').click()"
            >
            <button 
                id="addTaskBtn" 
                class="w-full sm:w-auto px-6 py-3 bg-purple-600 text-white font-semibold rounded-lg shadow-md hover:bg-purple-700 transition duration-150 transform hover:scale-[1.02] active:scale-95 disabled:bg-gray-400"
                disabled
            >
                Add Task
            </button>
        </div>

        
        <div id="taskListContainer" class="bg-gray-50 p-4 rounded-lg shadow-inner max-h-[60vh] overflow-y-auto">
            <div id="taskList" class="space-y-3">
                
                <p id="loadingMessage" class="text-center text-purple-500 font-medium">Initializing database and loading tasks...</p>
            </div>
        </div>

        
        <div class="flex justify-between items-center text-sm text-gray-500 pt-2 border-t border-gray-100">
            <span id="taskCount">0 tasks total</span>
            <button 
                id="clearAllBtn"
                class="text-red-500 hover:text-red-700 font-medium transition duration-150 disabled:text-gray-400"
                onclick="showClearAllModal()"
                disabled
            >
                Clear Completed
            </button>
        </div>
    </div>
    
    
    <div id="confirmationModal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-50 z-50 flex items-center justify-center p-4">
        <div class="bg-white rounded-xl shadow-2xl p-6 w-full max-w-sm space-y-4 transform scale-100 transition-transform duration-300">
            <h3 class="text-xl font-bold text-gray-800" id="modalTitle">Confirm Action</h3>
            <p class="text-gray-600" id="modalMessage">Are you sure you want to proceed?</p>
            <div class="flex justify-end space-x-3">
                <button 
                    onclick="hideModal(false)"
                    class="px-4 py-2 text-gray-600 bg-gray-200 rounded-lg hover:bg-gray-300 transition"
                >
                    Cancel
                </button>
                <button 
                    id="modalConfirmBtn"
                    onclick="hideModal(true)"
                    class="px-4 py-2 text-white bg-red-600 rounded-lg hover:bg-red-700 transition"
                >
                    Confirm
                </button>
            </div>
        </div>
    </div>
    
    
    <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, 
            doc, 
            addDoc, 
            updateDoc, 
            deleteDoc, 
            onSnapshot,
            query,
            where,
            writeBatch,
            getDocs,
            setLogLevel 
        } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- GLOBAL VARIABLES (provided by canvas environment) ---
        const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
        const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        // --- FIREBASE INITIALIZATION AND AUTHENTICATION ---
        let db = null;
        let auth = null;
        let userId = null;
        let tasksRef = null;
        let isAuthReady = false;

        // Set log level to Debug for better insights
        setLogLevel('Debug');

        try {
            const app = initializeApp(firebaseConfig);
            db = getFirestore(app);
            auth = getAuth(app);
            
            // 1. Authenticate the user
            onAuthStateChanged(auth, async (user) => {
                if (user) {
                    userId = user.uid;
                } else {
                    // Sign in anonymously if custom token is not available
                    if (initialAuthToken) {
                        try {
                            await signInWithCustomToken(auth, initialAuthToken);
                            // Auth state change will handle setting userId
                        } catch (error) {
                            console.error("Custom token sign-in failed, falling back to anonymous:", error);
                            await signInAnonymously(auth);
                        }
                    } else {
                        await signInAnonymously(auth);
                    }
                }
                
                // Once user is authenticated, set up the Firestore reference
                userId = auth.currentUser?.uid || crypto.randomUUID();
                document.getElementById('userIdDisplay').textContent = `User ID: ${userId}`;
                
                // Use a public, shared collection for this To-Do list
                const collectionPath = `artifacts/${appId}/public/data/todos`;
                tasksRef = collection(db, collectionPath);
                
                isAuthReady = true;
                document.getElementById('addTaskBtn').disabled = false;
                
                // 2. Start listening for tasks
                listenForTasks();
            });

        } catch (error) {
            console.error("Firebase initialization failed:", error);
            document.getElementById('loadingMessage').textContent = "Error loading app. Check console for details.";
            document.getElementById('addTaskBtn').disabled = true;
        }

        // --- STATE AND UTILITY ---
        let allTasks = [];
        let modalResolver = null; // Promise resolver for the modal confirmation

        // --- MODAL UTILITIES (replacement for alert/confirm) ---
        window.showClearAllModal = () => {
            document.getElementById('modalTitle').textContent = "Confirm Clear";
            document.getElementById('modalMessage').textContent = "Are you sure you want to clear ALL completed tasks for this shared list?";
            document.getElementById('modalConfirmBtn').classList.remove('bg-red-600', 'hover:bg-red-700');
            document.getElementById('modalConfirmBtn').classList.add('bg-purple-600', 'hover:bg-purple-700');
            document.getElementById('confirmationModal').classList.remove('hidden');

            // Return a promise that resolves when the user confirms or cancels
            return new Promise(resolve => {
                modalResolver = resolve;
            }).then(confirmed => {
                if (confirmed) {
                    clearCompletedTasks();
                }
            });
        };

        window.hideModal = (confirmed) => {
            document.getElementById('confirmationModal').classList.add('hidden');
            if (modalResolver) {
                modalResolver(confirmed);
                modalResolver = null;
            }
        };


        // --- FIRESTORE CRUD OPERATIONS ---

        async function addTask() {
            if (!isAuthReady || !tasksRef) {
                console.error("Database not ready.");
                return;
            }

            const input = document.getElementById('newTaskInput');
            const content = input.value.trim();

            if (content === "") return;

            try {
                await addDoc(tasksRef, {
                    content: content,
                    completed: false,
                    createdAt: Date.now(),
                    createdBy: userId
                });
                input.value = ''; // Clear input on success
            } catch (error) {
                console.error("Error adding document:", error);
            }
        }
        
        // Add event listener to the button
        document.getElementById('addTaskBtn').addEventListener('click', addTask);

        async function toggleTaskStatus(taskId, currentStatus) {
            if (!isAuthReady || !tasksRef) return;
            try {
                const taskDocRef = doc(db, tasksRef.path, taskId);
                await updateDoc(taskDocRef, {
                    completed: !currentStatus
                });
            } catch (error) {
                console.error("Error updating document:", error);
            }
        }

        async function deleteTask(taskId) {
            if (!isAuthReady || !tasksRef) return;
            try {
                const taskDocRef = doc(db, tasksRef.path, taskId);
                await deleteDoc(taskDocRef);
            } catch (error) {
                console.error("Error deleting document:", error);
            }
        }
        
        async function clearCompletedTasks() {
            if (!isAuthReady || !tasksRef) return;

            const completedTasksQuery = query(tasksRef, where("completed", "==", true));

            try {
                const snapshot = await getDocs(completedTasksQuery);
                const batch = writeBatch(db);

                snapshot.docs.forEach((d) => {
                    batch.delete(d.ref);
                });

                await batch.commit();
                console.log("Successfully cleared completed tasks.");

            } catch (error) {
                console.error("Error clearing completed tasks:", error);
            }
        }


        // --- REAL-TIME LISTENER & RENDERING ---

        function listenForTasks() {
            // Note: We are sorting in JavaScript to avoid Firestore index complexity
            // In a production app, use orderBy('createdAt', 'desc') if an index is guaranteed.
            
            // Listen for real-time updates on the task collection
            onSnapshot(tasksRef, (snapshot) => {
                // FIX: Remove the initial loading message element on the first successful snapshot
                // This prevents the error when renderTasks clears the list and removes the element.
                const loadingMsgElement = document.getElementById('loadingMessage');
                if (loadingMsgElement) {
                    loadingMsgElement.remove();
                }

                const taskList = [];
                snapshot.forEach((doc) => {
                    taskList.push({ id: doc.id, ...doc.data() });
                });
                
                // Sort tasks: Incomplete first, then by creation date (newest first)
                taskList.sort((a, b) => {
                    if (a.completed !== b.completed) {
                        return a.completed ? 1 : -1; // Incomplete (false) tasks come first
                    }
                    return b.createdAt - a.createdAt; // Newest first
                });

                allTasks = taskList;
                renderTasks();
            }, (error) => {
                console.error("Error listening to Firestore:", error);
            });
        }
        
        function renderTasks() {
            const listElement = document.getElementById('taskList');
            listElement.innerHTML = ''; // Clear the current list
            
            // The line that caused the error was removed and handled in listenForTasks()
            
            document.getElementById('taskCount').textContent = `${allTasks.length} tasks total`;
            document.getElementById('clearAllBtn').disabled = !allTasks.some(t => t.completed);

            if (allTasks.length === 0) {
                listElement.innerHTML = `

Your list is empty! Add a task above.

`; return; } allTasks.forEach(task => { const taskItem = document.createElement('div'); const completedClass = task.completed ? 'opacity-60 bg-green-50' : 'bg-white hover:bg-gray-50'; taskItem.className = `flex items-center justify-between p-3 rounded-lg shadow-sm transition duration-200 ${completedClass}`; // Left side: Checkbox and Content taskItem.innerHTML = ` <div class="flex items-center flex-1 min-w-0 mr-4"> <input type="checkbox" id="check-${task.id}" ${task.completed ? 'checked' : ''} class="h-5 w-5 text-purple-600 rounded border-gray-300 focus:ring-purple-500 cursor-pointer" onclick="window.toggleTaskStatus('${task.id}', ${task.completed})" > <label for="check-${task.id}" class="ml-3 text-gray-800 break-words flex-1 min-w-0 cursor-pointer ${task.completed ? 'line-through text-gray-500' : 'text-gray-900'}" > ${task.content} </label> </div> <button class="text-gray-400 hover:text-red-500 transition duration-150 p-1 rounded-full hover:bg-red-100" onclick="window.deleteTask('${task.id}')" > <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> </svg> </button> `; listElement.appendChild(taskItem); }); } // Expose functions to the global scope for event handlers window.toggleTaskStatus = toggleTaskStatus; window.deleteTask = deleteTask; </script> </body> </html>

Output



← Back to Projects