-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Everything works outside of escape. Would love some advice on that.
- Loading branch information
1 parent
6532826
commit 7a54662
Showing
10 changed files
with
439 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,101 @@ | ||
/* eslint-disable max-len */ | ||
/* eslint-disable jsx-a11y/control-has-associated-label */ | ||
import React from 'react'; | ||
import { UserWarning } from './UserWarning'; | ||
|
||
const USER_ID = 0; | ||
import React, { useEffect, useState } from 'react'; | ||
import { Todo } from './types/Todo'; | ||
import { Errors } from './types/Errors'; | ||
import { FilterOption } from './types/filterOption'; | ||
import { TodoList } from './components/todoList'; | ||
import { InCaseOfError } from './components/inCaseOfError'; | ||
import { InputOfTodos } from './components/inputOfTodos'; | ||
import { Footer } from './components/footer'; | ||
|
||
export const App: React.FC = () => { | ||
if (!USER_ID) { | ||
return <UserWarning />; | ||
} | ||
const [todos, setTodos] = useState<Todo[]>([ | ||
// Initialize with some example todos | ||
|
||
{ | ||
id: 1, | ||
title: 'Example Todo 1', | ||
completed: false, | ||
removed: false, | ||
editing: false, | ||
}, | ||
|
||
{ | ||
id: 2, | ||
title: 'Example Todo 2', | ||
completed: true, | ||
removed: false, | ||
editing: false, | ||
}, | ||
]); | ||
const [error, setError] = useState<Errors | null>(null); | ||
const [filterTodos, setFilterTodos] = useState<FilterOption>('All'); | ||
const [newTodo, setNewTodo] = useState<string>(''); // State for new todo input | ||
|
||
const handleSetFilter = (newFilter: FilterOption) => { | ||
setFilterTodos(newFilter); | ||
}; | ||
|
||
const closeError = () => { | ||
setError(null); | ||
}; | ||
|
||
const addTodo = () => { | ||
if (newTodo.trim() !== '') { | ||
const newTodoItem: Todo = { | ||
id: todos.length + 1, | ||
title: newTodo, | ||
completed: false, | ||
removed: false, | ||
editing: false, | ||
}; | ||
|
||
setTodos([...todos, newTodoItem]); | ||
setNewTodo(''); // Clear the input field | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
// You can perform any additional actions here when the todos state changes. | ||
}, [todos]); | ||
|
||
return ( | ||
<section className="section container"> | ||
<p className="title is-4"> | ||
Copy all you need from the prev task: | ||
<br /> | ||
<a href="https://github.com/mate-academy/react_todo-app-add-and-delete#react-todo-app-add-and-delete">React Todo App - Add and Delete</a> | ||
</p> | ||
|
||
<p className="subtitle">Styles are already copied</p> | ||
</section> | ||
<div className="todoapp"> | ||
<h1 className="todoapp__title">todos</h1> | ||
|
||
<div className="todoapp__content"> | ||
{/* Input for adding new todos */} | ||
<InputOfTodos | ||
setNewTodo={setNewTodo} | ||
newTodo={newTodo} | ||
addTodo={addTodo} | ||
todos={todos} | ||
setTodos={setTodos} | ||
/> | ||
|
||
{/* Pass the filtered todos to TodoList */} | ||
{todos && ( | ||
<TodoList | ||
todos={todos} | ||
filterTodos={filterTodos} | ||
setTodos={setTodos} | ||
/> | ||
)} | ||
|
||
{/* Hide the footer if there are no todos */} | ||
{todos.length > 0 && ( | ||
<Footer | ||
handleSetFilter={handleSetFilter} | ||
todos={todos} | ||
setTodos={setTodos} | ||
/> | ||
)} | ||
</div> | ||
|
||
{/* Notification is shown in case of any error */} | ||
{/* Add the 'hidden' class to hide the message smoothly */} | ||
{error !== null && ( | ||
<InCaseOfError error={error} closeError={closeError} /> | ||
)} | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React from 'react'; | ||
import { TodoCount } from './todoCount'; | ||
import { Todo } from '../types/Todo'; | ||
import { FilterOption } from '../types/filterOption'; | ||
|
||
interface Props { | ||
handleSetFilter: (newFilter: FilterOption) => void; | ||
todos: Todo[]; | ||
setTodos: (todos: Todo[]) => void; | ||
} | ||
|
||
// eslint-disable-next-line max-len | ||
export const Footer: React.FC<Props> = ({ handleSetFilter, todos, setTodos }) => { | ||
const handleRemoveComplited = () => { | ||
setTodos(todos.filter((todo) => todo.completed !== true)); | ||
}; | ||
|
||
return ( | ||
<footer className="todoapp__footer"> | ||
<TodoCount todos={todos} /> | ||
<nav className="filter"> | ||
<a | ||
href="#/" | ||
className="filter__link selected" | ||
onClick={() => handleSetFilter('All')} | ||
> | ||
All | ||
</a> | ||
|
||
<a | ||
href="#/active" | ||
className="filter__link" | ||
onClick={() => handleSetFilter('Active')} | ||
> | ||
Active | ||
</a> | ||
|
||
<a | ||
href="#/completed" | ||
className="filter__link" | ||
onClick={() => handleSetFilter('Completed')} | ||
> | ||
Completed | ||
</a> | ||
</nav> | ||
<button | ||
type="button" | ||
className="todoapp__clear-completed" | ||
onClick={handleRemoveComplited} | ||
> | ||
Clear completed | ||
</button> | ||
</footer> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import React, { useEffect } from 'react'; | ||
import { Errors } from '../types/Errors'; | ||
|
||
interface Props { | ||
error: Errors; | ||
closeError: () => void; | ||
} | ||
|
||
export const InCaseOfError: React.FC<Props> = ({ error, closeError }) => { | ||
useEffect(() => { | ||
// Set a timeout to call closeError after 3 seconds | ||
const timeoutId = setTimeout(() => { | ||
closeError(); | ||
}, 3000); | ||
|
||
// Clean up the timeout when the component unmounts | ||
return () => { | ||
clearTimeout(timeoutId); | ||
}; | ||
}, [closeError]); | ||
|
||
return ( | ||
<div className="notification is-danger is-light has-text-weight-normal"> | ||
<button type="button" className="delete" onClick={closeError}> | ||
{error} | ||
</button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
/* eslint-disable jsx-a11y/control-has-associated-label */ | ||
import React from 'react'; | ||
import { Todo } from '../types/Todo'; | ||
|
||
interface Props { | ||
setNewTodo: (newTodo: string) => void; | ||
newTodo: string; | ||
addTodo: (title: string) => void; // Updated the return type to void | ||
todos: Todo[]; | ||
setTodos: (todos: Todo[]) => void; | ||
} | ||
|
||
// eslint-disable-next-line max-len | ||
export const InputOfTodos: React.FC<Props> = ({ | ||
setNewTodo, newTodo, addTodo, todos, setTodos, | ||
}) => { | ||
const handleAddNewTodo = () => { | ||
if (newTodo) { // Removed the unnecessary check | ||
addTodo(newTodo); | ||
setNewTodo(''); | ||
} | ||
}; | ||
|
||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
setNewTodo(event.target.value); | ||
}; | ||
|
||
// eslint-disable-next-line max-len | ||
const handleInputKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (event.key === 'Enter') { | ||
event.preventDefault(); // Prevent form submission on Enter key press | ||
handleAddNewTodo(); | ||
} | ||
}; | ||
|
||
const handleTaggleAll = () => { | ||
let updatedTodos = todos; | ||
|
||
if (todos.some((t) => !t.completed)) { | ||
updatedTodos = todos.map((t) => ({ | ||
...t, | ||
completed: true, | ||
})); | ||
} else { | ||
updatedTodos = todos.map((t) => ({ | ||
...t, | ||
completed: !t.completed, | ||
})); | ||
} | ||
|
||
setTodos(updatedTodos); | ||
}; | ||
|
||
return ( | ||
<header className="todoapp__header"> | ||
{todos.length !== 0 && ( | ||
<button | ||
type="button" | ||
className={`todoapp__toggle-all ${todos.every((t) => t.completed) ? 'active' : ''}`} | ||
onClick={handleTaggleAll} | ||
/> | ||
)} | ||
|
||
{/* Add a todo on form submit */} | ||
<form> | ||
<input | ||
type="text" | ||
className="todoapp__new-todo" | ||
placeholder="What needs to be done?" | ||
value={newTodo} | ||
onChange={handleInputChange} | ||
onKeyPress={handleInputKeyPress} | ||
/> | ||
</form> | ||
</header> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
import React, { useState, useRef, useEffect } from 'react'; | ||
import { Todo } from '../types/Todo'; | ||
|
||
type Props = { | ||
todo: Todo; | ||
setTodos: React.Dispatch<React.SetStateAction<Todo[]>>; | ||
todos: Todo[]; | ||
}; | ||
|
||
export const Task: React.FC<Props> = ({ todo, setTodos, todos }) => { | ||
const [inputValue, setInputValue] = useState<string>(todo.title); | ||
const inputRef = useRef<HTMLInputElement | null>(null); | ||
|
||
const handleToggleCompletion = () => { | ||
const updatedTodo = { ...todo, completed: !todo.completed }; | ||
|
||
setTodos(todos.map((t) => (t.id === updatedTodo.id ? updatedTodo : t))); | ||
}; | ||
|
||
const handleRemove = () => { | ||
const updatedTodo = { ...todo, removed: true }; | ||
|
||
setTodos(todos.map((t) => (t.id === updatedTodo.id ? updatedTodo : t))); | ||
}; | ||
|
||
const handleEditStart = () => { | ||
const updatedTodo = { ...todo, editing: true }; | ||
|
||
setInputValue(todo.title); | ||
setTodos(todos.map((t) => (t.id === updatedTodo.id ? updatedTodo : t))); | ||
}; | ||
|
||
const saveChanges = () => { | ||
const updatedTodo = { ...todo, title: inputValue, editing: false }; | ||
|
||
if (updatedTodo.title === '') { | ||
handleRemove(); | ||
} else { | ||
setTodos(todos.map((t) => (t.id === updatedTodo.id ? updatedTodo : t))); | ||
} | ||
|
||
setInputValue(''); | ||
}; | ||
|
||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
setInputValue(event.target.value); | ||
}; | ||
|
||
const cancelEditing = () => { | ||
const updatedTodo = { ...todo, editing: false }; | ||
|
||
setTodos(todos.map((t) => (t.id === updatedTodo.id ? updatedTodo : t))); | ||
|
||
setInputValue(''); | ||
}; | ||
|
||
// eslint-disable-next-line max-len | ||
const handleInputKeyPress = (event: React.KeyboardEvent<HTMLInputElement>) => { | ||
if (event.key === 'Escape') { | ||
event.preventDefault(); | ||
cancelEditing(); | ||
} | ||
|
||
if (event.key === 'Enter') { | ||
event.preventDefault(); | ||
saveChanges(); | ||
} | ||
}; | ||
|
||
const handleInputBlur = () => { | ||
saveChanges(); | ||
}; | ||
|
||
useEffect(() => { | ||
if (todo.editing && inputRef.current) { | ||
inputRef.current.focus(); | ||
} | ||
}, [todo.editing]); | ||
|
||
return ( | ||
<div | ||
className={todo.completed ? 'todo completed' : 'todo'} | ||
onDoubleClick={handleEditStart} | ||
> | ||
<label className="todo__status-label"> | ||
<input | ||
type="checkbox" | ||
className="todo__status" | ||
checked={todo.completed} | ||
onChange={handleToggleCompletion} | ||
/> | ||
</label> | ||
{!todo.editing ? ( | ||
<> | ||
<span className="todo__title">{todo.title}</span> | ||
<button type="button" className="todo__remove" onClick={handleRemove}> | ||
× | ||
</button> | ||
</> | ||
) : ( | ||
<input | ||
ref={inputRef} | ||
type="text" | ||
className="todoapp__new" | ||
placeholder="Empty todo will be deleted" | ||
value={inputValue} | ||
onChange={handleInputChange} | ||
onKeyPress={handleInputKeyPress} | ||
onBlur={handleInputBlur} | ||
/> | ||
)} | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.