import { useState } from "react"; // ============================================= // Types (DO NOT MODIFY) // ============================================= interface Todo { id: string; text: string; completed: boolean; createdAt: string; } type FilterType = "all" | "active" | "completed"; // ============================================= // Initial seed data (DO NOT MODIFY) // ============================================= const INITIAL_TODOS: Todo[] = [ { id: "1", text: "Learn React fundamentals", completed: true, createdAt: new Date().toISOString() }, { id: "2", text: "Build a todo app", completed: false, createdAt: new Date().toISOString() }, { id: "3", text: "Practice system design", completed: false, createdAt: new Date().toISOString() }, ]; // ============================================= // TODO: Implement a Todo App // ============================================= // // Requirements: // 1. Add a new todo (text input + submit) // 2. Toggle a todo's completed state (checkbox) // 3. Delete a todo (× button) // 4. Edit a todo inline (double-click or edit button) // 5. Filter todos: All / Active / Completed // 6. Clear all completed todos at once // 7. Show counts: active and completed // // State: // - todos: Todo[] — the list of todos // - input: string — controlled input for new todo text // - filter: FilterType — current filter ("all" | "active" | "completed") // - editingId: string | null — id of the todo being edited (null if none) // - editText: string — text in the edit input // // Hints: // - addTodo: create a new Todo with crypto.randomUUID(), push to state // - toggleTodo: map over todos, flip completed for matching id // - deleteTodo: filter out the matching id // - startEdit(id, text): set editingId and editText // - saveEdit(id): map over todos, update text for matching id, clear editing state // - cancelEdit: reset editingId and editText // - clearCompleted: filter out all todos where completed === true // - getFilteredTodos: switch on filter to return the right subset export default function App() { const [todos, setTodos] = useState<Todo[]>(INITIAL_TODOS); const [input, setInput] = useState(""); const [filter, setFilter] = useState<FilterType>("all"); const [editingId, setEditingId] = useState<string | null>(null); const [editText, setEditText] = useState(""); // ============================================= // TODO: Implement addTodo // ============================================= // Should: // - Return early if input is empty/whitespace // - Create a new Todo object with unique id (crypto.randomUUID()) // - Append to todos state // - Clear the input const addTodo = () => { // Your code here }; // ============================================= // TODO: Implement toggleTodo // ============================================= // Flip the `completed` boolean for the todo with the given id const toggleTodo = (id: string) => { // Your code here }; // ============================================= // TODO: Implement deleteTodo // ============================================= // Remove the todo with the given id from state const deleteTodo = (id: string) => { // Your code here }; // ============================================= // TODO: Implement inline editing // ============================================= const startEdit = (id: string, text: string) => { // Your code here — set editingId and editText }; const saveEdit = (id: string) => { // Your code here — update the todo's text, clear editing state }; const cancelEdit = () => { // Your code here — reset editingId and editText }; // ============================================= // TODO: Implement clearCompleted // ============================================= // Remove all todos where completed === true const clearCompleted = () => { // Your code here }; // ============================================= // TODO: Implement getFilteredTodos // ============================================= // Return todos filtered by the current filter state: // "all" → all todos // "active" → only !completed // "completed" → only completed const getFilteredTodos = (): Todo[] => { // Your code here return todos; // ← replace with filtered version }; const filteredTodos = getFilteredTodos(); const completedCount = todos.filter((t) => t.completed).length; const activeCount = todos.length - completedCount; return ( <div style={{ maxWidth: 560, margin: "0 auto", padding: 24, fontFamily: "system-ui" }}> <h2 style={{ fontSize: 20, fontWeight: 700, marginBottom: 4 }}> Todo App </h2> <p style={{ fontSize: 14, color: "#666", marginBottom: 24 }}> Build a todo app with add, toggle, delete, inline edit, filtering, and clear completed. All state is local — no API needed. </p> {/* ============================================= */} {/* Input form */} {/* ============================================= */} <form onSubmit={(e) => { e.preventDefault(); addTodo(); }} style={{ display: "flex", border: "1px solid #e5e7eb", borderRadius: 8, overflow: "hidden", marginBottom: 20, }} > <input type="text" value={input} onChange={(e) => setInput(e.target.value)} placeholder="What needs to be done?" style={{ flex: 1, padding: "10px 14px", border: "none", outline: "none", fontSize: 14, }} /> <button type="submit" disabled={!input.trim()} style={{ padding: "10px 20px", background: input.trim() ? "#111" : "#e5e7eb", color: input.trim() ? "#fff" : "#999", border: "none", fontSize: 13, fontWeight: 600, cursor: input.trim() ? "pointer" : "default", }} > Add </button> </form> {/* ============================================= */} {/* Filter tabs */} {/* ============================================= */} <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16, }}> <div style={{ display: "flex", gap: 4 }}> {(["all", "active", "completed"] as FilterType[]).map((f) => ( <button key={f} onClick={() => setFilter(f)} style={{ padding: "6px 12px", borderRadius: 6, fontSize: 12, fontWeight: 600, textTransform: "capitalize", border: "none", cursor: "pointer", background: filter === f ? "#111" : "transparent", color: filter === f ? "#fff" : "#888", }} > {f} </button> ))} </div> <span style={{ fontSize: 12, color: "#999" }}> {activeCount} active · {completedCount} completed </span> </div> {/* ============================================= */} {/* Todo list */} {/* ============================================= */} {filteredTodos.length > 0 ? ( <div style={{ border: "1px solid #e5e7eb", borderRadius: 8, overflow: "hidden", }}> {filteredTodos.map((todo, i) => ( <div key={todo.id} style={{ display: "flex", alignItems: "center", gap: 12, padding: "10px 14px", borderTop: i > 0 ? "1px solid #f3f4f6" : "none", }} > {editingId === todo.id ? ( /* ======================================= */ /* Edit mode */ /* ======================================= */ <> <input type="text" value={editText} onChange={(e) => setEditText(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") saveEdit(todo.id); if (e.key === "Escape") cancelEdit(); }} autoFocus style={{ flex: 1, padding: "6px 10px", border: "1px solid #d1d5db", borderRadius: 6, fontSize: 13, outline: "none", }} /> <button onClick={() => saveEdit(todo.id)} style={{ fontSize: 12, fontWeight: 600, color: "#111", cursor: "pointer" }} > Save </button> <button onClick={cancelEdit} style={{ fontSize: 12, color: "#999", cursor: "pointer" }} > Cancel </button> </> ) : ( /* ======================================= */ /* Display mode */ /* ======================================= */ <> {/* TODO: Toggle checkbox */} <button onClick={() => toggleTodo(todo.id)} style={{ width: 20, height: 20, borderRadius: "50%", border: todo.completed ? "2px solid #111" : "2px solid #d1d5db", background: todo.completed ? "#111" : "transparent", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, cursor: "pointer", color: "#fff", fontSize: 11, }} > {todo.completed ? "✓" : ""} </button> {/* Todo text */} <span style={{ flex: 1, fontSize: 13, textDecoration: todo.completed ? "line-through" : "none", color: todo.completed ? "#999" : "#111", }}> {todo.text} </span> {/* TODO: Edit button */} <button onClick={() => startEdit(todo.id, todo.text)} style={{ color: "#ccc", cursor: "pointer", fontSize: 14 }} title="Edit" > ✎ </button> {/* TODO: Delete button */} <button onClick={() => deleteTodo(todo.id)} style={{ color: "#ccc", cursor: "pointer", fontSize: 16 }} title="Delete" > × </button> </> )} </div> ))} </div> ) : todos.length === 0 ? ( <div style={{ textAlign: "center", padding: "48px 0", border: "1px solid #e5e7eb", borderRadius: 8, color: "#999", fontSize: 14, }}> No todos yet. Add one above. </div> ) : ( <div style={{ textAlign: "center", padding: "32px 0", border: "1px solid #e5e7eb", borderRadius: 8, color: "#999", fontSize: 14, }}> No {filter} todos. </div> )} {/* ============================================= */} {/* Clear completed */} {/* ============================================= */} {completedCount > 0 && ( <div style={{ marginTop: 12, textAlign: "right" }}> <button onClick={clearCompleted} style={{ fontSize: 12, color: "#999", cursor: "pointer" }} > Clear completed ({completedCount}) </button> </div> )} {/* Hint */} <div style={{ marginTop: 24, padding: 16, background: "#f9fafb", borderLeft: "4px solid #111", borderRadius: "0 8px 8px 0", fontSize: 13, color: "#555", }}> <strong>Checklist:</strong> <ul style={{ margin: "8px 0 0 16px", padding: 0, lineHeight: 1.8 }}> <li><code>addTodo</code> — create with <code>crypto.randomUUID()</code>, append to state</li> <li><code>toggleTodo</code> — map and flip <code>completed</code></li> <li><code>deleteTodo</code> — filter out by id</li> <li><code>startEdit / saveEdit / cancelEdit</code> — inline editing with Enter/Escape</li> <li><code>getFilteredTodos</code> — switch on filter type</li> <li><code>clearCompleted</code> — filter out all completed</li> </ul> </div> </div> ); }