From e0f64c1f1c84645513be346e4bfc222c6cd99533 Mon Sep 17 00:00:00 2001 From: Mariana Trinko Date: Thu, 11 Jul 2024 16:19:50 +0200 Subject: [PATCH 01/19] check --- src/App.tsx | 191 +++++++++++++++++++++-- src/api/todos.ts | 32 ++++ src/components/TodoFooter/TodoFooter.tsx | 76 +++++++++ src/components/TodoForm/TodoForm.tsx | 116 ++++++++++++++ src/components/TodoItem/TodoItem.tsx | 131 ++++++++++++++++ src/components/TodoList/TodoList.tsx | 32 ++++ src/types/Status.ts | 5 + src/types/Todo.ts | 6 + src/utils/fetchClient.ts | 46 ++++++ 9 files changed, 623 insertions(+), 12 deletions(-) create mode 100644 src/api/todos.ts create mode 100644 src/components/TodoFooter/TodoFooter.tsx create mode 100644 src/components/TodoForm/TodoForm.tsx create mode 100644 src/components/TodoItem/TodoItem.tsx create mode 100644 src/components/TodoList/TodoList.tsx create mode 100644 src/types/Status.ts create mode 100644 src/types/Todo.ts create mode 100644 src/utils/fetchClient.ts diff --git a/src/App.tsx b/src/App.tsx index 81e011f43..ce5f949dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,193 @@ /* eslint-disable max-len */ /* eslint-disable jsx-a11y/control-has-associated-label */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { UserWarning } from './UserWarning'; +import { USER_ID } from './api/todos'; +import classNames from 'classnames'; +import { Todo } from './types/Todo'; +import * as todosFromServer from './api/todos'; +import { wait } from './utils/fetchClient'; +import { TodoForm } from './components/TodoForm/TodoForm'; +import { TodoList } from './components/TodoList/TodoList'; +import { TodoFooter } from './components/TodoFooter/TodoFooter'; +import { Status } from './types/Status'; +import { TodoItem } from './components/TodoItem/TodoItem'; -const USER_ID = 0; +const getTodosByStatus = (status: string, todos: Todo[]) => { + const preperedTodos = [...todos]; + + if (status) { + switch (status) { + case Object.keys(Status)[Object.values(Status).indexOf(Status.active)]: + return preperedTodos.filter(todo => !todo.completed); + case Object.keys(Status)[Object.values(Status).indexOf(Status.completed)]: + return preperedTodos.filter(todo => todo.completed); + default: + return preperedTodos; + } + } + + return preperedTodos; +}; export const App: React.FC = () => { + const [titleError, setTitleError] = useState(false); + const [todos, setTodos] = useState([]); + const [loadError, setLoadError] = useState(false); + const [addError, setAddError] = useState(false); + const [deleteError, setDeleteError] = useState(false); + const [updateError, setUpdateError] = useState(false); + const [toggleError, setToggleError] = useState(false); + const [status, setStatus] = useState('all'); + const [tempTodo, setTempTodo] = useState(null); + // const [currentTodo, setCurrentTodo] = useState(null); + + const filteredTodos = getTodosByStatus(status, todos); + + async function addTodo(newTodoTitle: string) { + const editedTitle = newTodoTitle.trim(); + + if (!editedTitle) { + setTitleError(true); + wait(3000).then(() => setTitleError(false)); + + return; + } else { + setTempTodo({ + id: 0, + userId: 839, + title: editedTitle, + completed: false, + }); + + return todosFromServer + .createTodos({ + userId: 839, + title: editedTitle, + completed: false, + }) + .then(newTodo => { + setTodos(prevTodos => [...prevTodos, newTodo]); + setTempTodo(null); + }) + .catch(error => { + setAddError(true); + setTempTodo(null); + wait(3000).then(() => setAddError(false)); + throw error; + }); + } + } + + const updateTodo = (updatedTodo: Todo) => { + todosFromServer + .updateTodos(updatedTodo) + .then((todo: Todo) => + setTodos(currentTodos => { + const newTodos = [...currentTodos]; + const index = newTodos.findIndex( + thisTodo => thisTodo.id === updatedTodo.id, + ); + + newTodos.splice(index, 1, todo); + + return newTodos; + }), + ) + .catch(() => { + setUpdateError(true); + wait(3000).then(() => setUpdateError(false)); + }); + }; + + const deleteTodo = (paramTodo: Todo) => { + todosFromServer + .deleteTodos(paramTodo.id) + .then(() => + setTodos(prevTodos => + prevTodos.filter(todo => todo.id !== paramTodo.id), + ), + ) + .catch(() => { + setDeleteError(true); + wait(3000).then(() => setDeleteError(false)); + }); + }; + + useEffect(() => { + todosFromServer + .getTodos() + .then(setTodos) + .catch(() => setLoadError(true)); + wait(3000).then(() => setLoadError(false)); + }, []); + if (!USER_ID) { return ; } + const deleteCompletedTodos = (paramTodos: Todo[]) => { + paramTodos.forEach(todo => deleteTodo(todo)); + }; + return ( -
-

- Copy all you need from the prev task: -
- - React Todo App - Add and Delete - -

+
+

todos

+ +
+
+ {/* Add a todo on form submit */} + +
+ + -

Styles are already copied

-
+ {tempTodo && } + + {!!todos.length && ( + // {/* Hide the footer if there are no todos */} + + )} + + + {/* {error && ( */} + {/* DON'T use conditional rendering to hide the notification */} + {/* Add the 'hidden' class to hide the message smoothly */} +
+
+ ); }; diff --git a/src/api/todos.ts b/src/api/todos.ts new file mode 100644 index 000000000..da18596f6 --- /dev/null +++ b/src/api/todos.ts @@ -0,0 +1,32 @@ +import { Todo } from '../types/Todo'; +import { client } from '../utils/fetchClient'; + +export const USER_ID = 839; +//https://mate.academy/students-api/todos?userId=839 + +export const getTodos = () => { + return client.get(`/todos?userId=${USER_ID}`); +}; + +// Add more methods here + +export const createTodos = ({ userId, title, completed }: Omit) => { + return client.post(`/todos`, { + userId, + title, + completed, + }); +}; + +export const updateTodos = ({ id, userId, title, completed }: Todo) => { + return client.patch(`/todos/${id}`, { + id, + userId, + title, + completed, + }); +}; + +export const deleteTodos = (todoId: number) => { + return client.delete(`/todos/${todoId}`); +}; diff --git a/src/components/TodoFooter/TodoFooter.tsx b/src/components/TodoFooter/TodoFooter.tsx new file mode 100644 index 000000000..1b6027408 --- /dev/null +++ b/src/components/TodoFooter/TodoFooter.tsx @@ -0,0 +1,76 @@ +import classNames from 'classnames'; +import { Todo } from '../../types/Todo'; +import { Status } from '../../types/Status'; + +type Props = { + todos: Todo[]; + setStatus: (status: string) => void; + status: string; + deleteCompletedTodos: (todos: Todo[]) => void; +}; + +const getDataCYByStatus = (status: string) => { + switch (status) { + case Object.keys(Status)[Object.values(Status).indexOf(Status.active)]: + return 'FilterLinkActive'; + case Object.keys(Status)[Object.values(Status).indexOf(Status.completed)]: + return 'FilterLinkCompleted'; + default: + return 'FilterLinkAll'; + } +}; + +export const TodoFooter: React.FC = ({ + todos, + setStatus, + status, + deleteCompletedTodos, +}) => { + const itemsLeft = todos.filter(todo => !todo.completed).length; + const handleTodosStatus = (currentStatus: string) => { + setStatus(currentStatus); + }; + + const isOneComplited = + todos.filter(todo => todo.completed).length > 0 ? false : true; + + const handleDeleteAllCompleted = () => { + deleteCompletedTodos(todos.filter(t => t.completed === true)); + }; + + return ( +
+ + {itemsLeft} items left + + + {/* Active link should have the 'selected' class */} + + + {/* this button should be disabled if there are no completed todos */} + +
+ ); +}; diff --git a/src/components/TodoForm/TodoForm.tsx b/src/components/TodoForm/TodoForm.tsx new file mode 100644 index 000000000..ccc3fb5dc --- /dev/null +++ b/src/components/TodoForm/TodoForm.tsx @@ -0,0 +1,116 @@ +/* eslint-disable no-param-reassign */ +import { ChangeEvent, FormEvent, useEffect, useRef, useState } from 'react'; +import { wait } from '../../utils/fetchClient'; +import classNames from 'classnames'; +import { Todo } from '../../types/Todo'; + +type Props = { + todos: Todo[]; + onSubmit: (title: string) => Promise; + setTitleError: (value: boolean) => void; + updateTodo: (todo: Todo) => void; + setToggleError: (toggle: boolean) => void; +}; + +export const TodoForm: React.FC = ({ + onSubmit, + setTitleError, + todos, + updateTodo, + setToggleError, +}) => { + const [title, setTitle] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + const titleField = useRef(null); + const [trig, setTrig] = useState(false); + + useEffect(() => { + titleField.current?.focus(); + }, [title, isSubmitting, onSubmit]); + + const handleTitleChange = (event: ChangeEvent) => { + setTitle(event.target.value); + }; + + const handleFormSubmit = (event: FormEvent) => { + event.preventDefault(); + if (!title.trim()) { + setTitleError(true); + wait(3000).then(() => setTitleError(false)); + } + + if (title.trim()) { + setIsSubmitting(true); + onSubmit(title.trim()) + .then(() => { + setTitle(''); + }) + .catch(() => setTitle(title)) + .finally(() => setIsSubmitting(false)); + } + }; + + const handleToggleAllTodo = () => { + if (!trig) { + setToggleError(true); + + todos.forEach(currentTodo => { + const newTodo = { + ...currentTodo, + completed: (currentTodo.completed = true), + }; + + updateTodo(newTodo); + }); + + setToggleError(false); + + setTrig(true); + } + + if (trig) { + setToggleError(true); + + setToggleError(true); + + todos.forEach(currentTodo => { + setToggleError(true); + + const newTodo = { + ...currentTodo, + completed: (currentTodo.completed = false), + }; + + updateTodo(newTodo); + setToggleError(false); + }); + + setTrig(false); + } + }; + + return ( + <> + {/* this button should have `active` class only if all todos are completed */} + + + ) : ( + // {/* This form is shown instead of the title and remove button */} +
+ +
+ )} + + {/* overlay will cover the todo while it is being deleted or updated */} +
+
+
+
+
+ ); +}; diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx new file mode 100644 index 000000000..d2941367a --- /dev/null +++ b/src/components/TodoList/TodoList.tsx @@ -0,0 +1,32 @@ +import { Todo } from '../../types/Todo'; +import { TodoItem } from '../TodoItem/TodoItem'; + +type Props = { + todos: Todo[]; + updateTodo: (todo: Todo) => void; + deletTodo: (todo: Todo) => void; + toggleError?: boolean; +}; + +export const TodoList: React.FC = ({ + todos, + updateTodo, + deletTodo, + toggleError, +}) => { + return ( + <> + {/* This is a completed todo */} + {todos.map(todo => ( +
+ +
+ ))} + + ); +}; diff --git a/src/types/Status.ts b/src/types/Status.ts new file mode 100644 index 000000000..1e0bd526c --- /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..129df59d1 --- /dev/null +++ b/src/utils/fetchClient.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +const BASE_URL = 'https://mate.academy/students-api'; + +// returns a promise resolved after a given delay +export 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', + }; + } + + // DON'T change the delay it is required for tests + return wait(100) + .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'), +}; From 1a3cdc80eb22cc610964bb18cb55a85357ff5b3f Mon Sep 17 00:00:00 2001 From: Mariana Trinko Date: Fri, 12 Jul 2024 14:27:36 +0200 Subject: [PATCH 02/19] message --- src/App.tsx | 16 +++++++++++----- src/components/TodoForm/TodoForm.tsx | 23 ++++++----------------- src/components/TodoItem/TodoItem.tsx | 15 +++++++-------- src/components/TodoList/TodoList.tsx | 9 ++++++--- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ce5f949dc..109b58979 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -37,10 +37,13 @@ export const App: React.FC = () => { const [addError, setAddError] = useState(false); const [deleteError, setDeleteError] = useState(false); const [updateError, setUpdateError] = useState(false); - const [toggleError, setToggleError] = useState(false); const [status, setStatus] = useState('all'); const [tempTodo, setTempTodo] = useState(null); - // const [currentTodo, setCurrentTodo] = useState(null); + const [tempArray, setTempArray] = useState([]); + + const temp = (currentTodo: Todo) => { + setTempArray(prevArray => [...prevArray, currentTodo]); + }; const filteredTodos = getTodosByStatus(status, todos); @@ -142,7 +145,7 @@ export const App: React.FC = () => { setTitleError={setTitleError} todos={todos} updateTodo={updateTodo} - setToggleError={setToggleError} + setTempArray={temp} /> @@ -150,10 +153,13 @@ export const App: React.FC = () => { todos={filteredTodos} updateTodo={updateTodo} deletTodo={deleteTodo} - toggleError={toggleError} + array={tempArray} + setTempArray={temp} /> - {tempTodo && } + {tempTodo && ( + + )} {!!todos.length && ( // {/* Hide the footer if there are no todos */} diff --git a/src/components/TodoForm/TodoForm.tsx b/src/components/TodoForm/TodoForm.tsx index ccc3fb5dc..1d8de6d00 100644 --- a/src/components/TodoForm/TodoForm.tsx +++ b/src/components/TodoForm/TodoForm.tsx @@ -9,7 +9,7 @@ type Props = { onSubmit: (title: string) => Promise; setTitleError: (value: boolean) => void; updateTodo: (todo: Todo) => void; - setToggleError: (toggle: boolean) => void; + setTempArray: (todo: Todo) => void; }; export const TodoForm: React.FC = ({ @@ -17,12 +17,12 @@ export const TodoForm: React.FC = ({ setTitleError, todos, updateTodo, - setToggleError, + setTempArray, }) => { const [title, setTitle] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const titleField = useRef(null); - const [trig, setTrig] = useState(false); + const trig = todos.every(({ completed }) => completed); useEffect(() => { titleField.current?.focus(); @@ -52,9 +52,9 @@ export const TodoForm: React.FC = ({ const handleToggleAllTodo = () => { if (!trig) { - setToggleError(true); - todos.forEach(currentTodo => { + setTempArray(currentTodo); + const newTodo = { ...currentTodo, completed: (currentTodo.completed = true), @@ -62,19 +62,11 @@ export const TodoForm: React.FC = ({ updateTodo(newTodo); }); - - setToggleError(false); - - setTrig(true); } if (trig) { - setToggleError(true); - - setToggleError(true); - todos.forEach(currentTodo => { - setToggleError(true); + setTempArray(currentTodo); const newTodo = { ...currentTodo, @@ -82,10 +74,7 @@ export const TodoForm: React.FC = ({ }; updateTodo(newTodo); - setToggleError(false); }); - - setTrig(false); } }; diff --git a/src/components/TodoItem/TodoItem.tsx b/src/components/TodoItem/TodoItem.tsx index 1fa84dfec..697b4f742 100644 --- a/src/components/TodoItem/TodoItem.tsx +++ b/src/components/TodoItem/TodoItem.tsx @@ -7,19 +7,19 @@ type Props = { todo: Todo; updateTodo?: (todo: Todo) => void; deletTodo?: (todo: Todo) => void; - toggleError?: boolean; + array: Todo[]; + setTempArray: (todo: Todo) => void; }; export const TodoItem: React.FC = ({ todo, updateTodo = () => {}, deletTodo = () => {}, - toggleError, + array, + setTempArray, }) => { - const [currentTodo, setCurrentTodo] = useState(null); const [isEdited, setIsEdited] = useState(false); const [tempTitle, setTempTitle] = useState(todo.title); - const [isSubmitting, setIsSubmitting] = useState(false); const handleIsCompleted = (paramTodo: Todo) => { const newTodo = { ...paramTodo, completed: !paramTodo.completed }; @@ -28,7 +28,7 @@ export const TodoItem: React.FC = ({ }; const handleDeleteButton = () => { - setCurrentTodo(todo); + setTempArray(todo); deletTodo(todo); }; @@ -47,14 +47,13 @@ export const TodoItem: React.FC = ({ setIsEdited(false); } - setIsSubmitting(true); + // setIsSubmitting(true); if (todo.title !== tempTitle) { const newTodo = { ...todo, title: tempTitle }; updateTodo(newTodo); } - setIsSubmitting(false); setIsEdited(false); }; @@ -120,7 +119,7 @@ export const TodoItem: React.FC = ({
diff --git a/src/components/TodoList/TodoList.tsx b/src/components/TodoList/TodoList.tsx index d2941367a..a5310ebde 100644 --- a/src/components/TodoList/TodoList.tsx +++ b/src/components/TodoList/TodoList.tsx @@ -5,14 +5,16 @@ type Props = { todos: Todo[]; updateTodo: (todo: Todo) => void; deletTodo: (todo: Todo) => void; - toggleError?: boolean; + array: Todo[]; + setTempArray: (todo: Todo) => void; }; export const TodoList: React.FC = ({ todos, updateTodo, deletTodo, - toggleError, + array, + setTempArray, }) => { return ( <> @@ -23,7 +25,8 @@ export const TodoList: React.FC = ({ todo={todo} updateTodo={updateTodo} deletTodo={deletTodo} - toggleError={toggleError} + array={array} + setTempArray={setTempArray} /> ))} From 5b3b423e480c02e1551f8a29225285db955d574f Mon Sep 17 00:00:00 2001 From: Mariana Trinko Date: Sat, 13 Jul 2024 17:02:15 +0200 Subject: [PATCH 03/19] message --- src/App.tsx | 19 +++++++++++++++---- src/components/TodoForm/TodoForm.tsx | 25 +++++++++++++------------ src/components/TodoItem/TodoItem.tsx | 27 ++++++++++++++++----------- src/components/TodoList/TodoList.tsx | 5 +++-- 4 files changed, 47 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 109b58979..11e60690a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -40,6 +40,7 @@ export const App: React.FC = () => { const [status, setStatus] = useState('all'); const [tempTodo, setTempTodo] = useState(null); const [tempArray, setTempArray] = useState([]); + const [edit, setEdit] = useState(false); const temp = (currentTodo: Todo) => { setTempArray(prevArray => [...prevArray, currentTodo]); @@ -82,7 +83,7 @@ export const App: React.FC = () => { } } - const updateTodo = (updatedTodo: Todo) => { + async function updateTodo(updatedTodo: Todo) { todosFromServer .updateTodos(updatedTodo) .then((todo: Todo) => @@ -99,9 +100,11 @@ export const App: React.FC = () => { ) .catch(() => { setUpdateError(true); + setEdit(true); wait(3000).then(() => setUpdateError(false)); + setTempArray([]); }); - }; + } const deleteTodo = (paramTodo: Todo) => { todosFromServer @@ -155,10 +158,11 @@ export const App: React.FC = () => { deletTodo={deleteTodo} array={tempArray} setTempArray={temp} + edit={edit} /> {tempTodo && ( - + )} {!!todos.length && ( @@ -179,7 +183,14 @@ export const App: React.FC = () => { data-cy="ErrorNotification" className={classNames( 'notification is-danger is-light has-text-weight-normal', - { hidden: !titleError && !loadError && !addError && !deleteError }, + { + hidden: + !titleError && + !loadError && + !addError && + !deleteError && + !updateError, + }, )} >
diff --git a/src/components/TodoFooter/TodoFooter.tsx b/src/components/TodoFooter/TodoFooter.tsx index 1b6027408..4b60e9042 100644 --- a/src/components/TodoFooter/TodoFooter.tsx +++ b/src/components/TodoFooter/TodoFooter.tsx @@ -7,6 +7,7 @@ type Props = { setStatus: (status: string) => void; status: string; deleteCompletedTodos: (todos: Todo[]) => void; + setTempArray: (todos: Todo[]) => void; }; const getDataCYByStatus = (status: string) => { @@ -25,6 +26,7 @@ export const TodoFooter: React.FC = ({ setStatus, status, deleteCompletedTodos, + setTempArray, }) => { const itemsLeft = todos.filter(todo => !todo.completed).length; const handleTodosStatus = (currentStatus: string) => { @@ -35,6 +37,7 @@ export const TodoFooter: React.FC = ({ todos.filter(todo => todo.completed).length > 0 ? false : true; const handleDeleteAllCompleted = () => { + setTempArray(todos); deleteCompletedTodos(todos.filter(t => t.completed === true)); }; diff --git a/src/components/TodoForm/TodoForm.tsx b/src/components/TodoForm/TodoForm.tsx index d3cb3d551..b00992ac2 100644 --- a/src/components/TodoForm/TodoForm.tsx +++ b/src/components/TodoForm/TodoForm.tsx @@ -23,7 +23,7 @@ export const TodoForm: React.FC = ({ const [title, setTitle] = useState(''); const [isSubmitting, setIsSubmitting] = useState(false); const titleField = useRef(null); - const trig = todos.every(({ completed }) => completed); + const allCompleted = todos.every(({ completed }) => completed); useEffect(() => { titleField.current?.focus(); @@ -54,18 +54,19 @@ export const TodoForm: React.FC = ({ const handleToggleAllTodo = () => { // проблема todos.forEach(currentTodo => { - setTempArray(currentTodo); if (!currentTodo.completed) { const newTodo = { ...currentTodo, completed: (currentTodo.completed = true), }; + setTempArray(currentTodo); + updateTodo(newTodo); } }); - if (trig) { + if (allCompleted) { todos.forEach(currentTodo => { setTempArray(currentTodo); @@ -86,7 +87,9 @@ export const TodoForm: React.FC = ({ // что значит !!
); diff --git a/src/components/TodoForm/TodoForm.tsx b/src/components/TodoForm/TodoForm.tsx index b00992ac2..cfeeed292 100644 --- a/src/components/TodoForm/TodoForm.tsx +++ b/src/components/TodoForm/TodoForm.tsx @@ -7,7 +7,7 @@ import { Todo } from '../../types/Todo'; type Props = { todos: Todo[]; onSubmit: (title: string) => Promise; - setTitleError: (value: boolean) => void; + setErrorMessage: (value: string) => void; updateTodo: (todo: Todo) => Promise; setTempArray: (todo: Todo) => void; edit: boolean; @@ -15,7 +15,7 @@ type Props = { export const TodoForm: React.FC = ({ onSubmit, - setTitleError, + setErrorMessage, todos, updateTodo, setTempArray, @@ -36,8 +36,8 @@ export const TodoForm: React.FC = ({ const handleFormSubmit = (event: FormEvent) => { event.preventDefault(); if (!title.trim()) { - setTitleError(true); - wait(3000).then(() => setTitleError(false)); + setErrorMessage('Title should not be empty'); + wait(3000).then(() => setErrorMessage('')); } if (title.trim()) { From fa12eb5b3acc00d8b399a150514a71f526d0a52b Mon Sep 17 00:00:00 2001 From: Mariana Trinko Date: Sat, 26 Oct 2024 17:58:45 +0200 Subject: [PATCH 18/19] message --- src/App.tsx | 27 +++++++++------------------ src/api/todos.ts | 1 - src/components/TodoItem/TodoItem.tsx | 9 --------- 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b0e4e3718..f182d9024 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -31,12 +31,7 @@ const getTodosByStatus = (status: string, todos: Todo[]) => { }; export const App: React.FC = () => { - // const [titleError, setTitleError] = useState(false); const [todos, setTodos] = useState([]); - // const [loadError, setLoadError] = useState(false); - // const [addError, setAddError] = useState(false); - // const [deleteError, setDeleteError] = useState(false); - // const [updateError, setUpdateError] = useState(false); const [status, setStatus] = useState('all'); const [tempTodo, setTempTodo] = useState(null); const [loadingTodos, setLoadingTodos] = useState([]); @@ -49,12 +44,12 @@ export const App: React.FC = () => { const filteredTodos = getTodosByStatus(status, todos); - async function addTodo(newTodoTitle: string) { + const addTodo = async (newTodoTitle: string) => { const editedTitle = newTodoTitle.trim(); if (!editedTitle) { setErrorMessage('Title should not be empty'); - wait(3000).then(() => setErrorMessage('Title should not be empty')); + wait(3000).finally(() => setErrorMessage('Title should not be empty')); return; } else { @@ -78,16 +73,13 @@ export const App: React.FC = () => { .catch(error => { setErrorMessage('Unable to add a todo'); setTempTodo(null); - wait(3000).then(() => setErrorMessage('')); + wait(3000).finally(() => setErrorMessage('')); throw error; }); } - } + }; - async function updateTodo( - updatedTodo: Todo, - // successUpdateState?: VoidFunction, - ): Promise { + const updateTodo = async (updatedTodo: Todo): Promise => { return todosFromServer .updateTodos(updatedTodo) .then((todo: Todo) => { @@ -102,16 +94,15 @@ export const App: React.FC = () => { return newTodos; }); setEdit(false); - // successUpdateState?.(); }) .catch(error => { setEdit(true); setErrorMessage('Unable to update a todo'); - wait(3000).then(() => setErrorMessage('')); + wait(3000).finally(() => setErrorMessage('')); setLoadingTodos([]); throw error; }); - } + }; const deleteTodo = (paramTodo: Todo) => { todosFromServer @@ -123,7 +114,7 @@ export const App: React.FC = () => { ) .catch(() => { setErrorMessage('Unable to delete a todo'); - wait(3000).then(() => { + wait(3000).finally(() => { setErrorMessage(''); }); setLoadingTodos(array => array.filter(a => a.id !== paramTodo.id)); @@ -135,7 +126,7 @@ export const App: React.FC = () => { .getTodos() .then(setTodos) .catch(() => setErrorMessage('Unable to load todos')); - wait(3000).then(() => setErrorMessage('')); + wait(3000).finally(() => setErrorMessage('')); }, []); if (!USER_ID) { diff --git a/src/api/todos.ts b/src/api/todos.ts index da18596f6..91503bc9b 100644 --- a/src/api/todos.ts +++ b/src/api/todos.ts @@ -2,7 +2,6 @@ import { Todo } from '../types/Todo'; import { client } from '../utils/fetchClient'; export const USER_ID = 839; -//https://mate.academy/students-api/todos?userId=839 export const getTodos = () => { return client.get(`/todos?userId=${USER_ID}`); diff --git a/src/components/TodoItem/TodoItem.tsx b/src/components/TodoItem/TodoItem.tsx index ce6f50f65..ef5d997e7 100644 --- a/src/components/TodoItem/TodoItem.tsx +++ b/src/components/TodoItem/TodoItem.tsx @@ -39,10 +39,6 @@ export const TodoItem: React.FC = ({ setTempTitle(event.target.value); }; - // const successUpdateState = () => { - // setIsEdited(false); - // }; - const handleFormSubmit = (event: FormEvent) => { event.preventDefault(); @@ -62,8 +58,6 @@ export const TodoItem: React.FC = ({ const newTodo = { ...todo, title: tempTitle.trim() }; - // setIsEdited(true); - updateTodo(newTodo) .then(() => { setIsEdited(false); @@ -175,9 +169,6 @@ export const TodoItem: React.FC = ({ {/* overlay will cover the todo while it is being deleted or updated */}
Date: Sun, 27 Oct 2024 16:10:04 +0100 Subject: [PATCH 19/19] message --- src/App.tsx | 12 ++++-------- src/components/TodoForm/TodoForm.tsx | 1 - 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index f182d9024..7dac70a87 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -49,7 +49,6 @@ export const App: React.FC = () => { if (!editedTitle) { setErrorMessage('Title should not be empty'); - wait(3000).finally(() => setErrorMessage('Title should not be empty')); return; } else { @@ -73,7 +72,6 @@ export const App: React.FC = () => { .catch(error => { setErrorMessage('Unable to add a todo'); setTempTodo(null); - wait(3000).finally(() => setErrorMessage('')); throw error; }); } @@ -98,7 +96,6 @@ export const App: React.FC = () => { .catch(error => { setEdit(true); setErrorMessage('Unable to update a todo'); - wait(3000).finally(() => setErrorMessage('')); setLoadingTodos([]); throw error; }); @@ -114,9 +111,6 @@ export const App: React.FC = () => { ) .catch(() => { setErrorMessage('Unable to delete a todo'); - wait(3000).finally(() => { - setErrorMessage(''); - }); setLoadingTodos(array => array.filter(a => a.id !== paramTodo.id)); }); }; @@ -126,8 +120,10 @@ export const App: React.FC = () => { .getTodos() .then(setTodos) .catch(() => setErrorMessage('Unable to load todos')); - wait(3000).finally(() => setErrorMessage('')); - }, []); + if (errorMessage) { + wait(3000).finally(() => setErrorMessage('')); + } + }, [errorMessage]); if (!USER_ID) { return ; diff --git a/src/components/TodoForm/TodoForm.tsx b/src/components/TodoForm/TodoForm.tsx index cfeeed292..bde02bb4e 100644 --- a/src/components/TodoForm/TodoForm.tsx +++ b/src/components/TodoForm/TodoForm.tsx @@ -84,7 +84,6 @@ export const TodoForm: React.FC = ({ <> {/* this button should have `active` class only if all todos are completed */} {!!todos.length && ( - // что значит !!