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.
For us to continue, we need the following:
https://github.com/Theodore-Kelechukwu-Onyejiaku/Task-Tracker-Chrome-Extension
Strapi is an open-source headless CMS that simplifies backend content management and API creation. It provides RESTful APIs out of the box and supports GraphQL, making it ideal for backend development.
ReactJS is a popular JavaScript library for building interactive user interfaces. Its component-based approach allows for efficient UI development, making it suitable for building web applications and extensions.
By integrating Strapi with ReactJS, we can create a dynamic and responsive Chrome extension that interacts seamlessly with our backend API.
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. 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.
We are building a simple Task Time Tracker. Below are the features of our application.
Note: a badge here refers to the character we see on the icon of some chrome extensions.
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.
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.
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.
Using Strapi and ReactJS offers several advantages:
Efficient Backend Management: Strapi simplifies backend development with its easy-to-use admin panel and auto-generated RESTful APIs.
Dynamic Frontend Development: ReactJS allows for building interactive and responsive user interfaces, ideal for extensions that require real-time updates.
Seamless Integration: Strapi and ReactJS integrate smoothly, enabling efficient data fetching and manipulation.
Scalability: Both technologies support scalable application development, making it easier to add new features in the future.
Installing Strapi is just the same way we install other NPM packages. We will have to open our CLI to run the command below:
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:
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/
.
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.
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.
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.
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:
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.
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
:
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:
1// path: ./src/api/task/controllers/task.js
2
3"use strict";
4/**
5 * task controller
6 */
7const { createCoreController } = require("@strapi/strapi").factories;
8// import moment
9const moment = require("moment");
10module.exports = createCoreController("api::task.task", ({ strapi }) => ({
11 async create(ctx) {
12 const { data } = ctx.request.body;
13 // convert to date with Moment
14 const dateCreated = moment(new Date());
15 // convert to string
16 data.dateCreated = dateCreated.toString();
17 // create or save task
18 let newTask = await strapi.service("api::task.task").create({ data });
19 const sanitizedEntity = await this.sanitizeOutput(newTask, ctx);
20 return this.transformResponse(sanitizedEntity);
21 },
22}));
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
.
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:
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.
cd task-time-tracker
npm start
If the command is successful, we should see the following open up in our browser:
Bravo! We are now ready to create our application!
Presently, we have to install Tailwind CSS. Enter the command below to stop our application:
command c // (for mac)
## OR
control c // (for windows)
Install Tailwind using the command below:
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.
1// path: ./tailwind.config.js
2
3/** @type {import('tailwindcss').Config} */
4module.exports = {
5 content: ["./src/**/*.{js,jsx,ts,tsx}"],
6 theme: {
7 extend: {},
8 },
9 plugins: [],
10};
Lastly, add this to the index.css
file in our root folder.
1// path: ./index.css
2
3@tailwind base;
4@tailwind components;
5@tailwind utilities;
Also, our application needs the following dependencies to run:
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.
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:
REACT_APP_STRAPI_SERVER=http://localhost:1337
Now, Head over to the App.js
file and add the following:
1// path: ./src/App.js
2
3import { useEffect, useState } from "react";
4import { BiMessageSquareAdd } from "react-icons/bi";
5import axios from "axios";
6import { ToastContainer } from "react-toastify";
7import Task from "./components/Task";
8import EditModal from "./components/EditModal";
9import AddTaskModal from "./components/AddTaskModal";
10import "react-toastify/dist/ReactToastify.css";
11const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
In the code above:
axios
that will help us make a HTTP request.react-toastify
is imported.Task
component was imported. This will display a task on the page.EditModal
component which we will create shortly. This represents a modal to edit a task.AddTaskModal
which is modal to add a task is also imported.react-toastify
is imported..env
file as serverURL
.Now let us add our State variables:
1 // path: ./src/App.js
2
3 ...
4 function App() {
5 const [tasks, setTasks] = useState([]);
6 const [fetchError, setFetchError] = useState([]);
7 const [tasksCompleted, setTasksCompleted] = useState([]);
8 const [taskToUpdate, setTaskToUpdate] = useState({});
9 const [showUpdateModal, setShowUpdateModal] = useState(false);
10 const [showAddTaskModal, setShowAddTaskModal] = useState(false);
11
12 return (
13 <div>
14 </div>
15 )
16 }
17 export default App;
From the code above:
tasksCompleted
and setTaskCompleted
which should display the tasks that have been completed on the page and the setter function.taskToUpdate
will represent a task we want to update at the click of a button. And also its corresponding setter function.Now add the following functions before the return
statement:
1// path: ./src/App.js
2
3// function to update extension badge
4const updateChromeBadge = (value) => {
5 const { chrome } = window;
6 chrome.action?.setBadgeText({ text: value });
7 chrome.action?.setBadgeBackgroundColor({ color: "##fff" });
8};
9// function to get all tasks
10const getAllTask = async () => {
11 setFetchError("");
12 try {
13 // fetch tasks from strapi
14 const res = await axios.get(`${serverUrl}/api/tasks`);
15 // get result from nested object destructuring
16 const {
17 data: { data },
18 } = res;
19 // get tasks that have not been completed
20 const tasks = data
21 .filter((task) => !task.attributes.completed)
22 .map((task) => {
23 // check if task has expired
24 if (Date.now() > parseInt(task?.attributes?.realTime)) {
25 task.attributes.realTime = 0;
26 return task;
27 }
28 return task;
29 });
30 // get completed tasks
31 const completedTasks = data.filter((task) => task.attributes.completed);
32 setTasksCompleted(completedTasks);
33 updateChromeBadge(completedTasks.length.toString());
34 setTasks(tasks.reverse());
35 } catch (err) {
36 setFetchError(err.message);
37 }
38};
39useEffect(() => {
40 // get all tasks
41 getAllTask();
42}, []);
In the code shown above:
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.useEffect
which fires the getAllTask()
function as soon as the component is mounted.Finally, add the following JSX to our App.js
.
1// path: ./src/App.js
2
3return (
4 <div>
5 <div className="w-full flex justify-center rounded">
6 <div className="w-96 sm:w-1/3 overflow-scroll border p-5 mb-20 relative bg-slate-50">
7 <div className="">
8 <h1 className="text-focus-in text-4xl font-bold text-slate-900">
9 Task Time Tracker
10 </h1>
11 <span className="text-slate-400">Seize The Day!</span>
12 </div>
13 <div>
14 <h1 className="font-bold text-lg my-5">Tasks</h1>
15 {fetchError && (
16 <div className="text-red-500 text-center">Something went wrong</div>
17 )}
18 <div>
19 {tasks.length ? (
20 tasks?.map((task) => (
21 <Task
22 key={task.id}
23 updateChromeBadge={updateChromeBadge}
24 setTaskToUpdate={setTaskToUpdate}
25 setShowUpdateModal={setShowUpdateModal}
26 showUpdateModal={showUpdateModal}
27 task={task}
28 />
29 ))
30 ) : (
31 <div className="text-center">No tasks at the moment</div>
32 )}
33 </div>
34 </div>
35 <div>
36 <h2 className="font-bold text-lg my-5">Completed Tasks</h2>
37 {fetchError && (
38 <div className="text-red-500 text-center">Something went wrong</div>
39 )}
40 <div>
41 {tasksCompleted.length ? (
42 tasksCompleted?.map((task) => <Task key={task.id} task={task} />)
43 ) : (
44 <div className="text-center">No tasks at the moment</div>
45 )}
46 </div>
47 </div>
48 <div className="fixed bottom-5 z-50 rounded w-full left-0 flex flex-col justify-center items-center">
49 <button
50 type="button"
51 onClick={() => {
52 setShowAddTaskModal(true);
53 }}
54 className="bg-white p-3 rounded-full"
55 >
56 <BiMessageSquareAdd className="text-green-500 bg-white" size={50} />
57 </button>
58 </div>
59 </div>
60 {/* Toast Notification, Edit-task and Add-task modals */}
61 <ToastContainer />
62 <EditModal
63 setShowUpdateModal={setShowUpdateModal}
64 showUpdateModal={showUpdateModal}
65 task={taskToUpdate}
66 />
67 <AddTaskModal
68 showAddTaskModal={showAddTaskModal}
69 setShowAddTaskModal={setShowAddTaskModal}
70 />
71 </div>
72 </div>
73);
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.
The full code to our App.js
can be found below:
https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/e56afcbd852fa82bbecd609cb56fc2df
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:
1// path: ./src/utils/showFieldsError.js
2
3import { toast } from "react-toastify";
4const showFieldsError = () => {
5 toast.error("😭 Please enter all field(s)", {
6 position: "top-right",
7 autoClose: 5000,
8 hideProgressBar: false,
9 closeOnClick: true,
10 pauseOnHover: true,
11 draggable: true,
12 progress: undefined,
13 theme: "light",
14 });
15};
16export 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:
1 // path: ./src/components/AddTaskModal.js
2
3 import React, { useRef, useState } from 'react';
4 import axios from 'axios';
5 import showFieldsError from '../utils/showFieldsError';
6 const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
7 export default function AddTaskModal({ showAddTaskModal, setShowAddTaskModal }) {
8 const [task, setTask] = useState({ title: '', taskTime: 0, realTime: 0 });
9 const formRef = useRef(null);
10 const handleSubmit = async (e) => {
11 e.preventDefault();
12 try {
13 // check all fields are complete
14 if (task.title.trim() === '' || task.taskTime === '') {
15 showFieldsError();
16 return;
17 }
18 // convert to milliseconds
19 const realTime = Date.now() + 1000 * 60 * task.taskTime;
20 task.realTime = realTime;
21 // create a task
22 await axios.post(`${serverUrl}/api/tasks`, {
23 data: task,
24 });
25 formRef.current.reset();
26 setTask({ title: '', taskTime: 0 });
27 setShowAddTaskModal(false);
28 window.location.reload(false);
29 } catch (err) {
30 alert(err.message);
31 }
32 };
33 const handleChange = (e) => {
34 setTask((prev) => ({ ...prev, [e.target.name]: e.target.value }));
35 };
36 return (
37 )
38 }
From the code above:
showFieldsdError
utility.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.title
, taskTime
and realTime
properties to nothing.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.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.
1// path: ./src/components/AddTaskModal.js
2
3return (
4 <div
5 className={`${
6 showAddTaskModal ? "visible opacity-100 " : "invisible opacity-0 "
7 } bg-black bg-opacity-40 h-screen fixed w-full flex justify-center items-center transition-all duration-500`}
8 >
9 <div className="p-10 shadow-md bg-white rounded relative">
10 <span className="font-bold">Add Task</span>
11 <form ref={formRef} onSubmit={handleSubmit}>
12 <div className="relative my-5 mb-3 ">
13 <label>Title</label>
14 <textarea
15 minLength={0}
16 maxLength={50}
17 onChange={handleChange}
18 value={task.title}
19 name="title"
20 className="w-full rounded-md border h-32 p-3 mb-5"
21 />
22 <span className="text-sm absolute bottom-0 right-0 font-thin text-slate-400">
23 {task.title.length}
24 /50
25 </span>
26 </div>
27 <div>
28 <label>duration</label>
29 <div className="flex justify-between items-center">
30 <input
31 onChange={handleChange}
32 value={task.taskTime}
33 name="taskTime"
34 type="number"
35 className="w-2/3 border rounded-md p-2"
36 />
37 <span>(minutes)</span>
38 </div>
39 </div>
40 <button
41 type="submit"
42 className="p-1 text-green-500 border border-green-500 bg-white rounded my-5"
43 >
44 Add Task
45 </button>
46 </form>
47 <button
48 type="button"
49 onClick={() => {
50 setShowAddTaskModal(false);
51 }}
52 className="absolute top-2 right-2 border border-red-500 p-1 text-red-500"
53 >
54 Close
55 </button>
56 </div>
57 </div>
58);
The full code to our AddTaskModal
can be found here:
https://gist.github.com/Theodore-Kelechukwu-Onyejiaku/f026db1ed39d2512bf068ddd775208f8
And this is what our page should look like:
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
.
1 // path: ./src/components/Task.js
2
3 import React, { useEffect, useState, useCallback } from 'react';
4 import { BiEditAlt } from 'react-icons/bi';
5 import { BsCheck2Square } from 'react-icons/bs';
6 import { RxTrash } from 'react-icons/rx';
7 import Tooltip from 'react-simple-tooltip';
8 import axios from 'axios';
9 import moment from 'moment';
10 const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
11 export default function Task({
12 task, setShowUpdateModal, setTaskToUpdate,
13 }) {
14 const [count, setCount] = useState(0);
15 const [timeString, setTimeString] = useState('');
16
17 // get moment time created
18 let dateCreated = new Date(task?.attributes?.dateCreated);
19 dateCreated = moment(dateCreated).calendar();
20
21 // get the time in string of minutes and seconds
22 const getTimeString = useCallback(() => {
23 console.log('get time string do run oo');
24 const mins = Math.floor((parseInt(task?.attributes?.realTime) - Date.now()) / 60000);
25 const seconds = Math.round((parseInt(task?.attributes?.realTime) - Date.now()) / 1000) % 60;
26 if (mins <= 0 && seconds <= 0) {
27 setTimeString('done');
28 return;
29 }
30 let timeString = 'starting counter';
31 timeString = `${mins} min ${seconds} secs`;
32 setTimeString(timeString);
33 }, [task]);
34
35 useEffect(() => {
36 // set timer to run every second
37 const timer = setInterval(() => {
38 setCount((count) => count + 1);
39 getTimeString();
40 }, 1000);
41 return () => clearInterval(timer);
42 }, [getTimeString]);
43 return (
44 )
45 }
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.
1 // path: ./src/components/Task.js
2 ...
3 return (
4 <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`}>
5 <div className="flex justify-center w-full">
6 <span className="absolute top-0 text-sm font-thin border p-1">{dateCreated}</span>
7 </div>
8 <div className="flex items-center justify-between mb-5">
9 {!task?.attributes?.completed ? (
10 <div className="w-1/4">
11 <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>
12 </div>
13 ) : null}
14 <div className="w-2/4 flex flex-col">
15 <span className="text-focus-in font-bold text-base">{task?.attributes?.title}</span>
16 {parseInt(task?.attributes?.realTime) ? <span className="font-thin">{timeString}</span> : <span className="font-thin">done</span>}
17 </div>
18 <div className="w-1/4 flex justify-end">
19 {!task?.attributes?.completed ? (
20 <Tooltip content="Edit task title!">
21 {' '}
22 <button type="button" onClick={() => { setTaskToUpdate(task); setShowUpdateModal(true); }}><BiEditAlt className="text-2xl hover:text-blue-500" /></button>
23 </Tooltip>
24 ) : null}
25 <Tooltip content="🙀 Delete this task?!"><button type="button" onClick={handleDelete}><RxTrash className="text-2xl hover:text-red-500" /></button></Tooltip>
26 </div>
27 </div>
28 <p className="absolute bottom-0 my-3 text-center text-sm w-full left-0">
29 {task?.attributes?.taskTime}
30 {' '}
31 minute(s) task
32 </p>
33 </div>
34 )
Here is what our application should look like when a task is added:
Note that in
line 6
of the code above, we used displayed the time formatted by moment.
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.
1// path: ./src/components/Task.js
2
3// mark task as completed
4const markCompleted = async () => {
5 try {
6 const res = await axios.put(`${serverUrl}/api/tasks/${task?.id}`, {
7 data: {
8 completed: true,
9 realTime: 0,
10 },
11 });
12 const { chrome } = window;
13 let badgeValue = 0;
14 chrome?.action?.getBadgeText({}, (badgeText) => {
15 badgeValue = parseInt(badgeText);
16 });
17 badgeValue = (badgeValue + 1).toString();
18 chrome.action?.setBadgeText({ text: badgeValue });
19 window.location.reload(false);
20 } catch (err) {
21 alert(err.message);
22 }
23};
This function has already been passed to a button in our code. See a demo of what the function does below:
Along with marking a task as completed, a task can as well be deleted. Add the function below to the Task.js
file.
1// path: ./src/components/Taks.js
2
3// delete task
4const handleDelete = async () => {
5 try {
6 const res = await axios.delete(`${serverUrl}/api/tasks/${task?.id}`, {
7 data: task,
8 });
9 window.location.reload(false);
10 } catch (err) {
11 alert(err.message);
12 }
13};
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:
1// path: ./src/components/EditModal.js
2
3import { useState } from "react";
4import axios from "axios";
5const serverUrl = process.env.REACT_APP_STRAPI_SERVER;
6export default function EditModal({
7 setShowUpdateModal,
8 showUpdateModal,
9 task,
10}) {
11 const [title, setTitle] = useState(task?.attributes?.title);
12 const [taskTime, setTaskTime] = useState();
13 const handleSubmit = async (e) => {
14 e.preventDefault();
15 try {
16 if (title === "") {
17 alert("Please enter a title");
18 return;
19 }
20 const taskTimeToNumber = parseInt(taskTime) || task?.attributes?.taskTime;
21 const realTime = Date.now() + 1000 * 60 * parseInt(taskTimeToNumber);
22 const res = await axios.put(`${serverUrl}/api/tasks/${task?.id}`, {
23 data: {
24 taskTime: taskTimeToNumber,
25 realTime,
26 title: title?.toString().trim(),
27 },
28 });
29 setShowUpdateModal(false);
30 window.location.reload(false);
31 } catch (err) {
32 alert("Somethind went wrong");
33 }
34 };
35 return (
36 <div
37 className={`${
38 showUpdateModal ? "visible opacity-100 " : "invisible opacity-0 "
39 } bg-black bg-opacity-40 h-screen fixed w-full flex justify-center items-center transition-all duration-500`}
40 >
41 <div className="p-10 shadow-md bg-white rounded relative">
42 <span className="font-bold">Update Task</span>
43 <form onSubmit={handleSubmit}>
44 <div className="relative my-5 ">
45 <label>Title</label>
46 <textarea
47 onChange={(e) => {
48 setTitle(e.target.value);
49 }}
50 defaultValue={task?.attributes?.title}
51 className="w-full border h-32 p-3 mb-5"
52 />
53 <span className="text-sm absolute bottom-0 right-0 font-thin text-slate-400">
54 {title?.length ? title?.length : task?.attributes?.title?.length}
55 /50
56 </span>
57 </div>
58 <div>
59 <label>duration</label>
60 <div className="flex justify-between items-center">
61 <input
62 min={0}
63 onChange={(e) => {
64 setTaskTime(e.target.value);
65 }}
66 defaultValue={task?.attributes?.taskTime}
67 name="taskTime"
68 type="number"
69 className="w-2/3 border rounded-md p-2"
70 />
71 <span>(minutes)</span>
72 </div>
73 </div>
74 <button
75 className="p-1 text-green-500 border border-green-500 bg-white rounded my-5"
76 type="submit"
77 >
78 Update
79 </button>
80 </form>
81 <button
82 type="button"
83 onClick={() => {
84 setShowUpdateModal(false);
85 }}
86 className="absolute top-2 right-2 border border-red-500 p-1"
87 >
88 Close
89 </button>
90 </div>
91 </div>
92 );
93}
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:
Now our application is ready! We can now convert it to a Chrome extension!
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:
1- 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.
2- 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.
3- 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:
1// path: ./public/manifest.json
2
3{
4 "name": "Task Time Tracker",
5 "description": "Powerful task tracker!",
6 "version": "0.0.0.1",
7 "manifest_version": 3,
8 "action": {
9 "default_popup": "index.html",
10 "default_title": "Check your tasks!"
11 },
12 "icons": {
13 "16": "logo.png",
14 "48": "logo.png",
15 "128": "logo.png"
16 }
17}
In the file above, here is what each of the fields means.
Note: we are using the PNG image file
logo.png
as the logo or icon for our chrome extension. see the image below
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.
npm run build
This will generate a build folder. Now, this folder is where our index.html
resides. See image below:
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:
When we click the icon, you should see a popup like the one shown below, now click the “Manage Extensions”.
Once that is done. We will see the page below. Make sure to toggle the “developer mode” first, then click on the “load unpacked”.
Now we select the directory to the build folder of our app.
If that was successful, our extension should be ready! Hurray!
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.
Finally we should be able to see our extension on the extension bar.
Here is the demo of our application!
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!
Theodore is a Technical Writer and a full-stack software developer. He loves writing technical articles, building solutions, and sharing his expertise.