Skip to content

Commit

Permalink
draft version
Browse files Browse the repository at this point in the history
  • Loading branch information
VvynnykV committed Oct 28, 2024
1 parent f1536f7 commit a24fe9e
Show file tree
Hide file tree
Showing 17 changed files with 491 additions and 149 deletions.
165 changes: 22 additions & 143 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,35 @@
//#region lint exception
/* eslint-disable react-hooks/rules-of-hooks */
/* eslint-disable jsx-a11y/label-has-associated-control */
/* eslint-disable jsx-a11y/control-has-associated-label */
import React from 'react';
//#endregion
import React, { useMemo, useState } from 'react';
import { FilterStatus } from './types/FilterStatus';
import { getFilteredTodos } from './utils/getFilteredTodos';
import { Header } from './components/Header';
import { TodoList } from './components/TodoList/TodoList';
import { Footer } from './components/Footer/Footer';
import { useGlobalState } from './context/Store';

export const App: React.FC = () => {
const [filter, setFilter] = useState(FilterStatus.All);
const todos = useGlobalState();

const filteredTodos = useMemo(
() => getFilteredTodos(todos, filter),
[filter, todos],
);

return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

<div className="todoapp__content">
<header className="todoapp__header">
{/* this button should have `active` class only if all todos are completed */}
<button
type="button"
className="todoapp__toggle-all active"
data-cy="ToggleAllButton"
/>

{/* Add a todo on form submit */}
<form>
<input
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
/>
</form>
</header>

<section className="todoapp__main" data-cy="TodoList">
{/* This is a completed todo */}
<div data-cy="Todo" className="todo completed">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
checked
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Completed Todo
</span>

{/* Remove button appears only on hover */}
<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is an active todo */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Not Completed Todo
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>

{/* This todo is being edited */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

{/* This form is shown instead of the title and remove button */}
<form>
<input
data-cy="TodoTitleField"
type="text"
className="todo__title-field"
placeholder="Empty todo will be deleted"
value="Todo is being edited now"
/>
</form>
</div>

{/* This todo is in loadind state */}
<div data-cy="Todo" className="todo">
<label className="todo__status-label">
<input
data-cy="TodoStatus"
type="checkbox"
className="todo__status"
/>
</label>

<span data-cy="TodoTitle" className="todo__title">
Todo is being saved now
</span>

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
</section>

{/* Hide the footer if there are no todos */}
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
3 items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
href="#/"
className="filter__link selected"
data-cy="FilterLinkAll"
>
All
</a>

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>
<Header />

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>
<TodoList filteredTodos={filteredTodos} />

{/* this button should be disabled if there are no completed todos */}
<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
>
Clear completed
</button>
</footer>
{!!todos.length && <Footer filter={filter} onFilter={setFilter} />}
</div>
</div>
);
Expand Down
20 changes: 20 additions & 0 deletions src/api/todos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Todo } from '../types/Todo';
import { client } from '../utils/fetchClient';

export const USER_ID = 968;

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

export const postTodo = (todo: Omit<Todo, 'id'>) => {
return client.post<Todo>(`/todos`, todo);
};

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

export const patchTodo = (id: number, todoData: Partial<Todo>) => {
return client.patch<Todo>(`/todos/${id}`, todoData);
};
61 changes: 61 additions & 0 deletions src/components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { FilterStatus } from '../../types/FilterStatus';
import cn from 'classnames';
import { useDispatch, useGlobalState } from '../../context/Store';

type Props = {
filter: FilterStatus;
onFilter: (filter: FilterStatus) => void;
};

export const Footer: React.FC<Props> = ({ filter, onFilter }) => {
const todos = useGlobalState();
const dispatch = useDispatch();

const activeTodos = todos.filter(todo => !todo.completed).length;

const haveCompletedTodos = todos.some(todo => todo.completed);

const handleClearCompleted = () =>
todos
.filter(todo => todo.completed)
.forEach(todo => dispatch({ type: 'delete', payload: todo.id }));

return (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{`${activeTodos} items left`}
</span>

<nav className="filter" data-cy="Filter">
{Object.values(FilterStatus).map(filterStatus => (
<a
key={filterStatus}
href={
filterStatus === FilterStatus.All
? '#/'
: `#/${filterStatus.toLowerCase()}`
}
className={cn('filter__link', {
selected: filter === filterStatus,
})}
data-cy={`FilterLink${filterStatus}`}
onClick={() => onFilter(filterStatus)}
>
{filterStatus}
</a>
))}
</nav>

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={!haveCompletedTodos}
onClick={handleClearCompleted}
>
Clear completed
</button>
</footer>
);
};
1 change: 1 addition & 0 deletions src/components/Footer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Footer';
77 changes: 77 additions & 0 deletions src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { FC, useEffect, useRef, useState } from 'react';
import cn from 'classnames';

import { useDispatch, useGlobalState } from '../../context/Store';

export const Header: FC = () => {
const titleField = useRef<HTMLInputElement>(null);
const [title, setTitle] = useState('');
const [isSubmitting, setIsSubmiting] = useState(false);

const todos = useGlobalState();
const dispatch = useDispatch();

const areAllTodosCompleted = todos.every(todo => todo.completed);

const handleToggleAll = () => {
const haveActive = todos.some(todo => !todo.completed);
const todosToUpdate = haveActive
? todos.filter(todo => !todo.completed)
: todos;

todosToUpdate.forEach(todo =>
dispatch({ type: 'update', payload: { ...todo, completed: haveActive } }),
);
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

setIsSubmiting(true);

const newTitle = title.trim();

if (newTitle) {
try {
dispatch({ type: 'add', payload: newTitle });
setTitle('');
} catch (error) {
throw error;
} finally {
setIsSubmiting(false);
}
}
};

useEffect(() => {
titleField.current?.focus();
}, [todos, isSubmitting]);

return (
<header className="todoapp__header">
{!!todos.length && (
<button
type="button"
className={cn('todoapp__toggle-all', {
active: areAllTodosCompleted,
})}
data-cy="ToggleAllButton"
onClick={handleToggleAll}
/>
)}

<form onSubmit={handleSubmit}>
<input
ref={titleField}
data-cy="NewTodoField"
type="text"
className="todoapp__new-todo"
placeholder="What needs to be done?"
value={title}
onChange={event => setTitle(event.target.value)}
disabled={isSubmitting}
/>
</form>
</header>
);
};
1 change: 1 addition & 0 deletions src/components/Header/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Header';
Loading

0 comments on commit a24fe9e

Please sign in to comment.