import React, { useState, useEffect, useRef, useCallback } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, getDoc, addDoc, setDoc, updateDoc, deleteDoc, onSnapshot, collection, query, where, getDocs } from 'firebase/firestore'; // Ensure Tailwind CSS is loaded (assuming it's configured in the environment) // This comment is for context; actual Tailwind setup is external to the React component. // Global variables for Firebase config and app ID 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) : {}; const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null; // Initialize Firebase outside of the component to avoid re-initialization let app; let db; let auth; try { app = initializeApp(firebaseConfig); db = getFirestore(app); auth = getAuth(app); } catch (error) { console.error("Firebase initialization failed:", error); // Provide a fallback or user-friendly message if Firebase fails to initialize } // Function to fetch emoji from Gemini API async function getEmojiForTask(taskText) { if (!taskText) return ''; try { const prompt = `Given the task description '${taskText}', suggest a single, relevant emoji. Only provide the emoji, no other text.`; let chatHistory = []; chatHistory.push({ role: "user", parts: [{ text: prompt }] }); const payload = { contents: chatHistory, generationConfig: { responseMimeType: "text/plain", // Expecting a plain text emoji } }; const apiKey = ""; // Canvas will provide this at runtime const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`; const response = await fetch(apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const result = await response.json(); if (result.candidates && result.candidates.length > 0 && result.candidates[0].content && result.candidates[0].content.parts && result.candidates[0].content.parts.length > 0) { const emoji = result.candidates[0].content.parts[0].text.trim(); return emoji; } else { console.warn("Gemini API did not return a valid emoji for:", taskText, result); return ''; } } catch (error) { console.error("Error fetching emoji from Gemini API:", error); return ''; } } const App = () => { const [tasks, setTasks] = useState([]); const [newTaskText, setNewTaskText] = useState(''); const [newTaskPriority, setNewTaskPriority] = useState('low'); const [newTaskDate, setNewTaskDate] = useState(''); const [newTaskTime, setNewTaskTime] = useState(''); const [userId, setUserId] = useState(null); const [isAuthReady, setIsAuthReady] = useState(false); const [loading, setLoading] = useState(true); const [showModal, setShowModal] = useState(false); const [modalMessage, setModalMessage] = useState(''); // Pomodoro states const [focusTime, setFocusTime] = useState(25); const [breakTime, setBreakTime] = useState(5); const [timerMode, setTimerMode] = useState('focus'); // 'focus' or 'break' const [timeLeft, setTimeLeft] = useState(focusTime * 60); const [isActive, setIsActive] = useState(false); const [inputFocusTime, setInputFocusTime] = useState(25); const [inputBreakTime, setInputBreakTime] = useState(5); const intervalRef = useRef(null); const dragItem = useRef(null); // Ref for the item being dragged const dragOverItem = useRef(null); // Ref for the item being dragged over // Firebase Authentication and Initialization useEffect(() => { if (!app || !auth || !db) { console.error("Firebase is not initialized. Cannot proceed."); setLoading(false); return; } const unsubscribe = onAuthStateChanged(auth, async (user) => { if (user) { setUserId(user.uid); } else { // Sign in anonymously if no user is present try { if (initialAuthToken) { await signInWithCustomToken(auth, initialAuthToken); } else { await signInAnonymously(auth); } setUserId(auth.currentUser?.uid || crypto.randomUUID()); // Fallback if anonymous sign-in is too slow } catch (error) { console.error("Error signing in:", error); setUserId(crypto.randomUUID()); // Generate a random ID if sign-in fails } } setIsAuthReady(true); setLoading(false); }); return () => unsubscribe(); }, []); // Firestore Data Listeners (Tasks and Settings) useEffect(() => { if (!isAuthReady || !userId || !db) return; // Listen for tasks const tasksCollectionRef = collection(db, `artifacts/${appId}/users/${userId}/tasks`); const unsubscribeTasks = onSnapshot(tasksCollectionRef, (snapshot) => { const fetchedTasks = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // Sort tasks by 'order' property for drag-and-drop persistence setTasks(fetchedTasks.sort((a, b) => (a.order || 0) - (b.order || 0))); }, (error) => { console.error("Error fetching tasks:", error); setModalMessage("Error loading tasks. Please try again."); setShowModal(true); }); // Listen for Pomodoro settings const settingsDocRef = doc(db, `artifacts/${appId}/users/${userId}/settings/pomodoro`); const unsubscribeSettings = onSnapshot(settingsDocRef, (docSnap) => { if (docSnap.exists()) { const data = docSnap.data(); setFocusTime(data.focusTime || 25); setBreakTime(data.breakTime || 5); setInputFocusTime(data.focusTime || 25); setInputBreakTime(data.breakTime || 5); // Reset timer based on loaded settings if not active if (!isActive) { setTimeLeft((data.focusTime || 25) * 60); } } else { // If no settings, save default settings setDoc(settingsDocRef, { focusTime: 25, breakTime: 5 }, { merge: true }).catch(e => console.error("Error setting default pomodoro settings:", e)); } }, (error) => { console.error("Error fetching settings:", error); setModalMessage("Error loading Pomodoro settings. Please try again."); setShowModal(true); }); return () => { unsubscribeTasks(); unsubscribeSettings(); }; }, [isAuthReady, userId, db, isActive]); // isActive added to ensure timer state doesn't reset on settings load if active // Pomodoro Timer Logic useEffect(() => { if (isActive && timeLeft > 0) { intervalRef.current = setInterval(() => { setTimeLeft((prevTime) => prevTime - 1); }, 1000); } else if (timeLeft === 0) { clearInterval(intervalRef.current); // Play a sound or show a notification setModalMessage(`${timerMode === 'focus' ? 'Focus time' : 'Break time'} ended!`); setShowModal(true); if (timerMode === 'focus') { setTimerMode('break'); setTimeLeft(breakTime * 60); } else { setTimerMode('focus'); setTimeLeft(focusTime * 60); } setIsActive(false); // Stop timer after completion } return () => clearInterval(intervalRef.current); }, [isActive, timeLeft, timerMode, focusTime, breakTime]); const toggleTimer = () => { setIsActive(!isActive); }; const resetTimer = () => { clearInterval(intervalRef.current); setIsActive(false); setTimerMode('focus'); setTimeLeft(focusTime * 60); }; const formatTime = (seconds) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; }; const handleFocusTimeChange = (e) => { const value = parseInt(e.target.value, 10); if (!isNaN(value) && value > 0) { setInputFocusTime(value); } }; const handleBreakTimeChange = (e) => { const value = parseInt(e.target.value, 10); if (!isNaN(value) && value > 0) { setInputBreakTime(value); } }; const savePomodoroSettings = async () => { if (!userId || !db) return; try { const settingsDocRef = doc(db, `artifacts/${appId}/users/${userId}/settings/pomodoro`); await setDoc(settingsDocRef, { focusTime: inputFocusTime, breakTime: inputBreakTime }, { merge: true }); setFocusTime(inputFocusTime); setBreakTime(inputBreakTime); if (!isActive) { // Only reset time if timer is not active setTimeLeft(inputFocusTime * 60); } setModalMessage("Pomodoro settings saved!"); setShowModal(true); } catch (error) { console.error("Error saving pomodoro settings:", error); setModalMessage("Error saving Pomodoro settings. Please try again."); setShowModal(true); } }; // Task Management Functions const handleAddTask = async () => { if (!newTaskText.trim() || !userId || !db) { setModalMessage("Task text cannot be empty."); setShowModal(true); return; } setLoading(true); try { const emoji = await getEmojiForTask(newTaskText); const tasksCollectionRef = collection(db, `artifacts/${appId}/users/${userId}/tasks`); const newOrder = tasks.length > 0 ? Math.max(...tasks.map(t => t.order || 0)) + 1 : 0; await addDoc(tasksCollectionRef, { text: newTaskText, priority: newTaskPriority, dueDate: newTaskDate, timeRequired: parseInt(newTaskTime, 10) || 0, isDone: false, emoji: emoji, order: newOrder, createdAt: new Date(), }); setNewTaskText(''); setNewTaskPriority('low'); setNewTaskDate(''); setNewTaskTime(''); setLoading(false); } catch (error) { console.error("Error adding task:", error); setModalMessage("Error adding task. Please try again."); setShowModal(true); setLoading(false); } }; const handleDeleteTask = async (id) => { if (!userId || !db) return; try { const taskDocRef = doc(db, `artifacts/${appId}/users/${userId}/tasks`, id); await deleteDoc(taskDocRef); } catch (error) { console.error("Error deleting task:", error); setModalMessage("Error deleting task. Please try again."); setShowModal(true); } }; const handleToggleDone = async (id, isDone) => { if (!userId || !db) return; try { const taskDocRef = doc(db, `artifacts/${appId}/users/${userId}/tasks`, id); await updateDoc(taskDocRef, { isDone: !isDone }); } catch (error) { console.error("Error toggling task done status:", error); setModalMessage("Error updating task status. Please try again."); setShowModal(true); } }; // Drag and Drop Handlers const handleDragStart = (e, index) => { dragItem.current = index; }; const handleDragEnter = (e, index) => { dragOverItem.current = index; }; const handleDrop = async () => { const dragItemIndex = dragItem.current; const dragOverItemIndex = dragOverItem.current; if (dragItemIndex === null || dragOverItemIndex === null) return; const _tasks = [...tasks]; const draggedTask = _tasks[dragItemIndex]; _tasks.splice(dragItemIndex, 1); // Remove the dragged item _tasks.splice(dragOverItemIndex, 0, draggedTask); // Insert it at the new position // Update the 'order' property for all affected tasks in Firestore const batchUpdates = _tasks.map((task, index) => { if (task.order !== index) { // Only update if order has changed const taskDocRef = doc(db, `artifacts/${appId}/users/${userId}/tasks`, task.id); return updateDoc(taskDocRef, { order: index }); } return Promise.resolve(); // No update needed }); try { await Promise.all(batchUpdates); setTasks(_tasks); // Update local state after successful Firestore update } catch (error) { console.error("Error reordering tasks:", error); setModalMessage("Error reordering tasks. Please try again."); setShowModal(true); } finally { dragItem.current = null; dragOverItem.current = null; } }; const handleDragEnd = () => { dragItem.current = null; dragOverItem.current = null; }; const getPriorityColor = (priority) => { switch (priority) { case 'low': return 'bg-green-200'; case 'medium': return 'bg-yellow-200'; case 'high': return 'bg-red-200'; default: return 'bg-gray-200'; } }; if (loading) { return (
Loading application...
); } return (
{/* User ID Display */} {userId && (
User ID: {userId}
)} {/* Task Tracker - Left Side */}

Your Tasks 📝

{/* Add Task Form */}
setNewTaskText(e.target.value)} />
setNewTaskDate(e.target.value)} /> setNewTaskTime(e.target.value)} min="0" />
{/* Task List */}
{tasks.length === 0 ? (

No tasks yet! Add one above. ✨

) : (
    {tasks.map((task, index) => (
  • handleDragStart(e, index)} onDragEnter={(e) => handleDragEnter(e, index)} onDragEnd={handleDragEnd} onDrop={handleDrop} onDragOver={(e) => e.preventDefault()} // Crucial for drop to work className={` p-4 rounded-xl shadow-md flex items-center justify-between transition-all duration-300 ${getPriorityColor(task.priority)} ${task.isDone ? 'opacity-60 line-through' : ''} ${dragItem.current === index ? 'border-2 border-purple-500' : ''} `} >
    handleToggleDone(task.id, task.isDone)} className="form-checkbox h-6 w-6 text-purple-600 rounded-md mr-4 cursor-pointer" />
    {task.emoji && {task.emoji}} {task.text}
    {task.dueDate && `Due: ${new Date(task.dueDate).toLocaleDateString()}`} {task.timeRequired > 0 && ` • Est: ${task.timeRequired} mins`}
  • ))}
)}
{/* Pomodoro Tracker - Right Side */}

Pomodoro Timer 🍅

{formatTime(timeLeft)}
{timerMode === 'focus' ? 'Focus Mode' : 'Break Mode'}
{/* Edit Time Settings */}

Edit Times

{/* Custom Modal for messages */} {showModal && (

{modalMessage}

)} {/* Custom Scrollbar Styling */}
); }; export default App;