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
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,34 @@
# Todo
# 📋 Todo App

A simple and accessible Todo application built with React and Zustand. This project helps users organize their tasks with features like marking tasks as completed, starring important ones, and saving everything in local storage for persistence.

🔗 **Live site:** [https://taskortoss.netlify.app](https://taskortoss.netlify.app)

## ✨ Features

- ✅ Add, complete, uncomplete, and delete tasks
- ⭐ Star your most important tasks
- 📅 Each task includes a creation date
- 📊 Task counter: shows completed and starred tasks
- 💾 Local storage support to keep your tasks between sessions
- 🎨 Responsive and clean UI (mobile to desktop)
- ♿ Follows web accessibility best practices

## 🧠 What I Learned

- Global state management with Zustand
- React component structure and props
- Handling user input and controlled forms
- Conditional rendering and sorting logic
- Accessibility techniques (semantic HTML, color contrast, screen reader labels)
- Working with local storage
- Clean code and folder structure

## 🛠️ Tech Stack

- **React**
- **Zustand**
- **Styled Components**
- **Moment.js** for date formatting
- **Vite** (development server & bundler)

38 changes: 19 additions & 19 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
import js from '@eslint/js'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import globals from 'globals'
import js from "@eslint/js";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";

export default [
{ ignores: ['dist'] },
{ ignores: ["dist"] },
{
files: ['**/*.{js,jsx}'],
files: ["**/*.{js,jsx}"],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
parserOptions: {
ecmaVersion: 'latest',
ecmaVersion: "latest",
ecmaFeatures: { jsx: true },
sourceType: 'module'
}
sourceType: "module",
},
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true }
]
}
}
]
"no-unused-vars": ["error", { varsIgnorePattern: "^[A-Z_]" }],
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
},
},
];
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<link rel="icon" type="image/svg+xml" href="./favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo</title>
<title>Todo list</title>
</head>
<body>
<div id="root"></div>
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,26 @@
"preview": "vite preview"
},
"dependencies": {
"moment": "^2.30.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-datepicker": "^8.4.0",
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"react-router-dom": "^7.6.0",
"styled-components": "^6.1.18",
"zustand": "^5.0.4"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4",
"eslint": "^9.21.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"prettier": "^3.5.3",
"vite": "^6.2.0"
}
}
Binary file added public/empty.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/favicon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

1 change: 0 additions & 1 deletion pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
Please include your Netlify link here.
44 changes: 44 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
body {
background: white;
font-family: arial;
}

p {
font-size: 18px;
margin: 0;
}

.image-wrapper {
display: flex;
justify-content: center;
align-items: center;
}

.image-wrapper img {
width: 150px;
margin: 20px;
}

.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}

.count-container {
display: flex;
flex-direction: column;
gap: 5px;
}

@media (min-width: 768px) {
.count-container {
flex-direction: row;
justify-content: space-between;
}
}
27 changes: 26 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
import styled from "styled-components";
import { TaskForm } from "./components/TaskForm";
import { HeaderComponent } from "./components/Header";
import { FooterComponent } from "./components/Footer";
import "./App.css"

const CardWrapper = styled.section`
background-color: rgb(237, 239, 239);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
max-width: 1000px;
margin: 50px auto;

@media (max-width: 768px) {
margin: 0px;
}
`;

export const App = () => {
return (
<h1>React Boilerplate</h1>
<>
<CardWrapper>
<HeaderComponent />
<TaskForm />
<FooterComponent />
</CardWrapper>
</>
)
}
51 changes: 51 additions & 0 deletions src/components/Count.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import styled from "styled-components";
import { useTaskStore } from "../stores/useTaskStore";
import moment from "moment";

const CountContainer = styled.div`
margin-top: 16px;
text-align: left;
`;

const WarningText = styled.p`
font-weight: bold;
color: darkred;
padding-top: 16px;

@media (min-width: 768px) {
text-align: center;
}
`;

const InfoText = styled.p`
margin: 4px 0;
`;

export const Count = () => {
const tasks = useTaskStore((state) => state.tasks);

const completed = tasks.filter((task) => task.isCompleted).length;
const starredTasks = tasks.filter((task) => task.isStarred).length;
const overdueTasks = tasks.filter(
(task) =>
task.dueDate &&
moment(task.dueDate).isValid() &&
moment(task.dueDate).isBefore(moment(), "day")
).length;
const totalTasks = tasks.length;

return (
<CountContainer>
<InfoText>
Completed tasks: {completed} / {totalTasks}
</InfoText>
<InfoText>Starred tasks: {starredTasks}</InfoText>
{overdueTasks > 0 && (
<WarningText>
You have {overdueTasks} {overdueTasks === 1 ? "task" : "tasks"} that{" "}
{overdueTasks === 1 ? "has" : "have"} passed the due date.
</WarningText>
)}
</CountContainer>
);
};
56 changes: 56 additions & 0 deletions src/components/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import styled from "styled-components";
import { FaGithub } from "react-icons/fa";

export const Footer = styled.footer`
padding: 20px;
background-color: rgb(237, 239, 239);
max-width: 600px;
margin-left: auto;
margin-right: auto;
margin-bottom: 50px;
box-sizing: border-box;
text-align: center;
font-size: 20px;
font-weight: bold;
`;

const FooterText = styled.h2`
font-size: 16px;
text-align: center;
color: #333;

a {
color: #333;
text-decoration: none;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;

&:hover {
text-decoration: none;
}

svg {
font-size: 18px;
padding-right: 5px;
}
}
`;

export const FooterComponent = () => {
return (
<Footer>
<FooterText>
© 2025 Designed and created by {" "}
<a
href="https://github.com/violacathrine/js-project-todo"
target="_blank"
rel="noopener noreferrer"
>
Cathi <FaGithub />
</a>
</FooterText>
</Footer>
);
};
31 changes: 31 additions & 0 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import styled from "styled-components";

export const Header = styled.header`
padding: 20px;
background-color:rgb(237, 239, 239);
max-width: 600px;
margin-left: auto;
margin-right: auto;
margin-top: 50px;
box-sizing: border-box;

text-align: center;
font-size: 20px;
font-weight: bold;
`

export const HeaderText = styled.h1`
font-size: 40px;
color:rgb(0, 0, 0);
margin: 0;
text-align: center;
letter-spacing: 5px;
`

export const HeaderComponent = () => {
return (
<Header>
<HeaderText>TODO LIST</HeaderText>
</Header>
);
}
Loading