import React, { useState, useEffect, createContext, useContext } from 'react';
import { initializeApp } from 'firebase/app';
import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged } from 'firebase/auth';
import { getFirestore, collection, query, onSnapshot, addDoc, updateDoc, deleteDoc, doc } from 'firebase/firestore';
import { X } from 'lucide-react';
// --- Shadcn/ui Component Reimplementations ---
// Since shadcn/ui imports don't work directly, we'll create our own simple
// versions of the necessary components using Tailwind CSS.
// Button component
const Button = React.forwardRef(({ className, variant, size, ...props }, ref) => {
const baseClass = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none';
const variantClass = variant === 'ghost' ? 'hover:bg-gray-100 hover:text-gray-900' : 'bg-blue-600 text-white hover:bg-blue-700';
const sizeClass = size === 'icon' ? 'h-10 w-10 p-0' : 'h-10 py-2 px-4';
const finalClassName = `${baseClass} ${variantClass} ${sizeClass} ${className || ''}`.trim();
return (
);
});
Button.displayName = 'Button';
// Input component
const Input = React.forwardRef(({ className, type, ...props }, ref) => (
));
Input.displayName = 'Input';
// Card components
const Card = React.forwardRef(({ className, ...props }, ref) => (
));
Card.displayName = 'Card';
const CardContent = React.forwardRef(({ className, ...props }, ref) => (
));
CardContent.displayName = 'CardContent';
// Checkbox component
const Checkbox = React.forwardRef(({ className, checked, onCheckedChange, ...props }, ref) => (
));
Checkbox.displayName = 'Checkbox';
// Toast Context for notifications
const ToastContext = createContext(null);
const useToast = () => {
const context = useContext(ToastContext);
if (!context) {
throw new Error('useToast must be used within a ToastProvider');
}
return context;
};
// Simple Toaster and Toast component
const Toaster = ({ position = 'top-center' }) => {
const { toasts, removeToast } = useContext(ToastContext);
const getPositionClasses = (pos) => {
switch (pos) {
case 'top-center':
default:
return 'top-4 left-1/2 -translate-x-1/2';
}
};
return (
{toasts.map((toast) => (
{toast.title}
{toast.description &&
{toast.description}
}
))}
);
};
const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const toast = ({ title, description }) => {
const id = Date.now();
setToasts((prevToasts) => [...prevToasts, { id, title, description }]);
setTimeout(() => removeToast(id), 5000);
};
const removeToast = (id) => {
setToasts((prevToasts) => prevToasts.filter((t) => t.id !== id));
};
return (
{children}
);
};
// The main application component that contains all the UI and logic.
// We've renamed it to ToDoListApp to allow the App component to be a wrapper.
function ToDoListApp() {
const [todos, setTodos] = useState([]);
const [newTodoText, setNewTodoText] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [db, setDb] = useState(null);
const [userId, setUserId] = useState(null);
const { toast } = useToast();
// Initialize Firebase and set up the real-time listener for the todos.
useEffect(() => {
// These global variables are provided by the Canvas environment.
const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {};
const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;
// Initialize Firebase app
const app = initializeApp(firebaseConfig);
const firestore = getFirestore(app);
const firebaseAuth = getAuth(app);
setDb(firestore);
// Sign in the user using the custom token or anonymously if the token is not available.
const signIn = async () => {
try {
if (initialAuthToken) {
await signInWithCustomToken(firebaseAuth, initialAuthToken);
} else {
await signInAnonymously(firebaseAuth);
}
} catch (error) {
console.error("Firebase authentication error:", error);
}
};
signIn();
// Set up a listener for authentication state changes.
// This ensures we have a valid user ID before attempting to fetch data.
const unsubscribeAuth = onAuthStateChanged(firebaseAuth, (user) => {
if (user) {
setUserId(user.uid);
} else {
setUserId(null);
}
});
// Clean up the auth listener when the component unmounts.
return () => unsubscribeAuth();
}, []);
// Set up the Firestore listener to get real-time updates.
useEffect(() => {
if (!db || !userId) {
// Don't proceed until Firebase is initialized and the user is authenticated.
return;
}
setIsLoading(true);
// This is the path for the public data collection shared among users.
const collectionPath = `artifacts/${typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'}/public/data/todos`;
const q = query(collection(db, collectionPath));
// The onSnapshot listener provides real-time updates.
// It's the most efficient way to handle collaborative data.
const unsubscribeSnapshot = onSnapshot(q, (querySnapshot) => {
try {
const todosArray = [];
querySnapshot.forEach((doc) => {
todosArray.push({ id: doc.id, ...doc.data() });
});
// Sort the todos by their timestamp to maintain a consistent order.
todosArray.sort((a, b) => (a.timestamp?.seconds || 0) - (b.timestamp?.seconds || 0));
setTodos(todosArray);
} catch (error) {
console.error("Error fetching todos:", error);
} finally {
setIsLoading(false);
}
}, (error) => {
console.error("Firestore onSnapshot error:", error);
setIsLoading(false);
});
// Clean up the Firestore listener when the component unmounts.
return () => unsubscribeSnapshot();
}, [db, userId]); // Re-run this effect if the database or user ID changes.
// Function to add a new to-do item to Firestore.
const addTodo = async () => {
if (newTodoText.trim() === '' || !db) {
toast({
title: "Input is empty!",
description: "Please enter a to-do item.",
});
return;
}
try {
// Add a new document to the todos collection.
await addDoc(collection(db, `artifacts/${typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'}/public/data/todos`), {
text: newTodoText,
completed: false,
timestamp: new Date(),
});
setNewTodoText('');
} catch (error) {
console.error("Error adding document:", error);
}
};
// Function to toggle the 'completed' status of a to-do item.
const toggleTodoCompletion = async (id, completed) => {
if (!db) return;
try {
const todoDocRef = doc(db, `artifacts/${typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'}/public/data/todos`, id);
// Update the document with the new 'completed' status.
await updateDoc(todoDocRef, {
completed: !completed,
});
} catch (error) {
console.error("Error updating document:", error);
}
};
// Function to delete a to-do item from Firestore.
const deleteTodo = async (id) => {
if (!db) return;
try {
const todoDocRef = doc(db, `artifacts/${typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'}/public/data/todos`, id);
// Delete the document.
await deleteDoc(todoDocRef);
} catch (error) {
console.error("Error deleting document:", error);
}
};
return (
Family To-Do List
setNewTodoText(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && addTodo()}
className="flex-1 rounded-lg border-gray-300 focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all"
/>
{todos.filter(todo => !todo.completed).length} items remaining
User ID: {userId || 'Loading...'}
{isLoading ? (
) : (
{todos.map((todo) => (
toggleTodoCompletion(todo.id, todo.completed)}
id={`todo-${todo.id}`}
className="peer h-5 w-5 rounded-sm border-2 border-gray-300 data-[state=checked]:bg-blue-600 data-[state=checked]:border-blue-600 transition-colors"
/>
))}
{todos.length === 0 && (
You have no tasks! Start by adding one above.
)}
)}
);
}
// The root component that wraps the main application in a context provider.
export default function App() {
return (
);
}