An NLP featured To-do List app in Next.js

An NLP featured To-do List app in Next.js

Efficiently Organising Your Tasks with NLP: A To-Do List Application with automatic deadline extractor

ยท

13 min read

A to-do list application is one of the simplest and most basic applications that any beginner web developer must build in order to learn and apply the basic concepts of web development. This tutorial focuses on building and deploying a to-do list application with a tiny twist of using NLP packages from npm to create a production-ready to-do list application featuring NLP capability and using local-storage of the browser to make the content persist upon reloads. This NLP package will help us get the deadline from the task text itself without having the user select it manually.

In this project, I am using is Next.js, TailwindCSS, and daisyui for developing the application's frontend. I am using chrono-node for extracting date and time from the task text. Further, I am using yarn as a package manager for this project. So, without any further ado let's begin...

Step 1: Setting up the project

This should be the first step in any project, howsoever small it be. First let's create an example Next.js application. I am using VS Code and its integrated terminal. If you don't have yarn, run

sudo npm install --global yarn

After successful yarn installation, create an example Next.js application inside an empty directory (folder for windows).

yarn create next-app ./

We will use JS in this project for development, so make sure you choose No to every prompt while this command is executing.

Now, you can run yarn dev in the terminal and go to the localhost:3000 to see if this works properly. You will see a standard Next.js screen.

Now let's install tailwindcss and daisyui plugin. I would recommend following this page from tailwindcss site to help you configure tailwindCSS. Also run yarn add daisyui to add this plugin and tell tailwind about it by require it in tailwind.config.js file. Finally this file becomes

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    fontFamily: {
      inter: ["Inter", "sans-serif"],
      montserrat: ["Montserrat", "serif"],
      russo: ["Russo One", "monospace"],
    },
    extend: {},
  },
  plugins: [require("daisyui")],
  daisyui: {
    themes: false,
  },
};

I have also defined some font-names which we'll use in this project. Now, remove everything from pages/index.js file inside main tag.

Make Sure You have this structure in pages/index.js file

import Head from "next/head";
import Image from "next/image";

const inter = Inter({ subsets: ["latin"] });

export default function Home() {
  return (
    <>
      <Head>
        <title>TodoFirst</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link
          rel="preconnect"
          href="https://fonts.gstatic.com"
          crossOrigin="anonymous"
        />
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&family=Montserrat:ital,wght@0,300;0,400;0,500;1,200&family=Russo+One&display=swap"
          rel="stylesheet"
        />
      </Head>
      <main className="text-2xl flex justify-center">
        <div className="">
          <div>Todo</div>
          <div>Card</div>
        </div>
      </main>
    </>
  );
}

Make sure the `styles/Home.module.css` file looks like this


* {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

html,
body {
  max-width: 100vw;
  overflow-x: hidden;
}

a {
  color: inherit;
  text-decoration: none;
}

@media (prefers-color-scheme: dark) {
  html {
    color-scheme: dark;
  }
}

And your `_app.js` looks like this

import '@/styles/globals.css'
import 'tailwindcss/tailwind.css'  //add this import here.

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />
}

Now, that you've completed the project setup, let's move to the next step.

Step 2: Logic for add, remove, complete features

Let's start with adding the functions for adding new task, deleted tasks, and marking the task as complete.

Now, we want to display the current time in our front-end. So, let's create a file `utils/date_time.js` and make sure it looks like this:

export default function getClockInfo() {
  const days = [
    "Sunday",
    "Monday",
    "Tuesday",
    "Wednesday",
    "Thursday",
    "Friday",
    "Saturday",
  ];
  const currentDate = new Date();
  const day = days[currentDate.getDay()];
  const date = currentDate.getDate();
  const time = currentDate.toLocaleTimeString().slice(0, 8);

  return { day, date, time };
}

Also, we want the time format to be 12-hour instead of 24-hour, so let's create another file in `utils/to12Hours.js`, which takes "hh:mm" and converts it into AM or PM format time.

function to12Hours(time24) {
  if (time24 == "undefined") return "undefined"

  let hour = parseInt(time24);
  let minutes = time24?.slice(3, 5);
  if (hour == 12) {
    return `12:${minutes} PM`;
  } else if (hour == 0) {
    return `12:${minutes} AM`;
  } else if (hour < 12) {
    return hour + `:${minutes} AM`;
  } else {
    return hour - 12 + `:${minutes} PM`;
  }
}

export { to12Hours };

Now let's add some frontend code and some functions in pages/index.js file. Also, you can find the images that I have used from my GitHub repo.

import Head from "next/head";
import getClockInfo from "../utils/date_time";
import { useState, useEffect } from "react";
import { day, date, time } from "../utils/date_time";
import TodoItem from "./components/TodoItem";

import Image from "next/image";

export default function Home() {
  // removing the hydration error
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => {
    setHydrated(true);
  }, []);

  const [input, setInput] = useState("");
  let [todos, setTodos] = useState([]);

  //changing the clock time
  const [time, setTime] = useState(getClockInfo().time);
  const [day, setDay] = useState(getClockInfo().day);
  const [date, setDate] = useState(getClockInfo().date);
  setInterval(() => {
    setTime(getClockInfo().time);
    setDay(getClockInfo().day);
    setDate(getClockInfo().date);
  }, 1000);

  const handleSubmit = (e) => {
    if (input == "") return;

    const todoItem = {
      id: Date.now(),
      deadline: "1674244809",
      task: taskString,
      completed: false,
    };
    setTodos([...todos, todoItem]);
    setInput("");
  };

  const handleKeyUp = (e) => {
    if (e.keyCode == 13) {
      handleSubmit(e);
    }
  };

  const handleDelete = (id) => {
    setTodos(todos.filter((todo) => todo.id != id));
  };

  const handleCompleted = (id) => {
    let todosCopy = [...todos];
    for (let i = 0; i < todosCopy.length; i++) {
      if (todosCopy[i].id == id) {
        todosCopy[i].completed = !todosCopy[i].completed;
      }
    }
    setTodos(todosCopy);
  };

  if (!hydrated) return null;

  return (
    <>
      <Head>
        <title>TodoFirst</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" className="rounded-full" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link
          rel="preconnect"
          href="https://fonts.gstatic.com"
          crossOrigin="anonymous"
        />
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&family=Montserrat:ital,wght@0,300;0,400;0,500;1,200&family=Russo+One&display=swap"
          rel="stylesheet"
        />
      </Head>
      <main className="flex h-screen flex-col items-center sm:flex-row justify-around">
        <div className="flex flex-col items-center md:mt-0 mt-20 mb-10 md:mb-0 md:ml-20 justify-center">
          <Image
            src="/images/todoicon.png"
            alt="To Do Icon"
            width={120}
            height={120}
            className="rounded-full w-40 mb-5"
          />
          <div className="mx-auto font-montserrat text-3xl md:text-6xl text-blue-600 font-black md:ml-6">
            TodoFirst
          </div>
        </div>
        <div className="mx-auto font-inter">
          <div className="card w-96 bg-base-100 shadow-xl">
            <figure>
              <Image
                src="/images/todoimage.jpg"
                alt="Abstract Image"
                width={500}
                height={500}
                className="z-40 h-56 w-80 rounded-xl shadow-md blur-xs"
              />
              <div className="absolute right-12 top-36 z-50 font-inter text-gray-700">
                <div className="text-md text-end font-russo">
                  {day} {date}
                </div>
                <div className="font-russo text-4xl">{time}</div>
              </div>
            </figure>
            <div className="card-body">
              <div>
                <div>
                  <div className="flex flex-row">
                    <input
                      type="text"
                      className="input input-bordered w-3/4 input-md font-montserrat ml-3"
                      onChange={(e) => {
                        setInput(e.target.value);
                      }}
                      onKeyUp={handleKeyUp}
                      value={input}
                      autoFocus={true}
                    />
                    <button
                      className="btn btn-primary btn-md font-montserrat ml-3 py-2"
                      onClick={handleSubmit}
                    >
                      +
                    </button>
                  </div>
                  <div className="mt-3 overflow-y-auto h-56">
                    {todos.length > 0 ? (
                      todos.map((todoItem, index) => (
                        <div
                          className="flex flex-row justify-between items-center "
                          key={index}
                        >
                          <TodoItem
                            key={index}
                            todoItem={todoItem}
                            handleCompleted={handleCompleted}
                            handleDelete={handleDelete}
                          />
                        </div>
                      ))
                    ) : (
                      <div className="font-montserrat text-gray-600 text-center pt-4">
                        Wow Such Empty ๐Ÿค™
                      </div>
                    )}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

You can note that in the above code we've added many event handler callback functions, prefixed with handle. Our todoItem is an object with following properties:

const todoItem = {
      id: Date.now(),    // id to recognize each task uniquely
      deadline: "1674244809", // time in unix timestamp
      task: "Task",     // what is the task string
      completed: false,     // Is the task completed?
    };

Other functions are pretty simple to understand. If you face any difficulty, please ask in the comments.

The below code snipped inside `pages/index.js` is used to remove any [hydration error](https://nextjs.org/docs/messages/react-hydration-error) that will be coming while displaying the time (with precision of seconds) in our application.

// removing the hydration error
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => {
    setHydrated(true);
  }, []);

The below code snippet gives us the modified day, date and time every second using `setInterval` method.

//changing the clock time
  const [time, setTime] = useState(getClockInfo().time);
  const [day, setDay] = useState(getClockInfo().day);
  const [date, setDate] = useState(getClockInfo().date);
  setInterval(() => {
    setTime(getClockInfo().time);
    setDay(getClockInfo().day);
    setDate(getClockInfo().date);
  }, 1000);

If you see any errors coming, in the [localhost:3000](localhost:3000), either try to debug the code, or best continue reading this article.

Now, let's create a TodoItem component that we've mentioned above, in `pages/index.js`.

import { to12Hours } from "../../utils/to12Hours";

export default function TodoItem(props) {
  return (
    <div className="flex flex-ro mt-1 items-center px-2 py-1 rounded-lg">
      <div className="ml-3">
        <div
          className={
            props.todoItem?.completed
              ? "line-through text-gray-700 w-52 text-md capitalize"
              : "w-52 text-md capitalize"
          }
        >
          {props.todoItem?.task == "" ? "Empty Task" : props.todoItem?.task}
        </div>
        <div className="text-xs text-gray-400">
          {props.todoItem?.deadline?.toString().slice(0, 15) || "No deadline"}{" "}
          {props.todoItem?.deadline && to12Hours(props.todoItem.deadline?.toLocaleTimeString().slice(0, 5))}
        </div>
      </div>

      <input
        type="checkbox"
        className="checkbox checkbox-warning ml-3 checkbox-sm"
        onClick={() => {
          props.handleCompleted(props.todoItem.id);
        }}
      />
      <div
        className="text-red-700 items-center ml-4"
        onClick={() => {
          props.handleDelete(props.todoItem.id);
        }}
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          strokeWidth={1.5}
          stroke="currentColor"
          className="w-6 h-6"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
          />
        </svg>
      </div>
    </div>
  );
}

Step 3: Add deadline extractor feature

We'll use [chrono-node](https://www.npmjs.com/package/chrono-node) npm library to add this feature to our application. This library standalone will not be able to remove stop-words like for, a, in, at, from, etc, so we use another npm package - `stopword` to remove custom stop words. I have also specified some stop words. You can add more stop words if you prefer to.

We'll modify our pages/index.js file using the below code snippet:

const handleSubmit = (e) => {
    if (input == "") return;

    // NLP parsing
    const referenceDate = new Date();
    const parsedResults = chrono.parse(input, referenceDate, {
      forwardDate: true,
    });

    //removing stop words from the task name
    const oldTaskStrings = input.slice(0, parsedResults[0]?.index).split(" ");
    const newTaskString = removeStopwords(oldTaskStrings, [
      "by",
      "BY",
      "By",
      "The",
      "the",
      "THE",
      "a",
      "A",
      "from",
      "From",
      "FROM",
      "at",
      "AT",
      "At",
      "in",
      "IN",
      "In",
      "for",
      "For",
      "FOR",
      "to",
      "To",
      "TO",
      "I",
      "i",
      "want",
      "Want",
      "WANT",
    ]);
    const taskString = newTaskString.join(" ");
    const todoItem = {
      id: Date.now(),
      deadline: parsedResults[0]?.start.date(),
      task: taskString,
      completed: false,
    };
    setTodos([...todos, todoItem]);
    setInput("");
  };

The modified pages/index.js file will now become:

import Head from "next/head";
import getClockInfo from "../utils/date_time";
import { useState, useEffect } from "react";
import TodoItem from "./components/TodoItem";
import * as chrono from "chrono-node";
import { removeStopwords } from "stopword";
import Image from "next/image";

export default function Home() {
  // removing the hydration error
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => {
    setHydrated(true);
  }, []);

  const [input, setInput] = useState("");
  let [todos, setTodos] = useState([]);

  //changing the clock time
  const [time, setTime] = useState(getClockInfo().time);
  const [day, setDay] = useState(getClockInfo().day);
  const [date, setDate] = useState(getClockInfo().date);
  setInterval(() => {
    setTime(getClockInfo().time);
    setDay(getClockInfo().day);
    setDate(getClockInfo().date);
  }, 1000);

  const handleSubmit = (e) => {
    if (input == "") return;

    // NLP parsing
    const referenceDate = new Date();
    const parsedResults = chrono.parse(input, referenceDate, {
      forwardDate: true,
    });

    //removing stop words from the task name
    const oldTaskStrings = input.slice(0, parsedResults[0]?.index).split(" ");
    const newTaskString = removeStopwords(oldTaskStrings, [
      "by",
      "BY",
      "By",
      "The",
      "the",
      "THE",
      "a",
      "A",
      "from",
      "From",
      "FROM",
      "at",
      "AT",
      "At",
      "in",
      "IN",
      "In",
      "for",
      "For",
      "FOR",
      "to",
      "To",
      "TO",
      "I",
      "i",
      "want",
      "Want",
      "WANT",
    ]);
    const taskString = newTaskString.join(" ");
    const todoItem = {
      id: Date.now(),
      deadline: parsedResults[0]?.start.date(),
      task: taskString,
      completed: false,
    };
    setTodos([...todos, todoItem]);
    setInput("");
  };

  const handleKeyUp = (e) => {
    if (e.keyCode == 13) {
      handleSubmit(e);
    }
  };

  const handleDelete = (id) => {
    setTodos(todos.filter((todo) => todo.id != id));
  };

  const handleCompleted = (id) => {
    let todosCopy = [...todos];
    for (let i = 0; i < todosCopy.length; i++) {
      if (todosCopy[i].id == id) {
        todosCopy[i].completed = !todosCopy[i].completed;
      }
    }
    setTodos(todosCopy);
  };

  if (!hydrated) return null;

  return (
    <>
      <Head>
        <title>TodoFirst</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" className="rounded-full" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link
          rel="preconnect"
          href="https://fonts.gstatic.com"
          crossOrigin="anonymous"
        />
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&family=Montserrat:ital,wght@0,300;0,400;0,500;1,200&family=Russo+One&display=swap"
          rel="stylesheet"
        />
      </Head>
      <main className="flex h-screen flex-col items-center sm:flex-row justify-around">
        <div className="flex flex-col items-center md:mt-0 mt-20 mb-10 md:mb-0 md:ml-20 justify-center">
          <Image
            src="/images/todoicon.png"
            alt="To Do Icon"
            width={120}
            height={120}
            className="rounded-full w-40 mb-5"
          />
          <div className="mx-auto font-montserrat text-3xl md:text-6xl text-blue-600 font-black md:ml-6">
            TodoFirst
          </div>
        </div>
        <div className="mx-auto font-inter">
          <div className="card w-96 bg-base-100 shadow-xl">
            <figure>
              <Image
                src="/images/todoimage.jpg"
                alt="Abstract Image"
                width={500}
                height={500}
                className="z-40 h-56 w-80 rounded-xl shadow-md blur-xs"
              />
              <div className="absolute right-12 top-36 z-50 font-inter text-gray-700">
                <div className="text-md text-end font-russo">
                  {day} {date}
                </div>
                <div className="font-russo text-4xl">{time}</div>
              </div>
            </figure>
            <div className="card-body">
              <div>
                <div>
                  <div className="flex flex-row">
                    <input
                      type="text"
                      className="input input-bordered w-3/4 input-md font-montserrat ml-3"
                      onChange={(e) => {
                        setInput(e.target.value);
                        // console.log(e.target.value);
                      }}
                      onKeyUp={handleKeyUp}
                      value={input}
                      autoFocus={true}
                    />
                    <button
                      className="btn btn-primary btn-md font-montserrat ml-3 py-2"
                      onClick={handleSubmit}
                    >
                      +
                    </button>
                  </div>
                  <div className="mt-3 overflow-y-auto h-56">
                    {todos.length > 0 ? (
                      todos.map((todoItem, index) => (
                        <div
                          className="flex flex-row justify-between items-center "
                          key={index}
                        >
                          <TodoItem
                            key={index}
                            todoItem={todoItem}
                            handleCompleted={handleCompleted}
                            handleDelete={handleDelete}
                          />
                        </div>
                      ))
                    ) : (
                      <div className="font-montserrat text-gray-600 text-center pt-4">
                        Wow Such Empty ๐Ÿค™
                      </div>
                    )}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

Now, our application is ready with a basic to-do app, but it has one deal breaker disadvantage. As soon, as we reload the page, the tasks automatically goes away. This is not very useful. This happens because we are storing this information in RAM memory allocated to browser. That's why when we reload the page, all the data is cleaned and we get the application in the initialized empty state. In the next step we'll try to eliminate this shortcoming.

Step 4: Storing data in local storage of Browser

You can read more about local storage [here](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).

We can use local storage of the browser to store small amounts of user data. However, you should never store any sensitive data in local storage unencrypted. I don't want this article to be much long so I am ignoring this security blunder. However, if you need this security feature, you can ask in the comment.

We will have to slightly modify, our add, delete and complete handler functions and also modify our initialization of `todos` array to look for local storage data first if present any then display it when the first component loads.

So, our pages/index.js will be modified and finally becomes:

import Head from "next/head";
import getClockInfo from "../utils/date_time";
import { useState, useEffect } from "react";
import TodoItem from "./components/TodoItem";
import * as chrono from "chrono-node";
import { removeStopwords } from "stopword";
import Image from "next/image";

export default function Home() {
  // removing the hydration error
  const [hydrated, setHydrated] = useState(false);
  useEffect(() => {
    setHydrated(true);
  }, []);

  // Because we want the browser's localStorage object and window object is not defined in server-side environment.
  const ISSERVER = typeof window === "undefined";

  const [input, setInput] = useState("");
  let [todos, setTodos] = useState(
    !ISSERVER && localStorage.getItem("todos")
      ? JSON.parse(localStorage.getItem("todos"))
      : []
  );

  //changing the clock time
  const [time, setTime] = useState(getClockInfo().time);
  const [day, setDay] = useState(getClockInfo().day);
  const [date, setDate] = useState(getClockInfo().date);
  setInterval(() => {
    setTime(getClockInfo().time);
    setDay(getClockInfo().day);
    setDate(getClockInfo().date);
  }, 1000);

  const handleSubmit = (e) => {
    if (input == "") return;

    // NLP parsing
    const referenceDate = new Date();
    const parsedResults = chrono.parse(input, referenceDate, {
      forwardDate: true,
    });

    //removing stop words from the task name
    const oldTaskStrings = input.slice(0, parsedResults[0]?.index).split(" ");
    const newTaskString = removeStopwords(oldTaskStrings, [
      "by",
      "BY",
      "By",
      "The",
      "the",
      "THE",
      "a",
      "A",
      "from",
      "From",
      "FROM",
      "at",
      "AT",
      "At",
      "in",
      "IN",
      "In",
      "for",
      "For",
      "FOR",
      "to",
      "To",
      "TO",
      "I",
      "i",
      "want",
      "Want",
      "WANT",
    ]);
    const taskString = newTaskString.join(" ");
    const todoItem = {
      id: Date.now(),
      deadline: parsedResults[0]?.start.date(),
      task: taskString,
      completed: false,
    };
    setTodos([...todos, todoItem]);
    setInput("");
    localStorage.setItem("todos", JSON.stringify([...todos, todoItem]));
  };

  const handleKeyUp = (e) => {
    if (e.keyCode == 13) {
      handleSubmit(e);
    }
  };

  const handleDelete = (id) => {
    setTodos(todos.filter((todo) => todo.id != id));

    localStorage.setItem(
      "todos",
      JSON.stringify(todos.filter((todo) => todo.id != id))
    );
    // console.log("deleted item with id: ", id);
    // console.log(todos);
  };

  const handleCompleted = (id) => {
    let todosCopy = [...todos];
    for (let i = 0; i < todosCopy.length; i++) {
      if (todosCopy[i].id == id) {
        todosCopy[i].completed = !todosCopy[i].completed;
      }
    }
    setTodos(todosCopy);
    localStorage.setItem("todos", JSON.stringify(todosCopy));  };

  if (!hydrated) return null;

  return (
    <>
      <Head>
        <title>TodoFirst</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" className="rounded-full" />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link
          rel="preconnect"
          href="https://fonts.gstatic.com"
          crossOrigin="anonymous"
        />
        <link
          href="https://fonts.googleapis.com/css2?family=Inter&family=Montserrat:ital,wght@0,300;0,400;0,500;1,200&family=Russo+One&display=swap"
          rel="stylesheet"
        />
      </Head>
      <main className="flex h-screen flex-col items-center sm:flex-row justify-around">
        <div className="flex flex-col items-center md:mt-0 mt-20 mb-10 md:mb-0 md:ml-20 justify-center">
          <Image
            src="/images/todoicon.png"
            alt="To Do Icon"
            width={120}
            height={120}
            className="rounded-full w-40 mb-5"
          />
          <div className="mx-auto font-montserrat text-3xl md:text-6xl text-blue-600 font-black md:ml-6">
            TodoFirst
          </div>
        </div>
        <div className="mx-auto font-inter">
          <div className="card w-96 bg-base-100 shadow-xl">
            <figure>
              <Image
                src="/images/todoimage.jpg"
                alt="Abstract Image"
                width={500}
                height={500}
                className="z-40 h-56 w-80 rounded-xl shadow-md blur-xs"
              />
              <div className="absolute right-12 top-36 z-50 font-inter text-gray-700">
                <div className="text-md text-end font-russo">
                  {day} {date}
                </div>
                <div className="font-russo text-4xl">{time}</div>
              </div>
            </figure>
            <div className="card-body">
              <div>
                <div>
                  <div className="flex flex-row">
                    <input
                      type="text"
                      className="input input-bordered w-3/4 input-md font-montserrat ml-3"
                      onChange={(e) => {
                        setInput(e.target.value);
                        // console.log(e.target.value);
                      }}
                      onKeyUp={handleKeyUp}
                      value={input}
                      autoFocus={true}
                    />
                    <button
                      className="btn btn-primary btn-md font-montserrat ml-3 py-2"
                      onClick={handleSubmit}
                    >
                      +
                    </button>
                  </div>
                  <div className="mt-3 overflow-y-auto h-56">
                    {todos.length > 0 ? (
                      todos.map((todoItem, index) => (
                        <div
                          className="flex flex-row justify-between items-center "
                          key={index}
                        >
                          <TodoItem
                            key={index}
                            todoItem={todoItem}
                            handleCompleted={handleCompleted}
                            handleDelete={handleDelete}
                          />
                        </div>
                      ))
                    ) : (
                      <div className="font-montserrat text-gray-600 text-center pt-4">
                        Wow Such Empty ๐Ÿค™
                      </div>
                    )}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
    </>
  );
}

I have deployed this application at [todonlp.vercel.app](todonlp.vercel.app) and the code for this application is open source at [todofirst-nlp](https://github.com/sadityakumar9211/todofirst-nlp).

If you're getting the error, while accessing the site, clear you're browser cookies.

If you face any difficulty, please comment below. I will be there to reply.

Adios ๐Ÿ‘‹

ย