Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat&family=Handlee&family=Rampart+One&family=Sriracha&display=swap" rel="stylesheet">

<title>Todo</title>
</head>
<body>
Expand Down
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": {
"lottie-react": "^2.4.1",
"moment": "^2.30.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"zustand": "^5.0.5"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
22 changes: 22 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.main {
padding: 20px 100px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}

.innerMain {
border: 1px solid black;
width: 400px;
}

h1 {
color: black;
display: flex;
justify-content: center;
align-items: center;
font-size: 70px;
font-family: "Rampart One", sans-serif;
font-weight: 400;
}
18 changes: 15 additions & 3 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
import NewTask from "./components/NewTask";
import StatusBar from "./components/StatusBar";
import TaskList from "./components/TaskList";
import "./App.css";

export const App = () => {
return (
<h1>React Boilerplate</h1>
)
}
<div className="main">
<div className="innerMain">
<h1>ToDo App</h1>
<NewTask />
<StatusBar />
<TaskList />
</div>
</div>
);
};
10 changes: 10 additions & 0 deletions src/components/NewTask.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.toDoBox {
border: 1px solid black;
background-color: #eee;
box-shadow: 0.25rem 0.25rem rgba(12, 23, 14, 0.388);
display: flex;
flex-direction: column;
padding: 1rem;
margin: 1rem;
gap: 10px;
}
34 changes: 34 additions & 0 deletions src/components/NewTask.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useState } from "react";
import { useNoteStore } from "../store";
import "./NewTask.css";

const NewTask = () => {
const [toDo, setToDo] = useState("");

const { addNewTask } = useNoteStore();

const addToDo = () => {
if (toDo) {
addNewTask(toDo);
}
setToDo("");
};

return (
<form className="toDoBox" onSubmit={(ev) => ev.preventDefault()}>
<label htmlFor="toDoArea">Add your task!</label>
<input
className="toDoArea"
id="toDoArea"
type="text"
value={toDo}
onChange={(ev) => setToDo(ev.target.value)}
/>
<button onClick={addToDo} className="addToDoButton">
Add
</button>
</form>
);
};

export default NewTask;
17 changes: 17 additions & 0 deletions src/components/StatusBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.statusBar {
border: 1px solid black;
background-color: #b9b8b4;
box-shadow: 0.25rem 0.25rem rgba(12, 23, 14, 0.388);
display: flex;
flex-direction: row;
padding: 1rem;
margin: 1rem;
gap: 10px;
justify-content: space-around;
}

.actionButton {
border-radius: 5px;
padding: 2px;
background-color: #2febe5;
}
22 changes: 22 additions & 0 deletions src/components/StatusBar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";
import { useNoteStore } from "../store";

import "./StatusBar.css";

const StatusBar = () => {
const { todos, checkAllTasks, removeAllDoneTasks } = useNoteStore();

return (
<div className="statusBar">
<span>{todos.length} tasks</span>
<button className="actionButton" onClick={checkAllTasks}>
Complete All
</button>
<button className="actionButton" onClick={removeAllDoneTasks}>
Clear Completed
</button>
</div>
);
};

export default StatusBar;
33 changes: 33 additions & 0 deletions src/components/Task.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.taskBox {
border: 1px solid black;
background-color: #74cd07;
box-shadow: 0.25rem 0.25rem rgba(12, 23, 14, 0.388);
display: flex;
flex-direction: column;
gap: 10px;
justify-content: space-around;
}

.taskDone {
background-color: #c5c3c1;
}

.taskDetails {
background-color: rgba(112, 82, 41, 0.363);
display: flex;
flex-direction: row;
justify-content: space-between;
}

.taskMain {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0.1rem 0.2rem;
}

.removeButton {
border-radius: 5px;
padding: 2px;
background-color: #b0d790;
}
41 changes: 41 additions & 0 deletions src/components/Task.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { useNoteStore } from "../store";
import moment from "moment";

import "./Task.css";

const Task = ({ todo }) => {
const { toggleChecked, removeTask } = useNoteStore();

const toggle = (todo) => {
toggleChecked(todo.text, todo.date);
};

return (
<div className={todo.checked ? "taskBox taskDone" : "taskBox"}>
<div className="taskMain">
<span>{todo.text}</span>
<label>Done
<input
type="checkbox"
checked={todo.checked}
onChange={() => toggle(todo)}
/>
</label>
</div>
<div className="taskDetails">
<span>{moment(todo.date).fromNow()}</span>
<button
className="removeButton"
onClick={() => {
removeTask(todo.text, todo.date);
}}
>
Remove
</button>
</div>
</div>
);
};

export default Task;
10 changes: 10 additions & 0 deletions src/components/TaskList.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.taskList {
border: 1px solid black;
background-color: #f1daa9;
display: flex;
flex-direction: column;
padding: 1rem;
margin: 1rem;
gap: 10px;
justify-content: space-around;
}
17 changes: 17 additions & 0 deletions src/components/TaskList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
import Task from "./Task";
import { useNoteStore } from "../store";
import "./TaskList.css";

const TaskList = () => {
const { todos } = useNoteStore();
return (
<div className="taskList">
{todos.map((todo) => (
<Task key={`${todo.date}-${todo.text}`} todo={todo} />
))}
</div>
);
};

export default TaskList;
8 changes: 7 additions & 1 deletion src/index.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-family: "Handlee", Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
83 changes: 83 additions & 0 deletions src/store.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import moment from "moment";
import { create } from "zustand";
import { persist } from "zustand/middleware";

export const useNoteStore = create( persist( (set) => {
return {
todos: [
{
text: "test",
date: moment.now(),
checked: false,
},
{
text: "test2",
date: moment.now(),
checked: true,
},
],
addNewTask: (newTodo) =>
set((state) => {
const newTodos = [...state.todos];
newTodos.push({ text: newTodo, date: moment.now(), checked: false });
return { todos: newTodos };
}),
removeTask: (todotext, date) =>
set((state) => {
const newTodos = [];
for (const index in state.todos) {
const todo = state.todos[index];
if (todotext === todo.text && date === todo.date) {
// skip
} else {
newTodos.push(todo);
}
}
return { todos: newTodos };
}),
toggleChecked: (todotext, date) =>
set((state) => {
const newTodos = [];
for (const index in state.todos) {
const todo = state.todos[index];
if (todotext === todo.text && date === todo.date) {
newTodos.push({
text: todo.text,
date: todo.date,
checked: !todo.checked,
});
} else {
newTodos.push(todo);
}
}
return { todos: newTodos };
}),
checkAllTasks: () =>
set((state) => {
const newTodos = [];
for (const index in state.todos) {
const todo = state.todos[index];
newTodos.push({
text: todo.text,
date: todo.date,
checked: true,
});
}
return { todos: newTodos };
}),
removeAllDoneTasks: () =>
set((state) => {
const newTodos = [];
for (const index in state.todos) {
const todo = state.todos[index];
if (todo.checked === false) {
newTodos.push(todo);
}
}
return { todos: newTodos };
}),
};
}, {
name: 'todo-tasks'
}
));