commit ff25257f46cb45a8ea496adb2089774c306abb50
parent 41fd139f11e941af298cdaa11516818419292557
Author: massi <git@massi.world>
Date: Fri, 11 Apr 2025 00:46:24 -0700
namespaces
Diffstat:
2 files changed, 205 insertions(+), 158 deletions(-)
diff --git a/src/app/todo-list/[title]/page.tsx b/src/app/todo-list/[title]/page.tsx
@@ -0,0 +1,203 @@
+"use client";
+import {
+ ChangeEvent,
+ HTMLInputTypeAttribute,
+ InputHTMLAttributes,
+ KeyboardEventHandler,
+ ReactEventHandler,
+ useEffect,
+ useState,
+} from "react";
+
+import { useLocalStorage } from "@/lib/useLocalStorage";
+import { useParams, useRouter } from "next/navigation";
+
+const MAX_TASK_LENGTH = 32;
+
+type TodoItem = {
+ description: string;
+ isDone: boolean;
+};
+
+function mkTodo(description: string): TodoItem {
+ return {
+ description,
+ isDone: false,
+ };
+}
+
+type CheckboxProps = {
+ isChecked: boolean;
+ onToggle: () => void;
+};
+
+function Checkbox(props: CheckboxProps) {
+ const { isChecked, onToggle } = props;
+ return (
+ <div
+ className="cursor-pointer w-[28px] h-[28px] bg-gray-800 relative leading-none hover:bg-gray-700 select-none"
+ onClick={onToggle}
+ >
+ {isChecked ? "✅" : ""}
+ </div>
+ );
+}
+
+type TodoInputProps = {
+ onEnter: (desc: string) => void;
+};
+function TodoInput(props: TodoInputProps) {
+ const [desc, setDesc] = useState<string>("");
+ const { onEnter } = props;
+ const addItem = () => {
+ if (desc.length) {
+ onEnter(desc);
+ setDesc(() => "");
+ }
+ };
+ const handleKeyPress: KeyboardEventHandler<HTMLInputElement> = (evt) => {
+ switch (evt.key) {
+ case "Enter": {
+ evt.preventDefault();
+ addItem();
+ break;
+ }
+ }
+ };
+ return (
+ <div className="text-2xl p-2 mx-4">
+ <input
+ value={desc}
+ onChange={(e) => setDesc(e.target.value)}
+ maxLength={MAX_TASK_LENGTH}
+ type="text"
+ onKeyDown={handleKeyPress}
+ placeholder="new todo goes here"
+ ></input>
+ <button onClick={addItem}>add</button>
+ </div>
+ );
+}
+
+type TodoItemProps = {
+ onDelete: () => void;
+ onToggleDone: () => void;
+ updateDescription: (description: string) => void;
+ todo: TodoItem;
+};
+
+function TodoItem(props: TodoItemProps) {
+ const { onDelete, onToggleDone, todo, updateDescription } = props;
+ return (
+ <div className="group">
+ <div className="text-2xl flex place-content-between p-2 mx-4 relative border-t-1 border-gray-600">
+ <button
+ onClick={onDelete}
+ className="absolute left-[-10px] items-center top-0 cursor-pointer bottom-0 text-red-700 hidden group-hover:flex"
+ >
+ x
+ </button>
+ <input
+ value={todo.description}
+ onChange={(e) => updateDescription(e.target.value)}
+ maxLength={MAX_TASK_LENGTH}
+ ></input>
+ <Checkbox isChecked={todo.isDone} onToggle={onToggleDone} />
+ </div>
+ </div>
+ );
+}
+
+type TitleProps = { title: string; rename: (newTitle: string) => boolean };
+function Title(props: TitleProps) {
+ const { title, rename } = props;
+ const [editingTitle, setEditingTitle] = useState<null | string>(null);
+ const handleKeyPress: KeyboardEventHandler<HTMLInputElement> = (evt) => {
+ switch (evt.key) {
+ case "Enter": {
+ evt.preventDefault();
+ if (editingTitle) {
+ rename(editingTitle);
+ }
+ break;
+ }
+ }
+ };
+ return (
+ <h1 className="text-4xl">
+ TODO:{" "}
+ <input
+ type="text"
+ value={editingTitle || title}
+ onChange={(evt) => setEditingTitle(evt.target.value)}
+ onKeyDown={handleKeyPress}
+ ></input>
+ </h1>
+ );
+}
+
+export default function TodoList() {
+ const router = useRouter();
+ const { title } = useParams<{ title: string }>();
+ const [items, setItems] = useLocalStorage<TodoItem[]>(title, []);
+ const renameTitle = (newTitle: string) => {
+ const dataAtNewTitle = !!localStorage.getItem(newTitle);
+ if (dataAtNewTitle) {
+ alert(
+ "AAAAAAAAAAAAAA THERES ALREADY A TODO LIST WITH THIS NAME AAAAAAAAAA CHANGE IT AAAAAAAAAAAAAAAAAAA",
+ );
+ } else {
+ localStorage.removeItem(title);
+ localStorage.setItem(newTitle, JSON.stringify(items));
+ router.replace(`/todo-list/${newTitle}`);
+ }
+ };
+ return (
+ <div className="flex flex-col items-center justify-items-center min-h-screen pb-20 gap-16 font-[family-name:var(--font-geist-sans)]">
+ <header>
+ <Title title={title} rename={renameTitle} />
+ </header>
+ <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start w-full">
+ <div className="w-[100%]">
+ <TodoInput
+ onEnter={(description: string) =>
+ setItems((items) => [mkTodo(description), ...items])
+ }
+ />
+ {!items.length
+ ? "all done :)"
+ : items.map((todo, idx) => {
+ return (
+ <TodoItem
+ todo={todo}
+ onToggleDone={() =>
+ setItems((items) => {
+ const newItems = [...items];
+ newItems[idx] = {
+ ...items[idx],
+ isDone: !items[idx].isDone,
+ };
+ return newItems;
+ })
+ }
+ onDelete={() =>
+ setItems((items) =>
+ items.filter((item, iidx) => idx !== iidx),
+ )
+ }
+ updateDescription={(description: string) =>
+ setItems((items) =>
+ items.map((item, iidx) =>
+ idx === iidx ? { ...item, description } : item,
+ ),
+ )
+ }
+ key={idx}
+ />
+ );
+ })}
+ </div>
+ </main>
+ </div>
+ );
+}
diff --git a/src/app/todo-list/page.tsx b/src/app/todo-list/page.tsx
@@ -1,159 +1,3 @@
-"use client";
-import {
- ChangeEvent,
- HTMLInputTypeAttribute,
- InputHTMLAttributes,
- KeyboardEventHandler,
- ReactEventHandler,
- useState,
-} from "react";
-
-import { useLocalStorage } from "@/lib/useLocalStorage";
-
-const MAX_TASK_LENGTH = 32;
-
-type TodoItem = {
- description: string;
- isDone: boolean;
-};
-
-function mkTodo(description: string): TodoItem {
- return {
- description,
- isDone: false,
- };
-}
-
-type CheckboxProps = {
- isChecked: boolean;
- onToggle: () => void;
-};
-
-function Checkbox(props: CheckboxProps) {
- const { isChecked, onToggle } = props;
- return (
- <div
- className="cursor-pointer w-[28px] h-[28px] bg-gray-800 relative leading-none hover:bg-gray-700 select-none"
- onClick={onToggle}
- >
- {isChecked ? "✅" : ""}
- </div>
- );
-}
-
-type TodoInputProps = {
- onEnter: (desc: string) => void;
-};
-function TodoInput(props: TodoInputProps) {
- const [desc, setDesc] = useState<string>("");
- const { onEnter } = props;
- const addItem = () => {
- if (desc.length) {
- onEnter(desc);
- setDesc(() => "");
- }
- };
- const handleKeyPress: KeyboardEventHandler<HTMLInputElement> = (evt) => {
- switch (evt.key) {
- case "Enter": {
- evt.preventDefault();
- addItem();
- break;
- }
- }
- };
- return (
- <div className="text-2xl p-2 mx-4">
- <input
- value={desc}
- onChange={(e) => setDesc(e.target.value)}
- maxLength={MAX_TASK_LENGTH}
- type="text"
- onKeyDown={handleKeyPress}
- placeholder="new todo goes here"
- ></input>
- <button onClick={addItem}>add</button>
- </div>
- );
-}
-
-type TodoItemProps = {
- onDelete: () => void;
- onToggleDone: () => void;
- updateDescription: (description: string) => void;
- todo: TodoItem;
-};
-
-function TodoItem(props: TodoItemProps) {
- const { onDelete, onToggleDone, todo, updateDescription } = props;
- return (
- <div className="group">
- <div className="text-2xl flex place-content-between p-2 mx-4 relative border-t-1 border-gray-600">
- <button
- onClick={onDelete}
- className="absolute left-[-10px] items-center top-0 cursor-pointer bottom-0 text-red-700 hidden group-hover:flex"
- >
- x
- </button>
- <input
- value={todo.description}
- onChange={(e) => updateDescription(e.target.value)}
- maxLength={MAX_TASK_LENGTH}
- ></input>
- <Checkbox isChecked={todo.isDone} onToggle={onToggleDone} />
- </div>
- </div>
- );
-}
-
-export default function TodoList() {
- const [items, setItems] = useLocalStorage<TodoItem[]>("todos", []);
- return (
- <div className="flex flex-col items-center justify-items-center min-h-screen pb-20 gap-16 font-[family-name:var(--font-geist-sans)]">
- <header>
- <h1 className="text-4xl">TODO APP</h1>
- </header>
- <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start w-full">
- <div className="w-[100%]">
- <TodoInput
- onEnter={(description: string) =>
- setItems((items) => [mkTodo(description), ...items])
- }
- />
- {!items.length
- ? "all done :)"
- : items.map((todo, idx) => {
- return (
- <TodoItem
- todo={todo}
- onToggleDone={() =>
- setItems((items) => {
- const newItems = [...items];
- newItems[idx] = {
- ...items[idx],
- isDone: !items[idx].isDone,
- };
- return newItems;
- })
- }
- onDelete={() =>
- setItems((items) =>
- items.filter((item, iidx) => idx !== iidx),
- )
- }
- updateDescription={(description: string) =>
- setItems((items) =>
- items.map((item, iidx) =>
- idx === iidx ? { ...item, description } : item,
- ),
- )
- }
- key={idx}
- />
- );
- })}
- </div>
- </main>
- </div>
- );
+export default function Page() {
+ return <h2>howde?</h2>;
}