React E-Commerce App for Digital Products: Part 3 (Create Product Management Dashboard)
Tags: react, webdev, ecommerce, digital
Published: 2024-01-31 07:21:28

---
title: React E-Commerce App for Digital Products: Part 3 (Create Product Management Dashboard)
published: true
date: 2024-01-23 14:37:13 UTC
tags: react, webdev, ecommerce, digital
canonical_url: https://www.syncfusion.com/blogs/post/react-ecommerce-app-part-3.aspx
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b60jdh1hvi6ihnwa4uja.png
---
Welcome to the third blog of the series “Designing a React E-Commerce App for Digital Products”. In the [first part](https://www.syncfusion.com/blogs/post/react-digital-ecommerce-app.aspx "Blog: Designing a React E-Commerce App for Digital Products—Part 1"), we created an architectural diagram to get an idea about the user navigation and wireframes. We also set up the boilerplate, complete with public and private routes.

In the [second part](https://www.syncfusion.com/blogs/post/react-ecommerce-app-part-2.aspx "Blog: Designing a React E-Commerce App for Digital Products—Part 2"), we delved into implementing the web app’s authentication and authorization. This involved the creation of signup and login pages and their integration with a database for data persistence and session maintenance.

In this part, we’ll focus on creating a product management dashboard. This will enable us to add, update, and preview products effectively.

## Designing the layout of the dashboard
Upon successful login, users will land on the dashboard page. It has a uniform layout for seamless navigation across various pages. To create this page, we’ll construct a wrapper encompassing a sidebar with navigational links, a header, and a main section for content display.

We’ll utilize Syncfusion’s [React Sidebar component](https://ej2.syncfusion.com/react/documentation/sidebar/getting-started "Syncfusion React Sidebar component") to create a docked drawer with a slight style modification. This wrapper will be employed across all pages within the admin section or post-login pages. We can use this wrapper as a standalone or [higher order component (HOC)](https://legacy.reactjs.org/docs/higher-order-components.html "Higher-Order Components in React").

In this instance, I’ve chosen to use it as a component, placed under each page rather than at the router level, as I believe it offers superior abstraction.

**Wrapper.js**

```js
import "@syncfusion/ej2-layouts/styles/material.css";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import "@syncfusion/ej2-base/styles/material.css";
import "@syncfusion/ej2-react-navigations/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import "./wrapper.css";
import { Link, NavLink } from "react-router-dom";
import { SidebarComponent } from "@syncfusion/ej2-react-navigations";

const Wrapper = ({ children }) => {
  return (
    <div className="wrapper">
      <header>
        <Link className="title" to="/dashboard" title="visit dashboard">
          Geva Digital Shop
        </Link>
        <span className="username">Hello Prashant</span>
      </header>
      <main>
        <SidebarComponent id="default-sidebar" className="e-card sidebar">
          <nav>
            <ul className="menu">
              <li>
                <NavLink
                  to="/dashboard"
                  className={({ isActive }) => (isActive ? "active" : "")}
                >
                  Dashboard
                </NavLink>
              </li>
              <li>
                <NavLink
                  to="/manage-products"
                  className={({ isActive }) => (isActive ? "active" : "")}
                >
                  Manage Products
                </NavLink>
              </li>
            </ul>
          </nav>
          <ButtonComponent
            cssClass="e-danger e-block"
            onClick={() => {}}
            style={{ fontSize: "1.2em" }}
          >
            Logout
          </ButtonComponent>
        </SidebarComponent>
        <section className="e-card" id="main-area">
          {children}
        </section>
      </main>
    </div>
  );
};

export default Wrapper;
```

**Wrapper.css**

```js
* {
  box-sizing: border-box;
}

.wrapper header {
  padding: 20px 30px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
  max-width: calc(100% - 40px);
  margin: 10px auto 0 auto;
}

.wrapper .title {
  font-size: 1.5em;
  text-decoration: none;
  color: #000;
  font-weight: bold;
}

.wrapper main {
  display: flex;
  align-items: stretch;
  justify-content: flex-start;
  flex-wrap: wrap;
  padding: 20px;
}

.wrapper .sidebar {
  flex: 0 250px;
  padding: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between !important;
  position: relative;
  height: auto;
}

.wrapper .username {
  font-size: 18px;
  font-weight: bold;
}

.wrapper section {
  flex: 1;
  height: calc(100vh - 120px);
  margin-left: 20px !important;
  padding: 10px;
}

.wrapper .menu {
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  padding: 0;
  flex-wrap: wrap;
}

.wrapper .menu > li {
  flex: 0 100%;
  list-style-type: none;
}

.wrapper .menu > li > a {
  text-align: center;
  padding: 10px;
  display: block;
  text-decoration: none;
  font-size: 1.2em;
  color: #000;
  background: #eee;
  border-bottom: 1px solid #f7f7f7;
  transition: all 0.2s ease;
}

.wrapper .menu > li > a.active {
  background-color: #3f3c3c;
  color: #fff;
}

.wrapper .menu > li > a:hover {
  background-color: #3f3c3c;
  color: #fff;
}

.e-card,
.e-card:hover {
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.5);
}

.e-card {
  justify-content: unset;
}

```

I want to design this platform with simplicity and user-friendliness in mind, featuring two primary pages:

- **Dashboard:** This home page serves as a comprehensive hub for all post-sales metrics. It gives users a snapshot of their sales performance, including key metrics and trends to help drive business decisions.
- **Manage Products:** This is a dedicated page where users can perform various product-related tasks. Users can add new products to the catalog, update existing product information, and preview product content before it is made live. This ensures that users have complete control over the products they offer.

In addition to these pages, we’ll incorporate a log out button for user convenience. This allows users to securely log out from the app, ensuring the safety of their account and data.

The header of our platform is designed to be informative and welcoming. On the left, we display the website title, reminding users of the platform they are on. On the right, we display the user’s name, adding a personal touch to the user experience. This balance of information and personalization helps create a comfortable and engaging environment for our users.

Refer to the following image.

![Dashboard layout page in React e-commerce app](https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Dashboard-layout-page-in-React-e-commerce-app-4.png)Now, we can create the previously mentioned pages and pass the content that will be visible inside the wrapper. The links will be highlighted on the active page.

## Designing the Dashboard page

Refer to the following code example to configure the **Dashboard** page **.**

**Dashboard.js**

```js
import Wrapper from "./wrapper";

const Dashboard = () => {
  return (
    <>
      <Wrapper>
        <h1>Dashboard 2</h1>
      </Wrapper>
    </>
  );
};

export default Dashboard;
```

Now, we include this page in the available routes that are accessible after login.

**App.js**

```js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./pages/login";
import Signup from "./pages/signup";
import PrivateRoutes from "./routes/private";
import PublicRoutes from "./routes/public";
import Dashboard from "./pages/dashboard";
import NotFound from "./pages/404";

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<PublicRoutes />}>
          <Route index element={<h1>Browse</h1>} />
          <Route path="product-list" element={<h1>Product List</h1>} />
          <Route path="login" element={<Login />} />
          <Route path="signup" element={<Signup />} />
        </Route>
        <Route path="/" element={<PrivateRoutes />}>
          <Route path="dashboard" element={<Dashboard />} />
          <Route path="checkout" element={<h1>checkout</h1>} />
          <Route path="thank-you" element={<h1>Thank You</h1>} />
        </Route>
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
};

export default App;
```

Now that we have the dashboard structure ready, we can start constructing the product management flow.

## Designing the Manage Products page

Let’s begin managing the products. We’ll divide the product management process into the following three distinct flows:

- Adding a new product.
- Editing an existing product.
- Previewing the created product.

Each flow will be categorized into individual entities, adhering to the [hook/view pattern](https://learnersbucket.com/examples/interview/container-and-presentational-pattern-in-react/ "Hooks/View Containerization Pattern in React"). This means the logic will be housed in the hook, while the view will be solely responsible for rendering the content.

First, we create the entry page for managing products, which we’ll call **manageProducts.js.**

**manageProducts.js**

```js
import { useState } from "react";
import Wrapper from "./wrapper";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import "./manageProduct.css";

const ManageProduct = () => {
  return (
    <Wrapper>
      <div className="manage-product"></div>
    </Wrapper>
  );
};

export default ManageProduct;
```

I incorporate this page into the routing configuration in the **App.js** file.

**App.js**

```js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import PrivateRoutes from "./routes/private";
import ManageProduct from "./pages/manageProduct";

const App = () => {
  return (
	<BrowserRouter>
  	 <Routes>
    	  <Route path="/" element={<PrivateRoutes />}>
      	   <Route path="manage-products" element={<ManageProduct />} />
    	  </Route>
  	 </Routes>
	</BrowserRouter>
  );
};

export default App;
```
### Add product

The first workflow we’re introducing under the Manage Products section is adding a new product. This process begins with a button in the page’s top-right corner. A modal will appear on the screen on clicking this button, rendering the **Add Product** component.

This modal is created using [Syncfusion’s React Dialog](https://www.syncfusion.com/react-components/react-modal-dialog "Syncfusion React Dialog") component. We’ve designed it as a common component to reuse during the product editing workflow, thus ensuring consistency and reducing redundancy.

Here’s the code for our **Modal.js** component.

```js
import "@syncfusion/ej2-base/styles/material.css";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import "@syncfusion/ej2-react-popups/styles/material.css";
import { DialogComponent } from "@syncfusion/ej2-react-popups";

const Modal = ({ showModal, handleClose, children }) => {
  const dialogClose = () => {
	handleClose();
  };

  return (
	<div className="modal" id="dialog-target">
  	 <DialogComponent
    	   width="95%"
    	   close={dialogClose}
    	   header="Fill the product details"
    	   target={"#main-area"}
    	   visible={!!showModal}
    	   showCloseIcon={true}
    	   position={{ X: "center", Y: "center" }}
  	 >
    	  {children}
  	 </DialogComponent>
	</div>
  );
};
export default Modal;
```

A modal will appear on the screen when clicking the **ADD NEW PRODUCT**  button. This modal serves as a container for the **ProductAdd** component. The **ProductAdd** component handles the functionality of adding a new product.

Here’s the code in the **manageProduct.js** file to accomplishes this.

```js
import { useState } from "react";
import Wrapper from "./wrapper";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import "./manageProduct.css";
import Modal from "../components/Modal";
import ProductAdd from "./productAdd";

const ManageProduct = () => {
  const [showModal, setShowModal] = useState(false);

  return (
	<Wrapper>
  	 <div className="manage-product">
    	  <div className="add-product">
      	   <ButtonComponent
        	cssClass="e-info"
        	onClick={() => {
          	setShowModal(true);
        	}}
        	style={{ fontSize: "1.2em" }}
      	   >
        	Add new product
      	   </ButtonComponent>
    	  </div>
    	  <Modal
      	     showModal={showModal}
      	     handleClose={() => {
        	setShowModal(false);
      	     }}
    	  >
      	    <ProductAdd />
    	  </Modal>
  	</div>
      </Wrapper>
  );
};

export default ManageProduct;
```

**manageProduct.css**

```js
.add-product {
  text-align: right;
  margin: 20px 0;
}
```

Refer to the following image.
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Designing-the-ADD-NEW-PRODUCT-button-in-the-React-e-commerce-app-1.png" alt="Designing the ADD NEW PRODUCT button in the React e-commerce app" style="width:100%">

  <figcaption>Designing the ADD NEW PRODUCT button in the React e-commerce app</figcaption>

</figure>

We’ve set up the initiation flow for adding a product. Let’s create the component to manage the product addition functionality. This component will encompass everything, from the input components to the API call required to create a new product.

The component will include four input fields and a rich text editor for the product description:

- Name of the product (input field)
- Price of the product (input field)
- Image URL of the product (input field)
- After purchase redirect URL (input field)
- Description of the product (rich text editor)

We won’t store any images in [SupaBase](https://supabase.com/ "SupaBase"). Instead, we will request the URLs of the images after the product’s sale. For example, after purchasing an ebook, the customer will be redirected to a Google Drive link that contains the downloadable PDF.

The data recorded by these input fields will be stored in the **SupaBase** database. As such, our first step will be to create a **products** table in the database. This table will store all the information related to the products.

```js
create table
  public.products (
	id bigint generated by default as identity,
	created_at timestamp with time zone not null default now(),
	product_details text null,
	product_name character varying null,
	product_price character varying null,
	product_thumbnail text null,
	user_details json null,
	product_offering text null,
	user_id character varying null,
	constraint products_pkey primary key (id)
  ) tablespace pg_default;
```

To create a new table, you can execute the appropriate query. This table will serve as a reference to the product, linked via the User ID. This linkage allows us to identify the users along with the products they have created, enabling us to display the relevant products on their dashboard.

Now, we can create the **ProductAdd** component. This component will handle the functionality of adding a new product.

**productAdd.js**

```js
import "@syncfusion/ej2-layouts/styles/material.css";
import "@syncfusion/ej2-react-inputs/styles/material.css";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import { useState, useRef } from "react";
import "./productAdd.css";
import TextEditor from "../components/TextEditor";
import useSupbase from "../hooks/supbase";
import Toast from "../components/Toast";
import productStore from "../store/products";

const ProductAdd = () => {
  const [productName, setProductName] = useState("");
  const [productPrice, setProductPrice] = useState("");
  const [productThumbnail, setProductThumbnail] = useState("");
  const [productOffering, setProductOffering] = useState("");
  const [toast, setToast] = useState({
	show: false,
	message: "",
	type: "error",
  });
  const productDescriptionRef = useRef();
  const SupaBase = useSupbase();
  const { productsList, setProductsList } = productStore((state) => state);

  const handleClick = async () => {
   try {
     const productDetails = productDescriptionRef.current.value;
      if (
    	productName ||
    	productPrice ||
    	productThumbnail ||
    	productDetails ||
    	productOffering
      ) {
      const { data, error: userError } = await SupaBase.auth.getSession();
      const { session } = data;

      const { data: newProduct, error: productError } = await SupaBase
      .from("products")
      .insert({
            product_name: productName,
            product_price: productPrice,
            product_thumbnail: productThumbnail,
            product_details: productDetails,
            product_offering: productOffering,
            user_details: session?.user,
            user_id: session?.user?.id,
      })
      .select();

      setProductsList([...productsList, ...newProduct]);

      setToast({
      	  message: "Successfully added a product",
      	  show: true,
      	  type: "success",
      });

      resetState();
      } else {
      console.log("Product detials missing");
      }
      } catch (e) {
  	console.error("An error occurred", e);
      }
  };

  const resetState = () => {
	setProductName("");
	setProductPrice("");
	setProductThumbnail("");
	setProductOffering("");
	productDescriptionRef.current.value = "";
  };

  return (
    <div>
      <div className="row">
    	<div className="field-area">
      	 <label htmlFor="product-name">Name *</label>
      	 <input
        	className="e-input"
        	type="text"
        	placeholder="product name..."
        	name="product-name"
        	id="product-name"
        	onChange={(e) => setProductName(e.target.value)}
        	value={productName}
        	required
      	 />
    	</div>

    	<div className="field-area">
      	 <label htmlFor="product-price">Price *</label>
      	 <input
        	className="e-input"
        	type="number"
        	placeholder="product price..."
        	name="product-price"
        	id="product-price"
        	onChange={(e) => setProductPrice(e.target.value)}
        	value={productPrice}
        	required
      	 />
    	</div>

    	<div className="field-area">
      	 <label htmlFor="product-thumbnail">Product URL *</label>
      	  <input
        	className="e-input"
        	type="url"
        	placeholder="product thumbnail url..."
        	name="product-thumbnail"
        	id="product-thumbnail"
        	onChange={(e) => setProductThumbnail(e.target.value)}
        	value={productThumbnail}
        	required
      	  />
    	</div>

    	<div className="field-area">
      	 <label htmlFor="product-offering">Product offering *</label>
      	  <input
        	className="e-input"
        	type="url"
        	placeholder="product offering"
        	name="product-offering"
        	id="product-offering"
        	onChange={(e) => setProductOffering(e.target.value)}
        	value={productOffering}
        	required
      	  />
    	</div>
      </div>

      <div>
       <label htmlFor="product-description" className="product-desc">
      	  Brief description
       </label>
       <TextEditor id="product-description" ref={productDescriptionRef} />
      </div>

      <ButtonComponent
    	className="e-success save-btn e-block e-large"
    	onClick={handleClick}
      >
    	Save
      </ButtonComponent>
      {toast.show && (
    	<Toast
      	  errorMessage={toast.message}
      	  type={toast.type}
      	  onClose={() => {
        	setToast({
          	  show: false,
          	  message: "",
          	  type: "error",
        	});
      	  }}
    	/>
      )}
    </div>
  );
};

export default ProductAdd;
```

**productAdd.css**

```js
.field-area {
  flex: 1 50%;
  padding: 20px 10px 0 10px;
  text-align: left;
}

.save-btn {
  margin: 20px 0;
  font-size: 1.2em;
}

.product-desc {
  font-size: 1.2em;
  margin: 20px 0;
  display: inline-block;
}
```
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Adding-a-new-product-dialog-box.png" alt="Adding a new product dialog box" style="width:100%">

  <figcaption>Adding a new product dialog box</figcaption>

</figure>

The **TextEditor.js** file is designed to be a reusable component for the Rich Text Editor. By abstracting the styles and functionality into this component, you can easily incorporate a rich text editor in your app wherever needed, ensuring consistency and reducing code duplication. Refer to the following code example.

**TextEditor.js**

```js
import "@syncfusion/ej2-base/styles/material.css";
import "@syncfusion/ej2-icons/styles/material.css";
import "@syncfusion/ej2-buttons/styles/material.css";
import "@syncfusion/ej2-splitbuttons/styles/material.css";
import "@syncfusion/ej2-inputs/styles/material.css";
import "@syncfusion/ej2-lists/styles/material.css";
import "@syncfusion/ej2-navigations/styles/material.css";
import "@syncfusion/ej2-popups/styles/material.css";
import "@syncfusion/ej2-richtexteditor/styles/material.css";

import {
  HtmlEditor,
  Image,
  Inject,
  Link,
  QuickToolbar,
  RichTextEditorComponent,
  Toolbar,
} from "@syncfusion/ej2-react-richtexteditor";

import { forwardRef } from "react";

const TextEditor = (props, ref) => {
  const toolbarSettings = {
	items: [
  	"Bold",
  	"Italic",
  	"Underline",
  	"StrikeThrough",
  	"FontName",
  	"FontSize",
  	"FontColor",
  	"BackgroundColor",
  	"LowerCase",
  	"UpperCase",
  	"|",
  	"Formats",
  	"Alignments",
  	"OrderedList",
  	"UnorderedList",
  	"Outdent",
  	"Indent",
  	"|",
  	"CreateLink",
  	"Image",
  	"|",
  	"ClearFormat",
  	"Print",
  	"SourceCode",
  	"FullScreen",
  	"|",
  	"Undo",
  	"Redo",
	],
  };
  const quickToolbarSettings = {
	image: [
  	"Replace",
  	"Align",
  	"Caption",
  	"Remove",
  	"InsertLink",
  	"OpenImageLink",
  	"-",
  	"EditImageLink",
  	"RemoveImageLink",
  	"Display",
  	"AltText",
  	"Dimension",
	],
  };

  return (
	<RichTextEditorComponent
  	 height={300}
  	 toolbarSettings={toolbarSettings}
  	 quickToolbarSettings={quickToolbarSettings}
  	 ref={ref}
	>
  	  <p>Fill in things in detail</p>
  	  <Inject services={[Toolbar, Image, Link, HtmlEditor, QuickToolbar]} />
	</RichTextEditorComponent>
  );
};

export default forwardRef(TextEditor);
```

The **TextEditor** component, defined in this file, utilizes the Syncfusion [React Rich Text Editor component](https://www.syncfusion.com/react-components/react-wysiwyg-rich-text-editor "React Rich Text Editor component"). The **toolbarSettings** and **quickToolbarSettings** determine the items displayed in the toolbar and the quick toolbar, respectively. The **TextEditor** component applies these settings and returns the **RichTextEditorComponent**.

The **forwardRef** function creates a reference and passes it as a property to the **TextEditor** component, enabling parent components to interact with the **RichTextEditorComponent**. You can incorporate this reusable component wherever your app requires a rich text editor. You can access the editor’s value through the forwarded reference.

The next step in the flow involves storing all the fetched products in a Zustand store. This implementation will occur in the **store/products.js** file.

**store/products.js**

```js
import { create } from "zustand";

let store = (set) => ({
  productsList: [],
  setProductsList: (productsList) => set(() => ({ productsList })),
});

//create the store
let productStore = create(store);

export default productStore;
```

This file creates a Zustand store named **productStore**. This store contains a state variable, the **productsList array** , which holds all the fetched products. The function **setProductsList** updates the **productsList** state. Other components can import and use this store to access and update the product list, making it easier to manage and track its state across different components.

When a new product is successfully added, the Zustand store also updates the entry. This update ensures the immediate visibility of the newly added product on the frontend.

All the component’s fields are controlled components. This control means that the local state of these fields resets once a product is added, preparing them for the following product addition. The store will work with the products fetch list hook, retrieving the records from the database.

### View products list

Creating a hook will be the first step in displaying the product list. This hook is designed to retrieve the product records from the database and store them in the Zustand store, allowing convenient access and display of the product list.

**hooks/useProduct.js**

```js
import { useState, useEffect } from "react";
import useSupbase from "./supbase";
import productStore from "../store/products";

const useProducts = () => {
  const setProductsList = productStore((state) => state.setProductsList);

  const SupaBase = useSupbase();

  const fetchProducts = async () => {
	const { data: userData, error: userError } =
  	await SupaBase.auth.getSession();
	const { session } = userData;

	const { data: productData, error: productError } = await SupaBase
  	.from("products")
  	.select()
  	.eq("user_id", session?.user?.id);

	setProductsList(productData);
	try {
	} catch (e) {
  	console.error("Error while fetching products list");
	}
  };

  useEffect(() => {
	fetchProducts();
  }, []);
};

export default useProducts;
```

In this hook, the **fetchProducts** function is designed to retrieve product records from the database associated with the current user. Once fetched, these products are stored in the Zustand store using the **setProductsList** function. The **useEffect** hook ensures that the **fetchProducts** function is called when the component that uses this hook is mounted. This guarantees that the product list is retrieved and prepared for display as soon as the component loads. This hook can be imported and utilized in any product list component.

With the product store ready, it can render the list of products. For this rendering process, we’ll leverage [Syncfusion React Data Grid](https://ej2.syncfusion.com/react/demos/#/material/grid/overview "Demos: Syncfusion's powerful React Data Grid"). This component offers built-in features such as pagination, sorting, filtering, and grouping. It also supports templates. This means you can render custom HTML elements for any row’s value. This feature is useful for adding elements like the product thumbnail and the edit or preview buttons. Refer to the following code example.

**DataGrid/index.js**

```js
import "@syncfusion/ej2-base/styles/material.css";
import "@syncfusion/ej2-buttons/styles/material.css";
import "@syncfusion/ej2-calendars/styles/material.css";
import "@syncfusion/ej2-dropdowns/styles/material.css";
import "@syncfusion/ej2-inputs/styles/material.css";
import "@syncfusion/ej2-navigations/styles/material.css";
import "@syncfusion/ej2-popups/styles/material.css";
import "@syncfusion/ej2-splitbuttons/styles/material.css";
import "@syncfusion/ej2-notifications/styles/material.css";
import "@syncfusion/ej2-react-grids/styles/material.css";

import {
  ColumnDirective,
  ColumnsDirective,
  GridComponent,
  Inject,
  Page,
} from "@syncfusion/ej2-react-grids";
import { Link } from "react-router-dom";
import * as React from "react";

const DefaultValueTemplate = (props) => {
  return props[props.column.field] || 0;
};

const ThumbnailTemplate = (props) => {
  return (
    <img
       src={props.product_thumbnail}
       alt={props.product_name}
       title={props.product_name}
       style={{ maxWidth: "50px" }}
    />
  );
};

const DataGrid = ({ data, ActionsTemplate }) => {
  return (
    <GridComponent
  	dataSource={data}
  	allowPaging={true}
  	pageSettings={{ pageSize: 6 }}
    >
    <ColumnsDirective>
      <ColumnDirective
      	field="product_thumbnail"
      	width="120"
      	textAlign="Right"
      	template={ThumbnailTemplate}
      	headerText="Thumbnail"
      />
      <ColumnDirective field="product_name" width="100" headerText="Name" />
       <ColumnDirective
      	 field="product_price"
      	 width="100"
      	 textAlign="Right"
      	 headerText="Price"
      	 template={DefaultValueTemplate}
       />
       <ColumnDirective
      	 field="product_sale"
      	 width="100"
      	 format="C2"
      	 textAlign="Right"
      	 headerText="Sale"
      	 template={DefaultValueTemplate}
       />
       <ColumnDirective
      	 field="product_profit"
      	 width="100"
      	 headerText="Profit"
      	 template={DefaultValueTemplate}
       />
       <ColumnDirective
      	 field="action"
      	 width="100"
      	 template={ActionsTemplate}
      	 headerText="Action"
       />
     </ColumnsDirective>
     <Inject services={[Page]} />
    </GridComponent>
  );
};
export default DataGrid;
```

We are passing the **ActionTemplates** as it triggers state updates to view the modal for editing a product. Thus, we will pass the component rather than passing the state as prop.

**productsList.js**

```js
import "./productList.css";
import { useState } from "react";
import { Link } from "react-router-dom";
import ProductEdit from "./productEdit";
import Modal from "../components/Modal";
import useProducts from "../hooks/useProducts";
import productStore from "../store/products";
import DataGrid from "../components/DataGrid";

const ProductList = () => {
  const [productToEdit, setProductToEdit] = useState({});
  const [showModal, setShowModal] = useState(false);
  useProducts();
  const productsList = productStore((state) => state.productsList);

  const ActionsTemplate = (e) => {
     const product = productsList.filter((f) => f.id === e.id)[0] || [];
     return (
      <span>
    	<span
      	  onClick={() => {
        	setProductToEdit(e);
        	setShowModal(true);
      	  }}
      	  className="edit-product"
    	>
      	  Edit
        </span>
        </>
        <Link to={`/product/${e.id}`} title={`preview ${e.Name}`}>
      	  Preview
        </Link>
      </span>
     );
  };

  return (
    <div className="product-list">
      <DataGrid data={productsList} ActionsTemplate={ActionsTemplate} />
      <Modal
    	  showModal={showModal}
    	  handleClose={() => {
      	    setShowModal(false);
      	    setProductToEdit({});
    	  }}
       >
    	  <ProductEdit product={productToEdit} />
       </Modal>
     </div>
  );
};

export default ProductList;
```

**productLists.css**

```js
.row {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  text-transform: capitalize;
  flex-wrap: wrap;
  text-align: center;
  padding: 10px;
  border: 1px solid #eee;
  margin-bottom: 10px;
}

.row > span {
  flex: 0 calc(100% / 6);
}

.row > span > img {
  width: 80px;
  vertical-align: middle;
}

.bold {
  font-weight: bold;
  line-height: 20px;
}

.edit-product {
  cursor: pointer;
  text-decoration: underline;
  color: purple;
}

.product-list {
  min-height: 600px;
}
```

Now, we should add this product list in the **manageProducts.js** file to render and view the list.

```js
import { useState } from "react";
import Wrapper from "./wrapper";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import ProductList from "./productList";
import "./manageProduct.css";
import Modal from "../components/Modal";
import ProductAdd from "./productAdd";

const ManageProduct = () => {
  const [showModal, setShowModal] = useState(false);

  return (
    <Wrapper>
     <div className="manage-product">
       <div className="add-product">
      	<ButtonComponent
           cssClass="e-info"
           onClick={() => {
              setShowModal(true);
           }}
           style={{ fontSize: "1.2em" }}
      	>
        	Add new product
      	</ButtonComponent>
       </div>
       <Modal
      	 showModal={showModal}
      	 handleClose={() => {
        	setShowModal(false);
      	 }}
       >
      	<ProductAdd />
       </Modal>
       <ProductList />
     </div>
    </Wrapper>
  );
};

export default ManageProduct;
```

Refer to the following image.
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Viewing-the-product-list-in-React-e-commerce-app.png" alt="Viewing the product list in React e-commerce app" style="width:100%">

  <figcaption>Viewing the product list in React e-commerce app</figcaption>

</figure>

As you may have noticed in the code and on the screen, each product has two call-to-action buttons: one for editing and another for previewing the product.

### Editing a product

For the editing functionality, we’ve created a separate component. Although, we could have used the **Add Product** component, we’ve separated these concerns for better organization and clarity.

The **Add Product** and **Edit Product** components share the same user interface (UI). Therefore, you could create a common form component that both could use. In this case, I’ve rewritten the input components in both.

The **Edit Product** component accepts the product details that need to be edited. It then updates the local state of the fields with the product data. Since these are controlled components, any changes in the state are immediately reflected in the fields.

When you make a change and click the **Update** button, it triggers the update action. After the update, it fetches the updated record from the table. We then replace the Zustand store’s corresponding record with this new one.

**productEdit.js**

```js
import "@syncfusion/ej2-layouts/styles/material.css";
import "@syncfusion/ej2-react-inputs/styles/material.css";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import { useState, useRef, useEffect } from "react";
import "./productAdd.css";
import TextEditor from "../components/TextEditor";
import useSupbase from "../hooks/supbase";
import Toast from "../components/Toast";
import productStore from "../store/products";

const ProductEdit = ({ product }) => {
  const [productName, setProductName] = useState("");
  const [productPrice, setProductPrice] = useState("");
  const [productThumbnail, setProductThumbnail] = useState("");
  const [productOffering, setProductOffering] = useState("");
  const [toast, setToast] = useState({
	show: false,
	message: "",
	type: "error",
  });
  const productDescriptionRef = useRef();
  const SupaBase = useSupbase();
  const { productsList, setProductsList } = productStore((state) => state);

  const handleClick = async () => {
    try {
     const productDetails = productDescriptionRef.current.value;
       if (
    	  productName ||
    	  productPrice ||
    	  productThumbnail ||
    	  productDetails ||
    	  productOffering
  	) {
    	const { data, error: userError } = await SupaBase.auth.getSession();
    	const { session } = data;

    	const { data: updatedProduct, error: productError } = await SupaBase
      	.from("products")
      	.update({
             product_name: productName,
             product_price: productPrice,
             product_thumbnail: productThumbnail,
             product_details: productDetails,
             product_offering: productOffering,
      	})
      	.eq("id", product.id)
      	.select();

    	const filteredProducts = productsList.filter(
      	  (e) => e.id !== product.id
    	);

    	setProductsList([...filteredProducts, ...updatedProduct]);

    	setToast({
      	  message: "Successfully update the product",
      	  show: true,
      	  type: "success",
    	});
  	} else {
    	console.log("Product detials missing");
  	}
	} catch (e) {
  	console.error("An error occurred", e);
	}
  };

  useEffect(() => {
     const {
  	product_name,
  	product_price,
  	product_thumbnail,
  	product_offering,
  	product_details,
     } = product;

     setProductName(product_name);
     setProductPrice(product_price);
     setProductThumbnail(product_thumbnail);
     setProductOffering(product_offering);
     productDescriptionRef.current.value = product_details;
  }, [product]);

  return (
    <div>
      <div className="row">
    	<div className="field-area">
      	  <label htmlFor="product-name">Name *</label>
      	  <input
        	className="e-input"
        	type="text"
        	placeholder="product name..."
        	name="product-name"
        	id="product-name"
        	onChange={(e) => setProductName(e.target.value)}
        	value={productName}
        	required
      	   />
    	</div>

    	<div className="field-area">
      	  <label htmlFor="product-price">Price *</label>
      	  <input
        	className="e-input"
        	type="number"
        	placeholder="product price..."
        	name="product-price"
        	id="product-price"
        	onChange={(e) => setProductPrice(e.target.value)}
        	value={productPrice}
        	required
      	  />
    	</div>

    	<div className="field-area">
      	  <label htmlFor="product-thumbnail">Product URL *</label>
      	  <input
        	className="e-input"
        	type="url"
        	placeholder="product thumbnail url..."
        	name="product-thumbnail"
        	id="product-thumbnail"
        	onChange={(e) => setProductThumbnail(e.target.value)}
        	value={productThumbnail}
        	required
      	  />
    	</div>

    	<div className="field-area">
      	  <label htmlFor="product-offering">Product offering *</label>
      	  <input
        	className="e-input"
        	type="url"
        	placeholder="product offering"
        	name="product-offering"
        	id="product-offering"
        	onChange={(e) => setProductOffering(e.target.value)}
        	value={productOffering}
        	required
      	  />
    	</div>
     </div>

     <div>
    	<label htmlFor="product-description" className="product-desc">
      	  Brief description
    	</label>
    	<TextEditor id="product-description" ref={productDescriptionRef} />
     </div>

     <ButtonComponent
    	className="e-info save-btn e-block e-large"
    	onClick={handleClick}
     >
    	Update
     </ButtonComponent>
     {toast.show && (
    	<Toast
      	  errorMessage={toast.message}
      	  type={toast.type}
      	  onClose={() => {
            setToast({
          	show: false,
          	message: "",
          	type: "error",
            });
      	  }}
    	/>
      )}
    </div>
  );
};

export default ProductEdit;
```

Refer to the following image.
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Editing-a-product-in-a-React-e-commerce-app.png" alt="Editing a product in a React e-commerce app" style="width:100%">

  <figcaption>Editing a product in a React e-commerce app</figcaption>

</figure>

### Preview a product

To enhance the product preview functionality, we’ll construct a distinct page. This page will be accessible via a public route, as authentication is not required to view a product. This page will be designed to accept the product ID as a URL parameter. Using this ID, we’ll fetch the corresponding product from the database and display it for preview.

This is how you can set up the route in your **App.js** file.

```js
<Route path="/product/:id" element={<ProductPreview />} />
```

In this code, a new route is defined for product previews. The **:id** in the path is a placeholder for the product ID. When a user navigates to this route, the **ProductPreview** component is rendered. This component is responsible for fetching the product data based on the ID in the URL and displaying it for preview. This setup allows each product to have its unique preview page, accessible via a unique URL.

**productPreview.js**

```js
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import useSupbase from "../hooks/supbase";
import "@syncfusion/ej2-layouts/styles/material.css";
import "@syncfusion/ej2-react-buttons/styles/material.css";
import { ButtonComponent } from "@syncfusion/ej2-react-buttons";
import "./productPreview.css";
import useStore from "../store/user";

const ProductPreview = () => {
  const params = useParams();
  const [product, setProduct] = useState({});
  const isLoggedIn = useStore((state) => state.isLoggedIn);
  const SupaBase = useSupbase();

  useEffect(() => {
	fetchProduct();
  }, [params.id]);

  const fetchProduct = async () => {
    try {
      const { data: productData, error: productError } = await SupaBase
    	.from("products")
    	.select()
    	.eq("id", params.id);

      setProduct(productData.length ? productData[0] : {});
    } catch (e) {
  	console.error(`There was an error while fetching the product with the id:
    	${params.id}`);
    }
  };

  const isProductFetched = Object.keys(product).length > 0;

  const userMetaData = product?.user_details?.user_metadata;
  const userName = `${userMetaData?.firstName} ${userMetaData?.lastName}`;

  return (
    <div className="product-preview">
      {isProductFetched ? (
    	<div>
      	  <div className="hero-image e-card">
             <img src={product.product_thumbnail} alt={product.product_name} />
      	  </div>
      	  <div className="product-details">
             <section className="e-card section-right">
               <h1>
            	 {product.product_name} | From: {userName}
               </h1>
               <div
            	 dangerouslySetInnerHTML={{ __html: product.product_details }}
               ></div>
             </section>
             <section className="e-card section-left">
               <h2>Price: ${product.product_price}</h2>
          	 {!isLoggedIn && (
            	   <ButtonComponent className="e-success">
              	       Purchase
            	   </ButtonComponent>
          	 )}
             </section>
      	  </div>
    	</div>
  	) : (
    	<h1>No Product found</h1>
      )}
    </div>
  );
};

export default ProductPreview;
```

In this file, the **ProductPreview** component is defined. This component fetches the product data based on the ID in the URL and displays it for preview. If the product data is successfully fetched, it is displayed in a structured layout. If not, a message indicating that no product was found is displayed. This component can be imported and used in any product preview component.

**productPreview.css**

```js
.product-preview {
  padding: 10px;
  max-width: 1000px;
  margin: 0 auto;
}

.product-preview .hero-image {
  margin-bottom: 10px;
  align-items: center;
  padding: 10px;
}

.product-preview .hero-image img {
  max-width: 1000px;
  width: 100%;
}

.product-preview .product-details {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  align-items: stretch;
}

.product-preview .product-details .section-right {
  flex: 0 70%;
  padding: 10px;
}

.product-preview .product-details .section-left {
  flex: 0 29%;
  padding: 10px;
}

.product-preview .product-details h1,
.product-preview .product-details h2 {
  text-transform: uppercase;
  padding-bottom: 15px;
  border-bottom: 1px solid #2d2b2b;
}
```
<figure>

  <img src="https://www.syncfusion.com/blogs/wp-content/uploads/2024/01/Previewing-a-product-in-a-React-e-commerce-app.png" alt="Previewing a product in a React e-commerce app" style="width:100%">

  <figcaption>Previewing a product in a React e-commerce app</figcaption>

</figure>

In this implementation, the product record is fetched directly within the component, as it’s not required elsewhere. However, you could create a separate hook dedicated to fetching the record.

The product list and preview components are designed to be reusable. This allows us to incorporate them into the home page easily to display all the products in the store.

## What’s next?

In the next section, we’ll focus on creating the home page. This will include a comprehensive list of products. Additionally, we’ll implement the checkout flow. This process will allow users to complete their purchases and store the details of each order. This approach ensures a seamless shopping experience for users while maintaining a detailed record of all transactions.

## Conclusion

We appreciate your time in reading this blog! We’ve walked through the process of building a product management dashboard, empowering us to add, update, and preview products in our React digital products e-commerce app. We urge you to apply what you’ve learned and share your journey in the comments below!

[Syncfusion’s Essential Studio for React](https://www.syncfusion.com/react-ui-components "React UI Components Library") is a one-stop solution offering over 80 high-performance, lightweight, modular, and responsive UI components. It’s the perfect suite to build any web app.

For our existing Syncfusion users, the latest version of Essential Studio is available for download on the [License and Downloads](https://www.syncfusion.com/account/downloads "Essential Studio License and Downloads page") page. If you’re new to Syncfusion, we invite you to explore our products’ features and capabilities with our [30-day free trial](https://www.syncfusion.com/downloads "Get the free evaluation of Essential Studio products").

For questions, you can contact us through our [support forum](https://www.syncfusion.com/forums "Syncfusion Support Forum"), [support portal](https://support.syncfusion.com/ "Syncfusion Support Portal"), or [feedback portal](https://www.syncfusion.com/feedback "Syncfusion Feedback Portal"). We are always here to assist you!

See you in our next blog!

## Related blogs

- [Designing a React E-Commerce App for Digital Products—Part 1](https://www.syncfusion.com/blogs/post/react-digital-ecommerce-app.aspx "Blog: Designing a React E-Commerce App for Digital Products—Part 1")
- [Designing a React E-Commerce App for Digital Products—Part 2](https://www.syncfusion.com/blogs/post/react-ecommerce-app-part-2.aspx "Blog: Designing a React E-Commerce App for Digital Products—Part 2")
- [How to Build Synchronized Charts in React for Trade Analysis](https://www.syncfusion.com/blogs/post/synchronized-charts-react.aspx "Blog: How to Build Synchronized Charts in React for Trade Analysis")
- [Level Up Your React Charts: Mastering Scrollbar Customization](https://www.syncfusion.com/blogs/post/customize-react-charts-scrollbar.aspx "Blog: Level Up Your React Charts: Mastering Scrollbar Customization")