Build an Ecommerce Store with Next.js, Auth0 and Cloudinary
Tags: auth0, cloudinary, nextjs, jamstack
Published: 2021-10-18 08:14:51

---
title: Build an Ecommerce Store with Next.js, Auth0 and Cloudinary
published: true
description: An Ecommerce app made to illustrate Auth0 and Cloudinary's features
tags: auth0, cloudinary, nextjs, jamstack

---

Multimedia content optimization is the process of delivering high-quality media files in the right dimension, size, and format without sacrificing quality. 

When poorly optimized, the images and videos in your websites can lead to a poor user experience, loss of traffic, and low SEO rankings. 

In this article, we will learn how Cloudinary enables us to serve fast-loading images and videos through their CDN. We will also learn to set up user authentication with Auth0 and create responsive components with Chakra UI. We will combine these technologies to develop an eCommerce application.  

We completed the project demo in [a codesandbox](https://codesandbox.io/s/broken-wood-4vb7o). 

You can find the deployed version [here](https://hackmamba-project-19or6wbnq-nefejames.vercel.app/) and the codebase in this [Github repo](https://github.com/nefejames/hackmamba-project).


## Prerequisites/Installation

Having some knowledge of and experience with, React.js and Chakra UI are required to follow the steps in this article. We will use the images and videos hosted on [Cloudinary](https://cloudinary.com/) so you will need a [Cloudinary account](https://cloudinary.com/users/register/free). [Auth0](https://auth0.com/) will handle the user authentication through their [universal login](https://auth0.com/docs/login/universal-login) service, so you must [create an account](https://auth0.com/signu p).

As I mentioned earlier, we will be using Next.js in this project, so we need to [scaffold a new Next.js application](https://nextjs.org/docs/api-reference/create-next-ap).
 
We will also use the following packages in the application:

- [cloudinary-react](https://github.com/cloudinary/cloudinary-react)’s Video component to display videos.
- [Auth0’s Next.js SDK](https://github.com/auth0/nextjs-auth0) to handle the user authentication.
- [Chakra UI](https://github.com/chakra-ui/chakra-ui) to create the user interface.

We install the required packages in our application using the CLI command:

    npm i chakra-ui cloudinary-react auth0/nextjs-auth0


## Creating the Homepage

The homepage consists of a header and a hero section; the header consists of the logo and a login/sign-in button, and the hero consists of the website copy on the left and an image on the right.

We can see the appearance of the homepage in the image below.

![PetShop Homepage](https://paper-attachments.dropbox.com/s_D65A090A5542872AAE6A8DDDEE64FCB43060C51AA27CE7D7D1C131AFDB8E304B_1632582978011_homepage.PNG)


Let’s begin by creating the layout for the homepage.
 
```
    import { Box, Container } from "@chakra-ui/react";
    import HomeHeader from "@components/Headers/Home";
    
    export default function HomeLayout({ children }) {
      return (
        <Box>
          <Container maxW={"7xl"}>
            <HomeHeader />
    
            {children}
          </Container>
        </Box>
      );
    }
```

For the homepage layout, we create a component, `HomeLayout`. `HomeLayout` consists of a `HomeHeader` component and the children we will pass into it later. `HomeLayout` is where the hero section will sit.

**Creating the Homepage Header**
Next, we create the homepage header.

```
    import NextLink from "next/link";
    import { Box, Flex, Button } from "@chakra-ui/react";
    import Logo from "@svgs/Logo";
    
    export default function HomeHeader() {
      return (
        <Box>
          <Flex>
            <NextLink href="/" rel="noopener noreferrer" target="_blank">
              <a>
                <Logo />
              </a>
            </NextLink>
          </Flex>      
        </Box>
      );
    }
```

The `HomeHeader` component is made up of the Logo. We will add a login button later in the article.

**Creating the Hero Section**
Now, we move on to the hero section. 

```
    import { Stack, Flex, Box, Heading, Text, Image } from "@chakra-ui/react";
    import HomeLayout from "@layout/Home";
    
    export default function Home() {
      return (
        <HomeLayout>
          <Stack>
            <Stack flex={1} spacing={{ base: 5, md: 10 }}>
              <Heading lineHeight={1.1} fontWeight={600} fontSize={["3xl", "4xl", "6xl"]}
              >
                Get your perfect companion{" "}
                <Text as="span" color="blue.500">
                  today!
                </Text>
              </Heading>
              <Text color="gray.200" fontSize={["lg"]}>
                Buyng a pet is a big decision, and we are here to help you through
                the process. It's time to add a new love to your family. Discover
                the right pet for you. It's time to find your new best friend.
              </Text>
            </Stack>
            <Flex>
              <Box>
                <Image alt="Hero Image" fit="cover" align="center" w="100%" h="100%"
                  src=" https://res.cloudinary.com/nefejames/image/upload/q_auto,f_auto,w_auto/v1632501665/Hackmamba/Images/hero.jpg"
                />
              </Box>
            </Flex>
          </Stack>
        </HomeLayout>
      );
    }
```

The hero image is gotten from our Cloudinary media repository. Let’s breakdown the URL of the image:

- `q_auto`: produces a high quality image with a minimal the file size.
- `f_auto`: delivers the image in the best format supported by the browser.
- `w_auto`: serves images proportional to users’ device resolution and screen sizes.


## Creating the Dashboard

The dashboard consists of a header, sidebar, and dashboard view that contains the products.
The header consists of the logo and a button to toggle the sidebar.
The sidebar consists of a link to illustrate how a dashboard looks in a full-scale application.

Let’s start by creating the layout of the dashboard.   

```
    import { useMediaQuery, Box, Stack } from "@chakra-ui/react";
    import Header from "@components/Headers/Dashboard/";
    import Page from "./Page";
    import { DesktopSidebar, MobileSidebar } from "@components/DashboardSidebar";
    
    export default function DashboardLayout({ children }) {
      const [isSmallScreen] = useMediaQuery("(max-width: 768px)");
    
      return (
        <Box w="full">
          <Header />
          <Box pos="relative" h="max-content">
            <Stack direction="row" spacing={{ md: 5 }}>
              <DesktopSidebar />
              {isSmallScreen && <MobileSidebar />}
              <Page>{children}</Page>
            </Stack>
          </Box>
        </Box>
      );
    }
```

The dashboard layout consists of two dashboards, a mobile dashboard, and another for desktops.
We create a `DashboardLayout` component and use Chakra’s `[useMediaQuery](https://chakra-ui.com/docs/hooks/use-media-query)` hook to set up a `isSmallScreen` boolean. We show the mobile sidebar when `isSmallScreen` is true - when the max-width is 768px.

**The Dashboard Context and Interactivity Flow**
Before we continue building the dashboard, we need to understand its flow. We want to add some interactivity to the dashboard to boost the user experience.

We want to be able to toggle the width of the desktop sidebar. We also want the mobile sidebar to slide in and out when toggled.  

A core aspect of this functionality is a sidebar context, which we will create. The value of the sidebar context is gotten from Chakra UI’s `[useDisclosure](https://chakra-ui.com/docs/hooks/use-disclosure)` [](https://chakra-ui.com/docs/hooks/use-disclosure)hook. `useDisclosure` is a custom hook used to help handle common open, close, or toggle scenarios.

When setting up the sidebar, we will create and export a `useSidebarAuth` hook that consumes the sidebar context and makes its values accessible throughout the application.

We will pass the `useSidebarAuth` hook to a `SidebarToggleButton` component which we will create later. `SidebarToggleButton` will enable us to toggle the sidebar context’s `isOpen` value when clicked. We will pass `SidebarToggleButton` to the dashboard header later.
Whenever a user clicks the `SidebarToggleButton`, we alter the behavior of the desktop or mobile sidebars based on the current value of `isOpen`.

The flow chart image below shows us how the sidebar context works with the dashboard components and how we will set up the interactivity.


![dashboard context and interactivity flow](https://paper-attachments.dropbox.com/s_D65A090A5542872AAE6A8DDDEE64FCB43060C51AA27CE7D7D1C131AFDB8E304B_1632614220694_Untitled+Diagram.drawio1.png)


**Creating the Sidebar Context**

Now that we understand the sidebar context and how it is used in the dashboard, let us set up the context. 

```
    import { createContext, useContext } from "react";
    import { useDisclosure } from "@chakra-ui/react";
    
    const SidebarContext = createContext(null);
    export const useSidebarAuth = () => useContext(SidebarContext);
    
    export default function SidebarContextProvider({ children }) {
      const sidebarState = useDisclosure();
    
      return (
        <SidebarContext.Provider value={sidebarState}>
          {children}
        </SidebarContext.Provider>
      );
    }
```

As stated earlier, we use the `useDisclosure` hook to set up the context and export a `useSidebarAuth` hook that consumes the context.

**Creating the Sidebar Toggle Button**
The next thing we need to do is create the  `SidebarToggleButton` component.

```
    import { Icon, IconButton } from "@chakra-ui/react";
    import { CgClose, CgMenu } from "react-icons/cg";
    import { useSidebarAuth } from "@context/sidebarContext";
    
    export default function SidebarToggleButton() {
      const { onToggle, isOpen } = useSidebarAuth();
      const icon = isOpen ? CgClose : CgMenu;
      return (
        <IconButton
          size="sm"
          fontSize="lg"
          variant="ghost"
          onClick={onToggle}
          icon={<Icon as={icon} />}
          aria-label="Toggle Actions"
          transition="all .4s ease-in-out"
        />
      );
    }
```

As explained earlier, when `SidebarToggleButton` is clicked, the `isOpen` value of the context is toggled.

**Creating the Dashboard Header**
Next we set up the dashboard header. 

```
    import NextLink from "next/link";
    import { Flex, Spacer, Stack, Button, Link } from "@chakra-ui/react";
    import SidebarToggleButton from "./components/SidebarToggleButton";
    import Logo from "@svgs/Logo";
    
    export default function DashboardHeader() {
      return (
        <Flex
          h="10vh"
          minH="70px"
          pos="sticky"
          top="0"
          zIndex="2"
        >
          <Stack direction="row" w="full" alignItems="center" spacing="8">
            <NextLink href="/" rel="noopener noreferrer" target="_blank">
              <a>
                <Logo />
              </a>
            </NextLink>
            <SidebarToggleButton />
            
          </Stack>
        </Flex>
      );
    }
```

As I explained with the flow chart, we add the `SidebarToggleButton` component to the dashboard header.

**Creating the Desktop Sidebar**
The dashboard has two sidebars, one for mobile devices, and another for desktop devices. Let’s start with the desktop sidebar.

```
    import { useRouter } from "next/router";
    import { useSidebarAuth } from "@context/sidebarContext";
    
    export default function Sidebar() {
      const router = useRouter();
    
      return (
        <Stack
          minH="full"
          h="90vh"
          pos="sticky"
          top="10vh"
          display={["none", , "initial"]}
          transition="width .4s ease-in-out"
        >
          <NavLink
            active={router.pathname === "/"}
            name="Home"
            href="/"
            icon={IoHomeSharp}
          />
        </Stack>
      );
    }
```

We want the desktop sidebar to be hidden on mobile, so we set the display to none. 

**Creating the Mobile Sidebar**
We make use of Chakra’s `[Drawer](https://chakra-ui.com/docs/overlay/drawe)` component to set up the mobile sidebar. 

```
    import { useEffect } from "react";
    import { useRouter } from "next/router";
    import {
      Drawer,
      DrawerCloseButton,
      DrawerContent,
      DrawerOverlay,
    } from "@chakra-ui/react";
    import { useSidebarAuth } from "@context/sidebarContext";
    import FullNavLink from "./FullNavLink";
    
    export default function MobileSidebar() {
      const router = useRouter();
      const { isOpen, onClose } = useSidebarAuth();
    
      useEffect(() => {
        router.events.on("routeChangeComplete", onClose);
        return () => {
          router.events.off("routeChangeComplete", onClose);
        };
      }, []);
      return (
        <Drawer isOpen={isOpen} onClose={onClose} placement="left">
          <DrawerOverlay display={["initial", , "none"]}>
            <DrawerContent layerStyle="neutral" py={12} bg="gray.900">
              <Stack spacing={2} fontSize="sm">
                <DrawerCloseButton />
                <FullNavLink
                  active={router.pathname === "/"}
                  name="Home"
                  href="/"
                  icon={IoHomeSharp}
                />
              </Stack>
            </DrawerContent>
          </DrawerOverlay>
        </Drawer>
      );
    }
```

Next.js provides a [router event](https://nextjs.org/docs/api-reference/next/router#routerevents), `routeChangeComplete`, which we use to toggle the mobile sidebar when a link is clicked.  

**The Dashboard View** 
Before we proceed, we need to break down the dashboard view. The dashboard view consists of the `DashboardLayout` component we set up earlier, a grid of products in a `ProductCard` component, and a `ModalProduct` component.

Let’s set up the dashboard view. 

```
    import { useState } from "react";
    import { Box, SimpleGrid } from "@chakra-ui/react";
    import { motion } from "framer-motion";
    import ProductCard from "@components/ProductCard";
    import DashboardLayout from "@layout/Dashboard";
    import ModalProduct from "@components/ModalProduct";
    import data from "@root/data";
    const MotionSimpleGrid = motion(SimpleGrid);
    const MotionBox = motion(Box);
    
    export default function Dashboard() {
      const [modalData, setModalData] = useState(null);
      return (
        <DashboardLayout>
          <Box>
            <MotionSimpleGrid>
              {data.map((product) => (
                <MotionBox variants={cardVariant} key={product.id}>
                  <ProductCard product={product} setModalData={setModalData} />
                </MotionBox>
              ))}
            </MotionSimpleGrid>
            <ModalProduct
              isOpen={modalData ? true : false}
              onClose={() => setModalData(null)}
              modalData={modalData}
            />
          </Box>
        </DashboardLayout>
      );
    }
```

Let’s breakdown the snippet above:

- We define a `modalData` state where the data of the clicked product will be stored. 
- We pass `setModalData` and the product data to the `ProductCard` component. We will use this to update the `modalData` state with the data from the current product being clicked.
- We pass an `isOpen` boolean, an `onClose` method and and the `modalData` state to the `ModalProduct` component. The value of `isOpen` depends on the value of `modalData`. If `modalData` is null, then `isOpen` is false. 

When `ProductCard` is clicked, it triggers `ModalProduct` and causes `ModalProduct` to pop up.


Here’s a sample of the data we map through and pass to `ProductCard`.

```
    const data = [
      {
        title: "First Pet",
        price: 250,
        img: "https://res.cloudinary.com/nefejames/image/upload/q_auto,f_auto,w_auto/Hackmamba/Images/pet1.jpg",
        video: "/Hackmamba/Videos/pet1",
      },
      {
        title: "Second Pet",
        price: 250,
        img: "https://res.cloudinary.com/nefejames/image/upload/q_auto,f_auto,w_auto/Hackmamba/Images/pet2.jpg",
        video: "/Hackmamba/Videos/pet2",
      },
      // other data objects below
    ]

```

**Creating the** `**ProductCard**` **and** `**ModalProduct**` **Component**
Now that we understand how `ProductCard` and `ModalProduct` work, let’s create them.

Let’s start with setting up `ProductCard`. 

```
    import Image from "next/image";
    import { Box, Flex } from "@chakra-ui/react";
    import { StarIcon } from "@chakra-ui/icons";
    
    export default function ProductCard({ product, setModalData }) {
      const { img, beds, baths, title, price } = product;
      const score = Math.floor(Math.random(5) * 5);
      const reviewCount = Math.floor(Math.random(50) * 50);
    
      return (
        <Flex>
          <Box onClick={() => setModalData(product)}>
            <Box>
              <Image
                src={img}
                objectFit="cover"
                alt="picture of an animal"
                layout="fill"
                objectFit="cover"
              />
            </Box>
    
            <Box p="6">
              <Box> {title} </Box>
              <Box>${price}</Box>
    
              <Box d="flex" mt="3" alignItems="center">
                {Array(5)
                  .fill("")
                  .map((_, i) => (
                    <StarIcon key={i} color={i < score ? "teal.500" : "gray.300"} />
                  ))}
                <Box as="span" ml="2" color="gray.600" fontSize="sm">
                  {reviewCount} reviews
                </Box>
              </Box>
            </Box>
          </Box>
        </Flex>
      );
    }
```

As stated earlier, we pass `setModalData` to `ProductCard`. When `ProductCard` is clicked, the `modalData` state is updated with the data of the product card that was clicked.

Next, let’s set up `ModalProduct`. To do that we use Chakra UI’s [Modal](https://chakra-ui.com/docs/overlay/modal) component. 

```
    import { Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody,
      ModalCloseButton, Button, useToast } from "@chakra-ui/react";
    import { Video } from "cloudinary-react";
    
    export default function ModalProduct({ isOpen, onClose, modalData }) {
      const { title, price, video } = modalData || {};
      const toast = useToast();
    
      const handleModalClose = () => {
        toast({
          title: "Purchase successsful.",
          description: "One more happy pet.",
          status: "success",
          duration: 3000,
          isClosable: true,
        });
        setTimeout(() => {
          onClose();
        }, 1000);
      };
    
      return (
        <Modal isOpen={isOpen} onClose={onClose} size="xl">
          <ModalOverlay />
          <ModalContent>
            <ModalCloseButton />
            <ModalHeader>Pet Details</ModalHeader>
            <ModalBody>
              <Box w="full" h="full">
                <Video controls publicId={video} width={300} crop="scale" />
    
                <Box pt="3">
                  <Box> {title} </Box>
                  ${price}
                </Box>
              </Box>
            </ModalBody>
            <ModalFooter>
              <Button onClick={handleModalClose}>
                Purchase
              </Button>
            </ModalFooter>
          </ModalContent>
        </Modal>
      );
    }
```

Earlier, we passed `isOpen`, `onClose`, and `modalData` to `ModalProduct` from the dashboard view. In the snippet above, we access them through props. We use `isOpen` and `onClose` to set up the modal and pass the data from `modalData` to the appropriate parts of `ModalProduct`.

We also define a function, `handleModalClose`, and pass it to the modal’s purchase button. The `handleModalClose` function displays a [success toast](https://chakra-ui.com/docs/feedback/toast) and closes the modal when it is called. 



## User Authentication

As stated earlier, we will use Auth0 to handle the user authentication in this application. Auth0 has provided [a guide](https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/) on how to work with their Next.js SDK. 
The `useUser` hook exports two objects, user and error, and an isLoading boolean. 

- `user` contains information about the authenticated user.
- We use `isLoading` to check if the SDK has completed loading.
- We use the `error` object to check if there were any authentication errors. 

We will use the logged-in user profile in the following parts of our application:

- the `index.js` page
- the homepage header
- the dashboard header

**The Index.js Page**
When the user logs in from the homepage, we want to redirect them to the dashboard route. We set up a `useEffect` and check if `user` exists. If it does, we redirect the user to the app route.

```
    import { useEffect } from "react";
    import HomePage from "@views/Home";
    import { useUser } from "@auth0/nextjs-auth0";
    import { useRouter } from "next/router";
    
    export default function Home() {
      const { user } = useUser();
      const router = useRouter();
    
      useEffect(() => {
        if (user) {
          router.push("/app");
        }
      }, [user, router]);
    
      return (
        <div>
          <Head>
            <title>Hackmamba PetShop</title>
          </Head>
    
          <HomePage />
        </div>
      );
    }
```

**The Homepage Header**
Here, we conditionally show the logout and login buttons. We show the login button if there is no user and the logout button if there is a logged-in user.

```
    import NextLink from "next/link";
    import { Box, Flex, Button } from "@chakra-ui/react";
    import Logo from "@svgs/Logo";
    import { useUser } from "@auth0/nextjs-auth0";
    
    export default function HomeHeader() {
      const { user, isLoading } = useUser();
    
      return (
        <Box>
          <Flex>
            <NextLink href="/" rel="noopener noreferrer" target="_blank">
              <a>
                <Logo />
              </a>
            </NextLink>
          
            {!isLoading && !user && (
              <NextLink href="/api/auth/login" passHref>
                <Button as="a" bg="blue.700" _hover={{ bg: "blue.900" }}>
                  Sign In/Log in
                </Button>
              </NextLink>
            )}
    
            {user && (
              <NextLink href="/api/auth/logout" passHref>
                <Button as="a" bg="blue.700" _hover={{ bg: "blue.900" }}>
                  Logout
                </Button>
              </NextLink>
            )}
          </Flex>
        </Box>
      );
    }

```
**The Dashboard Header**
Here, we show the logout button when a user is logged in.

```
    import NextLink from "next/link";
    import { Flex, Spacer, Stack, Button, Link } from "@chakra-ui/react";
    import SidebarToggleButton from "./components/SidebarToggleButton";
    import { useUser } from "@auth0/nextjs-auth0";
    import Logo from "@svgs/Logo";
    
    export default function DashboardHeader() {
      const { user } = useUser();
    
      return (
        <Flex>
          <Stack direction="row" w="full" alignItems="center" spacing="8">
            <NextLink href="/" rel="noopener noreferrer" target="_blank">
              <a>
                <Logo />
              </a>
            </NextLink>
            <SidebarToggleButton />
    
            <Spacer />
    
            {user && (
              <Link href="/api/auth/logout">
                <Button bg="blue.700" _hover={{ bg: "blue.900" }}>Logout</Button>
              </Link>
            )}
          </Stack>
        </Flex>
      );
    }

```
## Conclusion 

We learned how to use Auth0, Next.js, Chakra UI, and Cloudinary to create an eCommerce application. We also learned how to add interactivity to our applications and make them responsive with Chakra UI’s `useDisclosure` and `useMediaQuery` hooks.


## Further Reading

You can learn more about the technologies used from the links below.

**Cloudinary**
[React video transformations](https://cloudinary.com/documentation/react_video_manipulation)
[Image optimization](https://cloudinary.com/documentation/image_optimization)
[](https://github.com/cloudinary/cloudinary-react)[cloudinary-react](https://github.com/cloudinary/cloudinary-react)

**Auth0**
[How to authenticate Next.js apps with Auth0](https://auth0.com/docs/quickstart/webapp/nextjs/01-login) 
[The ultimate guide to Next.js authentication with Auth0](https://auth0.com/blog/ultimate-guide-nextjs-authentication-auth0/)

**Next.js**
[Getting started with Next.js](https://nextjs.org/docs/getting-started)


Content created for the [Hackmamba](https://content.hackmamba.io/) Jamstack Content Hackathon