Skip to content

Commit

Permalink
completely changed everything
Browse files Browse the repository at this point in the history
  • Loading branch information
DmytroHoncharuk committed Dec 24, 2024
1 parent d345046 commit 3ef1270
Show file tree
Hide file tree
Showing 22 changed files with 811 additions and 634 deletions.
436 changes: 124 additions & 312 deletions src/App.tsx

Large diffs are not rendered by default.

24 changes: 7 additions & 17 deletions src/api/todos.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { Todo } from '../types/Todo';
import { client } from '../utils/fetchClient';

export const USER_ID = 2161;
export const USER_ID = 2164;

export const getTodos = () => {
return client.get<Todo[]>(`/todos?userId=${USER_ID}`);
};

export const addTodo = ({ title, completed }: Omit<Todo, 'id' | 'userId'>) => {
return client.post<Todo>('/todos', { title, completed, userId: USER_ID });
export const addTodo = (newTodo: Omit<Todo, 'id' | 'userId'>) => {
return client.post<Todo>(`/todos`, { ...newTodo, userId: USER_ID });
};

export const deleteTodo = (id: number) => {
return client.delete(`/todos/${id}`);
export const deleteTodo = (todoId: number) => {
return client.delete(`/todos/${todoId}`);
};

export const changeTodoCompleted = ({
id,
completed,
}: Omit<Todo, 'userId' | 'title'>) => {
return client.patch(`/todos/${id}`, { completed });
};

export const changeTodoTitle = ({
id,
title,
}: Omit<Todo, 'userId' | 'completed'>) => {
return client.patch(`/todos/${id}`, { title });
export const updateTodo = (todo: Todo) => {
return client.patch<Todo>(`/todos/${todo.id}`, todo);
};
44 changes: 44 additions & 0 deletions src/components/ErrorNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { Dispatch, SetStateAction, useEffect } from 'react';
import { ErrorType } from '../types/ErrorTypes';
import classNames from 'classnames';

type Props = {
error: ErrorType;
setError: Dispatch<SetStateAction<ErrorType>>;
};

export const ErrorNotification: React.FC<Props> = props => {
const { error, setError } = props;

useEffect(() => {
if (error === ErrorType.Empty) {
return;
}

const timerId = setTimeout(() => {
setError(ErrorType.Empty);
}, 3000);

return () => {
clearTimeout(timerId);
};
}, [error, setError]);

return (
<div
data-cy="ErrorNotification"
className={classNames(
'notification is-danger is-light has-text-weight-normal',
{ hidden: error === ErrorType.Empty },
)}
>
<button
data-cy="HideErrorButton"
type="button"
className="delete"
onClick={() => setError(ErrorType.Empty)}
/>
{error}
</div>
);
};
34 changes: 34 additions & 0 deletions src/components/Filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import classNames from 'classnames';
import { FILTER_BY } from '../constants/constants';
import React from 'react';

interface Props {
filter: string;
onFilter: (v: string) => void;
}

export const Filter: React.FC<Props> = ({
filter: currentFilter,
onFilter,
}) => {
const getNameFilter = (str: string) =>
str.slice(0, 1).toUpperCase() + str.slice(1);

return (
<nav className="filter" data-cy="Filter">
{Object.values(FILTER_BY).map(filter => (
<a
key={filter}
href={`#/${filter}`}
className={classNames('filter__link', {
selected: currentFilter === filter,
})}
data-cy={`FilterLink${getNameFilter(filter)}`}
onClick={() => onFilter(filter)}
>
{getNameFilter(filter)}
</a>
))}
</nav>
);
};
41 changes: 41 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { Filter } from './Filter';
import { Todo } from '../types/Todo';
import { getCompletedTodos } from '../utils/methods';

interface Props {
sizeLeft: number;
filter: string;
onFilter: (v: string) => void;
onClear: () => void;
todos: Todo[];
}

export const Footer: React.FC<Props> = ({
sizeLeft,
filter,
onFilter,
onClear,
todos,
}) => {
return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${sizeLeft} items left`}
</span>

<Filter onFilter={onFilter} filter={filter} />

{/* this button should be disabled if there are no completed todos */}
<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={getCompletedTodos(todos).length === 0}
onClick={onClear}
>
Clear completed
</button>
</footer>
);
};
65 changes: 0 additions & 65 deletions src/components/Footer/Footer.tsx

This file was deleted.

90 changes: 90 additions & 0 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import classNames from 'classnames';
import React from 'react';
import { updateTodoCompleted } from '../api/todos';
import { Todo } from '../types/Todo';
import { pause } from '../utils/methods';
import { NewTodoField } from './NewTodoField';

interface Props {
todos: Todo[];
sizeLeft: number;
onTodos: React.Dispatch<React.SetStateAction<Todo[]>>;
onErrorMessage: (v: string) => void;
onMassLoader: (v: boolean) => void;
onLoader: (v: boolean) => void;
onTempTodo: (v: Todo | null) => void;
}

export const Header: React.FC<Props> = ({
todos,
sizeLeft,
onTodos,
onErrorMessage,
onMassLoader,
onLoader,
onTempTodo,
}) => {
const handleChangeAll = async () => {
const notCompletedTodos = todos.filter(e => !e.completed);

try {
onMassLoader(true);
await pause();
const todosToUpdate =
notCompletedTodos.length > 0 ? notCompletedTodos : todos;

const updatedTodos = await Promise.all(
todosToUpdate.map(async e => {
const newTodo = await updateTodoCompleted({
id: e.id,
completed: notCompletedTodos.length > 0 || sizeLeft !== 0,
});

return {
...e,
completed: newTodo.completed,
};
}),
);

onTodos(prev =>
prev.map(todo => {
const updated = updatedTodos.find(
updatedTodo => updatedTodo.id === todo.id,
);

return updated ? updated : todo;
}),
);
} catch {
onErrorMessage('Unable to update a todo');
} finally {
onMassLoader(false);
}
};

return (
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
{todos.length > 0 && (
<button
type="button"
className={classNames('todoapp__toggle-all', {
active: sizeLeft === 0,
})}
// className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
onClick={handleChangeAll}
/>
)}

{/* Add a todo on form submit */}
<NewTodoField
onErrorMessage={onErrorMessage}
onTempTodo={onTempTodo}
onLoader={onLoader}
onTodos={onTodos}
/>
</header>
);
};
Empty file removed src/components/Header/Header.tsx
Empty file.
44 changes: 44 additions & 0 deletions src/components/MyError.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React, { useEffect, useRef } from 'react';

interface Props {
errorMessage: string;
onErrorMessage: (v: string) => void;
}

export const MyError: React.FC<Props> = ({ errorMessage, onErrorMessage }) => {
const errorDiv = useRef<HTMLDivElement | null>(null);
const timerId = useRef(0);

useEffect(() => {
if (errorDiv.current && errorMessage) {
errorDiv.current.classList.remove('hidden');
window.clearTimeout(timerId.current);
timerId.current = window.setTimeout(() => {
errorDiv.current?.classList.add('hidden');
onErrorMessage('');
}, 3000);
}
}, [errorMessage, onErrorMessage]);

const closeError = () => {
window.clearTimeout(timerId.current);
errorDiv.current?.classList.add('hidden');
};

return (
<div
ref={errorDiv}
data-cy="ErrorNotification"
className="notification is-danger is-light has-text-weight-normal hidden"
>
<button
data-cy="HideErrorButton"
type="button"
className="delete"
onClick={closeError}
/>
{/* show only one message at a time */}
{errorMessage}
</div>
);
};
Loading

0 comments on commit 3ef1270

Please sign in to comment.