/** * ============================================================================= * TODOS PAGE - CRUD Example with Supabase * ============================================================================= * * Demonstrates CRUD operations with Supabase and Redux integration. * * INTERVIEW NOTES: * - This is a common interview pattern: todo list with CRUD * - Shows async data fetching with loading/error states * - Demonstrates optimistic updates and real-time sync * - Uses both Redux and React Query patterns */ import { useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '@/store'; import { fetchTodos, createTodo, updateTodo, deleteTodo, setFilter, selectFilteredTodos, selectTodosLoading, selectTodosError, selectTodosFilter, selectTodoStats, } from '@/store/slices/todosSlice'; import LoadingSpinner from '@/components/ui/LoadingSpinner'; export default function TodosPage() { const dispatch = useAppDispatch(); const todos = useAppSelector(selectFilteredTodos); const loading = useAppSelector(selectTodosLoading); const error = useAppSelector(selectTodosError); const filter = useAppSelector(selectTodosFilter); const stats = useAppSelector(selectTodoStats); const [newTodoTitle, setNewTodoTitle] = useState(''); const [editingId, setEditingId] = useState(null); const [editTitle, setEditTitle] = useState(''); // Fetch todos on mount useEffect(() => { dispatch(fetchTodos()); }, [dispatch]); // Handle form submission const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!newTodoTitle.trim()) return; await dispatch( createTodo({ title: newTodoTitle.trim(), completed: false, }) ); setNewTodoTitle(''); }; // Handle todo update const handleUpdate = async (id: string) => { if (!editTitle.trim()) return; await dispatch( updateTodo({ id, updates: { title: editTitle.trim() }, }) ); setEditingId(null); setEditTitle(''); }; // Handle todo toggle const handleToggle = async (id: string, completed: boolean) => { await dispatch( updateTodo({ id, updates: { completed: !completed }, }) ); }; // Handle todo delete const handleDelete = async (id: string) => { await dispatch(deleteTodo(id)); }; // Start editing const startEditing = (id: string, title: string) => { setEditingId(id); setEditTitle(title); }; return (

Todos

{/* Add todo form */}
setNewTodoTitle(e.target.value)} placeholder="What needs to be done?" className="input flex-1" aria-label="New todo title" />
{/* Filter tabs */}
{(['all', 'active', 'completed'] as const).map((f) => ( ))}
{/* Error message */} {error && (
{error}
)} {/* Loading state */} {loading ? (
) : todos.length === 0 ? (
{filter === 'all' ? 'No todos yet. Add one above!' : `No ${filter} todos.`}
) : ( /* Todo list */ )} {/* Stats */} {stats.total > 0 && (
{stats.active} item{stats.active !== 1 ? 's' : ''} left {stats.completed > 0 && ( {stats.completed} completed )}
)}
); }