Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
6.7 kB
1
Indexable
Never
/* eslint-disable jsx-a11y/control-has-associated-label */
import React, { useEffect, useState } from "react";
import { Todo } from "./types/Todo";
import { getTodos, postTodo, deleteTodo } from "./api/todos";
import { ErrorPopup } from "./components/ErrorPopup";

const USER_ID = 11546;

export const App: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [error, setError] = useState<string | null>(null);
  const [filterType, setFilterType] = useState<"All" | "Active" | "Completed">(
    "All"
  );
  const [newTitle, setNewTitle] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const handleAddTodo = async (title: string) => {
    try {
      const trimmedTitle = title.trim();

      if (trimmedTitle === "") {
        handleErrorMessage("Title should not be empty");
        return;
      }
      setIsLoading(true);

      const newTodo: Omit<Todo, "id"> = {
        title: trimmedTitle,
        completed: false,
        userId: USER_ID,
      };

      const createdTodo = await postTodo(newTodo);

      setTodos([...todos, createdTodo]);
      setIsLoading(false);
    } catch (e) {
      handleErrorMessage("Unable to add a todo");
      setIsLoading(false);
    }
  };

  const handleDeleteTodo = async (todo: Todo) => {
    try {
      await deleteTodo(todo.id);

      setTodos((prevTodos) => prevTodos.filter((t) => t.id !== todo.id));
    } catch (e) {
      handleErrorMessage("Unable to delete a todo");
    }
  };

  const handleErrorMessage = (message: string | null) => {
    setError(message);

    setTimeout(() => {
      setError(null);
    }, 3000);
  };

  const filterTodos = (
    todosArray: Todo[],
    filterStatus: "All" | "Active" | "Completed"
  ) => {
    return todosArray.filter((todo) => {
      if (filterStatus === "All") {
        return true;
      }

      if (filterStatus === "Active") {
        return !todo.completed;
      }

      return todo.completed;
    });
  };

  const changeFilterStatus = (type: "All" | "Active" | "Completed") => {
    setFilterType(type);
  };

  useEffect(() => {
    setError(null);
    if (USER_ID) {
      getTodos(USER_ID)
        .then((data) => {
          setTodos(data);
        })
        .catch(() => {
          handleErrorMessage("Unable to load todos");
        });
    }
  }, []);

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

      <div className="todoapp__content">
        <header className="todoapp__header">
          {todos.some((todo) => !todo.completed) && (
            <button
              type="button"
              className="todoapp__toggle-all active"
              data-cy="ToggleAllButton"
            />
          )}

          <form
            onSubmit={(e) => {
              e.preventDefault();
              handleAddTodo(newTitle);
              setNewTitle("");
            }}
          >
            <input
              data-cy="NewTodoField"
              type="text"
              className="todoapp__new-todo"
              placeholder="What needs to be done?"
              value={newTitle}
              onChange={(e) => setNewTitle(e.target.value)}
              autoFocus
              disabled={isLoading}
            />
          </form>
        </header>

        <section className="todoapp__main" data-cy="TodoList">
          {filterTodos(todos, filterType).map((todo) => (
            <div
              data-cy="Todo"
              className={`todo ${todo.completed ? "completed" : ""}`}
              key={todo.id}
            >
              <label className="todo__status-label">
                <input
                  data-cy="TodoStatus"
                  type="checkbox"
                  className="todo__status"
                  checked={todo.completed}
                />
              </label>

              <span data-cy="TodoTitle" className="todo__title">
                {todo.title}
              </span>

              {/* Remove button appears only on hover */}
              <button
                type="button"
                className="todo__remove"
                data-cy="TodoDelete"
                onClick={() => handleDeleteTodo(todo)}
              >
                ×
              </button>

              {/* overlay will cover the todo while it is being updated */}
              <div data-cy="TodoLoader" className="modal overlay">
                <div className="modal-background has-background-white-ter" />
                <div className="loader" />
              </div>
            </div>
          ))}
        </section>

        {todos.some((todo) => !todo.completed) && (
          <footer className="todoapp__footer" data-cy="Footer">
            <span className="todo-count" data-cy="TodosCounter">
              {`${todos.filter((todo) => !todo.completed).length} item${
                todos.filter((todo) => !todo.completed).length !== 1 ? "s" : ""
              } left`}
            </span>

            <nav className="filter" data-cy="Filter">
              <a
                href="#/"
                className={`filter__link ${
                  filterType === "All" ? "selected" : ""
                }`}
                onClick={() => changeFilterStatus("All")}
                data-cy="FilterLinkAll"
              >
                All
              </a>

              <a
                href="#/active"
                className={`filter__link ${
                  filterType === "Active" ? "selected" : ""
                }`}
                onClick={() => changeFilterStatus("Active")}
                data-cy="FilterLinkActive"
              >
                Active
              </a>

              <a
                href="#/completed"
                className={`filter__link ${
                  filterType === "Completed" ? "selected" : ""
                }`}
                onClick={() => changeFilterStatus("Completed")}
                data-cy="FilterLinkCompleted"
              >
                Completed
              </a>
            </nav>

            {todos.filter((todo) => todo.completed).length > 0 && (
              <button
                type="button"
                className="todoapp__clear-completed"
                data-cy="ClearCompletedButton"
              >
                Clear completed
              </button>
            )}
          </footer>
        )}
      </div>

      <ErrorPopup error={error} setError={setError} />
    </div>
  );
};