diff --git a/src/App.tsx b/src/App.tsx
index a399287bd..87f25a1c2 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,157 +1,13 @@
-/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
+import { Content } from './components/Content';
+import { TodosProvider } from './context/TodosContex';
export const App: React.FC = () => {
return (
-
todos
-
-
-
- {/* this button should have `active` class only if all todos are completed */}
-
-
- {/* Add a todo on form submit */}
-
-
-
-
-
- {/* Hide the footer if there are no todos */}
-
-
- 3 items left
-
-
- {/* Active link should have the 'selected' class */}
-
-
- All
-
-
-
- Active
-
-
-
- Completed
-
-
-
- {/* this button should be disabled if there are no completed todos */}
-
- Clear completed
-
-
-
+
+
+
);
};
diff --git a/src/components/Content.tsx b/src/components/Content.tsx
new file mode 100644
index 000000000..86fd338ba
--- /dev/null
+++ b/src/components/Content.tsx
@@ -0,0 +1,46 @@
+import React, { useContext, useEffect, useState } from 'react';
+import { Header } from './Header';
+import { TodoList } from './TodoList';
+import { Footer } from './Footer';
+import { TodosContext } from '../context/TodosContex';
+
+export const Content: React.FC = () => {
+ const { todos, setTodos } = useContext(TodosContext);
+
+ const [visibleTodos, setVisibleTodos] = useState(todos);
+
+ useEffect(() => {
+ localStorage.setItem('todos', JSON.stringify(todos));
+ }, [todos]);
+
+ const savedTodos = localStorage.getItem('todos');
+
+ useEffect(() => {
+ if (savedTodos) {
+ setTodos(JSON.parse(savedTodos));
+ }
+ }, []);
+
+ useEffect(() => {
+ setVisibleTodos(todos);
+ }, [todos]);
+
+ return (
+ <>
+ todos
+
+
+
+
+
+
+ {todos.length > 0 && (
+
+ )}
+
+ >
+ );
+};
diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx
new file mode 100644
index 000000000..2405edacd
--- /dev/null
+++ b/src/components/Footer.tsx
@@ -0,0 +1,100 @@
+import React, { useCallback, useContext, useEffect, useState } from 'react';
+import { Filter } from '../types/Filter';
+import classNames from 'classnames';
+import { TodosContext } from '../context/TodosContex';
+import { Todo } from '../types/Todo';
+
+type Props = {
+ visibleTodos: Todo[];
+ setVisibleTodos: (vilteredTodos: Todo[]) => void;
+};
+
+export const Footer: React.FC = ({ visibleTodos, setVisibleTodos }) => {
+ const { todos, setTodos } = useContext(TodosContext);
+
+ const [selectedFilter, setSelectedFilter] = useState(Filter.All);
+
+ const findFilterKey = (value: string): string | undefined => {
+ return Object.keys(Filter).find(
+ key => Filter[key as keyof typeof Filter] === value,
+ );
+ };
+
+ const todosCount = useCallback(
+ (type: Filter.Active | Filter.Completed) => {
+ const value = type === Filter.Active ? false : true;
+
+ return todos.filter(todo => todo.completed === value).length;
+ },
+ [todos],
+ );
+
+ const filterFunction = (filter: Filter) => {
+ switch (filter) {
+ case Filter.All:
+ setVisibleTodos(todos);
+ break;
+
+ case Filter.Active:
+ setVisibleTodos(todos.filter(todo => !todo.completed));
+ break;
+
+ case Filter.Completed:
+ setVisibleTodos(todos.filter(todo => todo.completed));
+ break;
+
+ default:
+ setTodos(todos);
+ }
+ };
+
+ const clearFunction = () => {
+ setTodos(todos.filter(t => !t.completed));
+ };
+
+ const setFilter = (filter: Filter) => {
+ setSelectedFilter(filter);
+
+ filterFunction(filter);
+ };
+
+ useEffect(() => {
+ if (selectedFilter) {
+ filterFunction(selectedFilter);
+ }
+ }, [visibleTodos]);
+
+ return (
+
+ );
+};
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
new file mode 100644
index 000000000..f6a26f1f4
--- /dev/null
+++ b/src/components/Header.tsx
@@ -0,0 +1,83 @@
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import { TodosContext } from '../context/TodosContex';
+import classNames from 'classnames';
+import { Todo } from '../types/Todo';
+
+export const Header: React.FC = () => {
+ const { todos, setTodos } = useContext(TodosContext);
+
+ const [title, setTitle] = useState('');
+
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, [todos]);
+
+ const checkActiveTodos = (): boolean => {
+ if (todos.length > 0) {
+ return todos.every(todo => todo.completed);
+ }
+
+ return false;
+ };
+
+ const onSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ const normalizedTitle = title.trim();
+
+ if (!normalizedTitle) {
+ return;
+ }
+
+ const newTodo: Todo = {
+ id: +new Date(),
+ title: normalizedTitle,
+ completed: false,
+ };
+
+ setTodos([...todos, newTodo]);
+ setTitle('');
+ };
+
+ const toggleAll = () => {
+ const allCompleted = checkActiveTodos();
+
+ const toggledTodos = todos.map(todo => ({
+ ...todo,
+ completed: allCompleted ? false : true,
+ }));
+
+ setTodos(toggledTodos);
+ };
+
+ return (
+
+ {todos.length > 0 && (
+
+ )}
+
+
+
+ );
+};
diff --git a/src/components/TodoItem.tsx b/src/components/TodoItem.tsx
new file mode 100644
index 000000000..57027a63c
--- /dev/null
+++ b/src/components/TodoItem.tsx
@@ -0,0 +1,111 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/control-has-associated-label */
+import React, { useContext, useEffect, useRef, useState } from 'react';
+import { Todo } from '../types/Todo';
+import classNames from 'classnames';
+import { TodosContext } from '../context/TodosContex';
+
+type Props = {
+ todo: Todo;
+};
+
+export const TodoItem: React.FC = ({ todo }) => {
+ const { todos, setTodos } = useContext(TodosContext);
+
+ const [onEdit, setOnEdit] = useState(false);
+ const [title, setTitle] = useState(todo.title);
+
+ const todoInputRef = useRef(null);
+
+ useEffect(() => {
+ if (todoInputRef.current) {
+ todoInputRef.current.focus();
+ }
+ }, [onEdit]);
+
+ const handleKeyUp = (event: React.KeyboardEvent) => {
+ if (event.key === 'Escape') {
+ setOnEdit(false);
+ setTitle(todo.title);
+ }
+ };
+
+ const updateTodo = (updatedTodo: Todo) => {
+ setTodos(todos.map(t => (t.id === updatedTodo.id ? updatedTodo : t)));
+ };
+
+ const onDelete = () => {
+ setTodos(todos.filter(currentTodo => currentTodo.id !== todo.id));
+ };
+
+ const statusChange = () => {
+ updateTodo({ ...todo, completed: !todo.completed });
+ };
+
+ const onSubmit = (event?: React.FormEvent) => {
+ event?.preventDefault();
+ setOnEdit(false);
+
+ const normalizedTitle = title.trim();
+
+ if (!normalizedTitle) {
+ onDelete();
+
+ return;
+ }
+
+ updateTodo({ ...todo, title: normalizedTitle });
+ };
+
+ return (
+ <>
+ setOnEdit(true)}
+ className={classNames('todo', { completed: todo.completed })}
+ >
+ statusChange()}>
+
+
+
+ {!onEdit ? (
+ <>
+
+ {todo.title}
+
+
+ onDelete()}
+ data-cy="TodoDelete"
+ >
+ ×
+
+ >
+ ) : (
+
+ )}
+
+ >
+ );
+};
diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx
new file mode 100644
index 000000000..036cde44c
--- /dev/null
+++ b/src/components/TodoList.tsx
@@ -0,0 +1,16 @@
+import { TodoItem } from './TodoItem';
+import { Todo } from '../types/Todo';
+
+type Props = {
+ visibleTodos: Todo[];
+};
+
+export const TodoList: React.FC = ({ visibleTodos }) => {
+ return (
+
+ {visibleTodos.map(todo => (
+
+ ))}
+
+ );
+};
diff --git a/src/context/TodosContex.tsx b/src/context/TodosContex.tsx
new file mode 100644
index 000000000..71047fdd4
--- /dev/null
+++ b/src/context/TodosContex.tsx
@@ -0,0 +1,32 @@
+import React, { createContext, useMemo, useState } from 'react';
+import { Todo } from '../types/Todo';
+
+type ContextProps = {
+ todos: Todo[];
+ setTodos: (todos: Todo[]) => void;
+};
+
+export const TodosContext = createContext({
+ todos: [],
+ setTodos: () => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const TodosProvider: React.FC = ({ children }) => {
+ const [todos, setTodos] = useState([]);
+
+ const value = useMemo(
+ () => ({
+ todos,
+ setTodos,
+ }),
+ [todos],
+ );
+
+ return (
+ {children}
+ );
+};
diff --git a/src/index.tsx b/src/index.tsx
index a9689cb38..8e38aa180 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,8 +1,9 @@
import { createRoot } from 'react-dom/client';
-import './styles/index.css';
-import './styles/todo-list.css';
-import './styles/filters.css';
+import './styles/index.scss';
+import './styles/todoapp.scss';
+import './styles/filter.scss';
+import './styles/todo.scss';
import { App } from './App';
diff --git a/src/styles/todoapp.scss b/src/styles/todoapp.scss
index e289a9458..2f9ee48e9 100644
--- a/src/styles/todoapp.scss
+++ b/src/styles/todoapp.scss
@@ -1,3 +1,7 @@
+* {
+ box-sizing: border-box;
+}
+
.todoapp {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 24px;
diff --git a/src/types/Filter.ts b/src/types/Filter.ts
new file mode 100644
index 000000000..174408fd6
--- /dev/null
+++ b/src/types/Filter.ts
@@ -0,0 +1,5 @@
+export enum Filter {
+ All = 'all',
+ Active = 'active',
+ Completed = 'completed',
+}
diff --git a/src/types/FuncType.ts b/src/types/FuncType.ts
new file mode 100644
index 000000000..74f3a0522
--- /dev/null
+++ b/src/types/FuncType.ts
@@ -0,0 +1,4 @@
+export enum FuncType {
+ update = 'update',
+ delete = 'delete',
+}
diff --git a/src/types/Todo.ts b/src/types/Todo.ts
new file mode 100644
index 000000000..f9e06b381
--- /dev/null
+++ b/src/types/Todo.ts
@@ -0,0 +1,5 @@
+export interface Todo {
+ id: number;
+ title: string;
+ completed: boolean;
+}