Skip to content

Commit

Permalink
Everything works outside of escape. Would love some advice on that.
Browse files Browse the repository at this point in the history
  • Loading branch information
szymonpachucki committed Sep 20, 2023
1 parent 6532826 commit 7a54662
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 18 deletions.
113 changes: 95 additions & 18 deletions src/App.tsx
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>
);
};
55 changes: 55 additions & 0 deletions src/components/footer.tsx
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>
);
};
29 changes: 29 additions & 0 deletions src/components/inCaseOfError.tsx
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>
);
};
77 changes: 77 additions & 0 deletions src/components/inputOfTodos.tsx
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>
);
};
114 changes: 114 additions & 0 deletions src/components/task.tsx
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>
);
};
Loading

0 comments on commit 7a54662

Please sign in to comment.