Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6a83ba9
setting up my app and creating the first store
christina-baldwin May 21, 2025
1255d8f
getting the todo store to create and delete todos
christina-baldwin May 21, 2025
7f76e74
creating the complete / uncomplete task functionality
christina-baldwin May 21, 2025
229f78d
showing a count of all and uncompleted tasks
christina-baldwin May 21, 2025
17cdd57
chenging text for emty state, will need styling to look nice later
christina-baldwin May 21, 2025
c77bef7
adding a category selection that is then displayed on my to-do
christina-baldwin May 22, 2025
88b4403
some structuring and basic design
christina-baldwin May 22, 2025
faf8708
adding a task filter
christina-baldwin May 22, 2025
9255a3e
simple spacing
christina-baldwin May 22, 2025
c0d3d81
styling complete and uncomplete
christina-baldwin May 22, 2025
eba5682
reorganising the message structure
christina-baldwin May 22, 2025
4f70713
styling what shows on the page when there are no tasks
christina-baldwin May 23, 2025
78dde63
fixing grammar
christina-baldwin May 23, 2025
9b02d74
adding the date posted on the task
christina-baldwin May 23, 2025
04e2e51
styling and colouring stuff
christina-baldwin May 23, 2025
9bc461f
some styling changes
christina-baldwin May 23, 2025
acbcbcb
removing repeated code
christina-baldwin May 23, 2025
41e2fc2
adding indication of which filter we are on
christina-baldwin May 23, 2025
2b931c2
heading size change
christina-baldwin May 23, 2025
2e3b732
placing the to-d-s side-by side
christina-baldwin May 23, 2025
f8d67be
making it repsonsive
christina-baldwin May 23, 2025
f2e7df5
resposnive design changes
christina-baldwin May 23, 2025
eb260f7
informative text when there's nothing to see
christina-baldwin May 23, 2025
7ec8ad4
adding an empty ui state
christina-baldwin May 23, 2025
6eaa56c
accessibility with aria-labels
christina-baldwin May 23, 2025
d76841b
styling a bit more
christina-baldwin May 24, 2025
0323a44
making complete/incomplete a toggle instead
christina-baldwin Jun 2, 2025
448354f
adding word break
christina-baldwin Jun 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,24 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<link href="/src/styles.css" rel="stylesheet" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;600&display=swap"
rel="stylesheet"
/>
<title>Todo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./src/main.jsx"></script>
<script
type="module"
src="./src/main.jsx">
</script>
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"
></script>
<script
nomodule
src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"
></script>
</body>
</html>
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"tailwindcss": "^4.1.7",
"zustand": "^5.0.4"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

16 changes: 13 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import ToDoForm from "./components/ToDoForm";
import TodoList from "./components/TodoList";

export const App = () => {
return (
<h1>React Boilerplate</h1>
)
}
<div className="flex items-center justify-center ">
<div className="flex items-center min-h-screen flex-col gap-10 p-8 text-blue-200 bg-gray-800 mx-auto w-full">
<h1 className="text-center text-6xl font-bold">Todoodle</h1>
<h2 className="text-center text-xl font-bold">My To-Do List</h2>
<ToDoForm />
<TodoList />
</div>
</div>
);
};
31 changes: 31 additions & 0 deletions src/components/TaskCounter.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import useTodoStore from "../stores/useTodoStore";

const TaskCounter = () => {
const todos = useTodoStore((state) => state.todos);

return (
<div className="flex flex-col gap-8 flex-wrap">
<h3 className="text-lg">Your progress:</h3>
<div className="flex flex-row gap-2">
<ion-icon name="chevron-forward-outline"></ion-icon>
<p>All tasks: {todos.length}</p>
</div>
<div className="flex flex-row gap-2">
<ion-icon name="chevron-forward-outline"></ion-icon>
<p>
Tasks completed:{" "}
{todos.filter((todo) => todo.complete === true).length}{" "}
</p>
</div>
<div className="flex flex-row gap-2">
<ion-icon name="chevron-forward-outline"></ion-icon>
<p>
Tasks remaining:{" "}
{todos.filter((todo) => todo.complete === false).length}
</p>
</div>
</div>
);
};

export default TaskCounter;
97 changes: 97 additions & 0 deletions src/components/ToDoForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { useState } from "react";
import useTodoStore from "../stores/useTodoStore";

const ToDoForm = () => {
const [message, setMessage] = useState("");
const [category, setCategory] = useState("");
const createTodo = useTodoStore((state) => state.createTodo);

const handleSubmit = (e) => {
e.preventDefault();
createTodo(message, category);
setMessage("");
setCategory("");
};

return (
<form
className="
border border-solid border-blue-200 rounded-sm p-4
flex flex-col gap-4
w-full max-w-md
mx-auto
"
onSubmit={handleSubmit}
>
<h2 className="text-lg">What do you need to do?</h2>
<input
aria-label="Task name"
type="text"
onChange={(e) => setMessage(e.target.value)}
required
value={message}
className="border border-gray-300 h-10 px-3 focus:border-rose-200 focus:ring-rose-300 focus:outline-none focus:ring-2 "
/>
<h3>Task category:</h3>
<div className="p-2 flex flex-col gap-2">
<label>
<input
type="radio"
name="category"
value="Work"
checked={category === "Work"}
onChange={(e) => setCategory(e.target.value)}
required
className="accent-rose-300 focus:ring-2 focus:ring-rose-300"
/>{" "}
Work
</label>
<label>
<input
type="radio"
name="category"
value="Study"
checked={category === "Study"}
onChange={(e) => setCategory(e.target.value)}
required
className="accent-rose-300 focus:ring-2 focus:ring-rose-300"
/>{" "}
Study
</label>
<label>
<input
type="radio"
name="category"
value="Chores"
checked={category === "Chores"}
onChange={(e) => setCategory(e.target.value)}
required
className="accent-rose-300 focus:ring-2 focus:ring-rose-300"
/>{" "}
Chores
</label>
<label>
<input
type="radio"
name="category"
value="Misc"
checked={category === "Misc"}
onChange={(e) => setCategory(e.target.value)}
required
className="appearence-none bg-gray-800 accent-rose-300 focus:ring-2 focus:ring-rose-300"
/>{" "}
Misc
</label>
</div>
<button
aria-label="Add task"
className="border border-solid border-blue-200 hover:bg-blue-200 hover:text-gray-800 rounded-md cursor-pointer"
type="submit"
>
Add my to-do
</button>
</form>
);
};

export default ToDoForm;
67 changes: 67 additions & 0 deletions src/components/ToDoMessage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import useTodoStore from "../stores/useTodoStore";

const ToDoMessage = ({ id, message, complete, category }) => {
const removeTodo = useTodoStore((state) => state.removeTodo);
const completeTodo = useTodoStore((state) => state.completeTodo);
const uncompleteTodo = useTodoStore((state) => state.uncompleteTodo);

return (
<div
className="
border border-solid border-blue-200 rounded-sm
flex flex-col gap-4 p-4
w-full max-w-md
mx-auto
"
>
<div className="bg-purple-200 text-gray-800 flex items-center justify-center rounded-lg p-1 text-sm">
<p>{category}</p>
</div>

<p
className={`text-lg p-4 rounded-sm border border-gray-100 break-words
${
complete
? "line-through text-gray-400 bg-gray-100 italic"
: "text-gray-800 bg-gray-50"
}`}
>
{message}
</p>

<div className="text-md flex flex-row items-center gap-2">
<p>Complete?</p>

{complete ? (
<ion-icon
aria-hidden="true"
name="checkmark-circle-outline"
></ion-icon>
) : (
<ion-icon aria-hidden="true" name="close-circle-outline"></ion-icon>
)}
</div>

<div className="flex flex-row gap-4">
<button
aria-label="Mark task as complete or incomplete"
className="border border-solid border-blue-200 rounded-md cursor-pointer p-2 hover:bg-blue-200 hover:text-gray-800 "
onClick={complete ? () => uncompleteTodo(id) : () => completeTodo(id)}
>
Toggle Task
</button>

<button
aria-label="Delete task"
className="bg-blue-200 text-gray-800 border border-solid border-blue-200 rounded-md cursor-pointer p-2 hover:bg-gray-800 hover:text-blue-200 "
onClick={() => removeTodo(id)}
>
Delete
</button>
</div>
<p className="italic">Task added: {new Date(id).toLocaleString()}</p>
</div>
);
};

export default ToDoMessage;
93 changes: 93 additions & 0 deletions src/components/TodoList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useState } from "react";
import useTodoStore from "../stores/useTodoStore";
import ToDoMessage from "./ToDoMessage";
import TaskCounter from "./TaskCounter";

const TodoList = () => {
const todos = useTodoStore((state) => state.todos);
const [filter, setFilter] = useState("all");

const filteredTodos = todos.filter((todo) => {
if (filter === "complete") return todo.complete;
if (filter === "incomplete") return !todo.complete;
return true;
});

return (
<div className="flex items-center flex-col gap-10 w-full max-w-xl mx-auto px-4 sm:px-8">
<TaskCounter />
<div className="flex flex-col gap-2">
<p>Filter tasks by:</p>
<div className="flex gap-2 flex-wrap">
<button
aria-label="Show all tasks"
className={`border border-solid border-blue-200 rounded-md cursor-pointer p-2 hover:bg-blue-200 hover:text-gray-800 ${
filter === "all" ? "bg-blue-200 text-gray-800 shadow-md" : ""
}`}
onClick={() => setFilter("all")}
>
All
</button>
<button
aria-label="Show complete tasks"
className={`border border-solid border-blue-200 rounded-md cursor-pointer p-2 hover:bg-blue-200 hover:text-gray-800 ${
filter === "complete" ? "bg-blue-200 text-gray-800 shadow-md" : ""
}`}
onClick={() => setFilter("complete")}
>
Complete
</button>
<button
aria-label="Show incomplete tasks"
className={`border border-solid border-blue-200 rounded-md cursor-pointer p-2 hover:bg-blue-200 hover:text-gray-800 ${
filter === "incomplete"
? "bg-blue-200 text-gray-800 shadow-md"
: ""
}`}
onClick={() => setFilter("incomplete")}
>
Incomplete
</button>
</div>
</div>
<div className="flex gap-6 flex-wrap justify-center w-full max-w-6xl mx-auto px-4 sm:px-8">
{filteredTodos.length === 0 ? (
filter === "complete" ? (
<div className="flex flex-col items-center gap-6">
<ion-icon
aria-hidden="true"
style={{ fontSize: "5rem" }}
name="document-outline"
></ion-icon>
<p>No completed tasks yet. Complete a task to see it here.</p>
</div>
) : filter === "incomplete" ? (
<div className="flex flex-col items-center text-center gap-6">
<ion-icon
aria-hidden="true"
style={{ fontSize: "5rem" }}
name="document-outline"
></ion-icon>
<p>
No incomplete tasks yet. Add a task or uncheck a completed one.
</p>{" "}
</div>
) : (
<div className="flex flex-col items-center gap-6">
<ion-icon
aria-hidden="true"
style={{ fontSize: "5rem" }}
name="document-outline"
></ion-icon>
<p>You have no tasks yet. Add a to-do to get started!</p>{" "}
</div>
)
) : (
filteredTodos.map((todo) => <ToDoMessage key={todo.id} {...todo} />)
)}
</div>
</div>
);
};

export default TodoList;
4 changes: 3 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@import "tailwindcss";

:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-family: Quicksand, system-ui, Avenir, Helvetica, Arial, sans-serif;
}
Loading