diff --git a/frontend/src/todos/TodoList.tsx b/frontend/src/todos/TodoList.tsx new file mode 100644 index 0000000..f836d05 --- /dev/null +++ b/frontend/src/todos/TodoList.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState, type FormEvent } from 'react'; +import { api, type Todo } from '../lib/api'; +import { supabase } from '../lib/supabase'; + +export function TodoList({ userEmail }: { userEmail: string }) { + const [todos, setTodos] = useState([]); + const [newTitle, setNewTitle] = useState(''); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + async function refresh() { + try { + setTodos(await api.list()); + } catch (e) { + setError(String(e)); + } + } + + useEffect(() => { + refresh().finally(() => setLoading(false)); + }, []); + + async function onAdd(e: FormEvent) { + e.preventDefault(); + const title = newTitle.trim(); + if (!title) return; + setError(null); + try { + const created = await api.create(title); + setTodos((prev) => [created, ...prev]); + setNewTitle(''); + } catch (e) { + setError(String(e)); + } + } + + async function onToggle(todo: Todo) { + setError(null); + try { + const updated = await api.setCompleted(todo.id, !todo.completed); + setTodos((prev) => prev.map((t) => (t.id === updated.id ? updated : t))); + } catch (e) { + setError(String(e)); + } + } + + async function onDelete(todo: Todo) { + setError(null); + try { + await api.remove(todo.id); + setTodos((prev) => prev.filter((t) => t.id !== todo.id)); + } catch (e) { + setError(String(e)); + } + } + + async function onSignOut() { + await supabase.auth.signOut(); + } + + return ( +
+
+

Todos

+ + {userEmail}{' '} + + +
+ +
+ setNewTitle(e.target.value)} + placeholder="What needs doing?" + maxLength={500} + style={{ flex: 1, padding: 6 }} + /> + +
+ + {error &&

{error}

} + {loading ? ( +

Loading…

+ ) : todos.length === 0 ? ( +

No todos yet.

+ ) : ( +
    + {todos.map((t) => ( +
  • + onToggle(t)} /> + + {t.title} + + +
  • + ))} +
+ )} +
+ ); +}