Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
fbec13e
updated document title in head
May 28, 2025
436d158
added globalstyles and changed the page heading
May 28, 2025
f3bdc3c
added quicksand font from google fonts
May 28, 2025
79f4ff9
added and imported globalstyle in app
May 28, 2025
da3176f
removed page heading in app and added header component with styled-co…
May 28, 2025
19c9bde
changed font sizes to fit different screen sizes
May 28, 2025
97f6295
added header image and made slight changes to header
May 28, 2025
9c856eb
added initial setup and taskform and changed image
May 28, 2025
d6deb34
added task creation
May 28, 2025
fa76f1e
added task deletion
May 28, 2025
3967001
added toogle completion
May 28, 2025
c835c4f
added clear task function
May 28, 2025
806c106
added reset to initial state
May 28, 2025
dfd6e9c
created basic tasklist component with mapped tasks and task component…
May 28, 2025
ad4bdc2
added summary of total and uncompleted tasks
May 28, 2025
39447fd
added empty state message for when there are notasks
May 28, 2025
ebdabbb
connected task component to zustand for actions
May 28, 2025
0711120
added complete and delete icons with actions to task component
May 28, 2025
57ece28
changed margin and added media queries
May 28, 2025
2539e03
added meta description for higher seo score
May 28, 2025
412b2ba
Update README.md
T-Thiry May 28, 2025
7195c04
Update README.md
T-Thiry May 29, 2025
d77a340
added explicit width and height to header image for better performance
May 29, 2025
5fc1619
removed vite icon from head
May 29, 2025
ae02793
changed image format to webp
May 29, 2025
4b41d30
preload fonts for better load time
May 29, 2025
4e8e332
preload image to improve LCP performance
May 29, 2025
ab68277
added lazy loading to taskform and tasklist components with React Sus…
May 29, 2025
10925d4
removed lazy loading to taskform and tasklist
May 29, 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# Todo
# Todo

A task management app where users can add, complete, and remove tasks. Built in React with Zustand for managing global state. Features include task creation, completion toggling, and task counters.

https://tthiry-todotaskmanagement.netlify.app
14 changes: 12 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap"
as="style"
onload="this.onload=null;this.rel='stylesheet'"
/>
<link rel="preload" as="image" href="/src/assets/images/Task_management.webp" />
<noscript>
<link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;700&display=swap" rel="stylesheet" />
</noscript>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Todo</title>
<meta name="description" content="Simple and efficient to-do app to manage your daily tasks." />
<title>Todo Task Management</title>
</head>
<body>
<div id="root"></div>
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"react-icons": "^5.5.0",
"styled-components": "^6.1.18",
"zustand": "^5.0.5"
},
"devDependencies": {
"@eslint/js": "^9.21.0",
Expand Down
12 changes: 11 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { GlobalStyle } from "./styles/GlobalStyle"
import { Header } from "./components/Header"
import { TaskForm } from "./components/TaskForm"
import { TaskList } from "./components/TaskList"

export const App = () => {
return (
<h1>React Boilerplate</h1>
<>
<GlobalStyle />
<Header />
<TaskForm />
<TaskList />
</>
)
}
Binary file added src/assets/images/Task_management.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
66 changes: 66 additions & 0 deletions src/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled from "styled-components"
import headerImg from "/src/assets/images/Task_management.webp"


export const Header = () => {
return (
<StyledHeader>
<TextContent>
<h1>ToDo Task Management</h1>
<p>Stay organized with your personal task manager</p>
</TextContent>
<HeaderImage src={headerImg} alt="Task management" width="500" height="300" />
</StyledHeader>
)
}

const StyledHeader = styled.header`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`

const TextContent = styled.div`
text-align: center;
margin: 1rem 1rem 0 1rem;

h1 {
font-size: 1.5rem;
}

p {
font-size: 1rem;
padding: 0 1rem
}

@media (min-width: 768px) {
h1 {
font-size: 1.75rem;
}

p {
font-size: 1.125rem;
}

margin: 1rem;
}

@media (min-width: 1024px) {
h1 {
font-size: 2rem;
}
}
`

const HeaderImage = styled.img`
width: 100%;
max-width: 500px;
height: auto;
padding: 1rem;
box-sizing: border-box;

@media (min-width: 768px) {
padding: 0;
}
`
77 changes: 77 additions & 0 deletions src/components/Task.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import styled from "styled-components"
import { useTaskStore } from "../stores/useTaskStore"
import { FaTrashAlt, FaCheckCircle, FaRegCircle } from "react-icons/fa"


export const Task = ({ id, task, completed }) => {
const deleteTask = useTaskStore(state => state.deleteTask)
const toggleComplete = useTaskStore(state => state.toggleComplete)

return (
<TaskWrapper>
<TaskContent>
<TaskMessage completed={completed}>{task}</TaskMessage>
<Icons>
<IconButton
aria-label={completed ? "Mark as incomplete" : "Mark as complete"}
onClick={() => toggleComplete(id)}
>
{completed ? <FaCheckCircle size={18}/> : <FaRegCircle size={18}/>}
</IconButton>
<IconButton
aria-label="Delete task"
onClick={() => deleteTask(id)}
>
<FaTrashAlt size={18} />
</IconButton>
</Icons>

</TaskContent>
</TaskWrapper>
)
}

const TaskWrapper = styled.div`
max-width: 500px;
padding: 0.7rem;
margin: 0 1rem 1rem 1rem;
background-color: #ededfb;
border-radius: 8px;
border-left: 3px solid #1c55e4;

@media (min-width: 768px) {
margin: 0 0 1rem 0;
}
`

const TaskContent = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
`

const TaskMessage = styled.p`
margin: 0;
flex: 1;
padding-right: 1rem;
text-decoration: ${props => (props.completed ? "line-through" : "none")};
`

const Icons = styled.div`
display: flex;
gap: 0.5rem;
`

const IconButton = styled.button`
background: #1c55e4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
padding: 0.5rem;
vertical-align: middle;

&:hover {
opacity: 0.8;
}
`
66 changes: 66 additions & 0 deletions src/components/TaskForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled from "styled-components"
import { useState } from "react"
import { useTaskStore } from "../stores/useTaskStore"


export const TaskForm = () => {
const [task, setTask] = useState("")
const createTask = useTaskStore(state => state.createTask)

const handleSubmit = e => {
e.preventDefault()
if (task.trim() === "") return
createTask(task)
setTask("")
}

return (
<PageWrapper>
<Form onSubmit={handleSubmit}>
<Textarea
value={task}
onChange={e => setTask(e.target.value)}
placeholder="Write your task here..."
aria-label="New task" />
<Button type="submit">Add Task</Button>
</Form>
</PageWrapper>
)
}

const PageWrapper = styled.main`
padding: 1rem;
display: flex;
justify-content: center;
`

const Form = styled.form`
display: flex;
flex-direction: column;
width: 100%;
max-width: 500px;
gap: 1rem;
`

const Textarea = styled.textarea`
width: 100%;
padding: 1rem;
font-size: 16px;
font-family: "Quicksand", sans-serif;
box-sizing: border-box;
`

const Button = styled.button`
padding: 0.5rem 1.5rem;
border-radius: 20px;
border: none;
background: #1c55e4;
color: white;
font-size: 16px;
font-weight: bold;
cursor: pointer;

&:hover {
opacity: 0.9;
}
`
47 changes: 47 additions & 0 deletions src/components/TaskList.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import styled from "styled-components"
import { useTaskStore } from "../stores/useTaskStore"
import { Task } from "./Task"

export const TaskList = () => {
const tasks = useTaskStore(state => state.tasks)
const total = tasks.length
const uncompleted = tasks.filter(t => !t.completed).length

if (total === 0) {
return (
<EmptyState>
<p>No tasks yet. Add your first one!</p>
</EmptyState>
)
}

return (
<Section>
<TaskSummary>
Total tasks: {total} | Uncompleted: {uncompleted}
</TaskSummary>
{tasks.map(task => (
<Task key={task.id} {...task} />
))}
</Section>
)
}

const Section = styled.section`
width: 100%;
max-width: 500px;
margin: 0 auto;
`

const TaskSummary = styled.p`
margin-bottom: 1rem;
font-weight: bold;
margin: 1rem;
`

const EmptyState = styled.div`
text-align: center;
padding: 2rem;
font-style: italic;
color: #777;
`
44 changes: 44 additions & 0 deletions src/stores/useTaskStore.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { create } from "zustand"


const initialState = {
tasks: [
{
id: 1,
task: "My first task...",
completed: false
}
]
}

export const useTaskStore = create((set) => ({
...initialState,

createTask: (task) => {
const newTask = {
id: Date.now(),
task,
completed: false
}

set(state => ({ tasks: [newTask, ...state.tasks] }))
},

deleteTask: (id) => {
set(state => ({
tasks: state.tasks.filter(task => task.id !== id)
}))
},

toggleComplete: (id) => {
set(state => ({
tasks: state.tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
)
}))
},

clearTasks: () => set({ tasks: [] }),

resetTasks: () => set(initialState)
}))
12 changes: 12 additions & 0 deletions src/styles/GlobalStyle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createGlobalStyle } from 'styled-components';

export const GlobalStyle = createGlobalStyle`
body {
margin: 0;
padding: 0;
font-family: "Quicksand", sans-serif;
background: #FFFFFF;
color: #1E1E1E;
box-sizing: border-box;
}
`;