{"id":28119,"date":"2022-03-23T22:25:34","date_gmt":"2022-03-23T22:25:34","guid":{"rendered":"http:\/\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-2-of-2"},"modified":"2022-03-23T22:25:34","modified_gmt":"2022-03-23T22:25:34","slug":"how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/","title":{"rendered":"How to build a Product Hunt Clone &#8211; Part 2 of 2"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><h2>Introduction<\/h2>\n<p>This jam is the final part of a two-part series on creating a Product Hunt Clone in Next.js. In <a href=\"https:\/\/mediajams.dev\/post\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-1-of-2\">the first part<\/a>, we configured the initial Next.js app with Auth0, Cloudinary, and Airtable. Then, we fetched and displayed the products on the homepage. We also discussed how to add and delete products in our app.<\/p>\n<p>In this part, we will build the User Profile page where users can see their products and add the functionality to update the products. We will also discuss how to upvote a product.<\/p>\n<p>If you want to jump right into the code, check out <a href=\"https:\/\/github.com\/lelouchB\/product-hunt-clone-part-2\">the GitHub Repo here<\/a>.<\/p>\n<p>You can refer to the first part here: <a href=\"https:\/\/mediajams.dev\/post\/How-to-build-a-Product-Hunt-Clone-with-Next.js-Part-1-of-2\">How to build a Product Hunt Clone with Next.js &#8211; Part 1 of 2<\/a><\/p>\n<h2>CodeSandbox<\/h2>\n<p>Since the preview in the CodeSandbox is embedded inside another page,  you will see the products but you will not be able to log in via the preview. Instead, to explore and play navigate to deployed version, <a href=\"https:\/\/d00re.sse.codesandbox.io\/\">https:\/\/d00re.sse.codesandbox.io\/<\/a>.<\/p>\n<\/div>\n  \n  <div class=\"wp-block-cloudinary-code-sandbox \">\n    <iframe\n      src=\"https:\/\/codesandbox.io\/embed\/product-hunt-clone-part-2-d00re?theme=dark&amp;codemirror=1&amp;highlights=&amp;editorsize=50&amp;fontsize=14&amp;expanddevtools=0&amp;hidedevtools=0&amp;eslint=0&amp;forcerefresh=0&amp;hidenavigation=0&amp;initialpath=%2F&amp;module=%2Fpages%2Findex.js&amp;moduleview=0&amp;previewwindow=&amp;view=&amp;runonclick=1\"\n      height=\"500\"\n      style=\"width: 100%;\"\n      title=\"product-hunt-clone-part-2\"\n      loading=\"lazy\"\n      allow=\"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking\"\n      sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"\n    ><\/iframe>\n  <\/div>\n\n  <div class=\"wp-block-cloudinary-markdown \"><h2>How to create User Profile page<\/h2>\n<p>In this section, you will create the <code>My Profile<\/code> page, showing the current user\u2019s information and their products. You have already created the link to the <code>My Profile<\/code> page in the Navbar component, i.e., <code>\/user\/profile<\/code>.<\/p>\n<p>You can split the <code>My Profile<\/code> page into two parts. First, you need to show the user information, and second, you need to show the products created by that user. You can easily extract user information from the <code>user<\/code> object of the <code>useUser()<\/code> hook. For the second part, you will need to create a function in the <code>lib\/api.js<\/code> file to get the products where the <code>Sub<\/code> of product is equal to the current user\u2019s <code>sub<\/code>.<\/p>\n<p>Create and export a function named <code>getProductsByUserSub()<\/code> by adding the following code to <code>lib\/api.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getProductsByUserSub = <span class=\"hljs-keyword\">async<\/span> (sub) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> table\n    .select({\n      <span class=\"hljs-attr\">filterByFormula<\/span>: <span class=\"hljs-string\">`Sub = \"<span class=\"hljs-subst\">${sub}<\/span>\"`<\/span>,\n    })\n    .firstPage();\n  <span class=\"hljs-keyword\">return<\/span> data.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">record<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">id<\/span>: record.id, ...record.fields };\n  });\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the above code, you pass the <code>sub<\/code> of the user as an argument to the function and, with the help of the <code>filterByFormula<\/code> method, filter the records where the <code>Sub<\/code> of product is equal to the current user\u2019s <code>sub<\/code>.<\/p>\n<p>The next step is to create the <code>profile.js<\/code> file under the <code>pages\/user<\/code> directory. Run the following command in the terminal.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir pages\/user\ntouch pages\/user\/profile.js\n<\/code><\/span><\/pre>\n<p>Add the following code to the <code>user\/profile.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-comment\">\/\/ pages\/user\/profile.js<\/span>\n<span class=\"hljs-keyword\">import<\/span> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser, withPageAuthRequired, getSession } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Product <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/components\/Product\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/components\/Navbar\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {getProductsByUserSub} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/api\"<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Profile<\/span>(<span class=\"hljs-params\">{products}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n\n  <span class=\"hljs-keyword\">if<\/span> (isLoading) {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto my-64 text-gray-800 text-center text-3xl\"<\/span>&gt;<\/span>\n        Loading\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\n  }\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>{error.message}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"contianer px-2\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span> User: {user.name}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"icon\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/favicon.ico\"<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex space-x-4 px-4 my-10 justify-around\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-36 w-96 rounded-3xl  shadow-lg relative flex flex-col \n\t    items-center justify-between md:items-start p-5 transition-all duration-150\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">img<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"rounded-full w-20 h-20 shadow-sm absolute -top-8\n\t    transform md:scale-110 duration-700\"<\/span>\n            <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{user.picture}<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{user.name}<\/span>\n          \/&gt;<\/span>\n\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\" align-middle text-2xl font-semibold text-gray-800 \n\t     text-center m-auto md:m-0 md:mt-8\"<\/span>&gt;<\/span>\n            {user.name}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">ul<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-lg text-gray-600 font-light md:block\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">li<\/span>&gt;<\/span>Products: {products ? products.length : 0}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">li<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">ul<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n      {products.length&gt;0 ? (\n        products.map((product) =&gt; (\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Product<\/span>\n            <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{product.id}<\/span>\n            <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">{product.id}<\/span>\n            <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">{product.Name}<\/span>\n            <span class=\"hljs-attr\">link<\/span>=<span class=\"hljs-string\">{product.Link}<\/span>\n            <span class=\"hljs-attr\">description<\/span>=<span class=\"hljs-string\">{product.Description}<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{product.PublicId}<\/span>\n            <span class=\"hljs-attr\">check<\/span>=<span class=\"hljs-string\">{true}<\/span>\n          \/&gt;<\/span>\n        ))\n      ) : (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-auto my-12 text-gray-800 text-center text-3xl\"<\/span>&gt;<\/span>\n          You are yet to create your first Product.\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getServerSideProps = withPageAuthRequired({\n  <span class=\"hljs-attr\">returnTo<\/span>: <span class=\"hljs-string\">\"\/api\/auth\/login\"<\/span>,\n  <span class=\"hljs-keyword\">async<\/span> getServerSideProps(ctx) {\n    <span class=\"hljs-keyword\">const<\/span> sub = <span class=\"hljs-keyword\">await<\/span> getSession(ctx.req).user.sub;\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> getProductsByUserSub(sub);\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">props<\/span>: {\n        <span class=\"hljs-attr\">products<\/span>: data,\n      },\n    };\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the above code, you are using Next.js <code>getServerSideProps<\/code> function to fetch the products created by the current user with the help of the <code>getProductsByUserSub()<\/code> function.<\/p>\n<p>You will notice that you have wrapped the <code>getServerSideProps()<\/code> function with  <code>withPageAuthRequired()<\/code> method from the <code>@auth0\/nextjs-auth0<\/code> SDK. This <code>withPageAuthRequired<\/code> protects the <code>My Profile<\/code> page from anonymous logins and redirects them to the login page. You can read more about this method <a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/modules\/helpers_with_page_auth_required.html#withpageauthrequired\">here<\/a>.<\/p>\n<p>The <code>getProductsByUserSub()<\/code> function requires the user\u2019s <code>sub<\/code> to fetch the products, accessed using the <code>getSession()<\/code> method. The <code>getSession()<\/code> method gets the user\u2019s session from the request. You can read more about this method <a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/modules\/session_get_session.html\">here<\/a>.<\/p>\n<p>The records from Airtable are passed as props to the <code>Profile<\/code> component. In this component, you use the <code>user<\/code> object to show the current user\u2019s name and profile picture.<\/p>\n<p>You also show the total number of products created by the user by calculating the length of the <code>products<\/code> array. You then map over the <code>products<\/code> array, and similar to the <code>index.js<\/code> file, show the products on the page using the <code>Product<\/code> component.<\/p>\n<p>Since the products fetched from Airtable are created by the current user, you pass <code>true<\/code> in the <code>check<\/code> prop of the <code>Product<\/code> component.<\/p>\n<p>There is also a default message if the current user has not created any products.<\/p>\n<p>Here is how the <code>My Profile<\/code> page will look if the user has not created any product.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623311563\/e-603fc45fe6c0b4006873802f\/spjradcm9windtm0ywme.png\" alt=\"My Profile - No Product\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1536\" height=\"688\"\/><\/p>\n<p>Here is the <code>My Profile<\/code> page after the user has created a product.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623311674\/e-603fc45fe6c0b4006873802f\/c3wpwj3el4mp0npce3vf.png\" alt=\"My Profile - Product\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<h2>How to update a Product<\/h2>\n<p>In this section, you will create the <code>update\/[id]<\/code> dynamic route to edit\/update a product.  In this jam, you will update only the textual data of the product and not its image.<\/p>\n<p>To update the product, you will create a form with <code>Name<\/code>, <code>Link<\/code>, and <code>Description<\/code> fields. Then, you will fetch the selected product and prefill the form with its data so that the user can see and update the product accordingly.<\/p>\n<p>You might remember that in the <code>Product<\/code> component, you were sending the <code>id<\/code> of the product in the query parameter.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-1 h-6 w-6\"<\/span>\n  <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span>\n    router.push({\n      pathname: \"\/update\/&#91;id]\",\n      query: {\n        id: id,\n      },\n    })\n  }\n&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You will use this <code>id<\/code> in the query to fetch the corresponding product from Airtable.<\/p>\n<p>Create and export functions named <code>getProductById()<\/code>  and <code>updateProduct()<\/code>  in <code>lib\/api.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getProductById = <span class=\"hljs-keyword\">async<\/span> (id) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> table.find(id);\n  <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">id<\/span>: data.id, ...data.fields };\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> updateProduct = <span class=\"hljs-keyword\">async<\/span> ({ id, name, link, description }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> table.update(\n    id,\n    {\n      <span class=\"hljs-attr\">Name<\/span>: name,\n      <span class=\"hljs-attr\">Description<\/span>: description,\n      <span class=\"hljs-attr\">Link<\/span>: link,\n    },\n\n    <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">err, records<\/span>) <\/span>{\n      <span class=\"hljs-keyword\">if<\/span> (err) {\n        <span class=\"hljs-built_in\">console<\/span>.error(err);\n      }\n    }\n  );\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the <code>getProductById<\/code>  function, you use Airtable\u2019s <code>find()<\/code> method to retrieve the product based on its id.<\/p>\n<p>In the <code>updateProduct<\/code> function, you use Airtable\u2019s <code>update<\/code> method to update the product. You pass the product\u2019s <code>id<\/code> to be updated as the first argument,  followed by an object containing the fields to be updated, i.e., <code>Name<\/code>, <code>Description<\/code>, and <code>Link<\/code>. The remaining fields like <code>Sub<\/code> and <code>PublicId<\/code> remain unchanged.<\/p>\n<p>Run the following command to create the <code>[id].js<\/code> under the <code>pages\/update<\/code> directory.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">mkdir pages\/update\ntouch pages\/update\/&#91;id].js\n<\/code><\/span><\/pre>\n<p>Add the following code to <code>[id].js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React, { useState } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Head <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/head\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser, withPageAuthRequired } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { getProductById } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/api\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Navbar <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/components\/Navbar\"<\/span>;\n\n<span class=\"hljs-keyword\">const<\/span> UpdateProduct = <span class=\"hljs-function\">(<span class=\"hljs-params\">{ product }<\/span>) =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n\n  <span class=\"hljs-keyword\">const<\/span> &#91;name, setName] = useState(product.Name);\n  <span class=\"hljs-keyword\">const<\/span> &#91;link, setLink] = useState(product.Link);\n  <span class=\"hljs-keyword\">const<\/span> &#91;description, setDescription] = useState(product.Description);\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n\n  <span class=\"hljs-keyword\">if<\/span> (isLoading) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>Loading<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n  <span class=\"hljs-keyword\">if<\/span> (error) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>{error.message}<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> handleSubmit = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> e.preventDefault();\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/updateProduct\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"POST\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">id<\/span>: router.query.id, name, description, link }),\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      }\n    });\n    <span class=\"hljs-keyword\">await<\/span> router.replace(<span class=\"hljs-string\">\"\/\"<\/span>);\n  };\n\n  <span class=\"hljs-keyword\">if<\/span> (user) {\n    <span class=\"hljs-keyword\">return<\/span> (\n      <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">title<\/span>&gt;<\/span> Update Product : {product.Name} <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">title<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">link<\/span> <span class=\"hljs-attr\">rel<\/span>=<span class=\"hljs-string\">\"icon\"<\/span> <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">\"\/favicon.ico\"<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Head<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Navbar<\/span> \/&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"md:grid  justify-items-center md:gap-6\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-0  md:mt-0 md:col-span-2\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">h3<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-4xl text-center font-normal leading-normal mt-0 text-indigo-800\"<\/span>&gt;<\/span>\n              Update Product: {product.Name}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">h3<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"shadow-md sm:rounded-md sm:overflow-hidden\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"px-4 py-5 bg-white space-y-6 sm:p-6\"<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-6\"<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"col-span-3 sm:col-span-2\"<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>\n                        <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"product_name\"<\/span>\n                        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block text-sm font-medium text-gray-700\"<\/span>\n                      &gt;<\/span>\n                        Name of the Product\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-1 flex rounded-md shadow-sm\"<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n                          <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n                          <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"product_name\"<\/span>\n                          <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"product_name\"<\/span>\n                          <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{name}<\/span>\n                          <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setName(e.target.value)}\n                          autoComplete=\"given-name\"\n                          className=\"mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md\"\n                        \/&gt;\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>\n                      <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"product_description\"<\/span>\n                      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block text-sm font-medium text-gray-700\"<\/span>\n                    &gt;<\/span>\n                      Description\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-1\"<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">textarea<\/span>\n                        <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"product_description\"<\/span>\n                        <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"product_description\"<\/span>\n                        <span class=\"hljs-attr\">rows<\/span>=<span class=\"hljs-string\">{3}<\/span>\n                        <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{description}<\/span>\n                        <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setDescription(e.target.value)}\n                        className=\"shadow-sm focus:ring-indigo-500 focus:border-indigo-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-md\"\n                        placeholder=\"Brief description for your Product.\"\n                      \/&gt;\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"grid grid-cols-3 gap-6\"<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"col-span-3 sm:col-span-2\"<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">label<\/span>\n                        <span class=\"hljs-attr\">htmlFor<\/span>=<span class=\"hljs-string\">\"product_link\"<\/span>\n                        <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block text-sm font-medium text-gray-700\"<\/span>\n                      &gt;<\/span>\n                        Link\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">label<\/span>&gt;<\/span>\n                      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-1 flex rounded-md shadow-sm\"<\/span>&gt;<\/span>\n                        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span>\n                          <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"text\"<\/span>\n                          <span class=\"hljs-attr\">name<\/span>=<span class=\"hljs-string\">\"product_link\"<\/span>\n                          <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"product_link\"<\/span>\n                          <span class=\"hljs-attr\">value<\/span>=<span class=\"hljs-string\">{link}<\/span>\n                          <span class=\"hljs-attr\">onChange<\/span>=<span class=\"hljs-string\">{(e)<\/span> =&gt;<\/span> setLink(e.target.value)}\n                          className=\"focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-md sm:text-sm border-gray-300\"\n                          placeholder=\"www.example.com\"\n                        \/&gt;\n                      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"px-4 py-2 bg-gray-50 text-right sm:px-6\"<\/span>&gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n                    <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"submit\"<\/span>\n                    <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{handleSubmit}<\/span>\n                    <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500\"<\/span>\n                  &gt;<\/span>\n                    Update\n                  <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">form<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n    );\n  }\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> UpdateProduct;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getServerSideProps = withPageAuthRequired({\n  <span class=\"hljs-attr\">returnTo<\/span>: <span class=\"hljs-string\">\"\/api\/auth\/login\"<\/span>,\n  <span class=\"hljs-keyword\">async<\/span> getServerSideProps(ctx) {\n    <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> getProductById(ctx.params.id);\n\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">props<\/span>: {\n        <span class=\"hljs-attr\">product<\/span>: data,\n      },\n    };\n  },\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p><em>You use the same code for the form as in the <code>product\/insert.js<\/code> file.<\/em><\/p>\n<p>Similar to <code>\/user\/profile.js<\/code> file, you use the <code>getServerSideProps()<\/code> function wrapped with the <code>withPageAuthRequired<\/code> method to redirect any anonymous user to the login page.<\/p>\n<p>You access the product\u2019s <code>id<\/code> using the <code>params<\/code> object and pass it to the <code>getProductById()<\/code> function. Finally, the product fetched is sent to the  <code>UpdateProduct<\/code> function as a prop.<\/p>\n<p>You define three states for name, description, and link of the product and set their default value to their corresponding value in the <code>product<\/code> object.<\/p>\n<p>Then, in the <code>UpdateProduct<\/code> function\u2019s <code>return<\/code> statement, you create a two-way data bind between the user input and the states.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623311746\/e-603fc45fe6c0b4006873802f\/isxkakmzhunhsgnindcr.png\" alt=\"Update Page\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<p>Once the user has updated the product and clicks the <code>Update<\/code> button, the <code>handleSubmit()<\/code> function is triggered, sending a POST request to <code>\/api\/updateProduct<\/code> API route with the <code>id<\/code>, <code>name<\/code>, <code>description<\/code>, and <code>link<\/code> of the product in the request body.<\/p>\n<p>Run the following commands to create the <code>\/api\/updateProduct<\/code> API route.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch pages\/api\/updateProduct.js\n<\/code><\/span><\/pre>\n<p>Add the following code to <code>updateProduct.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { withApiAuthRequired } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> {updateProduct} <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/api\"<\/span>\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"POST\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { name, description, link, id } = req.body;\n\n  <span class=\"hljs-keyword\">await<\/span> updateProduct({ id, name, description, link });\n  <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Product Updated\"<\/span> });\n  \n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You protect this API route with the <code>withApiAuthRequired()<\/code> method from <code>@auth0\/nextjs-auth0<\/code> SDK. Then, you import the <code>updateProduct<\/code> function from the <code>lib\/api.js<\/code> file and pass the arguments sent in the request body to it. Finally, after the product has been updated successfully, <code>Product Updated<\/code> is sent as a response.<\/p>\n<p>Here is a GIF showing the <strong>Update Product<\/strong> feature in action.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623311861\/e-603fc45fe6c0b4006873802f\/lly13ve3wte7umbwe7ve.gif\" alt=\"GIF Update\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1280\" height=\"720\"\/><\/p>\n<h2>How to upvote a Product<\/h2>\n<p>In this section, you will create the upvote feature in Products. Like the actual Product Hunt, a user should be able to upvote a product only once and should be able to remove their upvote from the product. For tutorial purposes, we will call the process of removing the upvote &#8211;  downvote.<\/p>\n<p><em>Note: You are not actually downvoting a product; you are removing your upvote from it.<\/em><\/p>\n<p>For every upvote, you need to keep track of the <code>id<\/code> of the product and the unique identifier of a user, i.e., the user\u2019s <code>sub<\/code>.  You can store the <code>id<\/code> and <code>sub<\/code> together to represent an upvote.<\/p>\n<p>By checking if the user\u2019s <code>sub<\/code> is already associated with a product, i.e., a pair of the current user\u2019s <code>sub<\/code> and the product <code>id<\/code>, you can make sure each user votes for a product only once. Then, when the user removes their upvote, you can delete this product <code>id<\/code> and <code>sub<\/code> pair.<\/p>\n<p>If you were using a NoSQL database like MongoDB, you could have created a new field in the schema and store the  <code>sub<\/code> of the current user in an array when the upvote button is clicked. This array would contain the <code>sub<\/code> of all the users who upvoted a particular product. And to downvote a product, you could remove that user\u2019s <code>sub<\/code> from the array.<\/p>\n<p>The resultant <code>product<\/code> object would look similar to this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"id\"<\/span>: <span class=\"hljs-string\">\"rec176WGNlRJGN0mg\"<\/span>,\n  <span class=\"hljs-attr\">\"Name\"<\/span>: <span class=\"hljs-string\">\"Dev Desks\"<\/span>,\n  <span class=\"hljs-attr\">\"Description\"<\/span>: <span class=\"hljs-string\">\"The place where Devs share Desks! \"<\/span>,\n  <span class=\"hljs-attr\">\"PublicId\"<\/span>: <span class=\"hljs-string\">\"product-hunt-clone\/wji2dipmt3cssvvwbr06\"<\/span>,\n  <span class=\"hljs-attr\">\"Link\"<\/span>: <span class=\"hljs-string\">\"https:\/\/devdesks.io\/\"<\/span>,\n  <span class=\"hljs-attr\">\"Sub\"<\/span>: <span class=\"hljs-string\">\"google-oauth2|10535765999545372338041\"<\/span>,\n  <span class=\"hljs-attr\">\"Votes\"<\/span>: &#91;\n    <span class=\"hljs-string\">\"google-oauth2|10535765999545372338041\"<\/span>,\n    <span class=\"hljs-string\">\"google-oauth2|1053576593456372338041\"<\/span>,\n    <span class=\"hljs-string\">\"google-oauth2|105357659995452323041\"<\/span>\n  ]\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>But, you cannot create an array object in Airtable, so its not possible to store the votes in the same manner as in a NoSQL database.<\/p>\n<p>Here are a few workarounds for this limitation:<\/p>\n<ul>\n<li>You can create a <code>Long text<\/code> field in the schema and store the <code>sub<\/code> as strings separated by commas and then use the <code>split(',')<\/code> and <code>join()<\/code> methods to convert them into an array. Here you will have to make sure that when the user upvotes a product, you add to the existing string to retain the previous votes.<\/li>\n<li>You can create another table in the same base named <code>votes<\/code>, and for each vote, you can store both <code>sub<\/code> and the <code>id<\/code> of the product in it. You can then filter out the votes of a product using its id.<\/li>\n<\/ul>\n<p>You will use the second method in this jam and create a new table named <code>votes<\/code> in the <code>Product Hunt Clone<\/code> base.<\/p>\n<p>Head over to your Airtable account and open the <code>Product Hunt Clone<\/code> base. Create a new table named <code>votes<\/code> with the following schema.<\/p>\n<ul>\n<li>\n<code>ProductId<\/code> &#8211; Id of the product upvoted.<\/li>\n<li>\n<code>Sub<\/code> &#8211; Sub of the user who upvoted the product.<\/li>\n<\/ul>\n<p>You can refer to the <code>votes<\/code> table of this project <a href=\"https:\/\/airtable.com\/shrO6QCi02vl2cL6H\/tblYgtJlTh0N8tUzG\/viw1Y2ILVrsHPUl1G?blocks=hide\">here<\/a>.<\/p>\n<p>Each record in the <code>votes<\/code> table represents a vote. So, for example, if there is one product and three users in your Product Hunt Clone app. And each user upvotes the product; there will be three records in the <code>votes<\/code> table and one record in the <code>products<\/code> table.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623311934\/e-603fc45fe6c0b4006873802f\/cexkw0y9lgbjbpk83ydk.png\" alt=\"Votes Table\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1920\" height=\"860\"\/><\/p>\n<p>The next step is to create the <code>getVotesByProductId<\/code> , <code>upvoteProduct<\/code> and <code>downvoteProduct<\/code> functions in <code>lib\/api.js<\/code>. Add the following code to <code>lib\/api.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> getVotesByProductId = <span class=\"hljs-keyword\">async<\/span> (productId) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"votes\"<\/span>)\n    .select({\n      <span class=\"hljs-attr\">view<\/span>: <span class=\"hljs-string\">\"Grid view\"<\/span>,\n      <span class=\"hljs-attr\">filterByFormula<\/span>: <span class=\"hljs-string\">`ProductId = \"<span class=\"hljs-subst\">${productId}<\/span>\"`<\/span>,\n    })\n    .firstPage();\n\n    <span class=\"hljs-keyword\">return<\/span> data.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">vote<\/span>) =&gt;<\/span> {\n    <span class=\"hljs-keyword\">return<\/span> { <span class=\"hljs-attr\">id<\/span>: vote.id, ...vote.fields };\n  });\n};\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> upvoteProduct = <span class=\"hljs-keyword\">async<\/span> ({ productId, sub }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"votes\"<\/span>).create({\n    <span class=\"hljs-attr\">ProductId<\/span>: productId,\n    <span class=\"hljs-attr\">Sub<\/span>: sub,\n  });\n};\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> downvoteProduct = <span class=\"hljs-keyword\">async<\/span> ({ id }) =&gt; {\n  <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"votes\"<\/span>).destroy(id);\n};\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>getVotesByProductId()<\/code> functions filter out the records based on the product <code>id<\/code>. This means that the response returned from this function is the votes of a single product.<\/p>\n<p>The <code>upvoteProduct<\/code> function creates a new record in the <code>votes<\/code> table with the current user\u2019s sub and the product\u2019s <code>id<\/code> upvoted. The <code>productId<\/code> passed to the <code>upvoteProduct<\/code> function is the <code>id<\/code> of the product.<\/p>\n<p>The <code>downvoteProduct<\/code> function deletes the vote or record from the <code>votes<\/code> table. The <code>id<\/code> passed to the <code>downvoteProduct<\/code> function is the <code>id<\/code> of the vote. You might ask how to know which vote or record to delete from the <code>votes<\/code> table. Or which vote <code>id<\/code> should be passed in the <code>downvoteProduct<\/code> function.<\/p>\n<p>Since each user can only vote once, if the user has upvoted a product, then the response returned from the <code>getVotesByProductId<\/code> function will have an object containing the <code>sub<\/code> of that user. You can filter out that object from all the objects in the array. That object will contain the <code>id<\/code> of the vote to be deleted.<\/p>\n<p>The next step is to create the API routes for these functions. Create the <code>getVotesByProductId\/[id]<\/code> , <code>upvote<\/code> and <code>downvote<\/code> API routes in the <code>api<\/code> directory.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch pages\/api\/upvote.js\ntouch pages\/api\/downvote.js\nmkdir pages\/api\/getVotesByProductId \ntouch pages\/api\/getVotesByProductId\/&#91;id].js\n<\/code><\/span><\/pre>\n<p>Add the following code to <code>getVotesByProductId\/[id].js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { getVotesByProductId } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/..\/lib\/api\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"GET\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> { id } = req.query;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> votes = <span class=\"hljs-keyword\">await<\/span> getVotesByProductId(id );\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).send(votes);\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You use the <code>id<\/code> of the product extracted from the request query and pass it to the <code>getVotesByProductId<\/code> function. The data returned from the <code>getVotesByProductId<\/code> function is sent as the response.<\/p>\n<p>Add the following code to <code>upvote.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { upvoteProduct } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/api\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { withApiAuthRequired, getSession } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> session = getSession(req, res);\n  <span class=\"hljs-keyword\">const<\/span> sub = <span class=\"hljs-keyword\">await<\/span> session.user.sub;\n\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"PUT\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { id } = req.body;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> upvotedProduct = <span class=\"hljs-keyword\">await<\/span> upvoteProduct({ <span class=\"hljs-attr\">productId<\/span>:id, sub });\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Upvoted\"<\/span> });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the above code, the product\u2019s <code>id<\/code> is extracted from the request body while the <code>sub<\/code> is accessed using the <code>getSession()<\/code> method from <code>@auth0\/nextjs-auth0<\/code> SDK. Finally, both the <code>id<\/code> and <code>sub<\/code> are passed to the <code>upvoteProduct<\/code> function.<\/p>\n<p>Add the following code to <code>downvote.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { downvoteProduct } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"..\/..\/lib\/api\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { withApiAuthRequired } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"PUT\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { id } = req.body;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> downvotedProduct = <span class=\"hljs-keyword\">await<\/span> downvoteProduct({ id });\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Downvoted\"<\/span> });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Every vote in the <code>votes<\/code> table is a record. You delete a vote is deleted in the same way  a product was deleted in the first part of the series, i.e., you use the <code>destroy<\/code> method and pass the <code>id<\/code> of the vote to it.<\/p>\n<p>You will fetch the votes using the <code>getVotesByProductId\/[id]<\/code> API route in the <code>Product<\/code> component. But, you cannot use the <code>getServerSideProps()<\/code> method in the <code>Product<\/code> component.<\/p>\n<p>You will need to install the <code>swr<\/code> package to fetch the votes. You can read more about <code>swr<\/code> package <a href=\"https:\/\/swr.vercel.app\/docs\/with-nextjs\">here<\/a>.<\/p>\n<p>Run the following command in the terminal.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">npm install swr \n<\/code><\/span><\/pre>\n<p>Update the <code>components\/Product.js<\/code> file like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { Image, Transformation } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"cloudinary-react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> useSWR <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"swr\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> Vote <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\".\/Vote\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Product<\/span>(<span class=\"hljs-params\">{\n  name,\n  id,\n  publicId,\n  description,\n  link,\n  check,\n}<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n  <span class=\"hljs-keyword\">const<\/span> { data, mutate } = useSWR(<span class=\"hljs-string\">`\/api\/getVotesByProductId\/<span class=\"hljs-subst\">${id}<\/span>`<\/span>);\n\n  <span class=\"hljs-keyword\">const<\/span> deleteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/deleteProduct\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"DELETE\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ id, publicId }),\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n    });\n    <span class=\"hljs-keyword\">await<\/span> router.reload();\n  };\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"max-w-md mx-auto my-4 bg-white rounded-xl shadow-xl overflow-hidden md:max-w-2xl\"<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"md:flex\"<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"md:flex-shrink-0\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Image<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-48 w-full object-cover md:w-48\"<\/span>\n            <span class=\"hljs-attr\">publicId<\/span>=<span class=\"hljs-string\">{publicId}<\/span>\n            <span class=\"hljs-attr\">cloudName<\/span>=<span class=\"hljs-string\">{process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME}<\/span>\n            <span class=\"hljs-attr\">alt<\/span>=<span class=\"hljs-string\">{name}<\/span>\n            <span class=\"hljs-attr\">format<\/span>=<span class=\"hljs-string\">\"webp\"<\/span>\n            <span class=\"hljs-attr\">secure<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Transformation<\/span> <span class=\"hljs-attr\">width<\/span>=<span class=\"hljs-string\">\"800\"<\/span> <span class=\"hljs-attr\">gravity<\/span>=<span class=\"hljs-string\">\"auto\"<\/span> <span class=\"hljs-attr\">crop<\/span>=<span class=\"hljs-string\">\"fill\"<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">Image<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"p-8 w-full\"<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex justify-between  items-start\"<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">a<\/span>\n              <span class=\"hljs-attr\">href<\/span>=<span class=\"hljs-string\">{link}<\/span>\n              <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"block mt-1 text-xl font-semibold leading-tight font-medium  text-indigo-700 hover:underline\"<\/span>\n            &gt;<\/span>\n              {name}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">a<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Vote<\/span> <span class=\"hljs-attr\">votes<\/span>=<span class=\"hljs-string\">{data}<\/span> <span class=\"hljs-attr\">refreshVotes<\/span>=<span class=\"hljs-string\">{mutate}<\/span> <span class=\"hljs-attr\">productId<\/span>=<span class=\"hljs-string\">{id}<\/span> \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mt-2 text-gray-600 w-10\/12\"<\/span>&gt;<\/span>{description} <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n\n          {check &amp;&amp; (\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\" flex justify-end\"<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n                <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"mx-1 h-6 w-6\"<\/span>\n                <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{()<\/span> =&gt;<\/span>\n                  router.push({\n                    pathname: \"\/update\/&#91;id]\",\n                    query: {\n                      id: id,\n                    },\n                  })\n                }\n              &gt;\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n                  <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n                  <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n                  <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n                  <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"gray\"<\/span>\n                &gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n                    <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n                    <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"<\/span>\n                  \/&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span> <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{deleteThisProduct}<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"px-1\"<\/span>&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n                  <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n                  <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n                  <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n                  <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n                  <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"gray\"<\/span>\n                &gt;<\/span>\n                  <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n                    <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n                    <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n                    <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"<\/span>\n                  \/&gt;<\/span>\n                <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n              <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n          )}\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n      <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span>\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You use the <code>useSWR<\/code> hook to fetch the data from the <code>getVotesByProductId\/[id]<\/code> API route.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> { data, mutate } = useSWR(<span class=\"hljs-string\">`\/api\/getVotesByProductId\/<span class=\"hljs-subst\">${id}<\/span>`<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The data fetched is stored in the <code>data<\/code> variable. This <code>data<\/code> array is passed to the <code>Votes<\/code> component along with <code>id<\/code> of the product and <code>mutate<\/code> function.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">Vote<\/span> <span class=\"hljs-attr\">votes<\/span>=<span class=\"hljs-string\">{data}<\/span> <span class=\"hljs-attr\">refreshVotes<\/span>=<span class=\"hljs-string\">{mutate}<\/span> <span class=\"hljs-attr\">productId<\/span>=<span class=\"hljs-string\">{id}<\/span> \/&gt;<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The <code>mutate<\/code> in the above code is a function that is pre-bound to the SWR\u2019s key or <code>\/api\/getVotesByProductId\/${id}<\/code>. This function updates the <code>data<\/code> array without any need to refresh the page. This means that if you upvote a product and call this function, the <code>data<\/code> array will be updated to include the latest addition without any reload of the whole page. You can read more about mutation <a href=\"https:\/\/swr.vercel.app\/docs\/mutation\">here<\/a>.<\/p>\n<p>The next step is to create the <code>Vote<\/code> component in the <code>components<\/code> directory. Run the following command to create the <code>Vote<\/code> component.<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">touch components\/Vote.js\n<\/code><\/span><\/pre>\n<p>Add the following code to the <code>Vote.js<\/code> file.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> React <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"react\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useUser } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">import<\/span> { useRouter } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"next\/router\"<\/span>;\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">Vote<\/span>(<span class=\"hljs-params\">{ votes, refreshVotes, productId }<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> router = useRouter();\n  <span class=\"hljs-keyword\">const<\/span> { user, error, isLoading } = useUser();\n\n  <span class=\"hljs-keyword\">const<\/span> voteOfThisUser = user &amp;&amp; votes ? votes.filter(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span>(<span class=\"hljs-params\">vote<\/span>)<\/span>{\n    <span class=\"hljs-keyword\">return<\/span> vote.Sub == user.sub;\n  }) : <span class=\"hljs-literal\">null<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> upvoteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> e.preventDefault();\n    <span class=\"hljs-keyword\">if<\/span> (!user) {\n      router.push(<span class=\"hljs-string\">\"\/api\/auth\/login\"<\/span>);\n    } <span class=\"hljs-keyword\">else<\/span> {\n      <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/upvote\"<\/span>, {\n        <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"PUT\"<\/span>,\n        <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">id<\/span>: productId }),\n        <span class=\"hljs-attr\">headers<\/span>: {\n          <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n        },\n      });\n      refreshVotes();\n    }\n  };\n\n  <span class=\"hljs-keyword\">const<\/span> downvoteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    <span class=\"hljs-keyword\">await<\/span> e.preventDefault();\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/downvote\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"PUT\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ <span class=\"hljs-attr\">id<\/span>: voteOfThisUser&#91;<span class=\"hljs-number\">0<\/span>].id }),\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n    });\n    refreshVotes();\n\n  };\n  <span class=\"hljs-keyword\">if<\/span> (!votes || isLoading || error) <span class=\"hljs-keyword\">return<\/span> <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span>&gt;<\/span>...<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n\n  <span class=\"hljs-keyword\">return<\/span> (\n    <span class=\"xml\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">div<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex\"<\/span>&gt;<\/span>\n      {user &amp;&amp; votes.some((elem) =&gt; elem.Sub === user.sub) ? (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex border shadow-sm border-purple-800 rounded-md p-1\"<\/span>\n          <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{downvoteThisProduct}<\/span>\n        &gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n            <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n            <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 20 20\"<\/span>\n            <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"purple\"<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n              <span class=\"hljs-attr\">fillRule<\/span>=<span class=\"hljs-string\">\"evenodd\"<\/span>\n              <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-8.707l-3-3a1 1 0 00-1.414 0l-3 3a1 1 0 001.414 1.414L9 9.414V13a1 1 0 102 0V9.414l1.293 1.293a1 1 0 001.414-1.414z\"<\/span>\n              <span class=\"hljs-attr\">clipRule<\/span>=<span class=\"hljs-string\">\"evenodd\"<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-purple-700\"<\/span>&gt;<\/span>\n            {  votes.length}\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      ) : (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">button<\/span>\n          <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"flex border  border-gray p-1   shadow-sm  rounded-md\"<\/span>\n          <span class=\"hljs-attr\">onClick<\/span>=<span class=\"hljs-string\">{upvoteThisProduct}<\/span>\n        &gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">svg<\/span>\n            <span class=\"hljs-attr\">xmlns<\/span>=<span class=\"hljs-string\">\"http:\/\/www.w3.org\/2000\/svg\"<\/span>\n            <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"h-6 w-6\"<\/span>\n            <span class=\"hljs-attr\">fill<\/span>=<span class=\"hljs-string\">\"none\"<\/span>\n            <span class=\"hljs-attr\">viewBox<\/span>=<span class=\"hljs-string\">\"0 0 24 24\"<\/span>\n            <span class=\"hljs-attr\">stroke<\/span>=<span class=\"hljs-string\">\"gray\"<\/span>\n          &gt;<\/span>\n            <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">path<\/span>\n              <span class=\"hljs-attr\">strokeLinecap<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n              <span class=\"hljs-attr\">strokeLinejoin<\/span>=<span class=\"hljs-string\">\"round\"<\/span>\n              <span class=\"hljs-attr\">strokeWidth<\/span>=<span class=\"hljs-string\">{2}<\/span>\n              <span class=\"hljs-attr\">d<\/span>=<span class=\"hljs-string\">\"M9 11l3-3m0 0l3 3m-3-3v8m0-13a9 9 0 110 18 9 9 0 010-18z\"<\/span>\n            \/&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">svg<\/span>&gt;<\/span>\n          <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">p<\/span> <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"text-gray-600\"<\/span>&gt;<\/span>\n            { votes.length}\n            <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">p<\/span>&gt;<\/span>\n        <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">button<\/span>&gt;<\/span>\n      )}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">div<\/span>&gt;<\/span><\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the above function, you show the number of votes using the length of the <code>votes<\/code> array. By default, a user is shown the <code>upvote<\/code> button, which triggers the <code>upvoteThisProduct<\/code> function. This function checks if the user is logged in or not. If an anonymous user tries to upvote a product, they are redirected to the login page.<\/p>\n<p>Otherwise, if the user is logged in, then a POST request is sent to the <code>\/api\/upvote<\/code> API route with the product\u2019s <code>id<\/code> in the request body. After which, the <code>refreshVotes<\/code> or <code>mutate<\/code> function is triggered to update the count of the votes.<\/p>\n<p>If the user has already upvoted the product, they are shown the <code>downvote<\/code> button, the colored version of the <code>upvote<\/code> button. This button triggers the <code>downvoteThisProduct<\/code> function, sending a POST request to the <code>api\/downvote<\/code> API route with the <code>id<\/code> of the vote in the request body. After the POST request, the <code>refreshVotes<\/code> function is triggered.<\/p>\n<p>Here is how the record to be deleted is filtered from the <code>votes<\/code> array.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> voteOfThisUser =\n  user &amp;&amp; votes\n    ? votes.filter(<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">vote<\/span>) <\/span>{\n        <span class=\"hljs-keyword\">return<\/span> vote.Sub == user.sub;\n      })\n    : <span class=\"hljs-literal\">null<\/span>;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623312015\/e-603fc45fe6c0b4006873802f\/aphht7gavv7xsrqeryua.png\" alt=\"Upvote\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"891\" height=\"556\"\/><\/p>\n<p>Here is a GIF showing the upvote and downvote in action.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/res.cloudinary.com\/jesse-thisdot\/image\/upload\/c_limit,w_2000\/f_auto\/q_auto\/v1623312058\/e-603fc45fe6c0b4006873802f\/xmnlpzfwmytlmbvhphxl.gif\" alt=\"Upvote GIF\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1280\" height=\"720\"\/><\/p>\n<h2>How to delete votes along with product<\/h2>\n<p>When a user deletes the product, the votes associated with that product should also get deleted. In this section, you will update the <code>deleteThisProduct<\/code> function in the <code>Product<\/code> component and the <code>deleteProduct<\/code> API route.<\/p>\n<p>Since there can be any number of votes associated with a product, you will first need to create an array of the <code>id<\/code> of all such votes.<\/p>\n<p>Modify <code>deleteThisProduct<\/code> function in <code>Product<\/code> component like this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> deleteThisProduct = <span class=\"hljs-keyword\">async<\/span> (e) =&gt; {\n    <span class=\"hljs-keyword\">const<\/span> votesId =\n      data &amp;&amp;\n      data.map(<span class=\"hljs-function\">(<span class=\"hljs-params\">vote<\/span>) =&gt;<\/span> {\n        <span class=\"hljs-keyword\">return<\/span> vote.id;\n      });\n\n    <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/deleteProduct\"<\/span>, {\n      <span class=\"hljs-attr\">method<\/span>: <span class=\"hljs-string\">\"DELETE\"<\/span>,\n      <span class=\"hljs-attr\">body<\/span>: <span class=\"hljs-built_in\">JSON<\/span>.stringify({ id, publicId, votesId }),\n      <span class=\"hljs-attr\">headers<\/span>: {\n        <span class=\"hljs-string\">\"Content-Type\"<\/span>: <span class=\"hljs-string\">\"application\/json\"<\/span>,\n      },\n    });\n    <span class=\"hljs-keyword\">await<\/span> router.reload();\n  };\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>In the above code, you map over the <code>votes<\/code> array to create a new array containing only the <code>id<\/code> of votes. Then, you pass this <code>voteId<\/code> array in the request body of the <code>\/api\/deleteProduct<\/code> DELETE request.<\/p>\n<p>Update <code>api\/deleteProduct.js<\/code> file this.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">import<\/span> { withApiAuthRequired } <span class=\"hljs-keyword\">from<\/span> <span class=\"hljs-string\">\"@auth0\/nextjs-auth0\"<\/span>;\n<span class=\"hljs-keyword\">const<\/span> cloudinary = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"cloudinary\"<\/span>).v2;\n<span class=\"hljs-keyword\">var<\/span> Airtable = <span class=\"hljs-built_in\">require<\/span>(<span class=\"hljs-string\">\"airtable\"<\/span>);\n\ncloudinary.config({\n  <span class=\"hljs-attr\">cloud_name<\/span>: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME,\n  <span class=\"hljs-attr\">api_key<\/span>: process.env.CLOUDINARY_API_KEY,\n  <span class=\"hljs-attr\">api_secret<\/span>: process.env.CLOUDINARY_API_SECRET,\n});\n\n<span class=\"hljs-keyword\">const<\/span> base = <span class=\"hljs-keyword\">new<\/span> Airtable({ <span class=\"hljs-attr\">apiKey<\/span>: process.env.AIRTABLE_API_KEY }).base(\n  process.env.AIRTABLE_BASE_ID\n);\n\n<span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">default<\/span> withApiAuthRequired(<span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">handler<\/span>(<span class=\"hljs-params\">req, res<\/span>) <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (req.method !== <span class=\"hljs-string\">\"DELETE\"<\/span>) {\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">405<\/span>);\n  }\n  <span class=\"hljs-keyword\">const<\/span> { id, publicId, votesId } = req.body;\n\n  <span class=\"hljs-keyword\">try<\/span> {\n    <span class=\"hljs-keyword\">const<\/span> deletedProduct = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"products\"<\/span>).destroy(id);\n    <span class=\"hljs-keyword\">const<\/span> deletedVotes = <span class=\"hljs-keyword\">await<\/span> base(<span class=\"hljs-string\">\"votes\"<\/span>).destroy(votesId)\n\n    <span class=\"hljs-keyword\">const<\/span> deleteImageFromCloudinary = cloudinary.uploader.destroy(\n      publicId,\n      <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> (<span class=\"hljs-params\">error, result<\/span>) <\/span>{\n        <span class=\"hljs-keyword\">if<\/span> (error) {\n          <span class=\"hljs-built_in\">console<\/span>.log(error);\n        }\n      }\n    );\n    <span class=\"hljs-keyword\">return<\/span> res.status(<span class=\"hljs-number\">200<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Product Deleted\"<\/span> });\n  } <span class=\"hljs-keyword\">catch<\/span> (err) {\n    <span class=\"hljs-built_in\">console<\/span>.error(err);\n    res.status(<span class=\"hljs-number\">500<\/span>).json({ <span class=\"hljs-attr\">msg<\/span>: <span class=\"hljs-string\">\"Something went wrong.\"<\/span> });\n  }\n});\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>You delete the votes using the <code>destroy<\/code> method and pass the <code>votesId<\/code> array to it. So now, when the user deletes a product, all the votes are deleted along with it.<\/p>\n<h2>Conclusion<\/h2>\n<p>In this media jam, we created a Profile page where the user can see the products created by them. Then, we built the dynamic route to update the product. Finally, we discussed how to upvote a product and how to delete votes along with the product.<\/p>\n<p>You can follow this jam and create your own unique version of this project. There are many features and functionality that you can add to this project.<\/p>\n<p>Here are a few ideas to get you started:<\/p>\n<ul>\n<li>In this project, the user cannot update the product\u2019s image. You can add the feature to update the product\u2019s image. You can either upload a new image to Cloudinary and update Airtable with the <code>public_id<\/code> of this new image. Or you can replace the existing image while retaining the original <code>public_id<\/code>.<\/li>\n<li>You can follow the same steps to create a YouTube or Instagram Clone.<\/li>\n<li>Style the app using UI libraries like <a href=\"https:\/\/chakra-ui.com\/\">Chakra UI<\/a>, <a href=\"https:\/\/material-ui.com\/\">Material UI<\/a>, etc.<\/li>\n<\/ul>\n<p>Here are a few resources that you might find helpful.<\/p>\n<ul>\n<li>\n<a href=\"https:\/\/auth0.github.io\/nextjs-auth0\/index.html\">@auth0\/nextjs-auth0 SDK Docs<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/cloudinary.com\/documentation\/image_upload_api_reference\">Cloudinary Docs<\/a>\n<\/li>\n<li>\n<a href=\"https:\/\/support.airtable.com\/hc\/en-us\/articles\/203313985-Public-REST-API\">Airtable API Docs<\/a>\n<\/li>\n<\/ul>\n<p>Happy coding!<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":41,"featured_media":28120,"comment_status":"","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[134,175,212,371],"class_list":["post-28119","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-guest-post","tag-jamstack","tag-next-js","tag-under-review"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>How to build a Product Hunt Clone - Part 2 of 2<\/title>\n<meta name=\"description\" content=\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"How to build a Product Hunt Clone - Part 2 of 2\" \/>\n<meta property=\"og:description\" content=\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2022-03-23T22:25:34+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"3643\" \/>\n\t<meta property=\"og:image:height\" content=\"2439\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\"},\"author\":{\"name\":\"\",\"@id\":\"\"},\"headline\":\"How to build a Product Hunt Clone &#8211; Part 2 of 2\",\"datePublished\":\"2022-03-23T22:25:34+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\"},\"wordCount\":10,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA\",\"keywords\":[\"Guest Post\",\"JAMStack\",\"Next.js\",\"Under Review\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2022\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\",\"url\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\",\"name\":\"How to build a Product Hunt Clone - Part 2 of 2\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA\",\"datePublished\":\"2022-03-23T22:25:34+00:00\",\"description\":\"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA\",\"width\":3643,\"height\":2439},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"How to build a Product Hunt Clone &#8211; Part 2 of 2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"How to build a Product Hunt Clone - Part 2 of 2","description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/","og_locale":"en_US","og_type":"article","og_title":"How to build a Product Hunt Clone - Part 2 of 2","og_description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","og_url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/","og_site_name":"Cloudinary Blog","article_published_time":"2022-03-23T22:25:34+00:00","og_image":[{"width":3643,"height":2439,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","type":"image\/jpeg"}],"twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/"},"author":{"name":"","@id":""},"headline":"How to build a Product Hunt Clone &#8211; Part 2 of 2","datePublished":"2022-03-23T22:25:34+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/"},"wordCount":10,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","keywords":["Guest Post","JAMStack","Next.js","Under Review"],"inLanguage":"en-US","copyrightYear":"2022","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/","url":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/","name":"How to build a Product Hunt Clone - Part 2 of 2","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","datePublished":"2022-03-23T22:25:34+00:00","description":"In this media jam series, we will build a clone of Product Hunt, a website to share and discover new products with Next.js. In addition, we will use Auth0 for authentication, Airtable for storing product data, and Cloudinary for storing product images.","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","width":3643,"height":2439},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/guest_post\/how-to-build-a-product-hunt-clone-with-next-js-part-2-of-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"How to build a Product Hunt Clone &#8211; Part 2 of 2"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":""}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1681925352\/Web_Assets\/blog\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20\/93a33e1e08e06c4b9b57bbef286c1096da790799-3643x2439-1_28120a4f20.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28119","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/41"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=28119"}],"version-history":[{"count":0,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/28119\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/28120"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=28119"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=28119"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=28119"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}