diff --git a/README.md b/README.md index da0116e63..297a8221c 100644 --- a/README.md +++ b/README.md @@ -70,4 +70,4 @@ Filter todos by status `All` / `Active` / `Completed`: - Install Prettier Extention and use this [VSCode settings](https://mate-academy.github.io/fe-program/tools/vscode/settings.json) to enable format on save. - Implement a solution following the [React task guideline](https://github.com/mate-academy/react_task-guideline#react-tasks-guideline). - Use the [React TypeScript cheat sheet](https://mate-academy.github.io/fe-program/js/extra/react-typescript). -- Replace `` with your Github username in the [DEMO LINK](https://.github.io/react_todo-app-loading-todos/) and add it to the PR description. +- Replace `` with your Github username in the [DEMO LINK](https://droopy-bit.github.io/react_todo-app-loading-todos/) and add it to the PR description. diff --git a/package-lock.json b/package-lock.json index 7c15974bf..322b43c0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1183,10 +1183,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index 3a362ba82..2a26e11a6 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/src/App.tsx b/src/App.tsx index bf9c75980..a508bdad1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,44 @@ /* eslint-disable jsx-a11y/label-has-associated-control */ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { UserWarning } from './UserWarning'; -import { USER_ID } from './api/todos'; +import { USER_ID, getTodos } from './api/todos'; +import { Todo } from './types/Todo'; +import { ErrorNotification } from './components/ErrorNotification'; +import { TodoList } from './components/TodoList'; +import { FilterStatus } from './types/FilterStatus'; +import { TodoFilter } from './components/TodoFilter'; + +enum ErrorMessage { + Load = 'Unable to load todos', +} export const App: React.FC = () => { + const [todos, setTodos] = useState([]); + const [errorMessage, setErrorMessage] = useState(''); + const [filter, setFilter] = useState(FilterStatus.All); + + useEffect(() => { + getTodos() + .then(setTodos) + .catch(() => { + setErrorMessage(ErrorMessage.Load); + setTimeout(() => setErrorMessage(''), 3000); + }); + }, []); + + const visibleTodos = todos.filter(todo => { + if (filter === FilterStatus.Active) { + return !todo.completed; + } else if (filter === FilterStatus.Completed) { + return todo.completed; + } + + return true; + }); + + const activeTodosCount = todos.filter(todo => !todo.completed).length; + if (!USER_ID) { return ; } @@ -32,173 +66,36 @@ export const App: React.FC = () => { /> - -
- {/* This is a completed todo */} -
- - - - Completed Todo - - - {/* Remove button appears only on hover */} - - - {/* overlay will cover the todo while it is being deleted or updated */} -
-
-
-
-
- - {/* This todo is an active todo */} -
- - - - Not Completed Todo - - - -
-
-
-
-
- - {/* This todo is being edited */} -
- - - {/* This form is shown instead of the title and remove button */} -
- -
- -
-
-
-
-
- - {/* This todo is in loadind state */} -
- - - - Todo is being saved now - - - - - {/* 'is-active' class puts this modal on top of the todo */} -
-
-
-
-
-
+ {todos.length > 0 && } {/* Hide the footer if there are no todos */} -
- - 3 items left - - - {/* Active link should have the 'selected' class */} - - - {/* this button should be disabled if there are no completed todos */} - -
+ Clear completed + + + )} {/* DON'T use conditional rendering to hide the notification */} + setErrorMessage('')} + /> {/* Add the 'hidden' class to hide the message smoothly */} -
-
); }; diff --git a/src/api/todos.ts b/src/api/todos.ts index 8a0b90aa0..af158f08f 100644 --- a/src/api/todos.ts +++ b/src/api/todos.ts @@ -1,7 +1,7 @@ import { Todo } from '../types/Todo'; import { client } from '../utils/fetchClient'; -export const USER_ID = 0; +export const USER_ID = 4354; export const getTodos = () => { return client.get(`/todos?userId=${USER_ID}`); diff --git a/src/components/ErrorNotification.tsx b/src/components/ErrorNotification.tsx new file mode 100644 index 000000000..29a8e8bc6 --- /dev/null +++ b/src/components/ErrorNotification.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +type Props = { + message: string; + onClose: () => void; +}; +export const ErrorNotification: React.FC = ({ message, onClose }) => ( +
+
+); diff --git a/src/components/TodoFilter.tsx b/src/components/TodoFilter.tsx new file mode 100644 index 000000000..46e8f2c9d --- /dev/null +++ b/src/components/TodoFilter.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { FilterStatus } from '../types/FilterStatus'; +type Props = { + filter: FilterStatus; + onFilterChange: (status: FilterStatus) => void; +}; +export const TodoFilter = ({ filter, onFilterChange }: Props) => ( + +); diff --git a/src/components/TodoList.tsx b/src/components/TodoList.tsx new file mode 100644 index 000000000..9bc69f925 --- /dev/null +++ b/src/components/TodoList.tsx @@ -0,0 +1,38 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +import React from 'react'; +import { Todo } from '../types/Todo'; +type Props = { + todos: Todo[]; +}; + +export const TodoList: React.FC = ({ todos }) => ( +
+ {todos.map(todo => ( +
+ + + {todo.title} + + + +
+
+
+
+
+ ))} +
+); diff --git a/src/types/FilterStatus.ts b/src/types/FilterStatus.ts new file mode 100644 index 000000000..13d1a2739 --- /dev/null +++ b/src/types/FilterStatus.ts @@ -0,0 +1,5 @@ +export enum FilterStatus { + All = 'all', + Active = 'active', + Completed = 'completed', +}