diff --git a/src/App.tsx b/src/App.tsx
index 5749bdf78..12c94cd58 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,24 +1,50 @@
-/* eslint-disable max-len */
-/* eslint-disable jsx-a11y/control-has-associated-label */
-import React from 'react';
-import { UserWarning } from './UserWarning';
-
-const USER_ID = 0;
+import React, { useEffect, useState } from 'react';
+import { TodoHeader } from './components/TodoHeader';
+import { TodoList } from './components/TodoList';
+import { TodoFooter } from './components/TodoFooter';
+import { TodoNotification } from './components/TodoNotification';
+import { useTodo } from './context/TodoContext';
+import { getTodos } from './api/todos';
+import { useError } from './context/ErrorContext';
+import { USER_ID } from './utils/variables';
export const App: React.FC = () => {
- if (!USER_ID) {
- return ;
- }
+ const { todos, setTodos } = useTodo();
+ const { setErrorMessage } = useError();
+
+ const [isActive, setIsActive] = useState(false);
+ const [isToggleActive, setIsToggleActive] = useState([]);
+
+ useEffect(() => {
+ getTodos(USER_ID)
+ .then(setTodos)
+ .catch(() => {
+ setErrorMessage('Unable to load todos');
+ });
+ }, []);
return (
-
+
+
todos
+
+
+
+
+
+
+ {Boolean(todos.length) && (
+
+ )}
+
+
+
+
);
};
diff --git a/src/UserWarning.tsx b/src/UserWarning.tsx
deleted file mode 100644
index db7dd16e3..000000000
--- a/src/UserWarning.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import React from 'react';
-
-export const UserWarning: React.FC = () => (
-
-
- Please get your
- {' '}
- userId
- {' '}
-
- here
-
- {' '}
- and save it in the app
- {' '}
-
const USER_ID = ...
-
- All requests to the API must be sent with this
- userId.
-
-
-);
diff --git a/src/api/todos.ts b/src/api/todos.ts
new file mode 100644
index 000000000..7624419fb
--- /dev/null
+++ b/src/api/todos.ts
@@ -0,0 +1,20 @@
+import { Todo } from '../types/Todo';
+import { client } from '../utils/fetchClient';
+
+export const getTodos = (userId: number) => {
+ return client.get(`/todos?userId=${userId}`);
+};
+
+export const deleteTodo = (todoId: number) => {
+ return client.delete(`/todos/${todoId}`);
+};
+
+export const createTodo = ({
+ userId, title, completed,
+}: Omit) => {
+ return client.post('/todos', { userId, title, completed });
+};
+
+export const updateTodo = (todoId: number, todo: Partial) => {
+ return client.patch(`/todos/${todoId}`, todo);
+};
diff --git a/src/components/TodoEditItem/TodoEditItem.tsx b/src/components/TodoEditItem/TodoEditItem.tsx
new file mode 100644
index 000000000..54ba86f73
--- /dev/null
+++ b/src/components/TodoEditItem/TodoEditItem.tsx
@@ -0,0 +1,130 @@
+import {
+ useEffect, useRef, useState,
+} from 'react';
+import classnames from 'classnames';
+import { Todo } from '../../types/Todo';
+import { useTodo } from '../../context/TodoContext';
+import { useError } from '../../context/ErrorContext';
+import { updateTodo } from '../../api/todos';
+
+type Props = {
+ todo: Todo;
+ onEditedId: () => void;
+ onDelete: (value: Todo) => void;
+ isLoading: boolean;
+ onLoad: (value: boolean) => void;
+};
+
+export const TodoEditItem: React.FC = ({
+ todo, onEditedId, onDelete, isLoading, onLoad,
+}) => {
+ const { id, completed } = todo;
+
+ const { todos, setTodos } = useTodo();
+ const { setErrorMessage } = useError();
+
+ const editInputRef = useRef(null);
+
+ useEffect(() => {
+ editInputRef.current?.focus();
+ }, []);
+
+ const [newTitle, setNewTitle] = useState(todo.title);
+
+ const updateNewTodo = () => {
+ if (!newTitle.trim()) {
+ onDelete(todo);
+
+ return;
+ }
+
+ const updatedTodos = todos.map((currentTodo) => {
+ if (currentTodo.id === id) {
+ return {
+ ...currentTodo,
+ title: newTitle,
+ };
+ }
+
+ return currentTodo;
+ });
+
+ const updatedTodo = updatedTodos.find(({ id: updatedId }) => {
+ return updatedId === id;
+ });
+
+ onLoad(true);
+
+ if (updatedTodo) {
+ updateTodo(todo.id, updatedTodo)
+ .then(() => setTodos(updatedTodos))
+ .catch(() => setErrorMessage('Unable to update a todo'))
+ .finally(() => {
+ onLoad(false);
+ onEditedId();
+ });
+ }
+ };
+
+ const handlePressEscape = (
+ event: React.KeyboardEvent,
+ ) => {
+ if (event.key === 'Escape') {
+ onEditedId();
+ }
+ };
+
+ const handleEditTodo = (
+ event: React.FormEvent
+ | React.FocusEvent,
+ ) => {
+ event.preventDefault();
+
+ if (todo.title === newTitle) {
+ onEditedId();
+
+ return;
+ }
+
+ updateNewTodo();
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/TodoEditItem/index.ts b/src/components/TodoEditItem/index.ts
new file mode 100644
index 000000000..d9de65de4
--- /dev/null
+++ b/src/components/TodoEditItem/index.ts
@@ -0,0 +1 @@
+export * from './TodoEditItem';
diff --git a/src/components/TodoFooter/TodoFooter.tsx b/src/components/TodoFooter/TodoFooter.tsx
new file mode 100644
index 000000000..09d050d9e
--- /dev/null
+++ b/src/components/TodoFooter/TodoFooter.tsx
@@ -0,0 +1,98 @@
+import { useMemo } from 'react';
+import classnames from 'classnames';
+import { Status } from '../../types/Status';
+import { useFilter } from '../../context/FilterContext';
+import { useTodo } from '../../context/TodoContext';
+import { deleteTodo } from '../../api/todos';
+import { useError } from '../../context/ErrorContext';
+
+export const TodoFooter = () => {
+ const { selectedFilter, setSelectedFilter } = useFilter();
+ const { todos, setTodos } = useTodo();
+ const { setErrorMessage } = useError();
+
+ const isActiveItemsLeft = todos
+ .filter(({ completed }) => !completed).length;
+
+ const hasCompletedTodo = useMemo(() => {
+ return todos.some(todo => todo.completed);
+ }, [todos]);
+
+ const clearCompleted = () => {
+ const deletePromises = todos
+ .filter((todo) => todo.completed)
+ .map(({ id }) => deleteTodo(id));
+
+ Promise.all(deletePromises)
+ .then(() => {
+ setTodos((prevState) => prevState.filter((todo) => !todo.completed));
+ })
+ .catch(() => {
+ setErrorMessage('Unable to delete a todo');
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/components/TodoFooter/index.ts b/src/components/TodoFooter/index.ts
new file mode 100644
index 000000000..544d07114
--- /dev/null
+++ b/src/components/TodoFooter/index.ts
@@ -0,0 +1 @@
+export * from './TodoFooter';
diff --git a/src/components/TodoHeader/TodoHeader.tsx b/src/components/TodoHeader/TodoHeader.tsx
new file mode 100644
index 000000000..8e7288303
--- /dev/null
+++ b/src/components/TodoHeader/TodoHeader.tsx
@@ -0,0 +1,138 @@
+import {
+ useEffect,
+ useRef,
+ useState,
+} from 'react';
+import classnames from 'classnames';
+
+import { useTodo } from '../../context/TodoContext';
+import { useError } from '../../context/ErrorContext';
+import { useTodoTemp } from '../../context/TodoTempContext';
+import { USER_ID } from '../../utils/variables';
+import { createTodo, updateTodo } from '../../api/todos';
+import { Todo } from '../../types/Todo';
+
+type Props = {
+ onHandleActive: (value: boolean) => void;
+ onToggleActive: (value: number[]) => void;
+};
+
+export const TodoHeader: React.FC = ({
+ onHandleActive, onToggleActive,
+}) => {
+ const { setTodoTemp } = useTodoTemp();
+ const [title, setTitle] = useState('');
+ const { todos, setTodos } = useTodo();
+ const { errorMessage, setErrorMessage } = useError();
+
+ const [isInputDisabled, setIsInputDisabled] = useState(false);
+
+ const inputRef = useRef(null);
+
+ const isActiveItemsLeft = todos.some(({ completed }) => !completed);
+
+ useEffect(() => {
+ inputRef?.current?.focus();
+ }, [todos, errorMessage]);
+
+ const toggleAll = async () => {
+ const isAllCompleted = todos.every(({ completed }) => completed);
+ const todosForUpdate = isAllCompleted
+ ? todos
+ : todos.filter(({ completed }) => !completed);
+ const todosIds = todosForUpdate.map(({ id }) => id);
+ const updatePromises = todos.map(todo => (
+ updateTodo(todo.id, { ...todo, completed: !isAllCompleted })
+ ));
+
+ onToggleActive(todosIds);
+
+ try {
+ await Promise.all(updatePromises);
+
+ setTodos(currentTodos => {
+ return currentTodos.map(todo => ({
+ ...todo,
+ completed: !isAllCompleted,
+ }));
+ });
+
+ onToggleActive([]);
+ } catch (error) {
+ setErrorMessage(`Error updating todos: ${error}`);
+ }
+ };
+
+ const handleSubmit = (event: React.FormEvent) => {
+ event.preventDefault();
+
+ if (!title.trim()) {
+ setErrorMessage('Title should not be empty');
+ inputRef?.current?.focus();
+
+ return;
+ }
+
+ const newTodo = {
+ userId: USER_ID,
+ title: title.trim(),
+ completed: false,
+ };
+
+ setTodoTemp({
+ ...newTodo,
+ id: 0,
+ });
+
+ setIsInputDisabled(true);
+ onHandleActive(true);
+
+ createTodo(newTodo)
+ .then((response) => {
+ setTodos((prevTodos: Todo[]) => [...prevTodos, response]);
+ setIsInputDisabled(false);
+ setTitle('');
+ })
+ .catch(() => {
+ setIsInputDisabled(false);
+ setErrorMessage('Unable to add a todo');
+ })
+ .finally(() => {
+ setTodoTemp(null);
+ inputRef?.current?.focus();
+ onHandleActive(false);
+ });
+ };
+
+ return (
+
+ {!!todos.length && (
+
+ )}
+
+
+
+ );
+};
diff --git a/src/components/TodoHeader/index.ts b/src/components/TodoHeader/index.ts
new file mode 100644
index 000000000..c4db4bc40
--- /dev/null
+++ b/src/components/TodoHeader/index.ts
@@ -0,0 +1 @@
+export * from './TodoHeader';
diff --git a/src/components/TodoItem/TodoItem.tsx b/src/components/TodoItem/TodoItem.tsx
new file mode 100644
index 000000000..c3ef4ca47
--- /dev/null
+++ b/src/components/TodoItem/TodoItem.tsx
@@ -0,0 +1,109 @@
+import { useState } from 'react';
+import classnames from 'classnames';
+import { Todo } from '../../types/Todo';
+import { useTodo } from '../../context/TodoContext';
+import { updateTodo } from '../../api/todos';
+import { useError } from '../../context/ErrorContext';
+
+type Props = {
+ todo: Todo;
+ onDelete?: (value: Todo) => void;
+ onEditedId?: (value: number | null) => void;
+ isDeleteActive?: boolean;
+ onDeleteActive?: (value: boolean) => void;
+ isLoading?: boolean;
+ isToggleActive?: number[];
+};
+
+export const TodoItem: React.FC = ({
+ todo,
+ onDelete = () => {},
+ onEditedId = () => {},
+ isDeleteActive,
+ isLoading,
+ isToggleActive,
+}) => {
+ const { setTodos } = useTodo();
+ const { setErrorMessage } = useError();
+ const { id, title, completed } = todo;
+
+ const [deletedTodoId, setDeletedTodoId] = useState(null);
+
+ const [isCompleteActive, setIsCompleteActive] = useState(false);
+
+ const handleComplete = (
+ event: React.ChangeEvent,
+ ) => {
+ const updatedTodo = { ...todo, completed: event.target.checked };
+
+ setIsCompleteActive(true);
+
+ updateTodo(todo.id, updatedTodo)
+ .then(() => {
+ setTodos((prevTodo) => prevTodo.map((currentTodo) => {
+ return currentTodo.id === todo.id ? updatedTodo : currentTodo;
+ }));
+ })
+ .catch(() => setErrorMessage('Unable to update todo'))
+ .finally(() => setIsCompleteActive(false));
+ };
+
+ const handleDelete = () => {
+ setDeletedTodoId(todo.id);
+ onDelete(todo);
+ };
+
+ const isActiveLoader = (isDeleteActive && id === deletedTodoId)
+ || isToggleActive?.includes(id) || isCompleteActive || isLoading;
+
+ return (
+
+
+
+
onEditedId(id)}
+ >
+ {title}
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/TodoItem/index.ts b/src/components/TodoItem/index.ts
new file mode 100644
index 000000000..21f4abac3
--- /dev/null
+++ b/src/components/TodoItem/index.ts
@@ -0,0 +1 @@
+export * from './TodoItem';
diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx
new file mode 100644
index 000000000..3923f2615
--- /dev/null
+++ b/src/components/TodoList/TodoList.tsx
@@ -0,0 +1,81 @@
+import { useState } from 'react';
+
+import { getFilteredTodos } from '../../utils/utils';
+import { TodoItem } from '../TodoItem';
+import { useTodo } from '../../context/TodoContext';
+import { useFilter } from '../../context/FilterContext';
+import { useTodoTemp } from '../../context/TodoTempContext';
+import { deleteTodo } from '../../api/todos';
+import { useError } from '../../context/ErrorContext';
+import { TodoEditItem } from '../TodoEditItem';
+import { Todo } from '../../types/Todo';
+
+type Props = {
+ isActive: boolean;
+ onHandleActive?: (value: boolean) => void;
+ isToggleActive: number[];
+};
+
+export const TodoList: React.FC = ({
+ isActive, isToggleActive,
+}) => {
+ const { todos, setTodos } = useTodo();
+ const { selectedFilter } = useFilter();
+ const { todoTemp } = useTodoTemp();
+ const { setErrorMessage } = useError();
+
+ const filteredTodos = getFilteredTodos(todos, selectedFilter);
+
+ const [isLoading, setIsLoading] = useState(false);
+ const [editedId, setEditedId] = useState(null);
+ const [isDeleteActive, setIsDeleteActive] = useState(false);
+
+ const handleDeleteTodo = (todo: Todo) => {
+ setIsLoading(true);
+ setIsDeleteActive(true);
+
+ deleteTodo(todo.id)
+ .then(() => {
+ setTodos(todos.filter(({ id }) => id !== todo.id));
+ })
+ .catch(() => {
+ setErrorMessage('Unable to delete a todo');
+ })
+ .finally(() => {
+ setIsLoading(false);
+ setIsDeleteActive(false);
+ });
+ };
+
+ return (
+
+ {filteredTodos.map(todo => (
+ editedId === todo.id ? (
+ setEditedId(null)}
+ onDelete={handleDeleteTodo}
+ isLoading={isLoading}
+ onLoad={setIsLoading}
+ />
+ ) : (
+
+ )
+ ))}
+
+ {todoTemp && (
+
+ )}
+
+ );
+};
diff --git a/src/components/TodoList/index.ts b/src/components/TodoList/index.ts
new file mode 100644
index 000000000..f239f4345
--- /dev/null
+++ b/src/components/TodoList/index.ts
@@ -0,0 +1 @@
+export * from './TodoList';
diff --git a/src/components/TodoNotification/TodoNotification.tsx b/src/components/TodoNotification/TodoNotification.tsx
new file mode 100644
index 000000000..1b563cc83
--- /dev/null
+++ b/src/components/TodoNotification/TodoNotification.tsx
@@ -0,0 +1,42 @@
+import classnames from 'classnames';
+import { useEffect } from 'react';
+import { useError } from '../../context/ErrorContext';
+
+export const TodoNotification: React.FC = () => {
+ const {
+ errorMessage,
+ isErrorHidden,
+ setIsErrorHidden,
+ } = useError();
+
+ useEffect(() => {
+ if (errorMessage.length) {
+ setIsErrorHidden(false);
+ }
+
+ setTimeout(() => {
+ setIsErrorHidden(true);
+ }, 3000);
+ }, [errorMessage]);
+
+ return (
+
+
+ );
+};
diff --git a/src/components/TodoNotification/index.ts b/src/components/TodoNotification/index.ts
new file mode 100644
index 000000000..f29aba7e0
--- /dev/null
+++ b/src/components/TodoNotification/index.ts
@@ -0,0 +1 @@
+export * from './TodoNotification';
diff --git a/src/context/ErrorContext.tsx b/src/context/ErrorContext.tsx
new file mode 100644
index 000000000..b5a6b0ead
--- /dev/null
+++ b/src/context/ErrorContext.tsx
@@ -0,0 +1,43 @@
+import {
+ useState, createContext, useContext,
+} from 'react';
+
+interface ErrorContextType {
+ errorMessage: string;
+ setErrorMessage: React.Dispatch>;
+ isErrorHidden: boolean;
+ setIsErrorHidden: React.Dispatch>;
+}
+
+export const ErrorContext = createContext({
+ errorMessage: '',
+ setErrorMessage: () => {},
+ isErrorHidden: true,
+ setIsErrorHidden: () => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const ErrorProvider: React.FC = ({ children }) => {
+ const [errorMessage, setErrorMessage] = useState('');
+ const [isErrorHidden, setIsErrorHidden] = useState(true);
+
+ const value = {
+ errorMessage,
+ setErrorMessage,
+ isErrorHidden,
+ setIsErrorHidden,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useError = (): ErrorContextType => {
+ return useContext(ErrorContext);
+};
diff --git a/src/context/FilterContext.tsx b/src/context/FilterContext.tsx
new file mode 100644
index 000000000..962be3892
--- /dev/null
+++ b/src/context/FilterContext.tsx
@@ -0,0 +1,35 @@
+import { useState, createContext, useContext } from 'react';
+import { Status } from '../types/Status';
+
+interface FilterContextType {
+ selectedFilter: Status;
+ setSelectedFilter: React.Dispatch>;
+}
+
+export const FilterContext = createContext({
+ selectedFilter: Status.All,
+ setSelectedFilter: () => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const FilterProvider: React.FC = ({ children }) => {
+ const [selectedFilter, setSelectedFilter] = useState(Status.All);
+
+ const value = {
+ selectedFilter,
+ setSelectedFilter,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useFilter = (): FilterContextType => {
+ return useContext(FilterContext);
+};
diff --git a/src/context/TodoContext.tsx b/src/context/TodoContext.tsx
new file mode 100644
index 000000000..2fcb714c7
--- /dev/null
+++ b/src/context/TodoContext.tsx
@@ -0,0 +1,37 @@
+import {
+ useState, createContext, useContext,
+} from 'react';
+import { Todo } from '../types/Todo';
+
+interface TodoContextType {
+ todos: Todo[];
+ setTodos: React.Dispatch>;
+}
+
+export const TodoContext = createContext({
+ todos: [],
+ setTodos: () => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const TodoProvider: React.FC = ({ children }) => {
+ const [todos, setTodos] = useState([]);
+
+ const value = {
+ todos,
+ setTodos,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTodo = (): TodoContextType => {
+ return useContext(TodoContext);
+};
diff --git a/src/context/TodoTempContext.tsx b/src/context/TodoTempContext.tsx
new file mode 100644
index 000000000..7b03a0c4a
--- /dev/null
+++ b/src/context/TodoTempContext.tsx
@@ -0,0 +1,37 @@
+import {
+ useState, createContext, useContext,
+} from 'react';
+import { Todo } from '../types/Todo';
+
+interface TodoTempContextType {
+ todoTemp: Todo | null;
+ setTodoTemp: React.Dispatch>;
+}
+
+export const TodoTempContext = createContext({
+ todoTemp: null,
+ setTodoTemp: () => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const TodoTempProvider: React.FC = ({ children }) => {
+ const [todoTemp, setTodoTemp] = useState(null);
+
+ const value = {
+ todoTemp,
+ setTodoTemp,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useTodoTemp = (): TodoTempContextType => {
+ return useContext(TodoTempContext);
+};
diff --git a/src/index.tsx b/src/index.tsx
index 7de19e0c7..5dce46795 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -5,6 +5,20 @@ import '@fortawesome/fontawesome-free/css/all.css';
import './styles/index.scss';
import { App } from './App';
+import { TodoProvider } from './context/TodoContext';
+import { FilterProvider } from './context/FilterContext';
+import { ErrorProvider } from './context/ErrorContext';
+import { TodoTempProvider } from './context/TodoTempContext';
createRoot(document.getElementById('root') as HTMLDivElement)
- .render();
+ .render(
+
+
+
+
+
+
+
+
+ ,
+ );
diff --git a/src/styles/todoapp.scss b/src/styles/todoapp.scss
index 836166156..2db666227 100644
--- a/src/styles/todoapp.scss
+++ b/src/styles/todoapp.scss
@@ -129,5 +129,9 @@
&:active {
text-decoration: none;
}
+
+ &:disabled {
+ visibility: hidden;
+ }
}
}
diff --git a/src/types/Status.ts b/src/types/Status.ts
new file mode 100644
index 000000000..2ec4b8714
--- /dev/null
+++ b/src/types/Status.ts
@@ -0,0 +1,5 @@
+export enum Status {
+ All = 'all',
+ Active = 'active',
+ Completed = 'completed',
+}
diff --git a/src/types/Todo.ts b/src/types/Todo.ts
new file mode 100644
index 000000000..3f52a5fdd
--- /dev/null
+++ b/src/types/Todo.ts
@@ -0,0 +1,6 @@
+export interface Todo {
+ id: number;
+ userId: number;
+ title: string;
+ completed: boolean;
+}
diff --git a/src/utils/fetchClient.ts b/src/utils/fetchClient.ts
new file mode 100644
index 000000000..ccf6f6cb6
--- /dev/null
+++ b/src/utils/fetchClient.ts
@@ -0,0 +1,47 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+const BASE_URL = 'https://mate.academy/students-api';
+// https://mate.academy/students-api/todos?userId=11458
+
+// returns a promise resolved after a given delay
+function wait(delay: number) {
+ return new Promise(resolve => {
+ setTimeout(resolve, delay);
+ });
+}
+
+// To have autocompletion and avoid mistypes
+type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
+
+function request(
+ url: string,
+ method: RequestMethod = 'GET',
+ data: any = null, // we can send any data to the server
+): Promise {
+ const options: RequestInit = { method };
+
+ if (data) {
+ // We add body and Content-Type only for the requests with data
+ options.body = JSON.stringify(data);
+ options.headers = {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ };
+ }
+
+ // we wait for testing purpose to see loaders
+ return wait(300)
+ .then(() => fetch(BASE_URL + url, options))
+ .then(response => {
+ if (!response.ok) {
+ throw new Error();
+ }
+
+ return response.json();
+ });
+}
+
+export const client = {
+ get: (url: string) => request(url),
+ post: (url: string, data: any) => request(url, 'POST', data),
+ patch: (url: string, data: any) => request(url, 'PATCH', data),
+ delete: (url: string) => request(url, 'DELETE'),
+};
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
new file mode 100644
index 000000000..096baeed7
--- /dev/null
+++ b/src/utils/utils.ts
@@ -0,0 +1,19 @@
+import { Status } from '../types/Status';
+import { Todo } from '../types/Todo';
+
+export const getFilteredTodos = (todos: Todo[], selectedFilter: Status) => {
+ const todosCopy = [...todos];
+
+ if (selectedFilter) {
+ switch (selectedFilter) {
+ case Status.Active:
+ return todosCopy.filter(({ completed }) => !completed);
+ case Status.Completed:
+ return todosCopy.filter(({ completed }) => completed);
+ default:
+ return todosCopy;
+ }
+ }
+
+ return todosCopy;
+};
diff --git a/src/utils/variables.ts b/src/utils/variables.ts
new file mode 100644
index 000000000..3dbfce90f
--- /dev/null
+++ b/src/utils/variables.ts
@@ -0,0 +1 @@
+export const USER_ID = 11458;