How to Create a Task Time Tracker Chrome Extension With Strapi and ReactJs
Published: 2023-03-23 18:35:21

# How to Create a Task Time Tracker Chrome Extension With Strapi and ReactJs

## Outline

- Prerequisites
- Github URL
- What is a Chrome Extension?
- What we are Building
- What is ReactJs?
- What is Tailwind CSS?
- What is Strapi - A Headless CMS?
- Strapi Installation
- Creating the Task Tracker Collection
- Customizing our Strapi Create Controller
  - InstallingMoment
- Bootstrapping our Reactjs App
- Installing Tailwind CSS
- Installing Other Dependencies
- Creating the Home page
- Adding a Task
- Displaying a Task
- Marking a Task as completed
- Deleting a Task
- Editing a Task
- Creating the Manifest.json file
- Generating a Build File
- Adding our Application to Google Chrome Extension
- Testing our Application
- Conclusion

Chrome extensions are potent tools at the disposal of any user. It can increase effectiveness, solve problems, and lots more. In summary, it extends the browser's capabilities. A task tracker is an application that allows users to allocate time to a particular task.

The goal of this tutorial is to enable us to see another awesome use case of Strapi by building a Chrome extension. It will introduce us to Chrome extensions, ReactJs, manifest.json, Strapi controller customization, and adding a ReactJs application to the extension menu of our Chrome browser.

## Prerequisites

For us to continue, we need the following:

1. Nodejs runtime installed on our local machine. Visit the [homepage](https://nodejs.org/en/download/) for the installation guide.
2. A basic understanding of Strapi Headless CMS. See the [get started](https://strapi.io/documentation/developer-docs/latest/getting-started/introduction.html) page.
3. A very basic knowledge of Reactjs.
4. And a basic understanding of Tailwind CSS.

## Github URL

https://github.com/Theodore-Kelechukwu-Onyejiaku/Task-Tracker-Chrome-Extension

## What is a Chrome Extension?

A Chrome Extension is an app built mostly with HTML, CSS, and JavaScript that performs a specific action, and that mostly **customizes the Chrome browsing experience**.
It adds additional functionality to your Chrome browser. They could be productivity tools, like our Task Time Tracker, a language translation tool, an ad blocker, and so on.

A production-ready extension can be found on the [Chrome Web Store](https://chrome.google.com/webstore/category/extensions). Also, we can create our own extension locally and use it locally on our machine. Some popular Chrome extensions include ad blockers, password managers, language translation tools, productivity tools, and social media extensions. Some popular Chrome Extensions include JSON formatter, Grammarly, Adblock Plus, LastPass, Metamask, and even the React Developer Tool Chrome extension and so much more.

## What we are Building

We are building a simple Task Time Tracker. Below are the features of our application.

1. We can add a task and the time we expect to finish the task.
2. We should see a countdown displaying the number of minutes and time we have left on the task.
3. We can be able to edit the task along with its duration.
4. We can mark a task as completed before and after expiration.
5. Our Chrome extension badge should show the number of completed tasks.

> Note: a badge here refers to the character we see on the icon of some chrome extensions.

6. The date or time in which the Task was created should be displayed.
7. We can then finally be able to delete a task.

## What is ReactJs?

ReactJs or React is an open-source JavaScript framework developed by Facebook that is used for building powerful user interfaces. It allows developers to build dynamic, fast, and reusable components. We are using Reactjs because when we run the build command which generates the `index.html` file our chrome extension needs.

## What is Tailwind CSS?

Tailwind CSS is a utility-first efficient CSS framework for creating unique user interfaces is called Tailwind CSS. It allows us to write class based CSS directly to our application. We need Tailwind in this project because it is fast to use and removes the need for many stylesheets.

## What is Strapi - A Headless CMS?

Strapi is an open-source and powerful headless CMS based on Node.js that is used to develop and manage content using Restful APIs and GraphQL. It is built on top of Koa.
With Strapi, we can scaffold our API faster and consume the content via APIs using any HTTP client or GraphQL enabled frontend.

## Strapi Installation

Installing Strapi is just the same way we install other NPM packages. We will have to open our CLI to run the command below:

```bash
    npx create-strapi-app task-time-tracker-api --quickstart
        ## OR
    yarn create strapi-app task-time-tracker-api --quick start
```

The command above will have Strapi installed for us. The name of the application in this command is `task-time-tracker`-api.

When it has been installed, cd into the `task-time-tracker` folder and run the command below to start up our application:

```bash
    yarn build
    yarn develop
    ## OR
    npm run build
    npm run develop
```

When this is complete, our Strapi application should be live on `http://localhost:1337/`.

## Creating the Task Tracker Collection

Next, we have to create our application collection. A collection acts as a database. Head on and create a collection called `task`.

This will save the details of each task.

![create-task-collection.png](/uploads/create_task_collection_d0170fb3e7.png)

Now, we have to create the fields for our application. Create the first field called `title`. This will serve as the title for any task we create.

![adding-title-field.png](/uploads/adding_title_field_ba2b922646.png)

Next, click on the “Advanced settings” tab to make sure that this field is “Required field”. This is so that the field will be required when creating a record.

![make-field-reqired.png](/uploads/make_field_reqired_096d82a1f9.png)

Go ahead and create the following fields:

| Field Name  | Field Type | Required | Unique |
| ----------- | ---------- | -------- | ------ |
| title       | Short text | true     | false  |
| taskTime    | Number     | true     | false  |
| realTime    | Number     | true     | false  |
| completed   | Boolean    | false    | false  |
| dateCreated | Text       | true     | false  |

The `taskTime` represents the time in minutes allocated to the task. `realTime` represents the real-time in milliseconds for a task which will be useful when we start a countdown.

`completed` is a boolean field representing the status of the task if completed or not completed.

` dateCreate``d ` will represent data in text format. This will be generated in the backend controller when we customize the create controller.

Allowing Access To Collection

Now that we have created our application, we need users to access or query our API. For this reason, we have to allow access to our task collection. We will set it to public since this extension is a personal extension.

Hence, allow the following access shown in the image below. Please go to _Settings > Users & Permissions Plugin > Roles,_ then click on Public. Then allow the following access:

![enable-bublic-access.png](/uploads/enable_bublic_access_2a1562a263.png)

## Customizing our Strapi Create Controller

Now, we have to see how to customize a controller. A controller is the function called when a route is accessed. It is responsible for handling incoming requests and returning responses to clients.

#### Installing Moment

Before we continue we need moment.js in our application. This is so that we can save the date our task was created in a `moment.js` date format. This is so that our client, which uses ` moment``.js ` will display the date in a calendar format.

Run the command below to install ` moment``.js `:

```bash
    npm i moment
```

Now we have to open our task controller at this path: `src/api/task/controllers/task.js`.

Replace the content with the one shown below:

```javascript
// path: ./src/api/task/controllers/task.js

"use strict";
/**
 * task controller
 */
const { createCoreController } = require("@strapi/strapi").factories;
// import moment
const moment = require("moment");
module.exports = createCoreController("api::task.task", ({ strapi }) => ({
  async create(ctx) {
    const { data } = ctx.request.body;
    // convert to date with Moment
    const dateCreated = moment(new Date());
    // convert to string
    data.dateCreated = dateCreated.toString();
    // create or save task
    let newTask = await strapi.service("api::task.task").create({ data });
    const sanitizedEntity = await this.sanitizeOutput(newTask, ctx);
    return this.transformResponse(sanitizedEntity);
  },
}));
```

In the code above, we customized the controller for the creation of a task, the `create()` core action. This will handle POST requests to our API on the route `/api/tasks/`.

In `line 12` , the code will take the `data` from the request body sent by the client and add the formatted `dateCreated` to it. And finally, it will save it and return in `line 29`.

## Bootstrapping our Reactjs App

In order to create a frontend ReactJs application, we will `cd` into the folder of our choice through the terminal and run the command below:

```bash
    npx create-react-app task-time-tracker
```

We specified that the name of our application is `task-time-tracker` .
Now run the following command to `cd` into and run our application.

```bash
    cd task-time-tracker
    npm start
```

If the command is successful, we should see the following open up in our browser:

![create-react-app.png](/uploads/create_react_app_21322b30a9.png)

Bravo! We are now ready to create our application!

## Installing Tailwind CSS

Presently, we have to install Tailwind CSS. Enter the command below to stop our application:

```bash
    command c  // (for mac)
    ## OR
    control c  // (for windows)
```

Install Tailwind using the command below:

```bash
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init -p
```

Next, we open our `tailwind.config.js` file which is in the root folder of our application and replace the content with the following code.

```javascript
// path: ./tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

Lastly, add this to the `index.css` file in our root folder.

```css
// path: ./index.css

@tailwind base;
@tailwind components;
@tailwind utilities;
```

## Installing Other Dependencies

Also, our application needs the following dependencies to run:

```bash
    npm i axios moment react-icons react-simple-tooltip react-toastify
```

`axios`: this will allow us to make HTTP requests to our Strapi.
`moment` : this will help us format the `dateCreated` field in our application.
`react-icons`: this will serve as our icons provider.
`react-simple-tooltip`: this will help us show tooltips
`react-toastify`: this will be used for showing of toasts.

## Creating the Home page

Before we continue, we have to create an environment variable for our server API URL. We have to create a `.env` file in the root of our project and the following should be added:

```bash
    REACT_APP_STRAPI_SERVER=http://localhost:1337
```

Now, Head over to the `App.js` file and add the following:

```jsx
// path: ./src/App.js

import { useEffect, useState } from "react";
import { BiMessageSquareAdd } from "react-icons/bi";
import axios from "axios";
import { ToastContainer } from "react-toastify";
import Task from "./components/Task";
import EditModal from "./components/EditModal";
import AddTaskModal from "./components/AddTaskModal";
import "react-toastify/dist/ReactToastify.css";
const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
```

In the code above:

- **line 3**: we import the useEffect and useState hooks.
- line 4: we import an icon that will show a button to add a task.
- line 5: we import `axios` that will help us make a HTTP request.
- line 6: the ToastContainer use to show toast from `react-toastify` is imported.
- line 7: `Task` component was imported. This will display a task on the page.
- line 8; we import the `EditModal` component which we will create shortly. This represents a modal to edit a task.
- line 9: `AddTaskModal` which is modal to add a task is also imported.
- line 10: the css for showing toast from `react-toastify` is imported.
- line 11: the URL to our Strapi API is imported from the `.env` file as `serverURL`.

Now let us add our State variables:

```jsx
    // path: ./src/App.js

    ...
    function App() {
      const [tasks, setTasks] = useState([]);
      const [fetchError, setFetchError] = useState([]);
      const [tasksCompleted, setTasksCompleted] = useState([]);
      const [taskToUpdate, setTaskToUpdate] = useState({});
      const [showUpdateModal, setShowUpdateModal] = useState(false);
      const [showAddTaskModal, setShowAddTaskModal] = useState(false);

      return (
          <div>
          </div>
      )
    }
    export default App;
```

From the code above:

- line 5: we create a state variable for our tasks.
- line 6: state variable for a fetch error and its setter function is created.
- line 7: we create the state variable `tasksCompleted` and `setTaskCompleted` which should display the tasks that have been completed on the page and the setter function.
- line 8: `taskToUpdate` will represent a task we want to update at the click of a button. And also its corresponding setter function.
- line 9: we declare a boolean state variable for when the update modal should be visible. And its corresponding setter function.
- line 10: also we declare a boolean state variable for making the adding of a task modal visible or invisible.

Now add the following functions before the `return` statement:

```jsx
// path: ./src/App.js

// function to update extension badge
const updateChromeBadge = (value) => {
  const { chrome } = window;
  chrome.action?.setBadgeText({ text: value });
  chrome.action?.setBadgeBackgroundColor({ color: "##fff" });
};
// function to get all tasks
const getAllTask = async () => {
  setFetchError("");
  try {
    // fetch tasks from strapi
    const res = await axios.get(`${serverUrl}/api/tasks`);
    // get result from nested object destructuring
    const {
      data: { data },
    } = res;
    // get tasks that have not been completed
    const tasks = data
      .filter((task) => !task.attributes.completed)
      .map((task) => {
        // check if task has expired
        if (Date.now() > parseInt(task?.attributes?.realTime)) {
          task.attributes.realTime = 0;
          return task;
        }
        return task;
      });
    // get completed tasks
    const completedTasks = data.filter((task) => task.attributes.completed);
    setTasksCompleted(completedTasks);
    updateChromeBadge(completedTasks.length.toString());
    setTasks(tasks.reverse());
  } catch (err) {
    setFetchError(err.message);
  }
};
useEffect(() => {
  // get all tasks
  getAllTask();
}, []);
```

In the code shown above:

- line 4-8: this is the function that will be used to show our badge icon text like we described in the beginning.
- line 10-34: this will be the function responsible for getting all tasks, and even completed tasks. In `line 20` , it checks if the task has expired by comparing the time when the task was created and the current time. If expired, it returns the task real time as 0. `line 27` filters out the completed tasks. And `line 29` updates the badge icon based on the number of tasks completed.
- line 35: we call the `useEffect` which fires the `getAllTask()` function as soon as the component is mounted.

Finally, add the following JSX to our `App.js`.

```jsx
// path: ./src/App.js

return (
  <div>
    <div className="w-full flex justify-center rounded">
      <div className="w-96 sm:w-1/3 overflow-scroll border p-5 mb-20 relative bg-slate-50">
        <div className="">
          <h1 className="text-focus-in text-4xl font-bold text-slate-900">
            Task Time Tracker
          </h1>
          <span className="text-slate-400">Seize The Day!</span>
        </div>
        <div>
          <h1 className="font-bold text-lg my-5">Tasks</h1>
          {fetchError && (
            <div className="text-red-500 text-center">Something went wrong</div>
          )}
          <div>
            {tasks.length ? (
              tasks?.map((task) => (
                <Task
                  key={task.id}
                  updateChromeBadge={updateChromeBadge}
                  setTaskToUpdate={setTaskToUpdate}
                  setShowUpdateModal={setShowUpdateModal}
                  showUpdateModal={showUpdateModal}
                  task={task}
                />
              ))
            ) : (
              <div className="text-center">No tasks at the moment</div>
            )}
          </div>
        </div>
        <div>
          <h2 className="font-bold text-lg my-5">Completed Tasks</h2>
          {fetchError && (
            <div className="text-red-500 text-center">Something went wrong</div>
          )}
          <div>
            {tasksCompleted.length ? (
              tasksCompleted?.map((task) => <Task key={task.id} task={task} />)
            ) : (
              <div className="text-center">No tasks at the moment</div>
            )}
          </div>
        </div>
        <div className="fixed bottom-5 z-50  rounded  w-full left-0 flex flex-col justify-center items-center">
          <button
            type="button"
            onClick={() => {
              setShowAddTaskModal(true);
            }}
            className="bg-white p-3 rounded-full"
          >
            <BiMessageSquareAdd className="text-green-500 bg-white" size={50} />
          </button>
        </div>
      </div>
      {/* Toast Notification, Edit-task and Add-task modals */}
      <ToastContainer />
      <EditModal
        setShowUpdateModal={setShowUpdateModal}
        showUpdateModal={showUpdateModal}
        task={taskToUpdate}
      />
      <AddTaskModal
        showAddTaskModal={showAddTaskModal}
        setShowAddTaskModal={setShowAddTaskModal}
      />
    </div>
  </div>
);
```

In the code above, `line 13` checks if there is a fetch error and displays “Something went wrong” if there is. `line 32` maps through the `taskCompleted` state variable and displays the tasks already completed. `line 43` displays our toast. `line 44` displays our `EditModal` and `line 45` displays our `AddToTaskModal`. Both the `EditModal` and `AddTaskModal` take some props.

Here is what our home page will look like but we still need to add the rest of the components so let's keep going.

![home-page.png](/uploads/home_page_6987c3de64.png)


The full code to our `App.js` can be found below:

[https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/e56afcbd852fa82bbecd609cb56fc2df](https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/e56afcbd852fa82bbecd609cb56fc2df)

## Adding a Task

Now we want to be able to add task to our collection. To do this we have to first create a utility file to show a toast error when the fields required are not provided. Inside the `src` folder, create a folder called `utils` and create a file inside of it called `showFieldsError.js` and add the following:

```javascript
// path: ./src/utils/showFieldsError.js

import { toast } from "react-toastify";
const showFieldsError = () => {
  toast.error("😭 Please enter all field(s)", {
    position: "top-right",
    autoClose: 5000,
    hideProgressBar: false,
    closeOnClick: true,
    pauseOnHover: true,
    draggable: true,
    progress: undefined,
    theme: "light",
  });
};
export default showFieldsError;
```

Now, let us create the `AddTaskModal` which will allow us add a task. Inside the `src` folder, create a folder called `components`. And inside this new folder, create a file called `AddTaskModal.js` and add the following:

```jsx
    // path: ./src/components/AddTaskModal.js

    import React, { useRef, useState } from 'react';
    import axios from 'axios';
    import showFieldsError from '../utils/showFieldsError';
    const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
    export default function AddTaskModal({ showAddTaskModal, setShowAddTaskModal }) {
      const [task, setTask] = useState({ title: '', taskTime: 0, realTime: 0 });
      const formRef = useRef(null);
      const handleSubmit = async (e) => {
        e.preventDefault();
        try {
          // check all fields are complete
          if (task.title.trim() === '' || task.taskTime === '') {
            showFieldsError();
            return;
          }
          // convert to milliseconds
          const realTime = Date.now() + 1000 * 60 * task.taskTime;
          task.realTime = realTime;
          // create a task
          await axios.post(`${serverUrl}/api/tasks`, {
            data: task,
          });
          formRef.current.reset();
          setTask({ title: '', taskTime: 0 });
          setShowAddTaskModal(false);
          window.location.reload(false);
        } catch (err) {
          alert(err.message);
        }
      };
      const handleChange = (e) => {
        setTask((prev) => ({ ...prev, [e.target.name]: e.target.value }));
      };
      return (
      )
    }

```

From the code above:

- Line 5: we import the `showFieldsdError` utility.
- line 7: remember that we added some props to our `AddTaskModal` in our `App.js` file. Here, we bring in the props `showAddTaskModal` and `setShowAddTaskModal` which basically shows this modal or hides it. Then we export the component.
- line 8: we create the state variable for the task we want to add by setting its `title` , `taskTime` and `realTime` properties to nothing.
- line 9: we create a ref for our form. This is so that we can reset the form of our modal after a task has been added.
- line 10-32: here, we create the function to handle the form when submitted. It first checks to see if the form fields are empty and displays the corresponding toast. It then converts the time added by the user to a real time in milliseconds. In `line 22` it makes a POST request to add a task by passing the task with its properties. And finally, we reset the form, set back the state task variable to nothing and modal display false and refresh our page using the `window.location.refresh(false)` function.
- line 33: this is the `handleChange` method that will be responsible for changing the form inputs and setting the state variable properties.

Now let us add the following JSX to the return statement.

```jsx
// path: ./src/components/AddTaskModal.js

return (
  <div
    className={`${
      showAddTaskModal ? "visible opacity-100 " : "invisible opacity-0  "
    } bg-black bg-opacity-40 h-screen fixed w-full flex justify-center items-center transition-all duration-500`}
  >
    <div className="p-10 shadow-md bg-white rounded relative">
      <span className="font-bold">Add Task</span>
      <form ref={formRef} onSubmit={handleSubmit}>
        <div className="relative  my-5 mb-3 ">
          <label>Title</label>
          <textarea
            minLength={0}
            maxLength={50}
            onChange={handleChange}
            value={task.title}
            name="title"
            className="w-full rounded-md border h-32 p-3 mb-5"
          />
          <span className="text-sm absolute bottom-0 right-0 font-thin text-slate-400">
            {task.title.length}
            /50
          </span>
        </div>
        <div>
          <label>duration</label>
          <div className="flex justify-between items-center">
            <input
              onChange={handleChange}
              value={task.taskTime}
              name="taskTime"
              type="number"
              className="w-2/3 border rounded-md p-2"
            />
            <span>(minutes)</span>
          </div>
        </div>
        <button
          type="submit"
          className="p-1 text-green-500 border border-green-500 bg-white rounded my-5"
        >
          Add Task
        </button>
      </form>
      <button
        type="button"
        onClick={() => {
          setShowAddTaskModal(false);
        }}
        className="absolute top-2 right-2 border border-red-500 p-1 text-red-500"
      >
        Close
      </button>
    </div>
  </div>
);
```

The full code to our `AddTaskModal` can be found here:

[https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/f026db1ed39d2512bf068ddd775208f8](https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/f026db1ed39d2512bf068ddd775208f8)

And this is what our page should look like:

![adding-task-modal.gif](/uploads/adding_task_modal_88fc50795b.gif)

## Displaying a Task

Now we have been able to implement the adding of task. We also want to be able to display a task.
For this reason, we have to create the file `Task.js` which will serve as a component to display our task. Inside the `components` folder, create a file `Task.js`.

```jsx
    // path: ./src/components/Task.js

    import React, { useEffect, useState, useCallback } from 'react';
    import { BiEditAlt } from 'react-icons/bi';
    import { BsCheck2Square } from 'react-icons/bs';
    import { RxTrash } from 'react-icons/rx';
    import Tooltip from 'react-simple-tooltip';
    import axios from 'axios';
    import moment from 'moment';
    const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
    export default function Task({
      task, setShowUpdateModal, setTaskToUpdate,
    }) {
      const [count, setCount] = useState(0);
      const [timeString, setTimeString] = useState('');

      // get moment time created
      let dateCreated = new Date(task?.attributes?.dateCreated);
      dateCreated = moment(dateCreated).calendar();

      // get the time in string of minutes and seconds
      const getTimeString = useCallback(() => {
        console.log('get time string do run oo');
        const mins = Math.floor((parseInt(task?.attributes?.realTime) - Date.now()) / 60000);
        const seconds = Math.round((parseInt(task?.attributes?.realTime) - Date.now()) / 1000) % 60;
        if (mins <= 0 && seconds <= 0) {
          setTimeString('done');
          return;
        }
        let timeString = 'starting counter';
        timeString = `${mins} min ${seconds} secs`;
        setTimeString(timeString);
      }, [task]);

      useEffect(() => {
        // set timer to run every second
        const timer = setInterval(() => {
          setCount((count) => count + 1);
          getTimeString();
        }, 1000);
        return () => clearInterval(timer);
      }, [getTimeString]);
      return (
      )
    }
```

In the code above, `line 10-13` allowed us to make use of the props `task` which represents the particular task. The `*setShowUpdateModal*` which should hide or display the modal for updating a task. And the setter function `*setTaskToUpdate*` that will set any task we want to update.

In `line 14`, we create our counter `count` that will be used to set a countdown for our tasks as they begin. And in `line 15`, we create a state variable `timeString` and its corresponding setter function that will display the time remaining for a task to expire in minutes and seconds.

Looking at `line 18-19` , we got the calendar time using `moment.calender()` method after converting the `dateCreated` string to an object.

`line 22-33` , here we create the `getTimeString()` function which basically sets the value for the `timeString` state variable. It does this by converting the real time of the task to current minutes and seconds. Note that we used the `useCallback()` react hook to memoize the `getTimeString()` function. This is so that `Task.js` component will not re-render for every count of our counter. Also it helps us to avoid performance issue in our application.

And lastly, in `line 34-42`, using the `useEffect()` hook, we pass the `getTimeString()` function as a dependency. This is so that our component will not re-render every second as regards the timer. It will only re-render if getTimeString() changes. And it won’t change unless its own dependency which is `task` changes. So for every second, the `getTimeString()` gets the time in string for a particular task.

Now add the following to our `Task.js` component.

```jsx
    // path: ./src/components/Task.js
    ...
     return (
      <div className={`${task?.attributes?.completed ? ' bg-green-500 text-white ' : ' bg-white '} scale-in-center p-8 hover:shadow-xl rounded-3xl shadow-md my-5 relative`}>
          <div className="flex justify-center w-full">
            <span className="absolute top-0 text-sm font-thin border p-1">{dateCreated}</span>
          </div>
          <div className="flex items-center justify-between mb-5">
            {!task?.attributes?.completed ? (
              <div className="w-1/4">
                <Tooltip content="😎 Mark as completed!"><button type="button" disabled={task?.attributes?.completed} onClick={markCompleted} className=" p-2 rounded"><BsCheck2Square className="text-2xl hover:text-green-500" /></button></Tooltip>
              </div>
            ) : null}
            <div className="w-2/4 flex flex-col">
              <span className="text-focus-in font-bold text-base">{task?.attributes?.title}</span>
              {parseInt(task?.attributes?.realTime) ? <span className="font-thin">{timeString}</span> : <span className="font-thin">done</span>}
            </div>
            <div className="w-1/4 flex justify-end">
              {!task?.attributes?.completed ? (
                <Tooltip content="Edit task title!">
                  {' '}
                  <button type="button" onClick={() => { setTaskToUpdate(task); setShowUpdateModal(true); }}><BiEditAlt className="text-2xl hover:text-blue-500" /></button>
                </Tooltip>
              ) : null}
              <Tooltip content="🙀 Delete this task?!"><button type="button" onClick={handleDelete}><RxTrash className="text-2xl hover:text-red-500" /></button></Tooltip>
            </div>
          </div>
          <p className="absolute bottom-0 my-3 text-center text-sm w-full left-0">
            {task?.attributes?.taskTime}
            {' '}
            minute(s) task
          </p>
        </div>
    )
```

Here is what our application should look like when a task is added:

![tasks.png](/uploads/tasks_1f2f41076f.png)

> Note that in `line 6` of the code above, we used displayed the time formatted by moment.

## Marking a Task as completed

Inside the `Task.js` file, we want to add a function that will allow a task to be completed when a certain button is clicked. Enter the code below inside the `Task.js` file.

```jsx
// path: ./src/components/Task.js

// mark task as completed
const markCompleted = async () => {
  try {
    const res = await axios.put(`${serverUrl}/api/tasks/${task?.id}`, {
      data: {
        completed: true,
        realTime: 0,
      },
    });
    const { chrome } = window;
    let badgeValue = 0;
    chrome?.action?.getBadgeText({}, (badgeText) => {
      badgeValue = parseInt(badgeText);
    });
    badgeValue = (badgeValue + 1).toString();
    chrome.action?.setBadgeText({ text: badgeValue });
    window.location.reload(false);
  } catch (err) {
    alert(err.message);
  }
};
```

This function has already been passed to a button in our code. See a demo of what the function does below:

![mark-complete-task.gif](/uploads/mark_complete_task_43e68600bc.gif)

## Deleting a Task

Along with marking a task as completed, a task can as well be deleted. Add the function below to the `Task.js` file.

```jsx
// path: ./src/components/Taks.js

// delete task
const handleDelete = async () => {
  try {
    const res = await axios.delete(`${serverUrl}/api/tasks/${task?.id}`, {
      data: task,
    });
    window.location.reload(false);
  } catch (err) {
    alert(err.message);
  }
};
```

## Editing a Task

Now we want to add the option of editing a task. To do this, we finally create the `EditModal.js` file we have already imported inside of our `App.js`. This will be responsible for displaying of the modal that will allow us to edit our app. Remember also the `setTaskToUpdate` props added to the `Task.js` component. When the edit icon of a task is clicked, this setter function sets the task to the one we want to edit.

Now paste the code below:

```jsx
// path: ./src/components/EditModal.js

import { useState } from "react";
import axios from "axios";
const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
export default function EditModal({
  setShowUpdateModal,
  showUpdateModal,
  task,
}) {
  const [title, setTitle] = useState(task?.attributes?.title);
  const [taskTime, setTaskTime] = useState();
  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      if (title === "") {
        alert("Please enter a title");
        return;
      }
      const taskTimeToNumber = parseInt(taskTime) || task?.attributes?.taskTime;
      const realTime = Date.now() + 1000 * 60 * parseInt(taskTimeToNumber);
      const res = await axios.put(`${serverUrl}/api/tasks/${task?.id}`, {
        data: {
          taskTime: taskTimeToNumber,
          realTime,
          title: title?.toString().trim(),
        },
      });
      setShowUpdateModal(false);
      window.location.reload(false);
    } catch (err) {
      alert("Somethind went wrong");
    }
  };
  return (
    <div
      className={`${
        showUpdateModal ? "visible opacity-100 " : "invisible opacity-0  "
      } bg-black bg-opacity-40 h-screen fixed w-full flex justify-center items-center transition-all duration-500`}
    >
      <div className="p-10 shadow-md bg-white rounded relative">
        <span className="font-bold">Update Task</span>
        <form onSubmit={handleSubmit}>
          <div className="relative  my-5 ">
            <label>Title</label>
            <textarea
              onChange={(e) => {
                setTitle(e.target.value);
              }}
              defaultValue={task?.attributes?.title}
              className="w-full border h-32 p-3 mb-5"
            />
            <span className="text-sm absolute bottom-0 right-0 font-thin text-slate-400">
              {title?.length ? title?.length : task?.attributes?.title?.length}
              /50
            </span>
          </div>
          <div>
            <label>duration</label>
            <div className="flex justify-between items-center">
              <input
                min={0}
                onChange={(e) => {
                  setTaskTime(e.target.value);
                }}
                defaultValue={task?.attributes?.taskTime}
                name="taskTime"
                type="number"
                className="w-2/3 border rounded-md p-2"
              />
              <span>(minutes)</span>
            </div>
          </div>
          <button
            className="p-1 text-green-500 border border-green-500 bg-white rounded my-5"
            type="submit"
          >
            Update
          </button>
        </form>
        <button
          type="button"
          onClick={() => {
            setShowUpdateModal(false);
          }}
          className="absolute top-2 right-2 border border-red-500 p-1"
        >
          Close
        </button>
      </div>
    </div>
  );
}
```

In `line 6`, we import we make use of the `setShowUpdateModal` and `showUpdateModal` which are responsible for hiding or displaying our edit modal. Then we also make use of the `task` prop which represents the current task we want to update.

For ` line``s ` `7` and `line 8`, we created state variables for the input title and the task time. These two will be what will be updated when sent to the server.

`line 9` is where we create the edit function called `handleSubmit()`. This makes the request to edit the task. If successful, it reloads the page. If there was an error, an alert will be shown.

Here is what our `EditModal` component looks like:

![preview-edit.png](/uploads/preview_edit_b1a3eaac58.png)

Now our application is ready! We can now convert it to a Chrome extension!

## Creating the Manifest.json file

In order for our application to work as an extension, we need to create a `manifest.json` file.
It is a JSON-formatted file that contains important information about the extension, such as its name, version, permissions, and other details. There are basically 3 different kinds of manifest.json file listed below:

    - Manifest v1: This is the first iteration of the "manifest.json" file, which was first introduced in 2010 with the introduction of Chrome extensions. Chrome continues to support it, but there are some restrictions on its functionality and security.
    - Manifest v2: Released in 2012, this version provides better security and functionality than Manifest v1. It unveiled a number of new features, including inline installation prevention and content security policies.
    - Manifest v3, which was released in 2019 and promises to significantly enhance Chrome extensions' security, privacy, and performance. It brings about a number of changes, such as a declarative API model that restricts extensions' ability to directly alter web pages.

For the purpose of our application, we will use version 3.

Now inside the public folder of our application, locate the `manifest.json` file and replace the contents with this:

```json
// path: ./public/manifest.json

{
  "name": "Task Time Tracker",
  "description": "Powerful task tracker!",
  "version": "0.0.0.1",
  "manifest_version": 3,
  "action": {
    "default_popup": "index.html",
    "default_title": "Check your tasks!"
  },
  "icons": {
    "16": "logo.png",
    "48": "logo.png",
    "128": "logo.png"
  }
}
```

In the file above, here is what each of the fields means.

- “name”: this stands for the name of the Chrome extension we are building.
- “description”: this is the description of our application.
- “version”: this stands for the current version of our application.
- “manifest_version”: here we specify the version of manifest to use.
- "action": This field specifies what happens when the extension icon is clicked. In this case, it opens a popup window with the file "index.html", and sets the default title to "Check your tasks!". We set it to “index.html” because that is what React creates for us when run the build command as we will see below:
- "icons": The icons used for the extension are specified in this field in various sizes (16, 48, and 128 pixels in this case). "logo.png" is the icon file that is utilized for all sizes.

> Note: we are using the PNG image file `logo.png` as the logo or icon for our chrome extension. see the image below

![icon.png](/uploads/icon_52b0424da7.png)

## Generating a Build File

Now since every chrome extension requires a single HTML file, we need to tell React that we need our application in a single HTML file. To do this, we have to run the build command.

```bash
    npm run build
```

This will generate a build folder. Now, this folder is where our `index.html` resides. See image below:

![generated-build-folder.png](/uploads/generated_build_folder_627e094572.png)

## Adding our Application to Google Chrome Extension

Now that we have generated the build folder. We need to add our application to the Chrome extension.
Click on the extension bar icon at the top right of our Chrome browser. See the icon circled red in the image below:

![extemsion-bar.png](/uploads/extemsion_bar_97187dba12.png)

When we click the icon, you should see a popup like the one shown below, now click the “Manage Extensions”.

![manage-extension.png](/uploads/manage_extension_c2b504002f.png)

Once that is done. We will see the page below. Make sure to toggle the “developer mode” first, then click on the “load unpacked”.

![enable-develop-mode.png](/uploads/enable_develop_mode_e3d4b5c70a.png)

Now we select the directory to the build folder of our app.

![select-build-folder.png](/uploads/select_build_folder_c84f291c0f.png)

If that was successful, our extension should be ready! Hurray!

![extension-ready.png](/uploads/extension_ready_86e62de6be.png)

Presently our extension is not displayed in the extension bar or menu. To do this, we have to pin our extension. Click the extension icon once again scroll down and you will see our new extension. Now click the pin button to pin it to the extension bar.

![pinning-extension.png](/uploads/pinning_extension_41981f02d2.png)

Finally we should be able to see our extension on the extension bar.

![display-extension.png](/uploads/display_extension_55e27e0305.png)

## Testing our Application (Demo)

Here is the demo of our application!

[https://youtu.be/gwTy6at6N-U](https://youtu.be/gwTy6at6N-U)

## Conclusion

In this tutorial, we have been able to learn about Chrome Extensions, ReactJs, Tailwind CSS, a manifest.json file, and how to build a Chrome extension. Most importantly, we have seen the power of Strapi once again in another use case. Go ahead and build amazing extensions!