Skip to content

Commit

Permalink
add task solution
Browse files Browse the repository at this point in the history
  • Loading branch information
VictorKomara committed Nov 22, 2024
1 parent f1536f7 commit 079a07e
Show file tree
Hide file tree
Showing 20 changed files with 729 additions and 161 deletions.
198 changes: 54 additions & 144 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,157 +1,67 @@
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/indent */
/* 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';
import React, { useContext, useState } from 'react';
import { Footer } from './components/Footer';
import { SelectedFilter } from './types/SelectedFilter';
import { Header } from './components/Header';
import { TodoList } from './components/TodoList';
import { ErrorSection } from './components/ErrorSection';
import { initialErrorMessage } from './constants/initialErrorMessage';
import { TodosContext } from './context/TodosContext';

export const App: React.FC = () => {
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>
const [errorMessage, setErrorMessage] = useState(initialErrorMessage);
const [selectedFilter, setSelectedFilter] = useState<SelectedFilter>(
SelectedFilter.all,
);
const [editingTitle, setEditingTitle] = useState<number>(0);

<button type="button" className="todo__remove" data-cy="TodoDelete">
×
</button>
</div>
</section>
const { todos } = useContext(TodosContext);

{/* 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>
const visibleTodos = todos.filter(todo => {
switch (selectedFilter) {
case SelectedFilter.all:
return true;

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
<a
href="#/"
className="filter__link selected"
data-cy="FilterLinkAll"
>
All
</a>
case SelectedFilter.active:
return !todo.completed;

<a
href="#/active"
className="filter__link"
data-cy="FilterLinkActive"
>
Active
</a>
case SelectedFilter.completed:
return todo.completed;
}
});

<a
href="#/completed"
className="filter__link"
data-cy="FilterLinkCompleted"
>
Completed
</a>
</nav>
return (
<div className="todoapp">
<h1 className="todoapp__title">todos</h1>

{/* 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>
<div className="todoapp__content">
<Header
visibleTodos={visibleTodos}
errorMessage={errorMessage}
setErrorMessage={setErrorMessage}
editingTitle={editingTitle}
/>

<TodoList
visibleTodos={visibleTodos}
editingTitle={editingTitle}
setEditingTitle={setEditingTitle}
/>

<Footer
selectedFilter={selectedFilter}
setSelectedFilter={setSelectedFilter}
/>
</div>

<ErrorSection
errorMessage={errorMessage}
setErrorMessage={setErrorMessage}
/>
</div>
);
};
46 changes: 46 additions & 0 deletions src/components/ErrorSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import classNames from 'classnames';
import { ErrorMessage } from '../types/ErrorMessage';
import { initialErrorMessage } from '../constants/initialErrorMessage';

type Props = {
errorMessage: ErrorMessage;
setErrorMessage: (errorMessage: ErrorMessage) => void;
};

export const ErrorSection: React.FC<Props> = ({
errorMessage,
setErrorMessage,
}) => (
<div
data-cy="ErrorNotification"
className={classNames(
'notification',
'is-danger',
'is-light',
'has-text-weight-normal',
{
hidden:
!errorMessage.load &&
!errorMessage.delete &&
!errorMessage.create &&
!errorMessage.emptyTitle &&
!errorMessage.updating,
},
)}
>
<button
data-cy="HideErrorButton"
type="button"
className="delete"
onClick={() => {
setErrorMessage(initialErrorMessage);
}}
/>
{/* show only one message at a time */}
{errorMessage.load && 'Unable to load todos'}
{errorMessage.create && 'Unable to add a todo'}
{errorMessage.delete && 'Unable to delete a todo'}
{errorMessage.emptyTitle && 'Title should not be empty'}
{errorMessage.updating && 'Unable to update a todo'}
</div>
);
65 changes: 65 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import classNames from 'classnames';
import { SelectedFilter } from '../types/SelectedFilter';
import { Todo } from '../types/Todo';
import { useContext } from 'react';
import { TodosContext } from '../context/TodosContext';

type Props = {
selectedFilter: SelectedFilter;
setSelectedFilter: (filter: SelectedFilter) => void;
};

export const Footer: React.FC<Props> = ({
selectedFilter,
setSelectedFilter,
}) => {
const { todos, setTodos } = useContext(TodosContext);

const itemLeft: number = todos.filter(todo => !todo.completed).length;
const completedTodos: Todo[] = todos.filter(todo => todo.completed);

function deleteCompletedTodos() {
const newTodos: Todo[] = todos.filter(todo => !todo.completed);

setTodos(newTodos);
}

return (
!!todos.length && (
<footer className="todoapp__footer" data-cy="Footer">
<span className="todo-count" data-cy="TodosCounter">
{itemLeft} items left
</span>

{/* Active link should have the 'selected' class */}
<nav className="filter" data-cy="Filter">
{Object.values(SelectedFilter).map(button => {
return (
<a
key={button}
href={button === SelectedFilter.all ? '#/' : `#/${button}`}
className={classNames('filter__link', {
selected: selectedFilter === button,
})}
data-cy={`FilterLink${button}`}
onClick={() => setSelectedFilter(button)}
>
{button}
</a>
);
})}
</nav>

<button
type="button"
className="todoapp__clear-completed"
data-cy="ClearCompletedButton"
disabled={completedTodos.length === 0}
onClick={deleteCompletedTodos}
>
Clear completed
</button>
</footer>
)
);
};
54 changes: 54 additions & 0 deletions src/components/FormEditingTitle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useEffect } from 'react';
import { Todo } from '../types/Todo';

type Props = {
todo: Todo;
titleEdit: string;
setTitleEdit: (titleEdit: string) => void;
handleChangeSubmit: (todo: Todo) => void;
setEditingTitle: (editingTitle: number) => void;
};

export const FormEditingTitle: React.FC<Props> = ({
todo,
titleEdit,
setTitleEdit,
handleChangeSubmit,
setEditingTitle,
}) => {
useEffect(() => {
function handleKeyPress(event: KeyboardEvent) {
if (event.key === 'Escape') {
setEditingTitle(0);
}
}

window.addEventListener('keyup', handleKeyPress);

return () => {
window.removeEventListener('keyup', handleKeyPress);
};
});

return (
<form
onSubmit={event => {
event.preventDefault();
handleChangeSubmit(todo);
}}
>
<input
data-cy="TodoTitleField"
type="text"
className="todoapp__edit-todo"
placeholder="What needs to be done?"
value={titleEdit}
onBlur={() => handleChangeSubmit(todo)}
autoFocus
onChange={event => {
setTitleEdit(event.target.value);
}}
/>
</form>
);
};
Loading

0 comments on commit 079a07e

Please sign in to comment.