<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ ecommerce - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ ecommerce - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 12 May 2026 22:45:11 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/ecommerce/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your First Shopify App: A Beginner’s Guide ]]>
                </title>
                <description>
                    <![CDATA[ Shopify powers more than a million online stores around the world.  Many store features you see every day, such as discounts, bundles, and order fulfillment are built using apps. These apps are created by developers to extend Shopify and solve real p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-first-shopify-app-a-beginners-guide/</link>
                <guid isPermaLink="false">696023bf8de30d6097e30e99</guid>
                
                    <category>
                        <![CDATA[ shopify ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Thu, 08 Jan 2026 21:38:07 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1767908229340/434160c9-891e-46d9-82fe-905d1b5ef2cb.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p><a target="_blank" href="http://shopify.com/">Shopify</a> powers more than a million online stores around the world. </p>
<p>Many store features you see every day, such as discounts, bundles, and order fulfillment are built using apps. These apps are created by developers to extend Shopify and solve real problems for merchants. </p>
<p>If you know JavaScript and basic web development, you already have enough skills to start building Shopify apps.</p>
<p>In this tutorial, you’ll learn what a Shopify app is, how Shopify apps work, and how to set up your development environment. You’ll also see three real examples of popular Shopify apps and how they are built. </p>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-is-a-shopify-app">What Is a Shopify App?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-shopify-apps-work">How Shopify Apps Work</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setting-up-your-development-environment">Setting Up Your Development Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-shopify-apis">Understanding Shopify APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-one-bundle-and-discount-apps">Case Study One: Bundle and Discount Apps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-two-printful-and-order-fulfilment">Case Study Two: Printful and Order Fulfilment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-three-shiprocket-and-shipping-rates">Case Study Three: Shiprocket and Shipping Rates</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-your-shopify-app">Testing Your Shopify App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-preparing-for-the-shopify-app-store">Preparing for the Shopify App Store</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<p>To follow this tutorial, you should be comfortable with JavaScript and APIs. Some experience with Node.js and npm will help, but you do not need to be an expert. No prior experience with Shopify is required.</p>
<h2 id="heading-what-is-a-shopify-app">What Is a Shopify App?</h2>
<p>A <a target="_blank" href="https://apps.shopify.com/">Shopify app</a> is a web application that connects to a Shopify store. The app runs on your own server or a serverless platform. </p>
<p>It talks to Shopify using <a target="_blank" href="https://shopify.dev/docs/api">secure APIs</a>. When a merchant installs your app, they allow it to access certain store data. This could include products, orders, or customers, depending on the permissions given.</p>
<p>There are different types of Shopify apps. </p>
<p>Public apps are listed on the Shopify App Store and can be installed by any merchant. These apps must pass a review before approval. </p>
<p>Custom apps are built for a single store, and private apps are used only inside a company. </p>
<p>Most Shopify apps include backend code that calls Shopify APIs, a frontend interface shown inside the Shopify Admin, storefront features that shoppers can see, and webhooks that react to store events.</p>
<h2 id="heading-how-shopify-apps-work">How Shopify Apps Work</h2>
<p>When a merchant installs your app, Shopify starts an <a target="_blank" href="https://auth0.com/intro-to-iam/what-is-oauth-2">OAuth process</a>. This is a secure way to ask the merchant for permission. </p>
<p>Once approved, Shopify sends your app an access token. This token allows your app to make API calls to the store.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767768422978/61189891-7a02-449a-9da7-30c6d1116638.png" alt="Oauth flow" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Shopify apps can add screens inside the admin area using <a target="_blank" href="https://shopify.dev/docs/api/app-bridge">App Bridge</a> and Polaris. These tools make your app feel like part of Shopify. Apps can also add features to the storefront using theme app extensions. </p>
<p>Shopify provides both REST and GraphQL APIs. REST is easy to use, but GraphQL is faster and more efficient. Today, most new apps use GraphQL.</p>
<h2 id="heading-setting-up-your-development-environment">Setting Up Your Development Environment</h2>
<p>Before you start coding, you’ll need a few tools. You’ll need to install Node.js and the <a target="_blank" href="https://shopify.dev/docs/api/shopify-cli">Shopify CLI</a>. You’ll also need a <a target="_blank" href="https://www.shopify.com/in/partners">Shopify Partner account</a>. The Partner account lets you create apps and test them without cost.</p>
<p>The Shopify CLI helps you create a starter app quickly. You can generate a working app by running these commands:</p>
<pre><code class="lang-python">shopify app create node
cd my-shopify-app
npm install
shopify app serve
</code></pre>
<p>This creates an app with login, authentication, and an embedded admin interface. It also sets up a secure tunnel so Shopify can reach your local server while you develop.</p>
<h2 id="heading-understanding-shopify-apis">Understanding Shopify APIs</h2>
<p>Shopify provides APIs for almost every part of a store. This includes products, orders, customers, and shipping. Your app can only access data that the merchant has allowed during installation.</p>
<p>Here’s a simple example of fetching products using the GraphQL Admin API:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> fetch <span class="hljs-keyword">from</span> <span class="hljs-string">"node-fetch"</span>;

<span class="hljs-keyword">async</span> function getProducts(shop, token) {
  const query = `
  {
    products(first: <span class="hljs-number">5</span>) {
      edges {
        node {
          id
          title
        }
      }
    }
  }
  `;
  const response = <span class="hljs-keyword">await</span> fetch(
    `https://${shop}/admin/api/<span class="hljs-number">2025</span><span class="hljs-number">-10</span>/graphql.json`,
    {
      method: <span class="hljs-string">"POST"</span>,
      headers: {
        <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
        <span class="hljs-string">"X-Shopify-Access-Token"</span>: token
      },
      body: JSON.stringify({ query })
    }
  );
  const data = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> data.data.products.edges;
}
</code></pre>
<p>This function gets the first five products from a store. The access token comes from the OAuth process during app installation.</p>
<h2 id="heading-case-study-one-bundle-and-discount-apps">Case Study One: Bundle and Discount Apps</h2>
<p>Bundle and discount apps help merchants offer deals like “Buy two, get ten percent off.” These apps must work with Shopify’s pricing rules and checkout system. A popular example is the <a target="_blank" href="https://apps.shopify.com/bundle-deals">Bundle Deals app</a>, which shows offers on product and cart pages.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767768457881/f98bd8fc-7659-4841-9dfe-8adb2aa6f191.png" alt="Bundle deals app" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>These apps usually add small UI elements to the storefront using theme app extensions. They use Shopify’s Discount APIs to apply offers safely. </p>
<p>They don’t change checkout directly. Instead, they enhance the storefront and let Shopify handle the final pricing.</p>
<p>A storefront script might look like this:</p>
<pre><code class="lang-python">fetch(<span class="hljs-string">"/apps/bundle-deals/api/bundles?productId=gid://shopify/Product/123"</span>)
  .then((res) =&gt; res.json())
  .then((bundles) =&gt; {
    displayBundles(bundles);
  });
</code></pre>
<p>This code runs in the store’s frontend. It fetches bundle rules from your app server and shows them to shoppers.</p>
<h2 id="heading-case-study-two-printful-and-order-fulfilment">Case Study Two: Printful and Order Fulfilment</h2>
<p><a target="_blank" href="https://apps.shopify.com/printful">Printful</a> is a popular app that connects Shopify stores with a print-on-demand service (for example, T-shirts, Mugs, and so on). When a customer places an order, Printful receives the order and starts production.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767768478088/0f225294-ce0a-429c-a131-d263db439beb.png" alt="Printful app" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Apps like this need access to orders and reliable event handling. They use webhooks to listen for new orders. When Shopify sends a webhook, the app forwards the data to an external system.</p>
<p>Here is a simple webhook example:</p>
<pre><code class="lang-python">app.post(<span class="hljs-string">"/webhooks/orders/create"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  const order = req.body;

<span class="hljs-keyword">await</span> printfulClient.createOrder({
    external_id: order.id,
    items: order.line_items.map(item =&gt; ({
      variant_id: item.variant_id,
      quantity: item.quantity
    }))
  });
  res.status(<span class="hljs-number">200</span>).send(<span class="hljs-string">"Order processed"</span>);
});
</code></pre>
<p>This keeps Shopify and Printful in sync. The same pattern is used for shipping tools, accounting software, and CRMs.</p>
<h2 id="heading-case-study-three-shiprocket-and-shipping-rates">Case Study Three: Shiprocket and Shipping Rates</h2>
<p><a target="_blank" href="https://apps.shopify.com/shiprocket">Shiprocket</a> helps merchants manage shipping and delivery. Shipping apps often calculate rates in real time and update order status after shipment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1767768510499/2e5351a1-c006-4ac2-b373-47acf63189a3.jpeg" alt="Shiprocket app" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Since Shopify restricts what can run during checkout, shipping apps typically calculate rates before checkout begins or use carrier service APIs. A simple rate endpoint might look like this:</p>
<pre><code class="lang-python">app.post(<span class="hljs-string">"/shipping/rates"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  const { destination, items } = req.body;
  const rates = <span class="hljs-keyword">await</span> fetchCarrierRates(destination, items);

res.json({
    rates: rates.map(rate =&gt; ({
      service_name: rate.name,
      total_price: rate.price
    }))
  });
});
</code></pre>
<p>This lets merchants show shipping options to customers before they reach checkout.</p>
<h2 id="heading-testing-your-shopify-app">Testing Your Shopify App</h2>
<p>Testing is a critical part of building a reliable Shopify app, especially if you plan to submit it to the App Store. Every feature should be tested thoroughly in a development store before you consider a release. A development store lets you simulate real merchant behavior without affecting live data, which makes it ideal for both manual and automated testing.</p>
<p>In addition to live testing, Shopify allows you to <a target="_blank" href="https://mock.shop/">mock API responses</a> during development. Mocking lets you test your business logic without relying on real API calls or rate limits. This is especially useful when simulating error scenarios or incomplete data, such as missing fields or unexpected values.</p>
<p>By combining real store testing with mocked responses, you can be confident that your app behaves correctly in both normal and failure conditions.</p>
<h2 id="heading-preparing-for-the-shopify-app-store">Preparing for the Shopify App Store</h2>
<p>Preparing for the Shopify App Store is an important step if you want to release a public app that merchants can trust and install with confidence. Shopify has a <a target="_blank" href="https://shopify.dev/docs/apps/launch/app-store-review/review-process">formal review process</a>, and your app must meet both technical and policy requirements before it can be listed.</p>
<p>Your app should request only the API permissions that are absolutely necessary for its core functionality. Asking for extra or unrelated permissions is one of the most common reasons apps get rejected. Shopify expects you to clearly explain why each permission is needed and how the data will be used. This helps merchants feel safe when installing your app.</p>
<p>You must also include basic legal and support information. This includes a clear privacy policy that explains what data you collect and how you handle it, terms of service that define usage rules, and a visible support contact such as an email address or help page.</p>
<p>Finally, Shopify looks closely at app quality. Your app should be stable, handle errors gracefully, and be tested across common store scenarios. Clear messaging, predictable behavior, and transparent data usage go a long way in passing the review process.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Building your first Shopify app takes time, but it’s very achievable. Start with login and API calls. Learn how to embed UI inside Shopify. Study real apps from the Shopify app store. Each one solves a different problem using a different design.</p>
<p>As you practice, the pieces will start to fit together. Keep building, testing, and reading the documentation. Your first Shopify app could be the start of something much bigger.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Vue E-commerce App Using MSW ]]>
                </title>
                <description>
                    <![CDATA[ Building an e-commerce app can be a time-consuming task, but with the right tools, it becomes much more manageable. In this guide, we'll explore how to create a robust Vue.js e-commerce application using Mock Service Worker (MSW) to simulate backend ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-vue-ecommerce-app-using-msw/</link>
                <guid isPermaLink="false">66bb55013c8a48be917afce0</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abhijeet Dave ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jul 2024 18:49:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/build-a-Vue-eCommerce-App-using-MSW.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building an e-commerce app can be a time-consuming task, but with the right tools, it becomes much more manageable. In this guide, we'll explore how to create a robust Vue.js e-commerce application using Mock Service Worker (MSW) to simulate backend interactions. </p>
<p>Whether you're a seasoned developer or just starting out, this step-by-step tutorial will help you understand the fundamentals of integrating MSW into your Vue project, enabling you to build and test your application more effectively without relying on a real backend. </p>
<p>Let's dive in and bring your e-commerce vision to life!</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a href="#what-is-a-mock-server">What is a Mock Server?</a></li>
<li><a href="#how-to-set-up-a-vue-e-commerce-app">How to Set Up a Vue E-commerce App</a></li>
<li><a href="#getting-started-with-vue-e-commerce-app">Getting Started With Vue E-commerce App</a></li>
<li><a href="#how-to-build-an-e-commerce-app-using-vue-3">How to Build an E-commerce App Using Vue 3</a></li>
<li><a href="#how-to-build-the-user-interface">How to Build the User Interface</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>

<p>In this article, we'll walk through the process of building an e-commerce app from scratch using <a target="_blank" href="https://vuejs.org/">Vue.js</a> and the power of MSW for mocking API calls.</p>
<p>Before we start with the project, let’s take an overview of the e-commerce pages that we are going to build for the app. It'll have mainly two pages:</p>
<ul>
<li>Shop page.</li>
<li>Product details page.</li>
</ul>
<p><strong>Shop page</strong>: This page will display all the products of the store.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/eCommerce-Products-Page.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mock-up created using Pika Style</em></p>
<p><strong>Product details page</strong>: This page displays all the details regarding the product.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/eCommerce-product-details-page.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Before building an e-commerce app, let’s clarify some basics about the mock server.</p>
<h2 id="heading-what-is-a-mock-server">What is a Mock Server?</h2>
<p>A mock server simply mimics a real server by providing predefined responses for an API request. A mock server is useful for testing and development as it can generate different test cases without risking the integrity of real data. You can configure a mock server to return specific responses, error messages and timeouts.</p>
<p>There are various tools/libraries out there that you can use to set up a mock server. In this article, we will use MSW (Mock Service Worker) to set up a mock server. You can learn more about MSW from its official <a target="_blank" href="https://mswjs.io/">documentation</a>.</p>
<p>Now, let’s set up a mock server (using MSW) for our Vue e-commerce app.</p>
<h2 id="heading-how-to-set-up-a-vue-e-commerce-app">How to Set Up a Vue E-commerce App</h2>
<p>For our e-commerce app, we'll need two API endpoints. Below is a brief description of API endpoints.</p>
<ol>
<li><code>/api/apps/ecommerce/products</code>: This will fetch the data of all products we have in store.</li>
<li><code>/api/apps/ecommerce/product/:id</code>: This will fetch the product details of a specific product by its ID.</li>
</ol>
<p>We'll use a free Vue admin dashboard that offers essential features such as:</p>
<ul>
<li>Beautifully crafted components</li>
<li>Auto import capabilities</li>
<li>Premade layout, and so on.</li>
</ul>
<p>These features will make it easier and faster to develop the Vue e-commerce app.</p>
<h2 id="heading-getting-started-with-vue-e-commerce-app">Getting Started With Vue E-commerce App</h2>
<p>There are plenty of <a target="_blank" href="https://vuejs.org/ecosystem/themes">Vue.js themes</a> available that you can consider for creating an e-commerce  app. </p>
<p>Here, we'll use the Materio Vue.js admin free template<em>.</em> First, go to the <a target="_blank" href="https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free">GitHub repo</a>.</p>
<p>To clone it, simply open your terminal. Navigate to the directory where you would like to clone the project and run the following command:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> &lt;https://github.com/themeselection/materio-vuetify-vuejs-admin-template-free.git&gt;
</code></pre>
<p>Open the project in your favorite IDE and run the command below in the terminal to install all the dependencies. </p>
<p>We'll use the <a target="_blank" href="https://pnpm.io/"><code>pnpm</code></a> package manager as recommended by the dashboard we're using. However, you are free to use your preferred package manager like npm or yarn.</p>
<pre><code class="lang-bash">pnpm install
<span class="hljs-comment"># npm install</span>
<span class="hljs-comment"># yarn</span>
</code></pre>
<p>Next, install MSW in your project directory:</p>
<pre><code class="lang-bash">pnpm add -D msw@latest
<span class="hljs-comment"># npm install msw@latest --save-dev</span>
<span class="hljs-comment"># yarn install -D msw@latest</span>
</code></pre>
<p>Run the theme using the following command:</p>
<pre><code class="lang-bash">pnpm run dev
<span class="hljs-comment"># npm run dev</span>
<span class="hljs-comment"># yarn run dev</span>
</code></pre>
<p>Next, initialize MSW using the command below. Running this command will create a <strong>mockServiceWorker.js</strong> file in a public directory.</p>
<pre><code class="lang-bash">npx msw init public
</code></pre>
<p>To manage all API endpoints and fake data, create a new folder called <strong>fake-server</strong> inside the <strong>plugins</strong> directory. We’ll set up our mock server in this <strong>fake-server</strong> directory. Create an <strong>index.ts</strong> file and paste the below code to register <code>MSW</code>.</p>
<pre><code class="lang-tsx">// file: src/plugins/fake-server/index.ts

import { setupWorker } from 'msw/browser'

const worker = setupWorker()

export default function () {
  const workerUrl = `${import.meta.env.BASE_URL ?? '/'}mockServiceWorker.js`

  worker.start({
    serviceWorker: {
      url: workerUrl,
    },
    onUnhandledRequest: 'bypass',
  })
}
</code></pre>
<p>Congratulations, you have now successfully set up <code>MSW</code> in the project. Now, we can start building an e-commerce app.</p>
<h2 id="heading-how-to-build-an-e-commerce-app-using-vue-3">How to Build an E-commerce App Using Vue 3</h2>
<p>In the <strong>fake-server</strong> directory, create a <strong>handlers</strong> directory for maintaining handlers. Inside the <strong>handlers</strong> directory, create an <strong>ecommerce</strong> directory for the e-commerce app’s handlers. Then, create a <strong>db.ts</strong> file inside <strong>ecommerce</strong> to store the fake data.</p>
<p>Your folder structure should look like this:</p>
<pre><code class="lang-tsx">.
└── fake-server/
    └── handlers/
        └── ecommerce/
            └── db.ts
</code></pre>
<p>I have generated some fake data for displaying products. Let’s place this fake data in <strong>db.ts</strong>:</p>
<pre><code class="lang-tsx">// file: src/plugins/fake-server/handlers/ecommerce/db.ts

import product5 from '@images/eCommerce/1.png'
import product3 from '@images/eCommerce/11.png'
import product6 from '@images/eCommerce/18.png'
import product1 from '@images/eCommerce/27.png'
import product4 from '@images/eCommerce/5.png'
import product2 from '@images/eCommerce/7.png'

export const db = {
  products: [
    {
      id: 1,
      productName: 'Gaming Mouse',
      category: 'Electronics',
      price: '$999',
      image: product1,
      rating: 5,
      productDescription: 'A mouse specifically designed for gamers.',
    },
    {
      id: 2,
      productName: 'Google Home',
      category: 'Electronics',
      price: '$25.50',
      image: product2,
      rating: 4,
      productDescription: 'A Smart speaker with Google Assistant.',
    },
    {
      id: 3,
      productName: 'INZCOU Running Shoes',
      category: 'Shoes',
      price: '$36.98',
      image: product3,
      rating: 5,
      productDescription: 'Lightweight Tennis Shoes Non Slip Gym Workout Shoes',
    },
    {
      id: 4,
      productName: 'MacBook Pro 16',
      category: 'Electronics',
      price: '$2648.95',
      image: product4,
      rating: 5,
      productDescription: 'Laptop M2 Pro chip with 12‑core CPU and 19‑core GPU',
    },
    {
      id: 5,
      productName: 'Apple Watch Series 7',
      category: 'Office',
      price: '$799',
      image: product5,
      rating: 5,
      productDescription: 'Starlight Aluminum Case with Starlight Sport Band.',
    },
    {
      id: 6,
      productName: 'Meta Quest 2',
      category: 'Office',
      price: '$299',
      image: product6,
      rating: 5,
      productDescription: 'Advanced All-In-One Virtual Reality Headset',
    },
  ],
}
</code></pre>
<p>As discussed in this app structure, we need to define two endpoints. Create an <strong>index.ts</strong> file in your e-commerce handler and define your endpoints in it:</p>
<pre><code class="lang-tsx">// file: src/plugins/fake-server/handlers/ecommerce/index.ts

import { HttpResponse, http } from 'msw'
import { db } from './db'

export const handlerEcommerce = [
  http.get('/api/ecommerce/products', () =&gt; {
    const products = db.products

    return HttpResponse.json(products, { status: 200 })
  }),

  http.get('/api/ecommerce/products/:id', ({ params }) =&gt; {
    const id = Number(params.id)

    const product = db.products.find(item =&gt; item.id === id)

    if (!product)
      return HttpResponse.error()

    return HttpResponse.json(product, { status: 200 })
  }),
]
</code></pre>
<p>Register the handler in the <strong>index.ts</strong> file in the <strong>fake-server</strong> direrctory. The updated <strong>index.ts</strong> file should look like this:</p>
<pre><code class="lang-tsx">// file: src/plugins/fake-server/index.ts

import { setupWorker } from 'msw/browser'

import { handlerEcommerce } from './handlers/ecommerce'

const worker = setupWorker(...handlerEcommerce)

export default function () {
  const workerUrl = `${import.meta.env.BASE_URL ?? '/'}mockServiceWorker.js`

  worker.start({
    serviceWorker: {
      url: workerUrl,
    },
    onUnhandledRequest: 'bypass',
  })
}
</code></pre>
<p>The setup of the mock server has been completed. Whenever you make an API call to your endpoint, MSW will intercept the HTTP request using the service worker and will provide a predefined response from the handlers.</p>
<p>Congratulations, you have successfully set up the mock server👍.🏻</p>
<h2 id="heading-how-to-build-the-user-interface">How to Build the User Interface</h2>
<p>Let's move on to the UI part of the e-commerce app. Create an <strong>apps</strong> directory in the <strong>pages</strong> directory. Inside <strong>apps</strong>, create a new directory called <strong>ecommerce</strong>. We'll place the e-commerce app in this directory.</p>
<p>The folder structure should look like this:</p>
<pre><code class="lang-tsx">.
└── pages/
    └── apps/
        └── ecommerce
</code></pre>
<p>The first page is a product listing page. Create a new directory of <strong>products</strong> inside <strong>ecommerce</strong>. Create an <strong>index.vue</strong> file in the <strong>products</strong> directory and paste the following code snippet:</p>
<pre><code class="lang-tsx">// file: src/pages/apps/ecommerce/products/index.vue

&lt;script setup lang="ts"&gt;
const router = useRouter()
const { data: products } = await useFetch('/api/ecommerce/products').json()
&lt;/script&gt;

&lt;template&gt;
  &lt;div&gt;
    &lt;div class="d-flex flex-wrap gap-6 justify-center"&gt;
      &lt;template
        v-for="(product, index) in products"
        :key="index"
      &gt;
        &lt;VCard width="300"&gt;
          &lt;VImg
            :src="product.image"
            cover
          /&gt;
          &lt;VCardItem&gt;
            &lt;VCardTitle&gt;{{ product.productName }}&lt;/VCardTitle&gt;
            &lt;VCardSubtitle&gt;Price: {{ product.price }}&lt;/VCardSubtitle&gt;
          &lt;/VCardItem&gt;
          &lt;VCardText&gt;
            &lt;p class="mb-0"&gt;
              {{ product.productDescription }}
            &lt;/p&gt;
            &lt;VRating
              :model-value="product.rating"
              readonly
              density="compact"
              class="my-3"
            /&gt;
            &lt;VBtn
              block
              @click="() =&gt; router.push({ path: `/apps/ecommerce/products/${product.id}` })"
            &gt;
              Buy Now
            &lt;/VBtn&gt;
          &lt;/VCardText&gt;
        &lt;/VCard&gt;
      &lt;/template&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>In this product listing page, we made an API call to the <code>'/api/ecommerce/products'</code> endpoint. This endpoint returns the array of all products. We'll use this data to display products on the page.</p>
<p>The second page in this app is the product display page. On this page, we'll display all the details of the product. To do so, create a new file <strong>[id].vue</strong> inside the <strong>products</strong> directory. Below is the code for the product details page.</p>
<p>Note that I have used Lorem ipsum to keep it generic. You can replace it with your desired description. </p>
<pre><code class="lang-tsx">// file: src/pages/apps/ecommerce/products/[id].vue

&lt;script setup lang="ts"&gt;
const route = useRoute()
const router = useRouter()

const { data: product } = await useFetch(`/api/ecommerce/products/${route.params.id}`).json()
const quantity = ref(0)
&lt;/script&gt;

&lt;template&gt;
  &lt;VCard class="pa-10"&gt;
    &lt;VRow&gt;
      &lt;VCol
        md="4"
        cols="12"
      &gt;
        &lt;div class="py-10 bg-background d-flex justify-center"&gt;
          &lt;VImg
            :src="product.image"
            width="auto"
            max-height="40vh"
          /&gt;
        &lt;/div&gt;
      &lt;/VCol&gt;

      &lt;VCol
        md="8"
        cols="12"
      &gt;
        &lt;div&gt;
          &lt;div class="text-h3 mb-4"&gt;
            {{ product.productName }}
          &lt;/div&gt;

          &lt;div class="text-h4 mb-4"&gt;
            {{ product.price }}
          &lt;/div&gt;

          &lt;div&gt;
            &lt;p&gt;
              {{ product.productDescription }}
              Lorem ipsum dolor, sit amet consectetur adipisicing elit. Dolor eum quam dolore ratione aspernatur nobis. Assumenda dicta voluptatibus reiciendis repudiandae?
            &lt;/p&gt;
            &lt;VRating
              :model-value="product.rating"
              readonly
              density="compact"
              class="mb-2 d-block"
            /&gt;

            &lt;VList&gt;
              &lt;VListItem&gt;
                &lt;template #prepend&gt;
                  &lt;VIcon
                    icon="ri-circle-fill"
                    size="10"
                  /&gt;
                &lt;/template&gt;
                Lorem ipsum, dolor sit amet consectetur adipisicing elit. Culpa, deserunt!
              &lt;/VListItem&gt;
              &lt;VListItem&gt;
                &lt;template #prepend&gt;
                  &lt;VIcon
                    icon="ri-circle-fill"
                    size="10"
                  /&gt;
                &lt;/template&gt;
                Lorem ipsum, dolor sit amet consectetur adipisicing elit. Culpa, deserunt!
              &lt;/VListItem&gt;
              &lt;VListItem&gt;
                &lt;template #prepend&gt;
                  &lt;VIcon
                    icon="ri-circle-fill"
                    size="10"
                  /&gt;
                &lt;/template&gt;
                Lorem ipsum, dolor sit amet consectetur adipisicing elit. Culpa, deserunt!
              &lt;/VListItem&gt;
              &lt;VListItem&gt;
                &lt;template #prepend&gt;
                  &lt;VIcon
                    icon="ri-circle-fill"
                    size="10"
                  /&gt;
                &lt;/template&gt;
                Lorem ipsum, dolor sit amet consectetur adipisicing elit. Culpa, deserunt!
              &lt;/VListItem&gt;
            &lt;/VList&gt;

            &lt;VBtn
              prepend-icon="ri-shopping-cart-line"
              class="text-center"
              size="large"
              @click="quantity += 1"
            &gt;
              Add to Cart
            &lt;/VBtn&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/VCol&gt;
    &lt;/VRow&gt;
  &lt;/VCard&gt;
  &lt;div class="text-center"&gt;
    &lt;VBtn
      class="my-6 text-center"
      @click="() =&gt; router.push({ path: '/apps/ecommerce/products' })"
    &gt;
      Continue Shopping
    &lt;/VBtn&gt;
  &lt;/div&gt;
&lt;/template&gt;
</code></pre>
<p>On this page, we made an API request to our second API endpoint: <code>/api/ecommerce/products/:id</code>. This endpoint returns product details related to a given product ID. We will use this data on our page to display product details.</p>
<p>Let’s add routes for the e-commerce app. All the routes are located in the <strong>src/plugins/router/routes.ts</strong> file. In the file, add the e-commerce app’s routes.</p>
<pre><code class="lang-tsx">// file: src/plugins/router/routes.ts
{
  path: '/apps/ecommerce/products',
  component: () =&gt; import('@/pages/apps/ecommerce/products/index.vue'),
},
{
  name: 'apps-ecommerce-products-id',
  path: '/apps/ecommerce/products/:id',
  component: () =&gt; import('@/pages/apps/ecommerce/products/[id].vue'),
},
</code></pre>
<p>Now, let’s add an e-commerce app to our navigation menu. We'll list all our menu items and groups in <code>NavItems.vue</code> components. We'll use a <code>VerticalNavGroup</code> component for adding a nav group and a <code>VerticleNavLink</code> component for adding a nav item. For adding an e-commerce app in the navigation menu, add the code below in the <code>Apps &amp; Pages</code> section.</p>
<pre><code class="lang-tsx">//file: src/layouts/components/NavItems.vue

&lt;VerticalNavGroup
    :item="{
      title: 'e-commerce',
      icon: 'ri-shopping-cart-line',
    }"
  &gt;
    &lt;VerticalNavLink
      :item="{
        title: 'Shop',
        to: '/apps/ecommerce/products',
      }"
    /&gt;
    &lt;VerticalNavLink
      :item="{
        title: 'Product',
        to: { name: 'apps-ecommerce-products-id', params: { id: 1 } },
      }"
    /&gt;
&lt;/VerticalNavGroup&gt;
</code></pre>
<p>Congrats, you’ve built a Vue e-commerce app using Vue.js and MSW. You can visit the dev server to see the e-commerce app we just crafted.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this article, you built an e-commerce app using Vue.js and MSW to mock API calls. We created two main pages: the shop page to display products and the product details page to show product information.</p>
<p>The mock server setup helped us to create a realistic development environment without building an actual backend. At the end of this guide, you had a working e-commerce app prototype. This setup provides a strong foundation for further customization and development.</p>
<p>For the complete implementation of the e-commerce app built in this article, please refer to this GitHub repository: <a target="_blank" href="https://github.com/themeselection/e-commerce-app">https://github.com/themeselection/e-commerce-app</a>.</p>
<p>I hope you all find this article helpful. In case you want to develop a full-fledged e-commerce app, you can use the pre-built <a target="_blank" href="https://themeselection.com/item/category/vuejs-admin-templates/">Vuejs admin template</a> as it offers many components and features that can be helpful in creating a professional e-commerce app.</p>
<p>I have prepared this article with help of <a target="_blank" href="https://x.com/me_jd_solanki">Jayendrasinh Solanki</a>. He is an expert in VueJS with over 7 years of experience. BTW, he is followed by Vue creator Evan You! Isn't it great?</p>
<h3 id="heading-some-helpful-guides-for-e-commerce-product-development">Some helpful guides for e-commerce product development:</h3>
<ul>
<li><a target="_blank" href="https://www.freecodecamp.org/news/develop-a-reusable-ecommerce-platform/">How to Develop a Reusable eCommerce Platform (freecodecamp.org)</a>.</li>
<li><a target="_blank" href="https://www.freecodecamp.org/news/how-to-create-an-ecommere-website-using-woocomerce/">How to Create an eCommerce Website Using WooCommerce (freecodecamp.org)</a>.</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Digital Products Store with Medusa and Next.js ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you will learn how to build an e-book online store using Medusa and Next.js. Throughout the course of the article, we will: Utilize the Medusa Next.js Starter Template along with the Digital Products Recipe build the store. Enhance... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-digital-products-store-with-medusa-and-next-js/</link>
                <guid isPermaLink="false">66ba0e6b79b7f411df58dea1</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Tue, 17 Oct 2023 22:59:34 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/digital-products.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you will learn how to build an e-book online store using Medusa and Next.js.</p>
<p>Throughout the course of the article, we will:</p>
<ol>
<li>Utilize the Medusa <a target="_blank" href="https://medusajs.com/nextjs-commerce/">Next.js Starter Template</a> along with the <a target="_blank" href="https://docs.medusajs.com/recipes/digital-products">Digital Products Recipe</a> build the store.</li>
<li>Enhance the product pages to suit digital products. This involves adding a button for previewing media content and displaying essential product details.</li>
<li>Refine the checkout process to make it more efficient for delivering digital products.</li>
<li>Create Next.js API routes to validate and conceal file paths for product downloads.</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/rmjjaunyells0it99c7a.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Demo of the final application</em></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-medusa">What is Medusa?</a></li>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-starting-out">Starting Out</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-typescript-type-definitions">How to Set Up TypeScript Type Definitions</a></li>
<li><a class="post-section-overview" href="#heading-how-to-incorporate-e-book-previews-into-the-product-details">How to Incorporate e-Book Previews into the Product Details</a></li>
<li><a class="post-section-overview" href="#heading-how-to-offer-e-book-previews">How to Offer e-Book Previews</a></li>
<li><a class="post-section-overview" href="#heading-how-to-adjust-the-product-and-shipping-details">How to Adjust the Product and Shipping Details</a></li>
<li><a class="post-section-overview" href="#heading-how-to-simplify-the-checkout">How to Simplify the Checkout</a></li>
<li><a class="post-section-overview" href="#heading-how-to-deliver-digital-products">How to Deliver Digital Products</a></li>
</ol>
<p>Let's get started.</p>
<h2 id="heading-what-is-medusa">What is Medusa?</h2>
<p>Medusa is a suite of tools and modules specifically designed for e-Commerce products. </p>
<p>Using Medusa, you can build modularized commerce logic like <a target="_blank" href="https://docs.medusajs.com/modules/carts-and-checkout/overview">carts</a>, <a target="_blank" href="https://docs.medusajs.com/modules/products/overview">products</a>, and <a target="_blank" href="https://docs.medusajs.com/modules/orders/overview">order management</a>. It also provide tools that help you orchestrate powerful ecommerce websites, POS applications, commerce-enabled products, and everything in between.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you get started with the tutorial, you should have installed:</p>
<ul>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment#nodejs">Node.js(V14 or later)</a></li>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment/#git">Git</a></li>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment#medusa-cli">Medusa CLI</a></li>
</ul>
<h2 id="heading-starting-out">Starting Out</h2>
<p>Using the Next.js starter, you can create a new Medusa app by running the following command:</p>
<pre><code class="lang-bash">npx create-medusa-app@latest --with-nextjs-starter
</code></pre>
<p>After that, you can opt to create a user account for admin panel access. Then, set up the backend infrastructure following the Medusa Digital Products Recipe. </p>
<p>Once the backend is set, create sample products through your Medusa admin interface. Make sure that these products include digital media files for previews and primary content. Also make sure to incorporate relevant product metadata values using key/value pairs linked to each product.</p>
<h2 id="heading-how-to-set-up-typescript-type-definitions">How to Set Up TypeScript Type Definitions</h2>
<p>If you’re using regular JavaScript, you can skip this step.</p>
<p>Before we continue, let's make sure to add in the necessary TypeScript type definitions for digital products in the Next.js storefront.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Product } <span class="hljs-keyword">from</span> <span class="hljs-string">"@medusajs/medusa"</span>
<span class="hljs-keyword">import</span> { ProductVariant } <span class="hljs-keyword">from</span> <span class="hljs-string">"@medusajs/product"</span>

<span class="hljs-keyword">export</span> enum ProductMediaVariantType {
  PREVIEW = <span class="hljs-string">"preview"</span>,
  MAIN = <span class="hljs-string">"main"</span>,
}

<span class="hljs-keyword">export</span> type ProductMedia = {
  <span class="hljs-attr">id</span>: string
  name?: string
  file?: string
  mime_type?: string
  created_at?: <span class="hljs-built_in">Date</span>
  updated_at?: <span class="hljs-built_in">Date</span>
  attachment_type?: ProductMediaVariantType
  variant_id?: string
  variants?: ProductMediaVariant[]
}

<span class="hljs-keyword">export</span> type ProductMediaVariant = {
  <span class="hljs-attr">id</span>: string
  <span class="hljs-attr">variant_id</span>: string
  <span class="hljs-attr">product_media_id</span>: string
  <span class="hljs-attr">type</span>: string
  <span class="hljs-attr">created_at</span>: <span class="hljs-built_in">Date</span>
  <span class="hljs-attr">updated_at</span>: <span class="hljs-built_in">Date</span>
}

<span class="hljs-keyword">export</span> type DigitalProduct = Omit&lt;Product, <span class="hljs-string">"variants"</span>&gt; &amp; {
  product_medias?: ProductMedia[]
  variants?: DigitalProductVariant[]
}

<span class="hljs-keyword">export</span> type DigitalProductVariant = ProductVariant &amp; {
  product_medias?: ProductMedia
}

      <span class="hljs-keyword">throw</span> err
    })

  <span class="hljs-keyword">return</span> product_medias[<span class="hljs-number">0</span>]
}
</code></pre>
<p>This code defines TypeScript types and interfaces for managing digital products and their associated media files in an e-commerce system. It introduces several crucial structures:</p>
<ol>
<li><code>ProductMedia</code>: This interface describes media files related to a product. These files can include images, documents, or any digital assets. It encompasses properties such as an <code>id</code> (a unique identifier for the media), <code>name</code> (an optional name for the media), <code>file</code> (representing the file path or URL), <code>mime_type</code> (the type of media, e.g., image/jpeg), <code>created_at</code> and <code>updated_at</code> timestamps, and <code>attachment_type</code> that categorizes the media as "preview" or "main." Additionally, a media item can have multiple variants, making it adaptable for various use cases.</li>
<li><code>ProductMediaVariant</code>: This interface represents different variants or versions of a product's media. Each variant has its unique <code>id</code>, <code>variant_id</code> (relating it to a specific product variant), <code>product_media_id</code> (linking it to a particular media item), and timestamps for <code>created_at</code> and <code>updated_at</code>.</li>
<li><code>DigitalProduct</code>: It extends the standard <code>Product</code> type by introducing an array called <code>product_medias</code>. This array enables the association of media files with a digital product, allowing the presentation of images or other media related to the product. The <code>variants</code> property is tailored for digital products, adapting the generic <code>ProductVariant</code> to digital product-specific requirements.</li>
<li><code>DigitalProductVariant</code>: This type, an extension of <code>ProductVariant</code>, allows the linking of media files with a specific variant of a digital product. This is particularly valuable for showcasing different digital assets associated with each variant of the product.</li>
</ol>
<h2 id="heading-how-to-incorporate-e-book-previews-into-the-product-details">How to Incorporate e-Book Previews into the Product Details</h2>
<p>Now, let's move forward by adding e-book previews to our product detail page. To do this, we'll get the media previews linked to the currently selected product variant. </p>
<p>In the <code>src/lib/data/index.ts</code> file, we'll create a function to get these previews based on the chosen variant.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ... other imports</span>
<span class="hljs-keyword">import</span> { DigitalProduct, ProductMedia } <span class="hljs-keyword">from</span> <span class="hljs-string">"types/product-media"</span>

<span class="hljs-comment">// ... rest of the functions</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getProductMediaPreviewByVariant</span>(<span class="hljs-params">
  variant: Variant
</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">ProductMedia</span>&gt; </span>{
  <span class="hljs-keyword">const</span> { product_medias } = <span class="hljs-keyword">await</span> medusaRequest(<span class="hljs-string">"GET"</span>, <span class="hljs-string">`/product-media`</span>, {
    <span class="hljs-attr">query</span>: {
      <span class="hljs-attr">variant_ids</span>: variant.id,
      <span class="hljs-attr">expand</span>: [<span class="hljs-string">"variants"</span>],
    },
  })
    .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.body)
    .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-keyword">throw</span> err
    })

  <span class="hljs-keyword">return</span> product_medias[<span class="hljs-number">0</span>]
}
</code></pre>
<p>This function is responsible for fetching information related to a specific product variant. It does this by making an HTTP request to the <code>/product-media</code> endpoint. It takes one argument, <code>variant</code>, which is expected to be of type <code>Variant</code>. The request includes query parameters specifying the <code>variant_ids</code> and requests additional details about related "variants". </p>
<p>The function awaits the response from the HTTP request and extracts the response body, which is assumed to be an array of product media objects. It then returns the first product media object from this array, presuming there is at least one such object. If an error occurs during the request, it catches the error and rethrows it.</p>
<h2 id="heading-how-to-offer-e-book-previews">How to Offer e-Book Previews</h2>
<p>To give customers a glimpse of the e-book's content, we'll provide a preview PDF with the first few pages. </p>
<p>To do this, we'll set up a Next API route to manage file downloads while keeping the file's location private. We'll also create a component for a straightforward "download free preview" button. If a product variant has preview media, it will be shown in the product-actions component.</p>
<p>You can use the newly created <code>DigitalProduct</code> and <code>DigitalProductVariant</code> types to fix any TypeScript errors that you may encounter.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
  <span class="hljs-comment">// Get the file info from the URL</span>
  <span class="hljs-keyword">const</span> { filepath, filename } = <span class="hljs-built_in">Object</span>.fromEntries(req.nextUrl.searchParams)

  <span class="hljs-comment">// Fetch the PDF file</span>
  <span class="hljs-keyword">const</span> pdfResponse = <span class="hljs-keyword">await</span> fetch(filepath)

  <span class="hljs-comment">// Handle the case where the PDF could not be fetched</span>
  <span class="hljs-keyword">if</span> (!pdfResponse.ok) <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> NextResponse(<span class="hljs-string">"PDF not found"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">404</span> })

  <span class="hljs-comment">// Get the PDF content as a buffer</span>
  <span class="hljs-keyword">const</span> pdfBuffer = <span class="hljs-keyword">await</span> pdfResponse.arrayBuffer()

  <span class="hljs-comment">// Define response headers</span>
  <span class="hljs-keyword">const</span> headers = {
    <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/pdf"</span>,
    <span class="hljs-string">"Content-Disposition"</span>: <span class="hljs-string">`attachment; filename="<span class="hljs-subst">${filename}</span>"`</span>, <span class="hljs-comment">// This sets the file name for the download</span>
  }

  <span class="hljs-comment">// Create a NextResponse with the PDF content and headers</span>
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">new</span> NextResponse(pdfBuffer, {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    headers,
  })

  <span class="hljs-keyword">return</span> response
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/components/button"</span>
<span class="hljs-keyword">import</span> { ProductMedia } <span class="hljs-keyword">from</span> <span class="hljs-string">"types/product-media"</span>

type Props = {
  <span class="hljs-attr">media</span>: ProductMedia
}

<span class="hljs-keyword">const</span> ProductMediaPreview: React.FC&lt;Props&gt; = <span class="hljs-function">(<span class="hljs-params">{ media }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> downloadPreview = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_BASE_URL}</span>/api/download/preview?filepath=<span class="hljs-subst">${media.file}</span>&amp;filename=<span class="hljs-subst">${media.name}</span>`</span>
  }

  <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">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"secondary"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{downloadPreview}</span>&gt;</span>
        Download free preview
      <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ProductMediaPreview
</code></pre>
<p>The <code>GET</code> function is designed to handle incoming HTTP GET requests using the Next.js framework. It first extracts information from the request URL, specifically the <code>filepath</code> and <code>filename</code>, which are expected to be query parameters. It then attempts to fetch a PDF file from the specified <code>filepath</code>. If the PDF is successfully retrieved, it proceeds to convert the PDF content into a buffer.</p>
<p>In case the PDF retrieval fails, for instance, if the file is not found, it returns a response with a "PDF not found" message and a 404 status code, indicating a not found error.</p>
<p>If the PDF is successfully fetched, it defines response headers, specifying that the content type is "application/pdf" and setting the "Content-Disposition" header to control the behavior of file downloads. The <code>Content-Disposition</code> header is set to "attachment," and the <code>filename</code> parameter is used to suggest a filename for the downloaded PDF.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/components/button"</span>
<span class="hljs-keyword">import</span> { ProductMedia } <span class="hljs-keyword">from</span> <span class="hljs-string">"types/product-media"</span>

type Props = {
  <span class="hljs-attr">media</span>: ProductMedia
}

<span class="hljs-keyword">const</span> ProductMediaPreview: React.FC&lt;Props&gt; = <span class="hljs-function">(<span class="hljs-params">{ media }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> downloadPreview = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_BASE_URL}</span>/api/download/preview?filepath=<span class="hljs-subst">${media.file}</span>&amp;filename=<span class="hljs-subst">${media.name}</span>`</span>
  }

  <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">Button</span> <span class="hljs-attr">variant</span>=<span class="hljs-string">"secondary"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{downloadPreview}</span>&gt;</span>
        Download free preview
      <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ProductMediaPreview
</code></pre>
<p> The above component displays a preview of a product's media along with a button to download a free preview of that media. The component receives a prop named <code>media</code>, which is expected to be of type <code>ProductMedia</code>.</p>
<p>Inside the component, there's a <code>downloadPreview</code> function that's called when a user clicks the "Download free preview" button. This function constructs a URL for downloading the preview using the <code>window.location.href</code> property. It combines the base URL from the environment variable <code>NEXT_PUBLIC_BASE_URL</code> with the "/api/download/preview" route and includes query parameters for the file path and file name, which are extracted from the <code>media</code> prop.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ...other imports</span>
<span class="hljs-keyword">import</span> ProductMediaPreview <span class="hljs-keyword">from</span> <span class="hljs-string">"../product-media-preview"</span>
<span class="hljs-keyword">import</span> { getProductMediaPreviewByVariant } <span class="hljs-keyword">from</span> <span class="hljs-string">"@lib/data"</span>

<span class="hljs-keyword">const</span> ProductActions: React.FC&lt;ProductActionsProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ product }</span>) =&gt;</span> {
    <span class="hljs-comment">// ...other code</span>

  <span class="hljs-keyword">const</span> [productMedia, setProductMedia] = useState({} <span class="hljs-keyword">as</span> ProductMedia)

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> getProductMedia = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">if</span> (!variant) <span class="hljs-keyword">return</span>
      <span class="hljs-keyword">await</span> getProductMediaPreviewByVariant(variant).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
        setProductMedia(res)
      })
    }
    getProductMedia()
  }, [variant])

  <span class="hljs-keyword">return</span> (
            <span class="hljs-comment">// ...other code</span>

      {productMedia &amp;&amp; <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ProductMediaPreview</span> <span class="hljs-attr">media</span>=<span class="hljs-string">{productMedia}</span> /&gt;</span></span>}

      &lt;Button onClick={addToCart}&gt;
        {!inStock ? <span class="hljs-string">"Out of stock"</span> : <span class="hljs-string">"Add to cart"</span>}
      &lt;/Button&gt;
    &lt;/div&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ProductActions
</code></pre>
<p>This component is responsible for displaying product-related actions, such as adding a product to the cart, and showing product media preview if available. It leverages asynchronous operations to fetch the media data based on the provided <code>variant</code>, making it a dynamic and interactive component.</p>
<h2 id="heading-how-to-adjust-the-product-and-shipping-details">How to Adjust the Product and Shipping Details</h2>
<p>Because product and shipping information differs between digital and physical products, we'll make changes to these sections on the product page as needed.</p>
<h3 id="heading-how-to-add-product-details">How to Add Product Details</h3>
<p>I've added product details to the e-book using the product's metadata section in the Medusa admin. Since we're not using the standard attributes, we'll enhance the <code>ProductInfoTab</code> component to display any additional metadata we include.</p>
<p>By default, metadata is structured as an object. To make it simpler to create our list of attributes, we'll change it into an array. </p>
<p>In this case, we'll feature four attributes from the metadata, splitting them into two columns. If you want to show a different number of attributes, you can easily adjust the values within the <code>slice()</code> function as needed.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ... other components</span>

<span class="hljs-keyword">const</span> ProductInfoTab = <span class="hljs-function">(<span class="hljs-params">{ product }: ProductTabsProps</span>) =&gt;</span> {
  <span class="hljs-comment">// map the metadata object to an array</span>
  <span class="hljs-keyword">const</span> metadata = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!product.metadata) <span class="hljs-keyword">return</span> []
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Object</span>.keys(product.metadata).map(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> [key, product.metadata?.[key]]
    })
  }, [product])

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Tab.Panel</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-small-regular py-8"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"grid grid-cols-2 gap-x-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col gap-y-4"</span>&gt;</span>
                {/* Map the metadata as product information */}
          {metadata &amp;&amp;
            metadata.slice(0, 2).map(([key, value], i) =&gt; (
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-semibold"</span>&gt;</span>{key}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{value}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <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 class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col gap-y-4"</span>&gt;</span>
          {metadata.length &gt; 2 &amp;&amp;
            metadata.slice(2, 4).map(([key, value], i) =&gt; {
              return (
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{i}</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-semibold"</span>&gt;</span>{key}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{value}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {product.tags?.length ? (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-semibold"</span>&gt;</span>Tags<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      ) : null}
    <span class="hljs-tag">&lt;/<span class="hljs-name">Tab.Panel</span>&gt;</span></span>
  )
}

<span class="hljs-comment">// ... other components</span>
</code></pre>
<h3 id="heading-how-to-adjust-the-shipping-details">How to Adjust the Shipping Details</h3>
<p>Shipping information isn't relevant for digital products, so we'll change the content in this tab. You can make any necessary adjustments to the content within the <code>ShippingInfoTab</code> component in the same file to better match your store's requirements.</p>
<pre><code class="lang-jaavscript">// ... other components

const ProductTabs = ({ product }: ProductTabsProps) =&gt; {
  const tabs = useMemo(() =&gt; {
    return [
      {
        label: "Product Information",
        component: &lt;ProductInfoTab product={product} /&gt;,
      },
      {
        label: "E-book delivery",
        component: &lt;ShippingInfoTab /&gt;,
      },
    ]
  }, [product])
    // ... rest of code
}

// ... other components

const ShippingInfoTab = () =&gt; {
  return (
    &lt;Tab.Panel className="text-small-regular py-8"&gt;
      &lt;div className="grid grid-cols-1 gap-y-8"&gt;
        &lt;div className="flex items-start gap-x-2"&gt;
          &lt;FastDelivery /&gt;
          &lt;div&gt;
            &lt;span className="font-semibold"&gt;Instant delivery&lt;/span&gt;
            &lt;p className="max-w-sm"&gt;
              Your e-book will be delivered instantly via email. You can also
              download it from your account anytime.
            &lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div className="flex items-start gap-x-2"&gt;
          &lt;Refresh /&gt;
          &lt;div&gt;
            &lt;span className="font-semibold"&gt;Free previews&lt;/span&gt;
            &lt;p className="max-w-sm"&gt;
              Get a free preview of the e-book before you buy it. Just click the
              button above to download it.
            &lt;/p&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/Tab.Panel&gt;
  )
}

// ... other components
</code></pre>
<p>The <code>ProductTabs</code> component is used for rendering a set of tabs. The component takes a <code>product</code> prop, and it uses the <code>useMemo</code> hook to create an array of tab objects. Each tab object consists of a label and a component to be displayed when that tab is active. </p>
<p>In the above snippet, there are two tabs: "Product Information" and "E-book delivery." The "Product Information" tab displays information about the product using the <code>ProductInfoTab</code> component, which we defined earlier. </p>
<p>The "E-book delivery" tab uses the <code>ShippingInfoTab</code> component to display information related to e-book delivery. Inside the <code>ShippingInfoTab</code> component, it provides details about the delivery process, mentioning instant delivery via email and the option to download from an account, as well as free e-book previews.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/10p0z2piqkpv60m4jgab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Product Page</em></p>
<h2 id="heading-how-to-simplify-the-checkout">How to Simplify the Checkout</h2>
<p>Selling digital products doesn't require gathering customers' physical addresses. We only need their first name and email address to deliver the e-book, making the checkout process simpler by removing unnecessary input fields. </p>
<p>In this example, we'll keep only the first name, last name, country, and email fields, completely removing the billing address section. Keep in mind that your specific requirements may require different input fields.</p>
<p>To start, we'll adjust the checkout types and context by removing any references to values that are no longer needed.</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>

<span class="hljs-keyword">import</span> { medusaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@lib/config"</span>
<span class="hljs-keyword">import</span> useToggleState, { StateType } <span class="hljs-keyword">from</span> <span class="hljs-string">"@lib/hooks/use-toggle-state"</span>
<span class="hljs-keyword">import</span> { Cart, Customer, StorePostCartsCartReq } <span class="hljs-keyword">from</span> <span class="hljs-string">"@medusajs/medusa"</span>
<span class="hljs-keyword">import</span> Wrapper <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/checkout/components/payment-wrapper"</span>
<span class="hljs-keyword">import</span> { isEqual } <span class="hljs-keyword">from</span> <span class="hljs-string">"lodash"</span>
<span class="hljs-keyword">import</span> {
  formatAmount,
  useCart,
  useCartShippingOptions,
  useMeCustomer,
  useRegions,
  useSetPaymentSession,
  useUpdateCart,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"medusa-react"</span>
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>
<span class="hljs-keyword">import</span> React, { createContext, useContext, useEffect, useMemo } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>
<span class="hljs-keyword">import</span> { FormProvider, useForm, useFormContext } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-hook-form"</span>
<span class="hljs-keyword">import</span> { useStore } <span class="hljs-keyword">from</span> <span class="hljs-string">"./store-context"</span>

type AddressValues = {
  <span class="hljs-attr">first_name</span>: string
  <span class="hljs-attr">last_name</span>: string
  <span class="hljs-attr">country_code</span>: string
}

<span class="hljs-keyword">export</span> type CheckoutFormValues = {
  <span class="hljs-attr">shipping_address</span>: AddressValues
  billing_address?: AddressValues
  <span class="hljs-attr">email</span>: string
}

interface CheckoutContext {
  cart?: Omit&lt;Cart, <span class="hljs-string">"refundable_amount"</span> | <span class="hljs-string">"refunded_total"</span>&gt;
  shippingMethods: { label?: string; value?: string; price: string }[]
  <span class="hljs-attr">isLoading</span>: boolean
  <span class="hljs-attr">readyToComplete</span>: boolean
  <span class="hljs-attr">sameAsBilling</span>: StateType
  <span class="hljs-attr">editAddresses</span>: StateType
  <span class="hljs-attr">initPayment</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-keyword">void</span>&gt;
  setAddresses: <span class="hljs-function">(<span class="hljs-params">addresses: CheckoutFormValues</span>) =&gt;</span> <span class="hljs-keyword">void</span>
  <span class="hljs-attr">setSavedAddress</span>: <span class="hljs-function">(<span class="hljs-params">address: AddressValues</span>) =&gt;</span> <span class="hljs-keyword">void</span>
  <span class="hljs-attr">setShippingOption</span>: <span class="hljs-function">(<span class="hljs-params">soId: string</span>) =&gt;</span> <span class="hljs-keyword">void</span>
  <span class="hljs-attr">setPaymentSession</span>: <span class="hljs-function">(<span class="hljs-params">providerId: string</span>) =&gt;</span> <span class="hljs-keyword">void</span>
  <span class="hljs-attr">onPaymentCompleted</span>: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">void</span>
}

<span class="hljs-keyword">const</span> CheckoutContext = createContext&lt;CheckoutContext | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)
</code></pre>
<p>In the above snippet, you define TypeScript types for address values and the overall form structure. The <code>CheckoutContext</code> is also created to serve as a context for sharing checkout-related data and functions with other components.</p>
<pre><code class="lang-javascript">interface CheckoutProviderProps {
  children?: React.ReactNode
}

<span class="hljs-keyword">const</span> IDEMPOTENCY_KEY = <span class="hljs-string">"create_payment_session_key"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> CheckoutProvider = <span class="hljs-function">(<span class="hljs-params">{ children }: CheckoutProviderProps</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> {
    cart,
    setCart,
    <span class="hljs-attr">addShippingMethod</span>: {
      <span class="hljs-attr">mutate</span>: setShippingMethod,
      <span class="hljs-attr">isLoading</span>: addingShippingMethod,
    },
    <span class="hljs-attr">completeCheckout</span>: { <span class="hljs-attr">mutate</span>: complete, <span class="hljs-attr">isLoading</span>: completingCheckout },
  } = useCart()

  <span class="hljs-keyword">const</span> { customer } = useMeCustomer()
  <span class="hljs-keyword">const</span> { countryCode } = useStore()

  <span class="hljs-keyword">const</span> methods = useForm&lt;CheckoutFormValues&gt;({
    <span class="hljs-attr">defaultValues</span>: mapFormValues(customer, cart, countryCode),
    <span class="hljs-attr">reValidateMode</span>: <span class="hljs-string">"onChange"</span>,
  })
</code></pre>
<p>The <code>CheckoutProvider</code> component manages cart data, customer information, form handling, and interactions with payment and shipping methods. It sets up various hooks and functions for these purposes.</p>
<p>You also define an idempotency key which will be used for preventing duplicate requests during payment session creation.</p>
<pre><code class="lang-javascript"> <span class="hljs-keyword">const</span> methods = useForm&lt;CheckoutFormValues&gt;({
    <span class="hljs-attr">defaultValues</span>: mapFormValues(customer, cart, countryCode),
    <span class="hljs-attr">reValidateMode</span>: <span class="hljs-string">"onChange"</span>,
  })

  <span class="hljs-keyword">const</span> {
    <span class="hljs-attr">mutate</span>: setPaymentSessionMutation,
    <span class="hljs-attr">isLoading</span>: settingPaymentSession,
  } = useSetPaymentSession(cart?.id!)

  <span class="hljs-keyword">const</span> { <span class="hljs-attr">mutate</span>: updateCart, <span class="hljs-attr">isLoading</span>: updatingCart } = useUpdateCart(
    cart?.id!
  )

  <span class="hljs-keyword">const</span> { shipping_options } = useCartShippingOptions(cart?.id!, {
    <span class="hljs-attr">enabled</span>: !!cart?.id,
  })

  <span class="hljs-keyword">const</span> { regions } = useRegions()

  <span class="hljs-keyword">const</span> { resetCart, setRegion } = useStore()
  <span class="hljs-keyword">const</span> { push } = useRouter()

  <span class="hljs-keyword">const</span> editAddresses = useToggleState()
  <span class="hljs-keyword">const</span> sameAsBilling = useToggleState(
    cart?.billing_address &amp;&amp; cart?.shipping_address
      ? isEqual(cart.billing_address, cart.shipping_address)
      : <span class="hljs-literal">true</span>
  )
</code></pre>
<p>In this section of code, several variables and hooks are initialized to facilitate the management of a checkout process. </p>
<p>We use the <code>methods</code> variable to manage the checkout form, with initial values populated by the <code>mapFormValues</code> function. The code also sets up mutation functions for updating the payment session and the cart (<code>setPaymentSessionMutation</code> and <code>updateCart</code>) and tracks their loading states. It retrieves available shipping options and regions using hooks, and it also handles cart resets and region selection. </p>
<p>It also employs boolean states (<code>editAddresses</code> and <code>sameAsBilling</code>) to manage whether the user is currently editing addresses and whether the billing address matches the shipping address. </p>
<p>These components collectively ensure smooth navigation and data management in the checkout process.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
   * Boolean that indicates if a part of the checkout is loading.
   */</span>
  <span class="hljs-keyword">const</span> isLoading = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
      addingShippingMethod ||
      settingPaymentSession ||
      updatingCart ||
      completingCheckout
    )
  }, [
    addingShippingMethod,
    completingCheckout,
    settingPaymentSession,
    updatingCart,
  ])

  <span class="hljs-comment">/**
   * Boolean that indicates if the checkout is ready to be completed. A checkout is ready to be completed if
   * the user has supplied a email, shipping address, billing address, shipping method, and a method of payment.
   */</span>
  <span class="hljs-keyword">const</span> readyToComplete = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
      !!cart &amp;&amp;
      !!cart.email &amp;&amp;
      !!cart.shipping_address &amp;&amp;
      !!cart.billing_address &amp;&amp;
      !!cart.payment_session &amp;&amp;
      cart.shipping_methods?.length &gt; <span class="hljs-number">0</span>
    )
  }, [cart])

  <span class="hljs-keyword">const</span> shippingMethods = useMemo(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (shipping_options &amp;&amp; cart?.region) {
      <span class="hljs-keyword">return</span> shipping_options?.map(<span class="hljs-function">(<span class="hljs-params">option</span>) =&gt;</span> ({
        <span class="hljs-attr">value</span>: option.id,
        <span class="hljs-attr">label</span>: option.name,
        <span class="hljs-attr">price</span>: formatAmount({
          <span class="hljs-attr">amount</span>: option.amount || <span class="hljs-number">0</span>,
          <span class="hljs-attr">region</span>: cart.region,
        }),
      }))
    }

    <span class="hljs-keyword">return</span> []
  }, [shipping_options, cart])
</code></pre>
<p>In the code above, first the <code>isLoading</code> boolean is computed using the <code>useMemo</code> hook. It reflects whether any part of the checkout is in a loading state. </p>
<p>This is determined by observing four loading flags: <code>addingShippingMethod</code>, <code>settingPaymentSession</code>, <code>updatingCart</code>, and <code>completingCheckout</code>. If any of these flags is <code>true</code>, the <code>isLoading</code> flag will also be <code>true</code>. This indicates that some part of the checkout is currently in progress.</p>
<p>The <code>readyToComplete</code> boolean, also computed with <code>useMemo</code>, assesses whether the checkout is prepared for completion. </p>
<p>To be deemed ready, several conditions must be met: there must be a valid <code>cart</code> object, an email address, a shipping address, a billing address, a payment session, and at least one shipping method selected. If all these conditions are satisfied, <code>readyToComplete</code> will be <code>true</code>, signaling that the checkout process is set to be finalized.</p>
<p>Finally, the <code>shippingMethods</code> variable is computed using <code>useMemo</code>. It is an array of available shipping methods with associated information. It maps the <code>shipping_options</code> (if they exist) to an array of objects, each containing a <code>value</code>, <code>label</code>, and <code>price</code>. </p>
<p>These objects represent the shipping options, their names, and prices, formatted using the <code>formatAmount</code> function. This data is used to display and select shipping methods during the checkout process.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">/**
   * Resets the form when the cart changed.
   */</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (cart?.id) {
      methods.reset(mapFormValues(customer, cart, countryCode))
    }
  }, [customer, cart, methods, countryCode])

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!cart) {
      editAddresses.open()
      <span class="hljs-keyword">return</span>
    }

    <span class="hljs-keyword">if</span> (cart?.shipping_address &amp;&amp; cart?.billing_address) {
      editAddresses.close()
      <span class="hljs-keyword">return</span>
    }

    editAddresses.open()
    <span class="hljs-comment">// eslint-disable-next-line react-hooks/exhaustive-deps</span>
  }, [cart])

  <span class="hljs-comment">/**
   * Method to set the selected shipping method for the cart. This is called when the user selects a shipping method, such as UPS, FedEx, etc.
   */</span>
  <span class="hljs-keyword">const</span> setShippingOption = <span class="hljs-function">(<span class="hljs-params">soId: string</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (cart) {
      setShippingMethod(
        { <span class="hljs-attr">option_id</span>: soId },
        {
          <span class="hljs-attr">onSuccess</span>: <span class="hljs-function">(<span class="hljs-params">{ cart }</span>) =&gt;</span> setCart(cart),
        }
      )
    }
  }

  <span class="hljs-comment">/**
   * Method to create the payment sessions available for the cart. Uses a idempotency key to prevent duplicate requests.
   */</span>
  <span class="hljs-keyword">const</span> createPaymentSession = <span class="hljs-keyword">async</span> (cartId: string) =&gt; {
    <span class="hljs-keyword">return</span> medusaClient.carts
      .createPaymentSessions(cartId, {
        <span class="hljs-string">"Idempotency-Key"</span>: IDEMPOTENCY_KEY,
      })
      .then(<span class="hljs-function">(<span class="hljs-params">{ cart }</span>) =&gt;</span> cart)
      .catch(<span class="hljs-function">() =&gt;</span> <span class="hljs-literal">null</span>)
  }

  <span class="hljs-comment">/**
   * Method that calls the createPaymentSession method and updates the cart with the payment session.
   */</span>
  <span class="hljs-keyword">const</span> initPayment = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">if</span> (cart?.id &amp;&amp; !cart.payment_sessions?.length &amp;&amp; cart?.items?.length) {
      <span class="hljs-keyword">const</span> paymentSession = <span class="hljs-keyword">await</span> createPaymentSession(cart.id)

      <span class="hljs-keyword">if</span> (!paymentSession) {
        <span class="hljs-built_in">setTimeout</span>(initPayment, <span class="hljs-number">500</span>)
      } <span class="hljs-keyword">else</span> {
        setCart(paymentSession)
        <span class="hljs-keyword">return</span>
      }
    }
  }

  <span class="hljs-comment">/**
   * Method to set the selected payment session for the cart. This is called when the user selects a payment provider, such as Stripe, PayPal, etc.
   */</span>
  <span class="hljs-keyword">const</span> setPaymentSession = <span class="hljs-function">(<span class="hljs-params">providerId: string</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (cart) {
      setPaymentSessionMutation(
        {
          <span class="hljs-attr">provider_id</span>: providerId,
        },
        {
          <span class="hljs-attr">onSuccess</span>: <span class="hljs-function">(<span class="hljs-params">{ cart }</span>) =&gt;</span> {
            setCart(cart)
          },
        }
      )
    }
  }

  <span class="hljs-keyword">const</span> prepareFinalSteps = <span class="hljs-function">() =&gt;</span> {
    initPayment()

    <span class="hljs-keyword">if</span> (shippingMethods?.length &amp;&amp; shippingMethods?.[<span class="hljs-number">0</span>]?.value) {
      setShippingOption(shippingMethods[<span class="hljs-number">0</span>].value)
    }
  }

  <span class="hljs-keyword">const</span> setSavedAddress = <span class="hljs-function">(<span class="hljs-params">address: AddressValues</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> setValue = methods.setValue

    setValue(<span class="hljs-string">"shipping_address"</span>, {
      <span class="hljs-attr">country_code</span>: address.country_code || <span class="hljs-string">""</span>,
      <span class="hljs-attr">first_name</span>: address.first_name || <span class="hljs-string">""</span>,
      <span class="hljs-attr">last_name</span>: address.last_name || <span class="hljs-string">""</span>,
    })
  }

  <span class="hljs-comment">/**
   * Method that validates if the cart's region matches the shipping address's region. If not, it will update the cart region.
   */</span>
  <span class="hljs-keyword">const</span> validateRegion = <span class="hljs-function">(<span class="hljs-params">countryCode: string</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (regions &amp;&amp; cart) {
      <span class="hljs-keyword">const</span> region = regions.find(<span class="hljs-function">(<span class="hljs-params">r</span>) =&gt;</span>
        r.countries.map(<span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> c.iso_2).includes(countryCode)
      )

      <span class="hljs-keyword">if</span> (region &amp;&amp; region.id !== cart.region.id) {
        setRegion(region.id, countryCode)
      }
    }
  }

  <span class="hljs-comment">/**
   * Method that sets the addresses and email on the cart.
   */</span>
  <span class="hljs-keyword">const</span> setAddresses = <span class="hljs-function">(<span class="hljs-params">data: CheckoutFormValues</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { shipping_address, billing_address, email } = data

    <span class="hljs-keyword">const</span> payload: StorePostCartsCartReq = {
      shipping_address,
      email,
    }

    <span class="hljs-keyword">if</span> (isEqual(shipping_address, billing_address)) {
      sameAsBilling.open()
    }

    <span class="hljs-keyword">if</span> (sameAsBilling.state) {
      payload.billing_address = shipping_address
    } <span class="hljs-keyword">else</span> {
      payload.billing_address = billing_address
    }

    updateCart(payload, {
      <span class="hljs-attr">onSuccess</span>: <span class="hljs-function">(<span class="hljs-params">{ cart }</span>) =&gt;</span> {
        setCart(cart)
        prepareFinalSteps()
      },
    })
  }

  <span class="hljs-comment">/**
   * Method to complete the checkout process. This is called when the user clicks the "Complete Checkout" button.
   */</span>
  <span class="hljs-keyword">const</span> onPaymentCompleted = <span class="hljs-function">() =&gt;</span> {
    complete(<span class="hljs-literal">undefined</span>, {
      <span class="hljs-attr">onSuccess</span>: <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> {
        resetCart()
        push(<span class="hljs-string">`/order/confirmed/<span class="hljs-subst">${data.id}</span>`</span>)
      },
    })
  }

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">FormProvider</span> {<span class="hljs-attr">...methods</span>}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">CheckoutContext.Provider</span>
        <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span>
          <span class="hljs-attr">cart</span>,
          <span class="hljs-attr">shippingMethods</span>,
          <span class="hljs-attr">isLoading</span>,
          <span class="hljs-attr">readyToComplete</span>,
          <span class="hljs-attr">sameAsBilling</span>,
          <span class="hljs-attr">editAddresses</span>,
          <span class="hljs-attr">initPayment</span>,
          <span class="hljs-attr">setAddresses</span>,
          <span class="hljs-attr">setSavedAddress</span>,
          <span class="hljs-attr">setShippingOption</span>,
          <span class="hljs-attr">setPaymentSession</span>,
          <span class="hljs-attr">onPaymentCompleted</span>,
        }}
      &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Wrapper</span> <span class="hljs-attr">paymentSession</span>=<span class="hljs-string">{cart?.payment_session}</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">Wrapper</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">CheckoutContext.Provider</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">FormProvider</span>&gt;</span></span>
  )
}
</code></pre>
<p>This code section orchestrates various aspects of an e-commerce checkout process. It manages form state, resets the form when the cart changes, and toggles address editing visibility. It handles the selection of shipping methods, the creation and initialization of payment sessions, and the choice of payment providers. And it ensures that shipping addresses, billing addresses, and email information are set appropriately, and validates the cart's region based on the shipping address. </p>
<p>It also coordinates the completion of the checkout process, including payment processing and order confirmation. </p>
<p>All of these functions and data are encapsulated within the <code>CheckoutProvider</code> component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> useCheckout = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> context = useContext(CheckoutContext)
  <span class="hljs-keyword">const</span> form = useFormContext&lt;CheckoutFormValues&gt;()
  <span class="hljs-keyword">if</span> (context === <span class="hljs-literal">null</span>) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
      <span class="hljs-string">"useProductActionContext must be used within a ProductActionProvider"</span>
    )
  }
  <span class="hljs-keyword">return</span> { ...context, ...form }
}

<span class="hljs-comment">/**
 * Method to map the fields of a potential customer and the cart to the checkout form values. Information is assigned with the following priority:
 * 1. Cart information
 * 2. Customer information
 * 3. Default values - null
 */</span>
<span class="hljs-keyword">const</span> mapFormValues = (
  customer?: Omit&lt;Customer, <span class="hljs-string">"password_hash"</span>&gt;,
  cart?: Omit&lt;Cart, <span class="hljs-string">"refundable_amount"</span> | <span class="hljs-string">"refunded_total"</span>&gt;,
  currentCountry?: string
): <span class="hljs-function"><span class="hljs-params">CheckoutFormValues</span> =&gt;</span> {
  <span class="hljs-keyword">const</span> customerShippingAddress = customer?.shipping_addresses?.[<span class="hljs-number">0</span>]
  <span class="hljs-keyword">const</span> customerBillingAddress = customer?.billing_address

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">shipping_address</span>: {
      <span class="hljs-attr">first_name</span>:
        cart?.shipping_address?.first_name ||
        customerShippingAddress?.first_name ||
        <span class="hljs-string">""</span>,
      <span class="hljs-attr">last_name</span>:
        cart?.shipping_address?.last_name ||
        customerShippingAddress?.last_name ||
        <span class="hljs-string">""</span>,
      <span class="hljs-attr">country_code</span>:
        currentCountry ||
        cart?.shipping_address?.country_code ||
        customerShippingAddress?.country_code ||
        <span class="hljs-string">""</span>,
    },
    <span class="hljs-attr">billing_address</span>: {
      <span class="hljs-attr">first_name</span>:
        cart?.billing_address?.first_name ||
        customerBillingAddress?.first_name ||
        <span class="hljs-string">""</span>,
      <span class="hljs-attr">last_name</span>:
        cart?.billing_address?.last_name ||
        customerBillingAddress?.last_name ||
        <span class="hljs-string">""</span>,
      <span class="hljs-attr">country_code</span>:
        cart?.shipping_address?.country_code ||
        customerBillingAddress?.country_code ||
        <span class="hljs-string">""</span>,
    },
    <span class="hljs-attr">email</span>: cart?.email || customer?.email || <span class="hljs-string">""</span>,
  }
}
</code></pre>
<p>The <code>useCheckout</code> hook is used to access the checkout context and form context, typically used in React components. It retrieves the <code>CheckoutContext</code> from the context of the application, and it also gets the form context of the checkout form, allowing components to access and utilize these contexts.</p>
<p>The <code>mapFormValues</code> function is responsible for mapping and prioritizing information for the checkout form. It takes customer and cart data, along with the current country, and generates values for the checkout form fields. </p>
<p>It prioritizes data in this order: 1) Cart information, 2) Customer information, and 3) Default values set to null if no information is available. This function helps populate the checkout form with the most relevant data, ensuring a smoother user experience during the checkout process.</p>
<p>Now that the context is updated, we’ll remove the redundant input fields from the checkout form.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useCheckout } <span class="hljs-keyword">from</span> <span class="hljs-string">"@lib/context/checkout-context"</span>
<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/components/button"</span>
<span class="hljs-keyword">import</span> Spinner <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/icons/spinner"</span>
<span class="hljs-keyword">import</span> ShippingAddress <span class="hljs-keyword">from</span> <span class="hljs-string">"../shipping-address"</span>

<span class="hljs-keyword">const</span> Addresses = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> {
    <span class="hljs-attr">editAddresses</span>: { <span class="hljs-attr">state</span>: isEdit, <span class="hljs-attr">toggle</span>: setEdit },
    setAddresses,
    handleSubmit,
    cart,
  } = useCheckout()
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl-semi flex items-center gap-x-4 px-8 pb-6 pt-8"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gray-900 w-8 h-8 rounded-full text-white flex justify-center items-center text-sm"</span>&gt;</span>
          1
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Shipping address<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      {isEdit ? (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"px-8 pb-8"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ShippingAddress</span> /&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">Button</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"max-w-[200px] mt-6"</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleSubmit(setAddresses)}</span>
          &gt;</span>
            Continue to delivery
          <span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span>
        <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 class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gray-50 px-8 py-6 text-small-regular"</span>&gt;</span>
            {cart &amp;&amp; cart.shipping_address ? (
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-start gap-x-8"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-green-400 rounded-full min-w-[24px] h-6 flex items-center justify-center text-white text-small-regular"</span>&gt;</span>
                  ✓
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-start justify-between w-full"</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
                      {cart.shipping_address.first_name}{" "}
                      {cart.shipping_address.last_name}
                      {cart.shipping_address.country}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-4 flex flex-col"</span>&gt;</span>
                      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{cart.email}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <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 class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{setEdit}</span>&gt;</span>Edit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                  <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            ) : (
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">""</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Spinner</span> /&gt;</span>
              <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 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>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Addresses
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/t1osg7g86bmd4s8qugdf.png" alt="Image" width="600" height="400" loading="lazy">
<em>Checkout Page</em></p>
<p>In the last step, we'll modify the <code>shipping-details</code> component to show important information after the order is successfully placed. In this situation, we'll remove any extra details and add the buyer's email address for reference.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Address, ShippingMethod } <span class="hljs-keyword">from</span> <span class="hljs-string">"@medusajs/medusa"</span>

type ShippingDetailsProps = {
  <span class="hljs-attr">address</span>: Address
  <span class="hljs-attr">shippingMethods</span>: ShippingMethod[]
  <span class="hljs-attr">email</span>: string
}

<span class="hljs-keyword">const</span> ShippingDetails = <span class="hljs-function">(<span class="hljs-params">{
  address,
  shippingMethods,
  email,
}: ShippingDetailsProps</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-base-regular"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-base-semi"</span>&gt;</span>Delivery<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"my-2"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-small-regular text-gray-700"</span>&gt;</span>Details<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{`${address.first_name} ${address.last_name}`}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{email}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <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 class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"my-2"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-small-regular text-gray-700"</span>&gt;</span>Delivery method<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
          {shippingMethods.map((sm) =&gt; {
            return <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{sm.id}</span>&gt;</span>{sm.shipping_option.name}<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 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>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ShippingDetails
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/io66rfgqkr88i1wcojj9.png" alt="Image" width="600" height="400" loading="lazy">
<em>Order Confirmation Page</em></p>
<h2 id="heading-how-to-deliver-digital-products">How to Deliver Digital Products</h2>
<p>There are various ways to get digital products to customers, like sending a download link by email, adding a download button on the order confirmation page, or giving access through their account.</p>
<p>In all these situations, our main goal is to confirm that only those who've purchased the product can get it. </p>
<p>To do this, I've set up the backend to create a special code (token) for each digital item in an order. We can use GET <code>/store/:token</code> to check the token and give the file to the user. But this method shows the file's web address to the user, which isn't great for preventing piracy. </p>
<p>So we are going to make a Next API route at <code>src/app/api/download/main/[token]/route.ts</code>. This route will handle the token, acting as a middleman to provide the file to the user without revealing where it's stored.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">
  req: NextRequest,
  { params }: { params: Record&lt;string, any&gt; }
</span>) </span>{
  <span class="hljs-comment">// Get the token from the URL</span>
  <span class="hljs-keyword">const</span> { token } = params

  <span class="hljs-comment">// Define the URL to fetch the PDF file data from</span>
  <span class="hljs-keyword">const</span> pdfUrl = <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}</span>/store/product-media/<span class="hljs-subst">${token}</span>`</span>

  <span class="hljs-comment">// Fetch the PDF file data</span>
  <span class="hljs-keyword">const</span> { file, filename } = <span class="hljs-keyword">await</span> fetch(pdfUrl).then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.json())

  <span class="hljs-comment">// Handle the case where the token is invalid</span>
  <span class="hljs-keyword">if</span> (!file) <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> NextResponse(<span class="hljs-string">"Invalid token"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">401</span> })

  <span class="hljs-comment">// Fetch the PDF file</span>
  <span class="hljs-keyword">const</span> pdfResponse = <span class="hljs-keyword">await</span> fetch(file)

  <span class="hljs-comment">// Handle the case where the PDF could not be fetched</span>
  <span class="hljs-keyword">if</span> (!pdfResponse.ok) <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> NextResponse(<span class="hljs-string">"PDF not found"</span>, { <span class="hljs-attr">status</span>: <span class="hljs-number">404</span> })

  <span class="hljs-comment">// Get the PDF content as a buffer</span>
  <span class="hljs-keyword">const</span> pdfBuffer = <span class="hljs-keyword">await</span> pdfResponse.arrayBuffer()

  <span class="hljs-comment">// Define response headers</span>
  <span class="hljs-keyword">const</span> headers = {
    <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/pdf"</span>,
    <span class="hljs-string">"Content-Disposition"</span>: <span class="hljs-string">`attachment; filename="<span class="hljs-subst">${filename}</span>"`</span>, <span class="hljs-comment">// This sets the file name for the download</span>
  }

  <span class="hljs-comment">// Create a NextResponse with the PDF content and headers</span>
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">new</span> NextResponse(pdfBuffer, {
    <span class="hljs-attr">status</span>: <span class="hljs-number">200</span>,
    headers,
  })

  <span class="hljs-keyword">return</span> response
}
</code></pre>
<p>This code defines a serverless function for handling HTTP GET requests in a Next.js application. It retrieves a PDF file using a token provided in the URL parameters, fetching the file from an external source. The function ensures the token's validity and the availability of the PDF file. If the token is invalid, it returns a "401 Unauthorized" response. If the PDF is not found, it returns a "404 Not Found" response. </p>
<p>When the PDF is successfully fetched, it constructs response headers, including the content type as "application/pdf" and a suggested filename for download, and returns the PDF file to the client as a downloadable attachment. This code is typically used to serve PDF files in response to specific GET requests.</p>
<p>We can now link to this API route from the delivery email like this: <code>{your_store_url}/api/download/main/{token}</code>.</p>
<p>You can add your own logic to invalidate tokens after a certain time or X number of downloads.</p>
<h2 id="heading-mission-accomplished"><strong>Mission Accomplished!</strong></h2>
<p>Congratulations, you've made it! Don't forget to explore more <a target="_blank" href="https://docs.medusajs.com/recipes">Recipes</a> for further ways to make the most of Medusa.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Shopping Cart with React and TypeScript ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial we are going to build a real-life shopping cart 🛒 application. We'll talk about the technology stack and the features it will have in a minute. I'll also walk you through the process step-by-step. But first, let me show you what it'... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-shopping-cart-with-react-and-typescript/</link>
                <guid isPermaLink="false">66d460483a8352b6c5a2aab4</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mihail Gaberov ]]>
                </dc:creator>
                <pubDate>Fri, 23 Jun 2023 17:09:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/06/shopping-cart-app-article.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial we are going to build a real-life shopping cart 🛒 application.</p>
<p>We'll talk about the technology stack and the features it will have in a minute. I'll also walk you through the process step-by-step. But first, let me show you what it's going to look like.</p>
<h2 id="heading-lets-sketch">Let’s sketch 🙃</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-214.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Product List</em></p>
<p>We are going to make our app mobile friendly by implementing a decent level of responsiveness.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-215.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Product List - Mobile</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-216.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Cart Desktop</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-217.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Cart Mobile</em></p>
<p>This time, before going directly into building this project, I decided to go more traditional. I took a pen and paper and drew these ugly sketches, just to make sure I had a visual idea of what I would be building.</p>
<p>And honestly, it really did the trick ✨. This technique helps when you are about to sit at your computer and ask the question:</p>
<blockquote>
<p>What do I start with now?</p>
</blockquote>
<h3 id="heading-tldr"><strong>TL;DR</strong></h3>
<p>💡 If you want to skip the reading, <a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app">here</a> 💁 is the GitHub repository with a detailed <a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/blob/main/README.md">README</a> 🙌, and here you can see the live <a target="_blank" href="https://shopping-cart-app-coral.vercel.app/">demo</a>.</p>
<h2 id="heading-whats-a-shopping-cart">What’s a Shopping Cart?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-218.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Shopping Carts</em></p>
<p>A shopping cart lets people carry or store their goods while they're shopping – either online or in real life.</p>
<p>In ecommerce apps, the shopping cart is a place where the user can store and view the items they are considering purchasing. Typically, this is a separate page or part of the page where people can view a list of the items they have chosen to buy before actually paying for them.</p>
<h2 id="heading-the-plan-for-our-app">The Plan for Our App</h2>
<p>We will build an application consisting of two pages: a product list page and a cart page.</p>
<p>The app will fetch the data from a 3rd party RESTful API and it'll use the browser’s localStorage to store selected items that should be displayed in the cart.</p>
<h3 id="heading-application-features">Application features</h3>
<p>The shopping cart application must fetch and display products from the API endpoint <a target="_blank" href="https://dummyjson.com/products">https://dummyjson.com/products</a>.</p>
<p>The Products List page should display the available items along with some specific information. For example, it should show 3 products per row for large viewports. Each item should display at least a thumbnail image, a title, the price (formatted as GBP, for example £100.23), and an “Add to Cart” button which adds the item to the cart.</p>
<p>Cart page should display the customer's chosen items. Each item should display at least a thumbnail image, a title, plus and minus buttons (for adding/removing items) and the current quantity of the item in the cart, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-219.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Plus and minus buttons</em></p>
<p>If there is a quantity of 1 item in the cart, hitting minus removes it from the cart. The cart should also display the total price of all items added (formatted as GBP, for example, £100.23).</p>
<p>In addition to these basic features, the UI/UX should be as sleek as possible. We also want to make sure we unit test the application.</p>
<h3 id="heading-tech-stack">Tech Stack</h3>
<p>If you've had the chance to take a look at some of my others tutorials, the tech stack I chose won’t surprise you much.</p>
<p>I picked these technologies taking into account the requirements for the application – that it would be performant, well tested, and have a sleek look and feel.</p>
<ul>
<li><p>React / TypeScript / Vite – for the UI library we're going again with a React and <a target="_blank" href="https://cloudfour.com/thinks/in-praise-of-vite/">Vite</a> development environment. But this time we will use it with TypeScript instead of JavaScript.</p>
</li>
<li><p>SASS / CSS Modules – for styling our app, we'll bet on the battle-tested solution of <a target="_blank" href="https://github.com/css-modules/css-modules">CSS Modules</a> with <a target="_blank" href="https://sass-lang.com/documentation/">SASS/SCSS</a>.</p>
</li>
<li><p>react-testing-library / Vitest – for testing the app, we'll use <a target="_blank" href="https://testing-library.com/docs/react-testing-library/intro/">react-testing-library</a> and <a target="_blank" href="https://vitest.dev/guide/">Vitest</a>.</p>
</li>
</ul>
<p>If you want to learn more about RTL, here is an insightful <a target="_blank" href="https://www.robinwieruch.de/react-testing-library/">tutorial</a> by a very knowledgable <a target="_blank" href="https://www.robinwieruch.de/about/">guy</a> that can help you out.</p>
<h2 id="heading-how-to-build-the-app">How to Build the App</h2>
<p>In this section we will take a look at the project structure and I'll explain why I chose it.</p>
<p>Then I'll go briefly through each of the components and describe its role.</p>
<p>Once you understand how the components work together to create a functioning application, we'll explore how to utilize the browser's local storage to store data that can be used in other parts of the app.</p>
<h3 id="heading-dependencies"><strong>📦 Dependencies</strong></h3>
<p>Let's take a brief look at our project's dependencies. These are external packages we need to install in order to ensure the successful execution of our project.</p>
<p>Along with Vite and Vitest, I have installed SASS, React Testing Library, and use-local-storage-state. See below my <a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/blob/main/package.json">package.json</a> file.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"shopping-cart-app"</span>,
  <span class="hljs-attr">"private"</span>: <span class="hljs-literal">false</span>,
  <span class="hljs-attr">"version"</span>: <span class="hljs-string">"1.0.0"</span>,
  <span class="hljs-attr">"author"</span>: <span class="hljs-string">"Mihail Gaberov"</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"module"</span>,
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"test"</span>: <span class="hljs-string">"vitest"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc &amp;&amp; vite build"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0"</span>,
    <span class="hljs-attr">"preview"</span>: <span class="hljs-string">"vite preview"</span>
  },
  <span class="hljs-attr">"dependencies"</span>: {
    <span class="hljs-attr">"react"</span>: <span class="hljs-string">"^18.2.0"</span>,
    <span class="hljs-attr">"react-dom"</span>: <span class="hljs-string">"^18.2.0"</span>,
    <span class="hljs-attr">"react-router-dom"</span>: <span class="hljs-string">"^6.13.0"</span>
  },
  <span class="hljs-attr">"devDependencies"</span>: {
    <span class="hljs-attr">"@testing-library/jest-dom"</span>: <span class="hljs-string">"^5.16.5"</span>,
    <span class="hljs-attr">"@testing-library/react"</span>: <span class="hljs-string">"^14.0.0"</span>,
    <span class="hljs-attr">"@types/react"</span>: <span class="hljs-string">"^18.0.37"</span>,
    <span class="hljs-attr">"@types/react-dom"</span>: <span class="hljs-string">"^18.0.11"</span>,
    <span class="hljs-attr">"@typescript-eslint/eslint-plugin"</span>: <span class="hljs-string">"^5.59.0"</span>,
    <span class="hljs-attr">"@typescript-eslint/parser"</span>: <span class="hljs-string">"^5.59.0"</span>,
    <span class="hljs-attr">"@vitejs/plugin-react"</span>: <span class="hljs-string">"^4.0.0"</span>,
    <span class="hljs-attr">"eslint"</span>: <span class="hljs-string">"^8.38.0"</span>,
    <span class="hljs-attr">"eslint-plugin-react-hooks"</span>: <span class="hljs-string">"^4.6.0"</span>,
    <span class="hljs-attr">"eslint-plugin-react-refresh"</span>: <span class="hljs-string">"^0.3.4"</span>,
    <span class="hljs-attr">"jsdom"</span>: <span class="hljs-string">"^22.1.0"</span>,
    <span class="hljs-attr">"sass"</span>: <span class="hljs-string">"^1.63.4"</span>,
    <span class="hljs-attr">"typescript"</span>: <span class="hljs-string">"^5.0.2"</span>,
    <span class="hljs-attr">"use-local-storage-state"</span>: <span class="hljs-string">"^18.3.3"</span>,
    <span class="hljs-attr">"vite"</span>: <span class="hljs-string">"^4.3.9"</span>,
    <span class="hljs-attr">"vitest"</span>: <span class="hljs-string">"^0.32.0"</span>
  }
}
</code></pre>
<h3 id="heading-installation"><strong>🧑🏻‍💻 Installation</strong></h3>
<p>In this step I assume you are starting from scratch. We are going to use Vite for scaffolding the project. In order to do that you need to have Node.js installed on your system – at least version 14.18. I suggest that you update it to the latest stable version. And as a package manager you may go with either <a target="_blank" href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm">npm</a> or <a target="_blank" href="https://classic.yarnpkg.com/lang/en/docs/install/">yarn</a>.</p>
<p>Once you have one of these installed on your system, open your Terminal app and run the following:</p>
<pre><code class="lang-bash">yarn create vite your-app-name --template react-ts
</code></pre>
<p>This command will install the initial application files in a folder named 'your-app-name'. After that step you will be able to open it in your favorite IDE and start working on it.</p>
<p>One last thing you should do here is to install to additional packages I mentioned in the previous section. You can do that by running the following:</p>
<pre><code class="lang-bash">yarn add -D sass @testing-library/react use-local-storage-state
</code></pre>
<h3 id="heading-project-structure"><strong>🏗️ Project Structure</strong></h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-220.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Project Structure</em></p>
<p>This won't be much of a surprise for those of you who have some experience in building React applications. The structure I've chosen is pretty standard.</p>
<p>The root level of the app contains files related to configurations and setup, as well as the HTML index file. This is where the main JavaScript module is loaded and the app is launched.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-221.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>index.html</em></p>
<p>The <code>src</code> folder (short for "source") contains two sub-folders: one for assets and one for components.</p>
<p>The <code>public</code> and <code>screenshots</code> folders have straightforward purposes. The <code>.github</code> folder contains the YAML configuration file that is used by <a target="_blank" href="https://github.com/features/actions">GitHub Actions</a>. We will discuss this in more detail later on.</p>
<h3 id="heading-components"><strong>🛠️</strong>Components</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-222.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>App Components</em></p>
<p>All components are organized into separate folders. Within each folder, you'll find an index.ts file that exports the component. This file uses <a target="_blank" href="https://react.dev/learn/importing-and-exporting-components#default-vs-named-exports">named exports</a>, as shown below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-223.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Named export - Header component</em></p>
<p>We'll start by examining the components from a top-to-bottom approach, as they are seen and used within the application. To help clarify, let me provide a visualization.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-224.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>App Components Visualized in Order</em></p>
<p>Here's what each of these components does in more detail:</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Header">Header</a> – holds the top part of the app. On the left side is the logo image, an SVG I have downloaded from <a target="_blank" href="https://icon-sets.iconify.design/noto-v1/shopping-bags/">Iconify</a>. On the right side sits the CartWidget component.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/CartWidget">CartWidget</a> – renders a button composed of an SVG image depicting a shopping cart and a number value indicating the count of products currently added to the cart. When clicked, the button takes the user to the cart page.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Products">Products</a> – this component is responsible for rendering the main content of the page, which consists of a list of products. On larger viewports, the products are displayed in three columns per row. Each product is represented by a thumbnail image, a title, price information, and an "Add to Cart" button. The price of each product is formatted to GBP using the CurrencyFormatter component.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/CurrencyFormatter">CurrencyFormatter</a> – formats given numeric amount to GBP – that is, 499 would become £499.00.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-225.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>App Components Visualized</em></p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Cart">Cart</a> – this component is responsible for rendering the main content of the page. It displays one product per row and includes a quantifier component that allows the user to update the quantity of the product. At the bottom of the page, it also shows the total price of the selected products, which is formatted as GBP using the CurrencyFormatter component.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Quantifier">Quantifier</a> – this component displays plus and minus buttons along with an input field positioned between them. It serves the purpose of indicating the current quantity of a product and enables the user to modify this value. Additionally, it offers functionality to remove the product entirely from the shopping cart.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Footer">Footer</a> – this component is designed to provide a simple and visually representative way to display information about the author and copyrights.</p>
<p><a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Loader">Loader</a> – this component is not visible on the screenshots above, but it represents a simple loading animation that becomes visible once the user opens the app for first time and the products data is still being loaded.</p>
<h3 id="heading-how-to-build-the-header">🧩 How to Build the Header</h3>
<p>As mentioned earlier, the upper section of the application, commonly referred to as the 'hat', is known as the header. In our specific case, the header comprises two elements: the logo positioned on the left and the CartWidget on the right.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-09.54.02.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Application Header</em></p>
<p>Now, let's go through the process of constructing the application together 🙌. The steps outlined below are applicable to every component we incorporate into the app.</p>
<p>To begin, I create a dedicated folder for the component and include an index.ts file within it. This file will serve as the export module for the component.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-09.56.31.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating Header Component</em></p>
<p>This is where we export the actual component like that:</p>
<pre><code class="lang-python">export { Header } <span class="hljs-keyword">from</span><span class="hljs-string">'./Header'</span>
</code></pre>
<p>Then we implement the component itself. This code will go into a file named the same thing – <code>Header.tsx</code></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> { FunctionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>


export const Header: FunctionComponent = () =&gt; {

  <span class="hljs-keyword">return</span> (
    &lt;header&gt;
      header content here...
    &lt;/header&gt;
  )
}
</code></pre>
<p>We're starting simple.</p>
<p>Currently, this component only displays the text 'header content here...' on the page. Our goal is to gradually enhance it until we achieve the final result depicted in the picture above.</p>
<p>To do this, it is important to incorporate styling into the process. By utilizing CSS Modules, we can import a separate SCSS file containing the styles required for our component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FunctionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">'./header.module.scss'</span> <span class="hljs-comment">// &lt;---- imports the styles</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Header: FunctionComponent = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">return</span> (
    &lt;header&gt;
      header content here...
    &lt;/header&gt;
  )
}
</code></pre>
<p>This file must exist in our component's folder. After including the tests file, the folder structure for this component will resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-10.16.21.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Header Component's Folder</em></p>
<p>Let's enhance the component's code by incorporating the necessary elements. On the left side, we'll add the logo element, which will function as a clickable link. We'll also include the <code>CartWidget</code> component that displays the count of the selected products.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FunctionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Header: FunctionComponent = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">return</span> (
    &lt;header className={classes.header}&gt;
      &lt;div&gt;
        &lt;Link to=<span class="hljs-string">"/"</span>&gt;
          &lt;img src={logo} className={classes.logo} alt=<span class="hljs-string">"Shopping Cart Application"</span> /&gt;
        &lt;/Link&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;CartWidget productsCount={productsCount} /&gt;
      &lt;/div&gt;
    &lt;/header&gt;
  )
}
</code></pre>
<p>To achieve a nice look and feel, and a decent level of responsiveness, we'll use the following styles:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.header</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#213547</span>;
  <span class="hljs-attribute">transition</span>: height <span class="hljs-number">0.3s</span> ease;
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">right</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.9</span>;
  backdrop-<span class="hljs-attribute">filter</span>: saturate(<span class="hljs-number">180%</span>) blur(<span class="hljs-number">20px</span>);
  <span class="hljs-attribute">justify-content</span>: space-between;
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>;

  <span class="hljs-selector-class">.logo</span> {
    <span class="hljs-attribute">height</span>: <span class="hljs-number">6em</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5em</span>;
    will-change: filter;
    <span class="hljs-attribute">transition</span>: filter <span class="hljs-number">300ms</span>;
    <span class="hljs-attribute">transform</span>: scaleX(-<span class="hljs-number">1</span>);


    &amp;<span class="hljs-selector-pseudo">:hover</span> {
      <span class="hljs-attribute">filter</span>: drop-shadow(<span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">2em</span> <span class="hljs-number">#646cffaa</span>);
    }
  }
}
</code></pre>
<p>I will show you the code for the widget component below, but before that I want you to notice how we pass the products count value a prop. This way we can free ourselves from implementing any logic in the component itself and use it only for representational purposes.</p>
<p>That being said, here is the code of the component:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> { FunctionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>

<span class="hljs-keyword">import</span> shoppingCart <span class="hljs-keyword">from</span> <span class="hljs-string">'../../assets/shopping-cart.svg'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">'./cart-widget.module.scss'</span>

interface Props {
  productsCount: number
}

export const CartWidget: FunctionComponent&lt;Props&gt; = ({ productsCount }) =&gt; {
  const navigate = useNavigate()

  const navigateToCart = () =&gt; {
    navigate(<span class="hljs-string">'/cart'</span>)
  }

  <span class="hljs-keyword">return</span> (
    &lt;button className={classes.container} onClick={navigateToCart}&gt;
      &lt;span className={classes.productsCount}&gt;{productsCount}&lt;/span&gt;
      &lt;img src={shoppingCart} className={classes.shoppingCart} alt=<span class="hljs-string">"Go to Cart"</span> /&gt;
    &lt;/button&gt;
  )
}
</code></pre>
<p>And its styling:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">background</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">flex-direction</span>: row-reverse;
  <span class="hljs-attribute">justify-content</span>: space-between;

  &amp;<span class="hljs-selector-pseudo">:hover</span> {
    <span class="hljs-attribute">outline</span>: <span class="hljs-number">1px</span> solid white;
  }


  <span class="hljs-selector-class">.shoppingCart</span> {
    <span class="hljs-attribute">height</span>: <span class="hljs-number">3em</span>;
    <span class="hljs-attribute">padding</span>: <span class="hljs-number">1.5rem</span> .<span class="hljs-number">4rem</span>;
    will-change: filter;
    <span class="hljs-attribute">transition</span>: filter <span class="hljs-number">300ms</span>;
  }

  <span class="hljs-selector-class">.productsCount</span> {
    <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2em</span>;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">38px</span>;
    <span class="hljs-attribute">color</span>: orange;
  }
}
</code></pre>
<h4 id="heading-header-shrinking">Header Shrinking</h4>
<p>Before we proceed, there is one more aspect to discuss: the smooth shrinking animation of the header that you can see while scrolling down.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screen-Recording-2023-06-22-at-10.50.40.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Header Shrinking</em></p>
<p>To accomplish this, I used React hooks along with a technique involving manual manipulation of styles for various DOM elements.</p>
<p>I implemented this functionality within a component method called <code>shrinkHeader</code>, which is invoked whenever a user scrolls. Within this method, I check if the current vertical scroll position exceeds a specified threshold value, <code>DISTANCE_FROM_TOP</code>, and accordingly apply different styles based on the outcome of this comparison.</p>
<p>One aspect we haven't discussed yet is using the hook for managing local storage, which we'll talk about later.</p>
<p>Here is the completed version of the component:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> { FunctionComponent, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>
<span class="hljs-keyword">import</span> useLocalStorageState <span class="hljs-keyword">from</span> <span class="hljs-string">'use-local-storage-state'</span>

<span class="hljs-keyword">import</span> logo <span class="hljs-keyword">from</span> <span class="hljs-string">'/logo.svg'</span>
<span class="hljs-keyword">import</span> { CartWidget } <span class="hljs-keyword">from</span> <span class="hljs-string">'../CartWidget'</span>
<span class="hljs-keyword">import</span> { CartProps } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Products/Products.tsx'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">'./header.module.scss'</span>

export const Header: FunctionComponent = () =&gt; {
  useEffect(() =&gt; {
    window.addEventListener(<span class="hljs-string">"scroll"</span>, () =&gt; shrinkHeader(), false)

    <span class="hljs-keyword">return</span> () =&gt; {
      window.removeEventListener(<span class="hljs-string">"scroll"</span>, () =&gt; shrinkHeader())
    }
  }, [])

  const shrinkHeader = () =&gt; {
    const DISTANCE_FROM_TOP = <span class="hljs-number">140</span>
    const headerElement = document.querySelector(<span class="hljs-string">"header"</span>) <span class="hljs-keyword">as</span> HTMLElement
    const logoElement = document.querySelectorAll(<span class="hljs-string">"img"</span>)[<span class="hljs-number">0</span>] <span class="hljs-keyword">as</span> HTMLElement
    const cartWidgetElement = document.querySelectorAll(<span class="hljs-string">"img"</span>)[<span class="hljs-number">1</span>] <span class="hljs-keyword">as</span> HTMLElement
    const productsCountElement = document.querySelector(<span class="hljs-string">"span"</span>) <span class="hljs-keyword">as</span> HTMLElement
    const scrollY = document.body.scrollTop || document.documentElement.scrollTop

    <span class="hljs-keyword">if</span> (scrollY &gt; DISTANCE_FROM_TOP) {
      headerElement.style.transition = <span class="hljs-string">"height 200ms ease-in"</span>
      headerElement.style.height = <span class="hljs-string">"80px"</span>
      logoElement.style.transition = <span class="hljs-string">"height 200ms ease-in"</span>
      logoElement.style.height = <span class="hljs-string">"4rem"</span>
      cartWidgetElement.style.transition = <span class="hljs-string">"height 200ms ease-in"</span>
      cartWidgetElement.style.height = <span class="hljs-string">"2rem"</span>
      productsCountElement.style.transition = <span class="hljs-string">"font-size 200ms ease-in"</span>
      productsCountElement.style.fontSize = <span class="hljs-string">"1.5em"</span>
    } <span class="hljs-keyword">else</span> {
      headerElement.style.height = <span class="hljs-string">"150px"</span>
      logoElement.style.height = <span class="hljs-string">"6rem"</span>
      cartWidgetElement.style.height = <span class="hljs-string">"3rem"</span>
      productsCountElement.style.fontSize = <span class="hljs-string">"2em"</span>
    }
  }
  const [cart,] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {})

  const productsCount: number = Object.keys(cart || {}).length

  <span class="hljs-keyword">return</span> (
    &lt;header className={classes.header}&gt;
      &lt;div&gt;
        &lt;Link to=<span class="hljs-string">"/"</span>&gt;
          &lt;img src={logo} className={classes.logo} alt=<span class="hljs-string">"Shopping Cart Application"</span> /&gt;
        &lt;/Link&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;CartWidget productsCount={productsCount} /&gt;
      &lt;/div&gt;
    &lt;/header&gt;
  )
}
</code></pre>
<h3 id="heading-how-to-build-the-product-list">🧩 How to Build the Product List</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-11.03.49.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Product List</em></p>
<p>To construct the product list component, I followed the same approach as above. Initially, I established the foundational structure, which involved creating the component code, exporting the component in the index file, and implementing a separate SCSS file to define the component's styles.</p>
<p>As a result, the folder structure after completing these steps should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-11.06.23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Product List Component Folder</em></p>
<p>One interesting aspect of this component is that it handles sending a request to a REST API in order to fetch the product data. This is accomplished through the <code>fetchData</code> method, which is invoked within a <code>useEffect</code> hook.</p>
<p>By specifying an empty dependency array, the code inside the <code>useEffect</code> hook is executed only once when the component is initially loaded. This ensures that redundant requests are avoided, optimizing the performance of our application and reducing bandwidth usage.</p>
<pre><code class="lang-typescript"> useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchData(API_URL)
  }, [])


  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchData</span>(<span class="hljs-params">url: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url)
      <span class="hljs-keyword">if</span> (response.ok) {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json()
        setProducts(data.products)
        setIsLoading(<span class="hljs-literal">false</span>)
      } <span class="hljs-keyword">else</span> {
        setError(<span class="hljs-literal">true</span>)
        setIsLoading(<span class="hljs-literal">false</span>)
      }
    } <span class="hljs-keyword">catch</span> (error) {
      setError(<span class="hljs-literal">true</span>)
      setIsLoading(<span class="hljs-literal">false</span>)
    }
  }
</code></pre>
<p>The rendering aspect of the component is relatively straightforward. Once we have successfully fetched the product data, we can iterate through it using a regular <code>map()</code> function. For each product, we can render its thumbnail image, title, price, and a button for adding it to the cart.</p>
<p>To ensure that each row displays three items when viewed on large viewports, we utilize CSS (SCSS) styles. We'll do this by harnessing the capabilities of <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Learn/CSS/CSS_layout/Flexbox">Flexbox</a>, as demonstrated in the following snippet:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.productPage</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">8rem</span>;

  <span class="hljs-selector-class">.container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-wrap</span>: wrap;
    <span class="hljs-attribute">justify-content</span>: space-between;

    <span class="hljs-selector-class">.product</span> {
      <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">33.33%</span>;
      <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">5rem</span>;
      <span class="hljs-attribute">text-align</span>: center;

      <span class="hljs-selector-tag">h3</span> {
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#007185</span>;
        <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
        <span class="hljs-attribute">line-height</span>: <span class="hljs-number">20px</span>;
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
      }

      <span class="hljs-selector-tag">img</span> {
        <span class="hljs-attribute">height</span>: <span class="hljs-number">6rem</span>;
      }

      <span class="hljs-selector-tag">button</span> {
        <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fbd815</span>;
        <span class="hljs-attribute">width</span>: <span class="hljs-number">13rem</span>;
        <span class="hljs-attribute">padding</span>: .<span class="hljs-number">5rem</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.1em</span>;
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">25px</span>;
        <span class="hljs-attribute">border-color</span>: <span class="hljs-number">#D5D9D9</span>;
        <span class="hljs-attribute">border-style</span>: solid;
        <span class="hljs-attribute">border-width</span>: <span class="hljs-number">1px</span>;

        &amp;<span class="hljs-selector-pseudo">:hover</span><span class="hljs-selector-pseudo">:not</span>(<span class="hljs-selector-attr">[disabled]</span>) {
          <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#eecf1d</span>;
          <span class="hljs-attribute">cursor</span>: pointer;
        }

        &amp;<span class="hljs-selector-pseudo">:disabled</span> {
          <span class="hljs-attribute">opacity</span>: .<span class="hljs-number">5</span>;
          <span class="hljs-attribute">background-color</span>: lightgray;
        }
      }
    }

    <span class="hljs-keyword">@media</span> (max-width: <span class="hljs-number">767px</span>) {
      <span class="hljs-selector-class">.product</span> {
        <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">50%</span>;
      }
    }

    <span class="hljs-keyword">@media</span> (max-width: <span class="hljs-number">400px</span>) {
      <span class="hljs-selector-class">.product</span> {
        <span class="hljs-attribute">flex-basis</span>: <span class="hljs-number">100%</span>;
      }
    }
  }
}

<span class="hljs-selector-class">.error</span> {
  <span class="hljs-attribute">color</span>: red;
  <span class="hljs-attribute">text-align</span>: center;
}
</code></pre>
<p>In addition to these features, the component code also includes functionality for handling the "Add to Cart" button click event, specifically adding the selected item to the local storage. We also implement basic error handling logic to display an error message in case the request to the third-party API fails.</p>
<p>Here is the completed component:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FunctionComponent, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> useLocalStorageState <span class="hljs-keyword">from</span> <span class="hljs-string">'use-local-storage-state'</span>

<span class="hljs-keyword">import</span> { CurrencyFormatter } <span class="hljs-keyword">from</span> <span class="hljs-string">'../CurrencyFormatter'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">'./products.module.scss'</span>
<span class="hljs-keyword">import</span> { Loader } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Loader'</span>

<span class="hljs-keyword">const</span> API_URL = <span class="hljs-string">'https://dummyjson.com/products'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> Product = {
  id: <span class="hljs-built_in">number</span>
  title: <span class="hljs-built_in">string</span>
  price: <span class="hljs-built_in">number</span>
  thumbnail: <span class="hljs-built_in">string</span>
  image: <span class="hljs-built_in">string</span>
  quantity: <span class="hljs-built_in">number</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> CartProps {
  [productId: <span class="hljs-built_in">string</span>]: Product
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Products: FunctionComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = useState(<span class="hljs-literal">true</span>)
  <span class="hljs-keyword">const</span> [products, setProducts] = useState&lt;Product[]&gt;([])
  <span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">false</span>)
  <span class="hljs-keyword">const</span> [cart, setCart] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {})


  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchData(API_URL)
  }, [])


  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchData</span>(<span class="hljs-params">url: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url)
      <span class="hljs-keyword">if</span> (response.ok) {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json()
        setProducts(data.products)
        setIsLoading(<span class="hljs-literal">false</span>)
      } <span class="hljs-keyword">else</span> {
        setError(<span class="hljs-literal">true</span>)
        setIsLoading(<span class="hljs-literal">false</span>)
      }
    } <span class="hljs-keyword">catch</span> (error) {
      setError(<span class="hljs-literal">true</span>)
      setIsLoading(<span class="hljs-literal">false</span>)
    }
  }

  <span class="hljs-keyword">const</span> addToCart = (product: Product):<span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    product.quantity = <span class="hljs-number">1</span>

    setCart(<span class="hljs-function">(<span class="hljs-params">prevCart</span>) =&gt;</span> ({
      ...prevCart,
      [product.id]: product,
    }))
  }

  <span class="hljs-keyword">const</span> isInCart = (productId: <span class="hljs-built_in">number</span>):<span class="hljs-function"><span class="hljs-params">boolean</span> =&gt;</span> <span class="hljs-built_in">Object</span>.keys(cart || {}).includes(productId.toString())

  <span class="hljs-keyword">if</span> (error) {
    <span class="hljs-keyword">return</span> &lt;h3 className={classes.error}&gt;An error occurred when fetching data. Please check the API and <span class="hljs-keyword">try</span> again.&lt;/h3&gt;
  }

  <span class="hljs-keyword">if</span> (isLoading) {
    <span class="hljs-keyword">return</span> &lt;Loader /&gt;
  }


  <span class="hljs-keyword">return</span> (
    &lt;section className={classes.productPage}&gt;
      &lt;h1&gt;Products&lt;/h1&gt;

      &lt;div className={classes.container}&gt;
        {products.map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> (
          &lt;div className={classes.product} key={product.id}&gt;
            &lt;img src={product.thumbnail} alt={product.title} /&gt;
            &lt;h3&gt;{product.title}&lt;/h3&gt;
            &lt;p&gt;Price: &lt;CurrencyFormatter amount={product.price} /&gt;&lt;/p&gt;
            &lt;button disabled={isInCart(product.id)} onClick={<span class="hljs-function">() =&gt;</span> addToCart(product)}&gt;Add to Cart&lt;/button&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
    &lt;/section&gt;
  )
}
</code></pre>
<h3 id="heading-how-to-build-the-cart">🧩 How to Build the Cart</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-11.31.02.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Cart Component</em></p>
<p>This component bears some resemblance to the Product List component in that it also lists products, but in a different manner with only one item per row.</p>
<p>It also introduces additional functionality by incorporating another component for updating the quantity of selected products. And it calculates the total price of all products in the cart.</p>
<pre><code class="lang-typescript"> &lt;section className={classes.cart}&gt;
      &lt;h1&gt;Cart&lt;/h1&gt;

      &lt;div className={classes.container}&gt;
        {getProducts().map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> (
          &lt;div className={classes.product} key={product.id}&gt;
            &lt;img src={product.thumbnail} alt={product.title} /&gt;
            &lt;h3&gt;{product.title}&lt;/h3&gt;
            &lt;Quantifier
              removeProductCallback={<span class="hljs-function">() =&gt;</span> handleRemoveProduct(product.id)}
              productId={product.id}
              handleUpdateQuantity={handleUpdateQuantity} /&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
      &lt;TotalPrice amount={totalPrice} /&gt;
    &lt;/section&gt;
</code></pre>
<p>The main distinction here is that instead of fetching the product data from an API, we retrieve it from the local storage. This is where we store the data for each selected product from the product list component.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> [cart, setCart] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {}) <span class="hljs-comment">// reading the local storage value via the hook here</span>

....
....

  <span class="hljs-keyword">const</span> getProducts = <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Object</span>.values(cart || {}) <span class="hljs-comment">// method for getting all products data as an array data structure, that will allow us easier iteration later</span>

....
....
</code></pre>
<p>In this case, we use the <code>useEffect</code> hook once again, but this time to reset the scroll position of the window whenever the user visits the page. This ensures that all relevant data is consistently visible to the user, regardless of how far they have scrolled on the product list page.</p>
<pre><code class="lang-typescript">
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.scrollTo(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
  }, [location])
</code></pre>
<p>Indeed, you can see that the methods for decreasing or increasing the quantity of a product are passed to the component as callbacks through its props. This approach is useful as it helps maintain a relatively clean component by elevating the responsibility of state management to a higher level.</p>
<p>By lifting the logic for managing the state outside of the component, it allows for better separation of concerns and promotes reusability.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> handleRemoveProduct = (productId: <span class="hljs-built_in">number</span>): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    setCart(<span class="hljs-function">(<span class="hljs-params">prevCart</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> updatedCart = { ...prevCart }
      <span class="hljs-keyword">delete</span> updatedCart[productId]
      <span class="hljs-keyword">return</span> updatedCart
    })
  }

  <span class="hljs-keyword">const</span> handleUpdateQuantity = <span class="hljs-function">(<span class="hljs-params">productId: <span class="hljs-built_in">number</span>, operation: Operation</span>) =&gt;</span> {
    setCart(<span class="hljs-function">(<span class="hljs-params">prevCart</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> updatedCart = { ...prevCart }
      <span class="hljs-keyword">if</span> (updatedCart[productId]) {
        <span class="hljs-keyword">if</span> (operation === <span class="hljs-string">'increase'</span>) {
          updatedCart[productId] = { ...updatedCart[productId], quantity: updatedCart[productId].quantity + <span class="hljs-number">1</span> }
        } <span class="hljs-keyword">else</span> {
          updatedCart[productId] = { ...updatedCart[productId], quantity: updatedCart[productId].quantity - <span class="hljs-number">1</span> }
        }
      }
      <span class="hljs-keyword">return</span> updatedCart
    })
  }
</code></pre>
<p>We can style the component using Flexbox to get our desired layout:</p>
<pre><code class="lang-scss"><span class="hljs-selector-class">.cart</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1rem</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">8rem</span>;

  <span class="hljs-selector-class">.container</span> {
    <span class="hljs-attribute">display</span>: flex;
    <span class="hljs-attribute">flex-direction</span>: column;

    <span class="hljs-selector-class">.product</span> {
      <span class="hljs-attribute">display</span>: flex;
      <span class="hljs-attribute">border-top</span>: <span class="hljs-number">1px</span> dotted;
      <span class="hljs-attribute">border-left</span>: <span class="hljs-number">1px</span> dotted;
      <span class="hljs-attribute">border-right</span>: <span class="hljs-number">1px</span> dotted;
      <span class="hljs-attribute">padding</span>: .<span class="hljs-number">3rem</span> .<span class="hljs-number">5rem</span>;
      <span class="hljs-attribute">align-items</span>: center;

      <span class="hljs-selector-tag">h3</span> {
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#007185</span>;
        <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">700</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1em</span>;
        <span class="hljs-attribute">line-height</span>: <span class="hljs-number">20px</span>;
        <span class="hljs-attribute">margin</span>: .<span class="hljs-number">3rem</span>;
        <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
      }

      <span class="hljs-selector-tag">img</span> {
        <span class="hljs-attribute">max-width</span>: <span class="hljs-number">3rem</span>;
        <span class="hljs-attribute">height</span>: auto;
        <span class="hljs-attribute">margin</span>: .<span class="hljs-number">875rem</span>;
      }
    }
  }
}
</code></pre>
<p>Here is the final version of the component's code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FunctionComponent, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> useLocalStorageState <span class="hljs-keyword">from</span> <span class="hljs-string">'use-local-storage-state'</span>

<span class="hljs-keyword">import</span> { Quantifier } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Quantifier'</span>
<span class="hljs-keyword">import</span> { CartProps } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Products/Products.tsx'</span>
<span class="hljs-keyword">import</span> { TotalPrice } <span class="hljs-keyword">from</span> <span class="hljs-string">'../TotalPrice'</span>
<span class="hljs-keyword">import</span> { Operation } <span class="hljs-keyword">from</span> <span class="hljs-string">'../Quantifier/Quantifier.tsx'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">'./cart.module.scss'</span>
<span class="hljs-keyword">import</span> { useLocation } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Cart: FunctionComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [cart, setCart] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {})
  <span class="hljs-keyword">const</span> location = useLocation()

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">window</span>.scrollTo(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>)
  }, [location])

  <span class="hljs-keyword">const</span> handleRemoveProduct = (productId: <span class="hljs-built_in">number</span>): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    setCart(<span class="hljs-function">(<span class="hljs-params">prevCart</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> updatedCart = { ...prevCart }
      <span class="hljs-keyword">delete</span> updatedCart[productId]
      <span class="hljs-keyword">return</span> updatedCart
    })
  }

  <span class="hljs-keyword">const</span> handleUpdateQuantity = <span class="hljs-function">(<span class="hljs-params">productId: <span class="hljs-built_in">number</span>, operation: Operation</span>) =&gt;</span> {
    setCart(<span class="hljs-function">(<span class="hljs-params">prevCart</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> updatedCart = { ...prevCart }
      <span class="hljs-keyword">if</span> (updatedCart[productId]) {
        <span class="hljs-keyword">if</span> (operation === <span class="hljs-string">'increase'</span>) {
          updatedCart[productId] = { ...updatedCart[productId], quantity: updatedCart[productId].quantity + <span class="hljs-number">1</span> }
        } <span class="hljs-keyword">else</span> {
          updatedCart[productId] = { ...updatedCart[productId], quantity: updatedCart[productId].quantity - <span class="hljs-number">1</span> }
        }
      }
      <span class="hljs-keyword">return</span> updatedCart
    })
  }


  <span class="hljs-keyword">const</span> getProducts = <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">Object</span>.values(cart || {})

  <span class="hljs-keyword">const</span> totalPrice = getProducts().reduce(<span class="hljs-function">(<span class="hljs-params">accumulator, product</span>) =&gt;</span> accumulator + (product.price * product.quantity), <span class="hljs-number">0</span>)

  <span class="hljs-keyword">return</span> (
    &lt;section className={classes.cart}&gt;
      &lt;h1&gt;Cart&lt;/h1&gt;

      &lt;div className={classes.container}&gt;
        {getProducts().map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> (
          &lt;div className={classes.product} key={product.id}&gt;
            &lt;img src={product.thumbnail} alt={product.title} /&gt;
            &lt;h3&gt;{product.title}&lt;/h3&gt;
            &lt;Quantifier
              removeProductCallback={<span class="hljs-function">() =&gt;</span> handleRemoveProduct(product.id)}
              productId={product.id}
              handleUpdateQuantity={handleUpdateQuantity} /&gt;
          &lt;/div&gt;
        ))}
      &lt;/div&gt;
      &lt;TotalPrice amount={totalPrice} /&gt;
    &lt;/section&gt;
  )
}
</code></pre>
<h3 id="heading-how-to-build-the-footer">🧩 How to Build the Footer</h3>
<p>To enhance the overall appearance of the application, I have included a footer component. Here is an example of how it looks:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/footer.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The implementation of the footer component is relatively straightforward. It consists of two links to social platforms and a copyright text.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { FunctionComponent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> classes <span class="hljs-keyword">from</span> <span class="hljs-string">"./footer.module.scss"</span>
<span class="hljs-keyword">import</span> packageJson <span class="hljs-keyword">from</span> <span class="hljs-string">'../../../package.json'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Footer: FunctionComponent = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> currentYear = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getFullYear()

  <span class="hljs-keyword">return</span> (
    &lt;footer className={classes.footer} data-cy=<span class="hljs-string">"footer"</span>&gt;
      &lt;ul&gt;
        &lt;li className={classes.footerLinks}&gt;
          &lt;a
            href=<span class="hljs-string">"https://twitter.com/mihailgaberov"</span>
            target=<span class="hljs-string">"_blank"</span>
            rel=<span class="hljs-string">"noopener noreferrer"</span>
            data-cy=<span class="hljs-string">"twitterLink"</span>
          &gt;
            twitter
          &lt;/a&gt;{<span class="hljs-string">" "</span>}
          &amp;bull;{<span class="hljs-string">" "</span>}
          &lt;a
            href=<span class="hljs-string">"https://github.com/mihailgaberov"</span>
            target=<span class="hljs-string">"_blank"</span>
            rel=<span class="hljs-string">"noopener noreferrer"</span>
            data-cy=<span class="hljs-string">"githubLink"</span>
          &gt;
            github
          &lt;/a&gt;
        &lt;/li&gt;
        &lt;li className={classes.footerCopyrights}&gt;
          © {packageJson.author} {currentYear}. All rights reserved.
        &lt;/li&gt;
        &lt;li&gt;
          &lt;div className={classes.version}&gt;v.{packageJson.version}&lt;/div&gt;
        &lt;/li&gt;
      &lt;/ul&gt;
    &lt;/footer&gt;
  )
}
</code></pre>
<p>Again, in the <a target="_blank" href="https://github.com/mihailgaberov/shopping-cart-app/tree/main/src/components/Footer">component folder</a> you will find the other files that are necessary for applying the styles and the tests for that component.</p>
<h2 id="heading-testing-the-app">🧪 Testing the App</h2>
<p>I am sure you've already noticed that in all components folders, except the Loader one, we have .test.tsx files. These are the files containing the component tests that we run on the front end.</p>
<p>In order to do a test run, open your CLI and run the following command from the root directory of your project:</p>
<pre><code class="lang-bash">yarn <span class="hljs-built_in">test</span>
</code></pre>
<p>This should run all tests and give you the following output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-2023-06-22-at-12.05.23.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Application Tests</em></p>
<p>Now that we've built all the necessary components, let's delve into a more detailed discussion about local storage and how we can leverage it to manage the application state more effectively.</p>
<h2 id="heading-what-is-localstorage">🧠 What is LocalStorage?</h2>
<p>Before we dive into the details, let's begin with a brief explanation of what local storage is. This will ensure that everyone, including those who are unfamiliar with it, can easily understand how we use this in the app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-226.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>App LocalStorage</em></p>
<p>LocalStorage is a feature provided by web browsers that allows web applications to store data locally on the user's device. It provides a simple key-value storage mechanism, similar to a dictionary or associative array.</p>
<p>Unlike session storage, which is temporary and gets cleared when the browser session ends, local storage persists even after the browser is closed and reopened. The data stored in local storage remains available until explicitly removed by the application or cleared by the user.</p>
<p>Local storage is primarily used for client-side data storage, enabling web applications to save user preferences, session data, or any other relevant information. This makes it a useful tool for creating personalized experiences and maintaining state across multiple visits to a website.</p>
<p>With that being said, you may have already guessed that local storage can be utilized to store the data of items added to the cart by the user. By doing so, when we navigate to the cart page, our application will be aware of which items should be displayed there.</p>
<p>To accomplish this, I utilized a React hook called <a target="_blank" href="https://www.npmjs.com/package/use-local-storage-state">use-local-storage-state</a>.</p>
<p>In the Product List component, we added products to the LocalStorage whenever the user clicked on the <code>Add to Cart</code> button for each item. This ensured that the selected products were stored persistently.</p>
<p>Then, when the user navigates to the cart page, we retrieve the stored data from the LocalStorage and render the products accordingly. By doing so, the user can easily view and interact with the products they previously added to their cart.</p>
<p>Here is a snippet from the code itself.</p>
<p>We add the items to the LocalStorage like this:</p>
<pre><code class="lang-python">const [cart, setCart] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {})
...
...
...

const addToCart = (product: Product):void =&gt; {
    product.quantity = <span class="hljs-number">1</span>

    setCart((prevCart) =&gt; ({
      ...prevCart,
      [product.id]: product,
    }))
  }
</code></pre>
<p>And then read the data like this:</p>
<pre><code class="lang-python">const [cart, setCart] = useLocalStorageState&lt;CartProps&gt;(<span class="hljs-string">'cart'</span>, {})
...
...
...

const getProducts = () =&gt; Object.values(cart || {})
</code></pre>
<p>And then render them like this:</p>
<pre><code class="lang-jsx">&lt;div className={classes.container}&gt;
        {getProducts().map(<span class="hljs-function"><span class="hljs-params">product</span> =&gt;</span> (
          <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{classes.product}</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{product.id}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{product.thumbnail}</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">{product.title}</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{product.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Quantifier</span>
              <span class="hljs-attr">removeProductCallback</span>=<span class="hljs-string">{()</span> =&gt;</span> handleRemoveProduct(product.id)}
              productId={product.id}
              handleUpdateQuantity={handleUpdateQuantity} /&gt;
          <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
        ))}
      &lt;/div&gt;
</code></pre>
<h2 id="heading-how-to-automate-unit-testing-with-github-actions">How to Automate Unit Testing with GitHub Actions</h2>
<p>GitHub Actions is a powerful automation tool provided by GitHub. It allows you to define and execute workflows directly within your GitHub repository.</p>
<p>With GitHub Actions, you can automate various tasks and processes, such as building, testing, and deploying your code, as well as performing code analysis, generating documentation, and more.</p>
<p>GitHub Actions workflows are defined using YAML syntax. A workflow consists of one or more jobs, and each job consists of a series of steps to be executed. Steps can include actions (reusable units of code), shell commands, or scripts.</p>
<pre><code class="lang-python">name: Shopping Cart CI

on:
  push:
    branches: [ <span class="hljs-string">"main"</span> ]
  pull_request:
    branches: [ <span class="hljs-string">"main"</span> ]

jobs:
  build:

    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [<span class="hljs-number">19.</span>x]
        <span class="hljs-comment"># See supported Node.js release schedule at https://nodejs.org/en/about/releases/</span>

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        <span class="hljs-keyword">with</span>:
          node-version: <span class="hljs-string">'19'</span>
          cache: <span class="hljs-string">'yarn'</span>
      - run: yarn install
      - run: yarn test
</code></pre>
<p>In our case, I utilized GitHub Actions to create a workflow that automatically runs the unit test suite whenever a new change is committed to the main branch. This ensures that no production build is triggered in the event of a test failure.</p>
<p>Implementing this workflow is straightforward and highly beneficial. It provides the reassurance that any issues inadvertently introduced into the main branch will be caught before reaching production. This level of reliability and peace of mind is easily achieved by leveraging the capabilities of GitHub Actions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-227.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Shopping Cart app - GitHub Actions</em></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The topic covered in this tutorial may not be novel or unfamiliar, but its scope of application is remarkably extensive.</p>
<p>Nearly every online application that involves trading or purchasing goods incorporates a shopping cart or basket functionality in some form. Because of this, if you're a software engineer working in this domain, you'll need to possess a solid understanding of how to implement this functionality and its underlying principles.</p>
<p>By acquiring a high-level knowledge of these concepts and understanding how things operate in general, we can effectively contribute to the development of robust and efficient e-commerce systems.</p>
<p>📘 You now understand the purpose and functionality of a shopping cart in an online application.</p>
<p>📘 You have learned how to implement a product list page by fetching data from a third-party REST API.</p>
<p>📘 You have learned how to create a cart component that retrieves and renders data from the browser's local storage.</p>
<p>📘 You have discovered the benefits of GitHub Actions and how they can provide confidence when promoting your code to production.</p>
<p>📘 And most importantly, you have embraced the mindset of continuous learning, understanding that there is always more to explore and discover in the vast world of software engineering.</p>
<p>By combining these newfound skills with a commitment to ongoing learning, you are well-equipped to create efficient and robust e-commerce systems while continuously improving your abilities in this dynamic field. Remember, the journey of knowledge acquisition never ends! 🎓</p>
<p>I hope you found this tutorial enjoyable and engaging. Most importantly, I hope you were able to actively follow along and even experiment with the code. Feel free to make your own modifications and observe the resulting changes in the end product. Hands-on exploration and experimentation are fantastic ways to solidify your understanding and expand your skills.</p>
<p>Remember, the best way to enhance your skills and understanding is through practice and experimentation. So feel free to dive into the code, make it your own, and observe the exciting results you can achieve. Happy coding and exploring!</p>
<p>Thanks for reading! 🙏</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Component-Based Architecture in Medusa – How to Build Robust User Interfaces ]]>
                </title>
                <description>
                    <![CDATA[ Medusa is a modern JavaScript framework that makes it easy to build robust user interfaces.  It is built around a component-based architecture, which is a design pattern that breaks down a UI into smaller, reusable components. This makes it easier to... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/exploring-component-based-architecture-in-medusa-building-robust-user-interfaces/</link>
                <guid isPermaLink="false">66ba610abab56b945824004b</guid>
                
                    <category>
                        <![CDATA[ components ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ User Interface ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ valentine Gatwiri ]]>
                </dc:creator>
                <pubDate>Thu, 18 May 2023 18:22:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-18-21-05-01.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Medusa is a modern JavaScript framework that makes it easy to build robust user interfaces. </p>
<p>It is built around a component-based architecture, which is a design pattern that breaks down a UI into smaller, reusable components. This makes it easier to maintain and update the UI, as well as to create new features.</p>
<p>Medusa is like a LEGO set for building user interfaces. Just as LEGO bricks can be assembled and combined to create various structures, Medusa allows you to build robust UIs by assembling and combining reusable components.</p>
<p>In this article, we'll explore the component-based architecture in Medusa. We will start by discussing the benefits of using this architecture, and then we will provide some code examples to show how you can use it to build user interfaces.</p>
<h2 id="heading-benefits-of-component-based-architecture">Benefits of Component-Based Architecture</h2>
<p>There are many benefits to using the component-based architecture in Medusa. Some of the most important benefits include:</p>
<ul>
<li><strong>Reusability:</strong> Components can be reused in multiple places throughout the UI, which can save time and effort.</li>
<li><strong>Maintainability:</strong> Components are isolated from each other, which makes it easier to maintain and update the UI.</li>
<li><strong>Scalability:</strong> Components can be easily added to or removed from the UI, which makes it easy to scale the UI as needed.</li>
<li><strong>Testability:</strong> Components can be easily tested in isolation, which makes it easier to ensure that they are working properly.</li>
</ul>
<h2 id="heading-understanding-the-medusa-framework">Understanding the Medusa Framework</h2>
<p>Medusa is a comprehensive framework and toolset for building e-commerce applications. It provides the necessary components and infrastructure to develop, deploy, and manage online stores.</p>
<h3 id="heading-the-medusa-backend">The Medusa Backend</h3>
<p>The backend of Medusa focuses on server-side operations and manages the core functionalities of the e-commerce application. It includes components such as:</p>
<ul>
<li><strong>Server</strong>: Medusa provides a server that handles the logic and data management of the application. It facilitates communication between the frontend and various services, such as databases and payment gateways.</li>
<li><strong>APIs</strong>: The backend exposes a set of APIs (Application Programming Interfaces) that allow the frontend and other applications to interact with the server. These APIs define the endpoints and data structures for performing actions like fetching products, processing orders, and managing user accounts.</li>
<li><strong>Business Logic</strong>: The backend of Medusa implements the business rules and workflows specific to the e-commerce domain. It handles tasks such as inventory management, order processing, payment handling, and applying discounts or promotions.</li>
<li><strong>Database Integrations</strong>: The backend interacts with a database to store and retrieve data related to products, orders, customers, and other entities. Medusa supports various databases, including PostgreSQL and MySQL, and provides an abstraction layer to simplify database operations.</li>
</ul>
<p>The Medusa backend contains the following directories and files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-44-27.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa backend</em></p>
<h3 id="heading-the-medusa-frontend">The Medusa Frontend</h3>
<p>The frontend of Medusa is responsible for the user-facing part of the e-commerce application. It focuses on providing an interactive and engaging interface for customers to browse products, add items to their cart, and complete the purchase. The frontend includes:</p>
<ul>
<li><strong>The Storefront</strong>: The storefront component represents the application where users interact with the e-commerce store. It includes product listings, search functionality, product details, shopping cart, and checkout flow. The frontend is responsible for rendering these components and handling user interactions.</li>
<li><strong>The User Interface (UI)</strong>: The frontend defines the visual layout, design, and user experience of the e-commerce application. It utilizes HTML, CSS, and JavaScript to create responsive and user-friendly interfaces. Medusa provides UI components and templates that can be customized and extended to match the branding and requirements of the online store.</li>
<li><strong>Integration with Backend APIs</strong>: The frontend interacts with the backend APIs provided by the Medusa server. It communicates with the server to fetch product data, submit orders, update user information, and perform other operations. The frontend consumes the data returned by the backend APIs and uses it to render dynamic content on the user interface.</li>
</ul>
<p>The Medusa frontend contains the following directories and files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-45-21.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa storefront</em></p>
<p>Medusa is written in TypeScript and uses React as its frontend framework. Medusa is divided into three main parts: the <code>server</code>, the <code>storefront</code>, and the <code>admin</code> panel. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-07-33-22.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Medusa is a complete e-commerce solution that includes various tools and components:</p>
<h3 id="heading-server">Server</h3>
<p>Medusa provides a server that handles the core functionalities of an e-commerce application, such as managing product catalogs, handling orders and payments, and managing user accounts.</p>
<p>Here is how to access the backend:</p>
<pre><code>cd my-medusa-store/backend
yarn start
</code></pre><p>To get started with Medusa, you can check out <a target="_blank" href="https://gatwirival.hashnode.dev/what-is-medusajs-and-why-use-it">this article</a>.</p>
<p>When we run <code>yarn start</code> in the backend, by default, <a target="_blank">localhost:9000</a> is used.</p>
<p>Navigate to <a target="_blank">localhost:9000/store/products</a> in your browser to view a JSON collection of items. Because the seeder only inserts one product, it will only contain one item in the JSON object.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-10-50-50-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-storefront">Storefront</h3>
<p>Medusa offers a storefront component, which is the user-facing part of the application. It includes the website or interface where customers browse products, add items to their cart, and proceed with the purchasing process.</p>
<pre><code>cd my-medusa-store/storefront
yarn develop # <span class="hljs-keyword">for</span> Gatsby storefront
yarn dev # <span class="hljs-keyword">for</span> Next.js storefront
</code></pre><p>Since I'm using Next.js in my store front, I'll be using <code>yarn dev</code>.When we run <code>yarn dev</code> in the storefront, by default, <a target="_blank">localhost:8000</a> is used.</p>
<p>Here's what the storefront should look like:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-11-58-02.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-admin-panel">Admin Panel</h3>
<p>Medusa provides an admin panel that enables the store owners or administrators to manage the e-commerce operations. It allows them to add and update products, process orders, handle inventory, configure shipping options, and perform other administrative tasks.</p>
<h2 id="heading-how-to-set-up-the-medusa-admin-dashboard">How to Set Up the Medusa Admin Dashboard</h2>
<p>To set up the Medusa admin dashboard, follow these steps:</p>
<p>To install the package, you'll have to navigate to the directory of your Medusa backend and run the following command to install the admin dashboard:</p>
<pre><code>yarn add @medusajs/admin
</code></pre><p>If you use npm, you can use the command below:</p>
<pre><code>npm install @medusajs/admin
</code></pre><h3 id="heading-how-to-enable-the-admin-plugin-in-the-medusa-configuration-file">How to Enable the Admin Plugin in the Medusa Configuration File</h3>
<p>To enable the admin plugin, open the <code>medusa-config.js</code> file in your project and locate the <code>plugins</code> array, then add the following lines:</p>
<pre><code><span class="hljs-keyword">const</span> plugins = [
  <span class="hljs-comment">// ...</span>
  {
    <span class="hljs-attr">resolve</span>: <span class="hljs-string">"@medusajs/admin"</span>,
    <span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('@medusajs/admin').PluginOptions}</span> </span>*/</span>
    <span class="hljs-attr">options</span>: {
      <span class="hljs-comment">// ...</span>
    },
  },
]
</code></pre><p>The admin plugin accepts various options for customization:</p>
<ul>
<li><code>serve</code> (default: <code>true</code>): A boolean indicating whether to serve the admin dashboard when the Medusa backend starts. Set it to <code>false</code> if you prefer to serve the admin dashboard separately using the <code>yarn dev</code> command.</li>
<li><code>path</code> (default: <code>"app"</code>): A string indicating the path on which the admin server should run. It should not be prefixed or suffixed with a slash ("/"), and it cannot be one of the reserved paths: <code>"admin"</code> and <code>"store"</code>.</li>
<li><code>outDir</code>: Optional path specifying where to output the admin build files.</li>
<li><code>autoRebuild</code> (default: <code>false</code>): A boolean indicating whether the admin UI should be automatically rebuilt if there are any changes or if a missing build is detected when the backend starts. If not set, you must manually build the admin dashboard.</li>
</ul>
<p>You can enable <code>autoRebuild</code>  by setting it to <code>true</code> in the plugin options to build the admin UI.</p>
<p>Run the admin dashboard using the <code>yarn dev</code> command. </p>
<p>This command starts both the Medusa Backend and the admin dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/Screenshot-from-2023-05-17-13-23-52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>By default, the admin dashboard will be accessible at <a target="_blank">localhost:9000/app</a>. If you have set a custom <code>path</code> option, the admin will be available at <code>localhost:9000/&lt;PATH&gt;</code>, with <code>&lt;PATH&gt;</code> being the value of the <code>path</code> option. You can learn more <a target="_blank" href="https://docs.medusajs.com/admin/quickstart">here</a>.</p>
<p>Make sure to adjust the configurations and paths according to your specific setup.</p>
<p>By following these steps, you should be able to successfully set up and access the Medusa admin dashboard.</p>
<h2 id="heading-understanding-medusas-component-based-architecture">Understanding Medusa's Component-Based Architecture</h2>
<p>Medusa uses a component-based architecture to make it easy to extend and customize. Each component is self-contained and can be reused in other parts of the app. This makes it easy to add new features or change the look and feel of the app without affecting other parts of the codebase.</p>
<p>Here is an example of a component from the Medusa starter app located in the <code>storefront/src/modules/components/empty-cart-message</code> folder:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> UnderlineLink <span class="hljs-keyword">from</span> <span class="hljs-string">"@modules/common/components/underline-link"</span>

<span class="hljs-keyword">const</span> EmptyCartMessage = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-amber-100 px-8 py-24 flex flex-col justify-center items-center text-center"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl-semi"</span>&gt;</span>Your shopping bag is empty<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-base-regular mt-4 mb-6 max-w-[32rem]"</span>&gt;</span>
        You don<span class="hljs-symbol">&amp;apos;</span>t have anything in your bag. Let<span class="hljs-symbol">&amp;apos;</span>s change that, use
        the link below to start browsing our products.
      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">UnderlineLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/store"</span>&gt;</span>Explore products<span class="hljs-tag">&lt;/<span class="hljs-name">UnderlineLink</span>&gt;</span>
      <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>
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> EmptyCartMessage
</code></pre>
<p>This component renders a message when the shopping cart is empty. The message includes a link to the store so that users can start browsing products.</p>
<p>Here is a breakdown of the code:</p>
<p>The <code>import UnderlineLink from "@modules/common/components/underline-link"</code> statement imports the <code>UnderlineLink</code> component from the <code>@modules/common/components</code> module. This component will be used to render the link to the store.</p>
<p>The <code>const EmptyCartMessage = () =&gt; {</code> statement defines the <code>EmptyCartMessage</code> component. This component takes no props and returns a <code>div</code> element with the following attributes: <code>className="bg-amber-100 px-8 py-24 flex flex-col justify-center items-center text-center"</code>.</p>
<p>The <code>return</code> statement returns the <code>div</code> element with the following content:</p>
<ul>
<li>An <code>h1</code> element with the text "Your shopping bag is empty".</li>
<li>A <code>p</code> element with the text "You don't have anything in your bag. Let's change that, use the link below to start browsing our products.".</li>
<li>A <code>div</code> element with the <code>UnderlineLink</code> component that links to the store.</li>
</ul>
<h2 id="heading-medusa-vs-shopify">Medusa vs Shopify</h2>
<p>Medusa is an open-source alternative to Shopify. Here are some comparisons between the two products:</p>
<h3 id="heading-customization-and-flexibility">Customization and Flexibility</h3>
<p>Medusa offers a high level of customization and flexibility. It allows developers to create highly tailored e-commerce applications that align with specific business requirements. With Medusa, developers have full control over the application's codebase and can customize various aspects, including the frontend and backend functionalities.</p>
<p>Shopify provides a less granular level of customization compared to Medusa. It operates as a hosted platform, meaning that the core infrastructure and backend are managed by Shopify. While Shopify offers a theme customization system and an app ecosystem to extend functionality, it may have limitations when it comes to implementing highly custom features.</p>
<h3 id="heading-hosting-and-infrastructure">Hosting and Infrastructure</h3>
<p>Medusa is a self-hosted framework, which means that developers need to set up their own hosting environment and manage the infrastructure themselves. This provides more control and scalability, but also requires additional technical expertise and resources.</p>
<p>Shopify is a fully hosted platform, meaning that hosting and infrastructure management are handled by Shopify. This eliminates the need for developers to set up and maintain hosting environments, making it more accessible to non-technical users.</p>
<h3 id="heading-pricing">Pricing</h3>
<p>As an open-source framework, Medusa is free to use, and there are no licensing fees. However, since it requires self-hosting, there may be associated costs for hosting services and infrastructure management.</p>
<p>Shopify operates on a subscription-based pricing model. It offers different pricing plans with varying features and transaction fees. The subscription fees cover hosting, security, and support services.</p>
<h3 id="heading-ecosystem-and-app-integrations">Ecosystem and App Integrations</h3>
<p>Medusa has a growing ecosystem of extensions and integrations, allowing developers to leverage third-party tools and services to enhance their e-commerce applications. While the ecosystem may not be as extensive as Shopify's, Medusa's open-source nature enables developers to create custom integrations as needed.</p>
<p>Shopify has a vast ecosystem of apps and integrations available through the Shopify App Store. These apps can add functionality, extend features, and integrate with various services such as marketing, analytics, and shipping providers. The extensive app ecosystem of Shopify is one of its major strengths.</p>
<h3 id="heading-target-audience">Target Audience</h3>
<p>Medusa is suited for businesses that require advanced customization and flexibility in their e-commerce applications. It is a suitable choice for developers and businesses with technical expertise looking for full control over their e-commerce infrastructure.</p>
<p>Shopify is designed to cater to a wide range of users, including small to medium-sized businesses and individuals, who want a user-friendly and hassle-free e-commerce solution. It is suitable for users with limited technical knowledge or resources who prioritize ease of use and convenience.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>The component-based architecture is a powerful design pattern that can be used to build robust user interfaces in Medusa. By using components, you can create reusable, maintainable, scalable, and testable UIs.</p>
<p>Medusa is a comprehensive e-commerce framework that includes a server, a storefront component, and an admin panel. It provides developers with the necessary tools to build customizable and scalable e-commerce applications, tailored to specific business needs.</p>
<p>I hope this article has been helpful. Please let me know if you have any questions. </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own E-Commerce Site with Medusa ]]>
                </title>
                <description>
                    <![CDATA[ In today's digital age, having an online presence is crucial for businesses of all sizes.  Whether you're an established retailer or an aspiring entrepreneur, an ecommerce site can provide you with a platform to reach a global audience and sell your ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-own-e-commerce-site-with-medusa/</link>
                <guid isPermaLink="false">66ba0e86f602a81788fe2189</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Mon, 27 Feb 2023 15:36:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/02/medusa.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's digital age, having an online presence is crucial for businesses of all sizes. </p>
<p>Whether you're an established retailer or an aspiring entrepreneur, an ecommerce site can provide you with a platform to reach a global audience and sell your products or services around the clock.</p>
<p>Building an ecommerce site may seem like a daunting task, but with the right tools and guidance, anyone can create a professional and functional online store. </p>
<p>In this tutorial, you'll learn how to build your own e-commerce site with Medusa.</p>
<h2 id="heading-what-is-medusa">What is Medusa?</h2>
<p><a target="_blank" href="https://medusajs.com">Medusa</a> is an <em>open source</em>, <em>composable</em> commerce platform that's perfect for developers who want to create a customized ecommerce solution. With its flexible architecture and powerful features, Medusa offers a seamless and straightforward way to build a robust and scalable ecommerce site.</p>
<p>Medusa offers a variety of features, including order management with automated swaps, returns, and claims. It also enables customer management and assignment to customer groups, along with product customization and collection sorting. </p>
<p>With the ability to manage multiple regions and currencies, users can integrate various plugins and third-party services, create advanced pricing and discount rules, configure taxes, and set up multiple sales channels. </p>
<p>Additionally, Medusa offers bulk import and export strategies, and complete customization capabilities to create custom endpoints, services, subscribers, batch job strategies, and more.</p>
<p>In just over a year since its launch, this platform has quickly risen to become the <a target="_blank" href="https://medusajs.com/blog/nodejs-ecommerce-backend/">#1 Node.js ecommerce solution</a>, garnering over 17K+ stars on <a target="_blank" href="https://github.com/medusajs/medusa">GitHub</a> due to its immense popularity.</p>
<h2 id="heading-architecture-of-medusa">Architecture of Medusa</h2>
<p>With a composable architecture, Medusa's frontend and backend components are loosely coupled. The platform consists of three different components: the <strong>headless backend</strong>, the <strong>admin dashboard</strong>, and the <strong>storefront</strong>.</p>
<p>You can choose to use the complete Medusa platform or just the parts that you require for your store. </p>
<p>Additionally, with the backend decoupled from the frontend, developers can focus on their respective areas of expertise. Backend developers can concentrate on the Medusa server, while frontend developers can focus on either the storefront or admin.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/medusa-arch.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa Architecture</em></p>
<p>In the upcoming sections, you will see how Medusa delivers most ecommerce store functionalities right out of the box, with the ability to configure these features to your specific use case.</p>
<h3 id="heading-medusa-server"><strong>Medusa Server</strong></h3>
<p>The Medusa server, which is a Node.js ecommerce backend, serves as a core component of the platform. It contains all the store's data and logic, and the other two components use its REST APIs to create, retrieve, and modify data.</p>
<p>The server provides most of the features related to an ecommerce workflow. These functionalities include managing products, carts, and orders, as well as integrating with shipping and payment providers and managing users. </p>
<p>In addition to that, you can configure your store including your store’s region, tax rules, discounts, gift cards, and more.</p>
<p>Since Medusa is highly extensible, it allows developers to build custom features such as <a target="_blank" href="https://medusajs.com/blog/customer-group-automation/">automating customer group assignments</a> and <a target="_blank" href="https://medusajs.com/blog/chatgpt-medusa/">integrating ChatGPT to automate writing product descriptions</a>. </p>
<p>In addition to that, you can also integrate third-party services into Medusa using <a target="_blank" href="https://docs.medusajs.com/advanced/backend/plugins/overview">Plugins</a>. You can find both official and community plugins to assist you with your common needs. Learn more about these plugins <a target="_blank" href="https://docs.medusajs.com/advanced/backend/plugins/overview/">here</a>.</p>
<p>Medusa also allows you to extend its functionalities with new endpoints, business logic, and database entities. Additionally, you can create subscribers that listen to events and trigger specific actions.</p>
<h3 id="heading-medusa-storefront">Medusa Storefront</h3>
<p>The storefront serves as the main presentation layer or frontend of your ecommerce store where customers can view and purchase your products. These storefronts can be in the form of progressive web applications or mobile apps.</p>
<p>Medusa offers two storefront starters built with <a target="_blank" href="https://docs.medusajs.com/starters/nextjs-medusa-starter">Next.js</a> and <a target="_blank" href="https://docs.medusajs.com/starters/gatsby-medusa-starter/">Gatsby</a>, respectively. The storefront includes several features such as product listing and detail pages, customer authentication and profile functionalities, and a full checkout flow supporting shipping details and methods. It also comes with search capabilities ready for integration with top solutions like Algolia and MeiliSearch.</p>
<p>Below is a demonstration of what the Next.js storefront looks like:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/TmV2xNbNs4w" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>In addition to the provided storefront starters, you have the freedom to create your own custom storefront using the <a target="_blank" href="https://docs.medusajs.com/api/store/">Storefront REST APIs</a>.</p>
<h3 id="heading-medusa-admin"><strong>Medusa Admin</strong></h3>
<p>An ecommerce store's essential component is the Admin dashboard, which enables merchants to view, create, and modify data such as products and orders.</p>
<p>With the Medusa Admin dashboard, Medusa provides functionalities for store management, including product management, order management, and user management. You can manage your orders from various regions and channels using a single dashboard.</p>
<p>For merchants migrating their stores from other platforms, the admin dashboard provides easy import and export functionalities for large datasets of products, orders, and customers. The dashboard also offers customer-group functionality to create customized pricing lists, discounts, and gift cards.</p>
<p>This is what the Medusa Admin Dashboard looks like:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/Z6uoN7TR0Z0" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>However, if you are not satisfied with the existing admin dashboard, you can utilize the Admin REST APIs to extend it beyond.</p>
<h2 id="heading-how-to-set-up-an-ecommerce-store-using-medusa">How to Set Up an Ecommerce Store Using Medusa</h2>
<p>In this section, you'll set up the three components of your Medusa ecommerce store.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before you get started with the tutorial, you should have installed:</p>
<ul>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment#nodejs">Node.js(V14 or later)</a></li>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment/#git">Git</a></li>
<li><a target="_blank" href="https://docs.medusajs.com/tutorial/set-up-your-development-environment#medusa-cli">Medusa CLI</a></li>
</ul>
<h3 id="heading-how-to-set-up-the-medusa-server">How to Set Up the Medusa Server</h3>
<p>Creating a new Medusa server is a simple process using the Medusa CLI. Navigate to the directory where you want to create your Medusa server and run the following command to create a new Medusa server with the name <code>my-medusa-store</code>:</p>
<pre><code class="lang-bash">medusa new my-medusa-store --seed
</code></pre>
<p>This command will create a new Medusa server with the specified name. The <code>--seed</code> option tells the Medusa CLI to seed the data after creating the server. The seed data includes sample data such as products, categories, and more.</p>
<p>Once the command finishes executing, navigate to the <code>my-medusa-store</code> directory and start the server by running the following command:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-medusa-store
medusa develop
</code></pre>
<p>In a couple of minutes, the server will start running on the default <code>9000</code> port. You can test it out by sending a request using a tool like Postman or through the command line:</p>
<pre><code class="lang-bash">curl localhost:9000/store/products
</code></pre>
<p>If your server is successfully set up, you will see a list of products and other details as below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/curl-output.png" alt="Image" width="600" height="400" loading="lazy">
<em>Output of cURL command</em></p>
<h3 id="heading-how-to-set-up-the-nextjs-storefront">How to Set Up the Next.js Storefront</h3>
<p>Now that your Medusa server is up and running, it's time to configure your storefront. In this section, you'll set up the Next.js storefront.</p>
<p>Create a new Next.js project using the <a target="_blank" href="https://github.com/medusajs/nextjs-starter-medusa">Medusa Next.js starter template</a>:</p>
<pre><code class="lang-bash">npx create-next-app -e https://github.com/medusajs/nextjs-starter-medusa my-medusa-storefront
</code></pre>
<p>Navigate to the newly created directory <code>my-medusa-storefront</code> folder and rename the template environment variable file to use environment variables in development:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-medusa-storefront
mv .env.template .env.local
</code></pre>
<p>Make sure the Medusa server is running, then run the local Next.js server:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Your Next.js storefront will now be running on its default port <code>8000</code>.</p>
<p>Note: Medusa also provides you with the <a target="_blank" href="https://docs.medusajs.com/starters/gatsby-medusa-starter/">Gatsby starter template</a> to create the storefront.</p>
<h3 id="heading-how-to-set-up-the-medusa-admin-dashboard">How to Set Up the Medusa Admin Dashboard</h3>
<p>Since the Medusa Admin uses the Medusa server, make sure your server is up and running.</p>
<p>Start by cloning the <a target="_blank" href="https://github.com/medusajs/admin">Admin GitHub repository</a>:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/medusajs/admin my-medusa-admin
</code></pre>
<p>Navigate to the cloned <code>my-medusa-admin</code> folder and install all the dependencies:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> my-medusa-admin
npm install
</code></pre>
<p>Run the development server:</p>
<pre><code class="lang-bash">npm run start
</code></pre>
<p>In a couple of minutes, the admin will start running on the default <code>7000</code> port. So, in your browser, go to <code>localhost:7000</code> to view your admin.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/medusa-admin.png" alt="Image" width="600" height="400" loading="lazy">
<em>Medusa Admin Login</em></p>
<p>If you had already seeded the data in your Medusa server, you can use the email <code>admin@medusa-test.com</code> and password <code>supersecret</code> to log in. If you hadn't, you can <a target="_blank" href="https://docs.medusajs.com/admin/quickstart#create-a-new-admin-user">create a new admin user</a>.</p>
<h2 id="heading-how-to-set-up-medusa-with-create-medusa-app">How to Set Up Medusa with <code>create-medusa-app</code></h2>
<p>Up to this point, you have seen how to set up individual components of a Medusa project. But in this section, you will learn how to set up a complete Medusa project with all three components using a single command.</p>
<p>Medusa now provides you with a <code>create-medusa-app</code> command to set up a project. This command provides an interactive prompt that guides you through the setup process.</p>
<p>To use <code>create-medusa-app</code>, simply run the following command:</p>
<pre><code class="lang-bash">npx create-medusa-app
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/02/create-medusa-app.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Creating Medusa Project Using create-medusa-app</em></p>
<p>In this interactive setup, you will be prompted to enter the name of the directory where you want to install the Medusa project. The default name is <code>my-medusa-store</code>, but you can choose to name it something else.</p>
<p>Next, you will be asked to select a Medusa server starter from the available options, which include the default starter, the Contentful starter, and the option to enter a custom starter URL. The server will be installed in the <code>backend</code> directory of your project, and a demo SQLite database will be created within it.</p>
<p>Following the Medusa server setup, you will be prompted to choose a storefront starter from the available options. If you choose to install a storefront, it will be installed in the <code>storefront</code> directory of your project. If you choose "None", no storefront will be installed.</p>
<p>The admin is set up automatically inside the <code>admin</code> directory of your project.</p>
<p>Once the setup is complete, you will receive instructions on how to start each component of the Medusa project.</p>
<pre><code class="lang-bash">Your project is ready. The available commands are:

Medusa API
<span class="hljs-built_in">cd</span> my-medusa-store/backend
yarn start

Admin
<span class="hljs-built_in">cd</span> my-medusa-store/admin
yarn start

Storefront
<span class="hljs-built_in">cd</span> my-medusa-store/storefront
yarn develop <span class="hljs-comment"># for Gatsby storefront</span>
yarn dev <span class="hljs-comment"># for Next.js storefront</span>
</code></pre>
<p>Just keep in mind that the commands can differ based on your choices in previous prompts.</p>
<h3 id="heading-project-directory-structure">Project Directory Structure</h3>
<p>Inside the root project directory (which was specified at the beginning of the installation process – <code>my-medusa-store</code> in this case) you’ll find the following directory structure:</p>
<pre><code class="lang-bash">my-medusa-store
  ├── admin
  ├── backend
  └── storefront
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, we've explored Medusa, an open-source e-commerce platform that provides a robust set of features for building online stores. We've looked at the architecture of Medusa, its key features, and how to set up the server, admin dashboard, and storefront.</p>
<p>One of the strengths of Medusa is its flexibility and extensibility. With a variety of plugins and extensions, you can customize your e-commerce site to meet your specific needs. You can also create your own plugins or themes, making it easy to build a site that reflects your brand.</p>
<p>If you're looking to build an e-commerce site, Medusa is a solid choice that offers a lot of functionality out of the box. With its user-friendly interface, setting up an online store is a straightforward process. Plus, since it's an open-source platform, you have the freedom to modify and extend it as needed.</p>
<h3 id="heading-additional-resources">Additional Resources</h3>
<p>Here are some resources that you can use to extend the functionalities:</p>
<ul>
<li>Integrate <a target="_blank" href="https://docs.medusajs.com/add-plugins/sendgrid">SendGrid</a> as a notification provider.</li>
<li>Integrate <a target="_blank" href="https://docs.medusajs.com/add-plugins/stripe">Stripe</a> as a payment provider.</li>
<li><a target="_blank" href="https://docs.medusajs.com/advanced/backend/services/create-service">Create a service</a>.</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Integrate PayPal into Your HTML, CSS, and JS Product Pages ]]>
                </title>
                <description>
                    <![CDATA[ By Shane Duggan Imagine if you had an amazing product landing page, with customers lined up to make a purchase. But your manual JavaScript payment processing with server-side scripts, Authorize.net, or 2Checkout failed you. Frustrated by being unable... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/integrate-paypal-into-html-css-js-product-pages/</link>
                <guid isPermaLink="false">66d460f73dce891ac3a9681c</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ payments ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 25 Jan 2023 17:35:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/PayPalHTMLCSSJSCoverImage.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Shane Duggan</p>
<p>Imagine if you had an amazing product landing page, with customers lined up to make a purchase. But your manual JavaScript payment processing with server-side scripts, Authorize.net, or 2Checkout failed you.</p>
<p>Frustrated by being unable to make the purchase, your customer leaves to go to your competitors.</p>
<p>I imagine that would be a terrible feeling. Surely, there is a better way to do this with modern, widely accepted payment gateways. By the end of this tutorial, I will make sure that you won’t find yourself in that situation.</p>
<p>When it comes to accepting payments online, PayPal is one of the most widely-used and trusted options available. There are many new software startups appearing every day that use PayPal to handle their transactions and clients’ payments. </p>
<p>Integrating PayPal into your own HTML/CSS/JS product pages can greatly improve the user experience and streamline the checkout process for your customers.</p>
<p>PayPal also has great <a target="_blank" href="https://developer.paypal.com/">developer documentation</a> that makes this integration all the more user-friendly.</p>
<p>In this tutorial, we will take a look at the steps involved in integrating PayPal into your product pages. You'll learn how to set up your PayPal for integration and implement the code into your HTML/CSS/JS pages. Then we'll look at how you can use your newfound knowledge going forward.</p>
<h2 id="heading-how-can-you-use-this-tutorial">How Can You Use This Tutorial?</h2>
<p>Now, let me use an example to give you a better idea of how you can use this tutorial. (Fictitious story alert)</p>
<p>Recently, I decided to start a new online startup. After deliberating through countless ideas for software startups, I decided to start an online eBook store. </p>
<p>As my very first product, I published an eBook that covered 10 years of my life experience in programming. Excitedly, I created a product landing page with all my knowledge of HTML, CSS, and JavaScript.</p>
<p>Introducing MyProgrammingBook, a glorious $0.99 programming guide that contains everything you need to know, including how to get all of your searching algorithms to O(1) (not really…). Here is what it looks like:</p>
<p><img src="https://lh6.googleusercontent.com/615Geb0nmIE6CxpOqjvItkMBBDTPv7cPLk0MjkBRJYks2EBHJWosCxEubYKbMnOpz_IAdNBRfKx7f9V1FeUPdqRGy4y_Vc8eu63XgWDTucW4KvW7Xn5KxH9nUwjhmMBEK0ZTazkGU1s31toG-Sgg2oaeITZM_0eYavT37l-6IitZAOP2IcW0q4gLV4dtTw" alt="Image" width="600" height="400" loading="lazy">
<em>My first eBook's product landing page</em></p>
<p>I created this product landing page within an hour using some simple vanilla HTML and CSS. Here are the files I used:</p>
<h3 id="heading-indexhtml">index.html</h3>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>MyProgrammingBook<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"style.css"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-image-container"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"myprogrammingbook.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"MyProgrammingBook"</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-info-container"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>MyProgrammingBook<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"price"</span>&gt;</span>$0.99<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-description"</span>&gt;</span>
          Want to make all of your code run at O(1) speed? Then let me introduce MyProgrammingBook! Written by the world's leading expert on algorithmic optimization (me), this book will teach you everything you need to know to make your code lightning fast. Plus, it comes with a free unicorn* to help you implement all the techniques you learn. Don't miss out on this once-in-a-lifetime opportunity!
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>*Terms and Conditions Apply<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">form</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"name"</span>&gt;</span>Name:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"name"</span> <span class="hljs-attr">required</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"address"</span>&gt;</span>Address:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"address"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"address"</span> <span class="hljs-attr">required</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-info"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"card-number"</span>&gt;</span>Card Number:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"card-number"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"card-number"</span> <span class="hljs-attr">required</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-info"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"expiry-date"</span>&gt;</span>Expiry Date:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"expiry-date"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"expiry-date"</span> <span class="hljs-attr">required</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"cvv"</span>&gt;</span>CVV:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"cvv"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"cvv"</span> <span class="hljs-attr">required</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Buy Now"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"submitForm()"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-stylecss">style.css</h3>
<pre><code class="lang-css">* {
  <span class="hljs-attribute">box-sizing</span>: border-box;
}

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
  <span class="hljs-attribute">font-family</span>: Arial, sans-serif;
}

<span class="hljs-selector-class">.product-container</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">600px</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">8px</span> <span class="hljs-number">0</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>);
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
}

<span class="hljs-selector-class">.product-image-container</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30%</span>;
}

<span class="hljs-selector-class">.product-info-container</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">70%</span>;
  <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">20px</span>;
}

<span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: auto;
}

<span class="hljs-selector-tag">form</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
}

<span class="hljs-selector-tag">label</span>, <span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"text"</span>]</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">8px</span>;
  <span class="hljs-attribute">box-sizing</span>: border-box;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">2px</span> solid <span class="hljs-number">#ccc</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
}

<span class="hljs-selector-tag">label</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30%</span>;
  <span class="hljs-attribute">padding-right</span>: <span class="hljs-number">10px</span>;
}
<span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"text"</span>]</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">70%</span>;
}

<span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"button"</span>]</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#ff5722</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">12px</span> <span class="hljs-number">24px</span>;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
}

<span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"text"</span>]</span><span class="hljs-selector-attr">[name=<span class="hljs-string">"card-number"</span>]</span>, <span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"text"</span>]</span><span class="hljs-selector-attr">[name=<span class="hljs-string">"expiry-date"</span>]</span>, <span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"text"</span>]</span><span class="hljs-selector-attr">[name=<span class="hljs-string">"cvv"</span>]</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30%</span>;
}


<span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">0</span>;
}

<span class="hljs-selector-class">.price</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#ff5722</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
  <span class="hljs-attribute">font-weight</span>: bold;
}
</code></pre>
<h3 id="heading-scriptjs">script.js</h3>
<p>With my outdated knowledge, I accept payment through a form to collect their payment details before manually processing it with a payment provider. A sample JavaScript file might look like this:</p>
<pre><code class="lang-js"><span class="hljs-comment">// Get references to the form elements</span>
<span class="hljs-keyword">const</span> form = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"payment-form"</span>);
<span class="hljs-keyword">const</span> cardNumber = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"card-number"</span>);
<span class="hljs-keyword">const</span> expiryDate = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"expiry-date"</span>);
<span class="hljs-keyword">const</span> cvv = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"cvv"</span>);
<span class="hljs-keyword">const</span> submitButton = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"submit-button"</span>);

<span class="hljs-comment">// Handle form submission</span>
form.addEventListener(<span class="hljs-string">"submit"</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  event.preventDefault();

  <span class="hljs-comment">// Disable the submit button to prevent multiple submissions</span>
  submitButton.disabled = <span class="hljs-literal">true</span>;

  <span class="hljs-comment">// Create an object to hold the form data</span>
  <span class="hljs-keyword">const</span> formData = {
    <span class="hljs-attr">cardNumber</span>: cardNumber.value,
    <span class="hljs-attr">expiryDate</span>: expiryDate.value,
    <span class="hljs-attr">cvv</span>: cvv.value,
  };

  <span class="hljs-comment">// Perform client-side validation on the form data</span>
  <span class="hljs-keyword">if</span> (!validateFormData(formData)) {
    <span class="hljs-comment">// If the data is invalid, re-enable the submit button and return</span>
    submitButton.disabled = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-comment">// Send the form data to the server</span>
  <span class="hljs-comment">// The following is just an example and should not be used in a real-world scenario</span>
  <span class="hljs-comment">// as it lacks security measures and proper payment gateway integration</span>
  fetch(<span class="hljs-string">"/charge"</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(formData),
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
  })
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json())
    .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
      <span class="hljs-comment">// Handle the server response</span>
      <span class="hljs-keyword">if</span> (data.success) {
        <span class="hljs-comment">// Payment was successful</span>
        alert(<span class="hljs-string">"Payment successful!"</span>);
      } <span class="hljs-keyword">else</span> {
        <span class="hljs-comment">// Payment failed</span>
        alert(<span class="hljs-string">"Payment failed. Please try again."</span>);
        submitButton.disabled = <span class="hljs-literal">false</span>;
      }
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(error);
      alert(<span class="hljs-string">"An error occurred. Please try again."</span>);
      submitButton.disabled = <span class="hljs-literal">false</span>;
    });
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">validateFormData</span>(<span class="hljs-params">data</span>) </span>{
  <span class="hljs-comment">// Example validation checks</span>
  <span class="hljs-keyword">if</span> (!data.cardNumber || data.cardNumber.length !== <span class="hljs-number">16</span>) {
    alert(<span class="hljs-string">"Please enter a valid card number."</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
  <span class="hljs-keyword">if</span> (!data.expiryDate || data.expiryDate.length !== <span class="hljs-number">5</span>) {
    alert(<span class="hljs-string">"Please enter a valid expiry date in the format MM/YY."</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
  <span class="hljs-keyword">if</span> (!data.cvv || data.cvv.length !== <span class="hljs-number">3</span>) {
    alert(<span class="hljs-string">"Please enter a valid CVV."</span>);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  }
  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>However, this old-school way of taking a payment creates several problems:</p>
<ul>
<li><strong>Security:</strong> Without proper security measures, sensitive information such as credit card numbers and personal information can be vulnerable to hacking and fraud.</li>
<li><strong>Compliance:</strong> A business may face penalties and legal issues without compliance with regulations such as PCI-DSS.</li>
<li><strong>Scalability:</strong> Processing payments using only HTML, CSS, and JavaScript may not be able to handle a high volume of transactions, especially during peak periods.</li>
<li><strong>Maintenance:</strong> Maintaining and updating a custom payment system can be time-consuming and costly.</li>
<li><strong>Limited features:</strong> Collecting payments using only HTML, CSS, and JavaScript may lack features such as recurring payments, subscriptions, refunds, and fraud detection.</li>
<li><strong>Lack of integration with other tools:</strong> Vanilla HTML, CSS, and JavaScript do not have built-in integrations with other tools like accounting software, <a target="_blank" href="https://www.demandsage.com/inventory-management-software/">inventory management software</a>, and shipping providers.</li>
</ul>
<p>So a much simpler way, not to mention more seamless and functional, would be to abstract away all of the payment handlings to a third party. Especially so as customers will more often than not be more familiar with using these third-party payment gateways already.</p>
<p>Even if you are not selling a product, this can be great for websites that are selling some kind of freelance service or consultation, a trend we have been seeing in recent times. Allowing the customer to checkout with a familiar <a target="_blank" href="https://www.yaguara.co/best-payment-gateways/">third-party payment gateway</a> directly from your website will add greatly to the user experience of your service.</p>
<p>It’s a win-win for you and the customer. You program less and they receive more. So let’s dive in to find out how we can implement this into your own websites.</p>
<h2 id="heading-how-to-integrate-paypal-into-your-product-pages">How to Integrate PayPal into Your Product Pages</h2>
<p>Now that we know what we want to achieve, let’s get into action. I have broken down the process into 6 easy-to-follow steps. They are:</p>
<ol>
<li>Access PayPal’s developer tools to get your APIs.</li>
<li>Set up a PayPal sandbox environment.</li>
<li>Create and customize your PayPal button.</li>
<li>Integrate your button into your current files.</li>
<li>Test the integration using the PayPal sandbox environment.</li>
<li>Go live and start accepting real payments.</li>
</ol>
<p>We’ve got a lot to cover, so let’s dive right in and get started!</p>
<h2 id="heading-how-to-access-paypals-developer-tools-to-get-your-apis">How to Access PayPal’s Developer Tools to Get Your APIs</h2>
<p>Step 1 for integrating PayPal into your HTML/CSS/JS product pages is creating a PayPal account. This step is crucial as it will give you access to PayPal's developer tools and APIs, which are necessary for integrating PayPal into your product pages. </p>
<p>If you already have one, just hop straight to the PayPal developer tools.</p>
<p>To create a PayPal account, you'll need to visit the PayPal website and follow the on-screen instructions to sign up. You'll be asked to provide some personal and financial information, such as your name, email address, and credit card details.</p>
<p>Once you've completed the sign-up process, you'll be able to log in to your PayPal account and access the developer tools and APIs needed for integration. It's important to use a valid email address as you will receive important information and updates from PayPal.</p>
<p>Then, you can find the developer tools at the top right of your PayPal dashboard.</p>
<p><img src="https://lh4.googleusercontent.com/a3-V_L-K2tqnzNWOTwGbQvgr0omXa14r-qnA6sd1B8AZqNhi7FxsQGHsH8z_0cXWUhOqmXIwPVtficgBrdci5nWGHOSlfh5L5ZHNHrwpuHNmM6vq7LQQCSA9sU9FzT5cxAF9tlDUZOmsxQmSWFlAqzetprRw-_1o7OyoL8AFePScGgMopJtZJTLRkWf-Gg" alt="Image" width="600" height="400" loading="lazy">
<em>Developer tab at the top right of your PayPal dashboard</em></p>
<h2 id="heading-how-to-set-up-a-paypal-sandbox-environment">How to Set Up a PayPal Sandbox Environment</h2>
<p>The next step is to create a PayPal sandbox environment.</p>
<p>What is that, you might ask?</p>
<p>Well, a sandbox environment is a testing environment that mimics the live PayPal environment. It allows you to test your integration before going live, which is a great feature from PayPal to ensure that everything is working as expected.</p>
<p>To set up a PayPal sandbox environment, you'll need to log in to your PayPal account and navigate to the developer dashboard. From there, you can create a new sandbox account by following <a target="_blank" href="https://developer.paypal.com/api/rest/sandbox/">the on-screen instructions</a>. </p>
<p>Once the sandbox account is created, you'll be able to use it to test your integration by making test payments. You can use the sandbox account in parallel with your live account.</p>
<p><img src="https://lh4.googleusercontent.com/lXbcuiZbxEb2mawwHyQLqsK8sUDarUl9jMOJeUBbm9jTfsTTWxsS0GZKiFVNl5SG_wNRdbHgBU4fNmo9HpR8Yvv6j4-GvPtRRE-UbbxyuRtBrz3RuYcUQwX1arXRXDHsIQfxN1yQN2QTyTQcdKLfBgorGPU1ilLIZKcD0XfesZeG4e-HPRoflzSGbf1wLg" alt="Image" width="600" height="400" loading="lazy">
<em>Check your URL to contain "sandbox"</em></p>
<p>It's important to test your integration thoroughly and ensure that everything is working as expected before going live.</p>
<h2 id="heading-how-to-create-and-customize-your-paypal-button">How to Create and Customize Your PayPal Button</h2>
<p>This step involves creating the button that will be placed on your product page, allowing customers to initiate the payment process.</p>
<p>One way to create a PayPal button is by using the PayPal Button Creation Tool, which provides a user-friendly interface for customizing your button. You can choose from 3 different checkout styles, available on <a target="_blank" href="https://developer.paypal.com/docs/checkout/#home">PayPal’s website</a>.</p>
<p><img src="https://lh6.googleusercontent.com/SdtWGXEvNhBbgl_kMWh4EROOAIyRCjkO6z1yTYpr6pwkZnN73H0zKUBgvDco_5yS9pZyjTB0IyuaXJnR0tjULEKmjMnDg_BwpPX0r7mX-Ifh6sqhWEhlUVkseIrvPQw_By2ZncKvpIvPXHs5iqmJbdIevN00K2wmFUZeB9P1FnKWxmOBe2Hp54sfWLV5Kg" alt="Image" width="600" height="400" loading="lazy">
<em>Types of smart buttons offered by PayPal</em></p>
<p>Navigate to business tools &gt; PayPal Buttons &gt; Get Started &gt; Smart Buttons. This will bring you to the button customization interface. Adjust the stylings to your liking, then simply click on “Copy Code”.</p>
<p><img src="https://lh5.googleusercontent.com/yVxrqZd7V-NPKd68xnqnKFua8QmQP3Z5bx0NDpThLS5YfQ0KuN6vAUt4G7PqAfgEZ2xzJRCHBWtrlqX-9mZOwnH54hYUKPJ6oqLQEWFR_-NIGNQ8ivczYOb-MRG7NNeh4Sge9_HiYbGOgKKB1PVVUv2GhDC2qjTOI9qGwWuOj6ZGilA_w3PR7ZXYV60Xgg" alt="Image" width="600" height="400" loading="lazy">
<em>PayPal's smart button customization interface</em></p>
<p>Here is an example of the code for a PayPal Smart Button. Then, we must update our previous files to remove the form input and insert the following code instead. We will be covering that in the next step. Before that, this is the snippet of what you get from <a target="_blank" href="https://www.paypal.com/buttons/">PayPal’s Smart Buttons</a>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"smart-button-container"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"text-align: center;"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"paypal-button-container"</span>&gt;</span><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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://www.paypal.com/sdk/js?client-id=sb&amp;enable-funding=venmo&amp;currency=USD"</span> <span class="hljs-attr">data-sdk-integration-source</span>=<span class="hljs-string">"button-factory"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initPayPalButton</span>(<span class="hljs-params"></span>) </span>{
        paypal.Buttons({
            <span class="hljs-attr">style</span>: {
                <span class="hljs-attr">shape</span>: <span class="hljs-string">'rect'</span>,
                <span class="hljs-attr">color</span>: <span class="hljs-string">'gold'</span>,
                <span class="hljs-attr">layout</span>: <span class="hljs-string">'vertical'</span>,
                <span class="hljs-attr">label</span>: <span class="hljs-string">'paypal'</span>,
            },

            <span class="hljs-attr">createOrder</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data, actions</span>) </span>{
                <span class="hljs-keyword">return</span> actions.order.create({
                    <span class="hljs-attr">purchase_units</span>: [{<span class="hljs-string">"amount"</span>:{<span class="hljs-string">"currency_code"</span>: <span class="hljs-string">"USD"</span>, <span class="hljs-string">"value"</span>: <span class="hljs-number">0.99</span>}}]
                });
            },

            <span class="hljs-attr">onApprove</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data, actions</span>) </span>{
                <span class="hljs-keyword">return</span> actions.order.capture().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">orderData</span>) </span>{

                    <span class="hljs-comment">// Full available details</span>
                    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Capture result'</span>, orderData, <span class="hljs-built_in">JSON</span>.stringify(orderData, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));

                    <span class="hljs-comment">// Show a success message within this page, for example:</span>
                    <span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'paypal-button-container'</span>);
                    element.innerHTML = <span class="hljs-string">''</span>;
                    element.innerHTML = <span class="hljs-string">'&lt;h3&gt;Thank you for your payment!&lt;/h3&gt;'</span>;

                    <span class="hljs-comment">// Or go to another URL:  actions.redirect('thank_you.html');</span>

                });
            },

            <span class="hljs-attr">onError</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">err</span>) </span>{
                <span class="hljs-built_in">console</span>.log(err);
            }
        }).render(<span class="hljs-string">'#paypal-button-container'</span>);
    }
    initPayPalButton();
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Do note that you will have different fields for your own needs, so do customize your own button and reference the code for that, not from my tutorial. </p>
<p>There are also different button styles to choose from, so feel free to play around to find something that suits your style.</p>
<p>Now that we’re ready, let’s integrate this code into our current files.</p>
<h2 id="heading-how-to-integrate-your-button-into-your-current-files">How to Integrate Your Button into Your Current Files</h2>
<p>With our button code fresh out of the box, let’s integrate this into our current files. Very simply, we just need to replace the HTML fields that we are not going to use, namely the form and replace it with the button we created.</p>
<p>To remain consistent with the programming style we used for the example, I am going to abstract away the JavaScript to the script.js file. That should give us the following files:</p>
<h3 id="heading-indexhtml-1">index.html</h3>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>MyProgrammingBook<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"style.css"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-image-container"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"myprogrammingbook.jpg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"MyProgrammingBook"</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-info-container"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>MyProgrammingBook<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"price"</span>&gt;</span>$0.99<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product-description"</span>&gt;</span>
          Want to make all of your code run at O(1) speed? Then let me introduce MyProgrammingBook! Written by the world's leading expert on algorithmic optimization (me), this book will teach you everything you need to know to make your code lightning fast. Plus, it comes with a free unicorn* to help you implement all the techniques you learn. Don't miss out on this once-in-a-lifetime opportunity!
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>*Terms and Conditions Apply<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"smart-button-container"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"text-align: center;"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"paypal-button-container"</span>&gt;</span><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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://www.paypal.com/sdk/js?client-id=sb&amp;enable-funding=venmo&amp;currency=USD"</span> <span class="hljs-attr">data-sdk-integration-source</span>=<span class="hljs-string">"button-factory"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
      <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 class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<h3 id="heading-stylecss-1">style.css</h3>
<pre><code class="lang-css">* {
  <span class="hljs-attribute">box-sizing</span>: border-box;
}

<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f5f5f5</span>;
  <span class="hljs-attribute">font-family</span>: Arial, sans-serif;
}

<span class="hljs-selector-class">.product-container</span> {
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">600px</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">500px</span>;
  <span class="hljs-attribute">overflow</span>: hidden;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> auto;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">8px</span> <span class="hljs-number">0</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>);
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
}

<span class="hljs-selector-class">.product-image-container</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">30%</span>;
}

<span class="hljs-selector-class">.product-info-container</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">70%</span>;
  <span class="hljs-attribute">padding-left</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-wrap</span>: wrap;
  <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: auto;
}

<span class="hljs-selector-class">.product-info-container</span> <span class="hljs-selector-tag">form</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">align-self</span>: flex-end;
}

<span class="hljs-selector-tag">h1</span> {
  <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">0</span>;
}

<span class="hljs-selector-class">.price</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#ff5722</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
  <span class="hljs-attribute">font-weight</span>: bold;
}
</code></pre>
<h3 id="heading-scriptjs-1">script.js</h3>
<pre><code class="lang-js"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">initPayPalButton</span>(<span class="hljs-params"></span>) </span>{
  paypal.Buttons({
    <span class="hljs-attr">style</span>: {
      <span class="hljs-attr">shape</span>: <span class="hljs-string">'rect'</span>,
      <span class="hljs-attr">color</span>: <span class="hljs-string">'gold'</span>,
      <span class="hljs-attr">layout</span>: <span class="hljs-string">'vertical'</span>,
      <span class="hljs-attr">label</span>: <span class="hljs-string">'paypal'</span>,

    },

    <span class="hljs-attr">createOrder</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data, actions</span>) </span>{
      <span class="hljs-keyword">return</span> actions.order.create({
        <span class="hljs-attr">purchase_units</span>: [{<span class="hljs-string">"amount"</span>:{<span class="hljs-string">"currency_code"</span>:<span class="hljs-string">"USD"</span>,<span class="hljs-string">"value"</span>:<span class="hljs-number">0.99</span>}}]
      });
    },

    <span class="hljs-attr">onApprove</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data, actions</span>) </span>{
      <span class="hljs-keyword">return</span> actions.order.capture().then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">orderData</span>) </span>{

        <span class="hljs-comment">// Full available details</span>
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Capture result'</span>, orderData, <span class="hljs-built_in">JSON</span>.stringify(orderData, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));

        <span class="hljs-comment">// Show a success message within this page, for example:</span>
        <span class="hljs-keyword">const</span> element = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'paypal-button-container'</span>);
        element.innerHTML = <span class="hljs-string">''</span>;
        element.innerHTML = <span class="hljs-string">'&lt;h3&gt;Thank you for your payment!&lt;/h3&gt;'</span>;

        <span class="hljs-comment">// Or go to another URL:  actions.redirect('thank_you.html');</span>

      });
    },

    <span class="hljs-attr">onError</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">err</span>) </span>{
      <span class="hljs-built_in">console</span>.log(err);
    }
  }).render(<span class="hljs-string">'#paypal-button-container'</span>);
}
initPayPalButton();
</code></pre>
<p>These files are going to give us the following slick product landing page:</p>
<p><img src="https://lh6.googleusercontent.com/ZpupgG6OYiC0pSk9cdqIXp2SluOZ1Vh31WdzRLmAkvdLxlwWlfGx05JyhPFnf7dJdjcf76aeFRZua31cGKcTCwphR3RLuMvevHp_s0DUxU3lop_0Dq-GZK1tyUh7UcpU9SaC2K2x9GkfrNt1Eio-0Wb8MR1Jb3XPeb5p1X-ZQGoKjDjQLgxtV16CydKJwQ" alt="Image" width="600" height="400" loading="lazy">
<em>My new landing page with PayPal's smart buttons</em></p>
<h2 id="heading-how-to-test-the-integration-using-the-paypal-sandbox-environment">How to Test the Integration Using the PayPal Sandbox Environment</h2>
<p>Before going live with your PayPal integration on your product page, it is important to test the integration to ensure that it is working correctly and to identify any issues. </p>
<p>One way to do this is by using the PayPal Sandbox environment that we discussed earlier.</p>
<p>To use the PayPal Sandbox with the account you created earlier, you will be given a set of Sandbox test accounts (buyer and seller) that you can use to test your integration.</p>
<p>With these Sandbox accounts set up, you will need to ensure your PayPal button code on your product page points to the Sandbox environment. </p>
<p>You can do this by checking that the URL in the PayPal button code redirects you to "https://www.sandbox.paypal.com" instead of "https://www.paypal.com".</p>
<p><img src="https://lh5.googleusercontent.com/h_jQc1xmhqmETV2LA_2wsfx2ucRwx_eKhmsWr0ZXVER3c89m73xvBj8L0D3-_meGToje6ElWhfyeGc4Gf_as5fdHdxTtRwCDL5vv7wb9uKK8BYQqCBtLZC3yVzgkX1gjgIAPI_bAbtM_B6ANmSg_NXuibAe-dFjO1yCXI1_qnIPmNsS7AdevCydXiflLmg" alt="Image" width="600" height="400" loading="lazy">
<em>Check your URL to contain "sandbox"</em></p>
<p>With your PayPal button code working, you can now test the integration by visiting your product page and clicking on the PayPal button. You will be taken to the PayPal Sandbox checkout page where you can enter your Sandbox test account credentials and simulate a purchase.</p>
<p>Once you've completed a test transaction, you can log in to your Sandbox account to view the transaction details and confirm that it was processed correctly.</p>
<h2 id="heading-how-to-go-live-and-start-accepting-real-payments">How to Go Live and Start Accepting Real Payments</h2>
<p>After successfully testing your PayPal integration in the Sandbox environment, you are now ready to go live and start accepting real payments on your product page. </p>
<p>Going live involves a few simple steps to ensure that your integration is set up correctly and ready to process real payments.</p>
<p>The first step is to update your PayPal button code on your product page to point to the live PayPal environment. Conversely to earlier, ensure that the URL in the PayPal button code redirects you to "https://www.paypal.com" instead of "https://www.sandbox.paypal.com".</p>
<p>Next, you will need to update your PayPal account settings to ensure that your account is set up to process live payments. This typically involves confirming your email address and phone number, as well as adding a valid payment method such as a bank card.</p>
<p>It is also important to ensure that your product page is fully functional and that any necessary information such as product details, prices, and shipping costs are accurate.</p>
<p>Finally, you should notify your customers that you are now accepting payments via PayPal, and provide them with clear instructions on how to complete a purchase.</p>
<p>With all these steps complete, you can now start accepting real payments via PayPal on your product page!</p>
<h2 id="heading-where-to-go-from-here">Where To Go From Here?</h2>
<p>Now that you’re all up to speed with getting PayPal integrations up and running on your own websites, why would this be useful to you? Are there no easier ways to integrate payments into your website? Let me give you some context.</p>
<p>Personally, I thought that no-code tools were becoming the norm in recent times, thinking that I probably wouldn’t need to be coding any web pages in vanilla HTML, CSS, and JavaScript soon. But with the new wave of online startups, such as <a target="_blank" href="https://www.misaias.com/best-ai-productivity-tools/">AI tools</a> and <a target="_blank" href="https://lanagerton.com/best-data-analytics-tools-and-software/">data analytics software</a>, revolving around making API requests from a simple user interface, I think the 3 big languages might make a bit of a comeback.</p>
<p>Wanting to start one of my own (so the story earlier wasn’t entirely fictitious…), I found myself coming back to the good old vanilla web development languages to get the job done. And they did so faster than no-code tools – like this simple <a target="_blank" href="https://shaneduggan.com/sentence-length-counter">sentence-length checker</a> I programmed in under an hour.</p>
<p>So I wouldn’t forget about learning how to integrate features, such as this, into standard HTML, CSS, and JavaScript pages. Sometimes, when it comes to wanting to create a simple product or service page that gets the job done, you might want to rely on old ways with new integrations as your solution.</p>
<h2 id="heading-wrapping-up-and-conclusion">Wrapping Up and Conclusion</h2>
<p>Let me give you a quick recap:</p>
<p>If you're looking to get a simple <a target="_blank" href="https://themoneymaniac.com/saas-ideas">SaaS</a> company going, then vanilla HTML, CSS, and JavaScript might be your best friends. When it comes to integrating PayPal into your product pages, it is important to remember a few key points to ensure a smooth and successful integration.</p>
<ul>
<li>Firstly, it is essential to have a PayPal account and to be familiar with the PayPal Developer documentation.</li>
<li>Secondly, ensure that your HTML and CSS files are well structured and designed to match your product page.</li>
<li>Lastly, use the PayPal Sandbox environment for testing to ensure everything is spick and span before going live with real payments.</li>
</ul>
<p>As a reminder, it is important to keep in mind that collecting payments through a form on your website is not a secure way of accepting payments, and it is recommended to use a payment gateway such as PayPal to ensure the security of your customer's sensitive information.</p>
<p>With these points in mind, you can now confidently set up PayPal on your product pages and start accepting payments securely and easily. I hope you enjoyed this tutorial, and if you did, don't hesitate to <a target="_blank" href="https://shaneduggan.com/">get in contact</a>. Wishing you the best of luck selling your products to the masses!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Production Ready eCommerce Website with ReactJS, TailwindCSS, PlanetScale and Stripe ]]>
                </title>
                <description>
                    <![CDATA[ Hello, welcome to this tutorial. Today we're going to build a production-ready eCommerce website using ReactJS, TailwindCSS, PlanetScale, and Stripe. Before we begin, you should be familiar with the basics of React.js and Next.js to get the most out ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-ecommerce-website-using-next-js-and-planetscale/</link>
                <guid isPermaLink="false">66d460ef4a0edd9b48e83583</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ stripe ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tailwind ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Sharvin Shah ]]>
                </dc:creator>
                <pubDate>Tue, 25 Oct 2022 16:30:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/10/Add-a-heading.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hello, welcome to this tutorial. Today we're going to build a production-ready eCommerce website using ReactJS, TailwindCSS, PlanetScale, and Stripe.</p>
<p>Before we begin, you should be familiar with the basics of React.js and Next.js to get the most out of this guide.</p>
<p>If you're not and need to brush up, I recommend you go through the <a target="_blank" href="https://reactjs.org/docs/getting-started.html">ReactJS</a> and <a target="_blank" href="https://nextjs.org/docs/getting-started">NextJS documentation</a>.</p>
<h2 id="heading-the-stack-we-will-use">The stack we will use:</h2>
<ol>
<li><p><a target="_blank" href="https://reactjs.org/docs/getting-started.html">ReactJS</a> is a JavaScript library for building user interfaces. It is declarative and component-based.</p>
</li>
<li><p><a target="_blank" href="https://nextjs.org/docs/getting-started">NextJS</a> is a React-based framework that lets us render data on the server side. It helps Google crawl the application which results in SEO benefits.</p>
</li>
<li><p><a target="_blank" href="https://planetscale.com/docs">PlanetScale</a> is a database as a service that is developed on Vitess, an open-source technology that powers YouTube and uses MySQL internally.</p>
</li>
<li><p><a target="_blank" href="https://tailwindcss.com/">TailwindCSS</a> is a utility-first CSS framework packed with classes that can be composed to build any design, directly in our markup.</p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io/docs/">Prisma</a> is an ORM built for NodeJS and TypeScript which handles automated migrations, type-safety, and auto-completion.</p>
</li>
<li><p><a target="_blank" href="https://vercel.com/docs">Vercel</a> will host our application. It scales well, all without any configuration, and deployment is instant.</p>
</li>
<li><p><a target="_blank" href="https://stripe.com">Stripe</a> is a payment gateway, and we will use it to accept online payments on the website.</p>
</li>
</ol>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#how-to-configure-planetscale-prisma-nextjs-and-stripe-">How to Configure PlanetScale, Stripe, NextJS, Prisma and Other Libraries</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-mock-data-category-products-api-and-all-category-single-category-ui">How to Implement Mock Data, Category-Products API and All Category-Single Category UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-implement-single-product-ui-and-stripe-checkout">How to Implement Single Product UI and Stripe Checkout</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-the-website-to-production">How to Deploy the Website to Production</a></p>
</li>
</ol>
<p>I am going to divide this tutorial into four separate sections.</p>
<p>At the start of every section, you will find a Git commit that has the code developed in that section. Also, if you want to see the complete code, then it is available in this <a target="_blank" href="https://github.com/Sharvin26/Ecommerce-Website-ReactJS-TailwindCSS-PlanetScale-Stripe">repository</a>.</p>
<h2 id="heading-how-to-configure-planetscale-stripe-nextjs-tailwindcss-and-prisma">How to Configure PlanetScale, Stripe, NextJS, TailwindCSS, and Prisma.</h2>
<p>In this section, we'll implement the following functionality:</p>
<ol>
<li><p>Create a PlanetScale Account and Database.</p>
</li>
<li><p>Create a Stripe Account.</p>
</li>
<li><p>Configure NextJS, TailwindCSS, and Prisma.</p>
</li>
</ol>
<p>You can find the <strong>eCommerce</strong> website <strong>code</strong> implemented in this section at this <a target="_blank" href="https://github.com/Sharvin26/Ecommerce-Website-ReactJS-TailwindCSS-PlanetScale-Stripe/tree/afa389dc07f565a39eacac5e3801fcc4e8d9041f">commit</a>.</p>
<h3 id="heading-how-to-configure-planetscale">How to Configure PlanetScale:</h3>
<p>To create a PlanetScale account, visit this <a target="_blank" href="https://planetscale.com/">URL</a>. Click on Get started button at the top right corner.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.01.59-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Landing Page</em></p>
<p>You can either create an account using GitHub or a traditional email-password. Once the account is created, then click on the "create" link.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-05-at-5.00.59-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Dashboard Page</em></p>
<p>You'll receive the following modal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-05-at-5.08.12-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale New Database Modal</em></p>
<p>Fill in the details and click on the Create database button. Once the database is created you'll be redirected to the following page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.06.05-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Ecommerce Website Database Page</em></p>
<p>Click on connect and a modal will open. This modal will contain a Database URL and this password cannot be generated again. So copy and paste it into a safe location.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.07.27-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Database Username and Password Modal</em></p>
<h3 id="heading-how-to-configure-stripe">How to Configure Stripe:</h3>
<p>To create a Stripe account, go to this <a target="_blank" href="https://dashboard.stripe.com/register">URL</a>. Once you've created the account, click on the Developer Button from the Nav menu. You'll see API keys on the left side and you'll find the Publishable key and Secret key under Standard keys.</p>
<p>Publishable key: These are the keys that can be publicly-accessible in a web or mobile app’s client-side code.</p>
<p>Secret key: This is a secret credential and should be securely stored in the server code. This key is used to call the Stripe API.</p>
<h3 id="heading-how-to-configure-nextjs-tailwindcss-and-prisma">How to Configure NextJS, TailwindCSS, and Prisma.</h3>
<p>First, we will create a NextJS app using the following command:</p>
<pre><code class="lang-shell">npx create-next-app ecommerce-tut --ts --use-npm
</code></pre>
<p>Once the project is created, open it with your favourite editor. You'll get the following structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-05-at-6.03.07-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Project Structure</em></p>
<p>Let's create a directory named <code>src</code>. We will move the <code>pages</code> and <code>styles</code> directory to that <code>src</code> folder. You'll get the following structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.28.16-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Project Structure after moving Pages and Styles.</em></p>
<p>Install the following packages:</p>
<pre><code class="lang-shell">npm i @ngneat/falso @prisma/client @stripe/stripe-js @tanstack/react-query currency.js next-connect react-icons react-intersection-observer stripe
</code></pre>
<p>We also need to install dev dependencies:</p>
<pre><code class="lang-shell">npm i --save-dev @tanstack/react-query-devtools autoprefixer postcss tailwindcss
</code></pre>
<p>Let's understand each of the packages:</p>
<ol>
<li><p><a target="_blank" href="https://ngneat.github.io/falso/">@ngneat/falso</a>: We will use this library to create mock data for our eCommerce website. In an ideal world, you would have an admin panel to add the products, but it is not in the scope of this tutorial.</p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io/docs/concepts/components/prisma-client">@prisma/client</a>: We will use this library to connect to our database, run migrations, and do all CRUD operations on the database.</p>
</li>
<li><p><a target="_blank" href="https://stripe.com/docs/js">@stripe/stripe-js</a>: We will use this library to redirect users to the stripe checkout page and process payment.</p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query/v4/">@tanstack/react-query</a>: We will use this library for managing our asynchronous state, that is caching API responses.</p>
</li>
<li><p><a target="_blank" href="https://currency.js.org/">currency.js</a>: We will use this library for converting our prices to two decimal format.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/next-connect">next-connect</a>: We will use this library for routing purposes on our Next API layer.</p>
</li>
<li><p><a target="_blank" href="https://react-icons.github.io/react-icons/">react-icons</a>: We will use this library for adding icons to our buttons and links.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/react-intersection-observer">react-intersection-observer</a>: Have you seen infinite scrolling on a lot of websites and wondered how it is implemented? We will use this library to implement that based on the viewport.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/stripe">stripe:</a> We will use the Stripe library to connect with Stripe API from our Next API layer.</p>
</li>
<li><p><a target="_blank" href="https://tanstack.com/query/v4/docs/devtools">@tanstack/react-query-devtools</a>: We will use this library as the only dev dependency to view and manage our cache during development time.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/tailwindcss">TailwindCSS:</a> We will use this as our CSS library that also requires PostCSS and AutoPrefixer.</p>
</li>
</ol>
<p>Let's configure TailwindCSS into our project using the following command:</p>
<pre><code class="lang-shell">npx tailwindcss init -p
</code></pre>
<p>You'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.29.52-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>TailwindCSS Config Success</em></p>
<p>Now go to <code>tailwind.config.js</code> and update it with the following code:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('tailwindcss').Config}</span> </span>*/</span>
<span class="hljs-built_in">module</span>.exports = {
    <span class="hljs-attr">content</span>: [
        <span class="hljs-string">"./src/pages/**/*.{js,ts,jsx,tsx}"</span>,
        <span class="hljs-string">"./src/components/**/*.{js,ts,jsx,tsx}"</span>,
    ],
    <span class="hljs-attr">theme</span>: {
        <span class="hljs-attr">extend</span>: {},
    },
    <span class="hljs-attr">plugins</span>: [],
};
</code></pre>
<p>To generate the CSS, Tailwind needs access to all the HTML Elements. We will be writing the UI components under pages and components only, so we pass it under content.</p>
<p>If you need to use any plugins, for example, typography, then you need to add them under the plugins array. If you need to extend the default theme provide by Tailwind, then you need to add it under <code>theme.extend</code> section.</p>
<p>Now go to <code>/src/styles/globals.css</code> and replace the existing code with the following:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<p>We will add these three directives in our <code>globals.css</code> file. The meaning of each directive is as follows:</p>
<ol>
<li><p>@tailwind base: This injects a base style provided by Tailwind.</p>
</li>
<li><p>@tailwind components: This injects classes and any other classes added by the plugin.</p>
</li>
<li><p>@tailwind utilities: This injects hover, focus, responsive, dark mode and any other utility added by the plugin.</p>
</li>
</ol>
<p>Remove the <code>Home.module.css</code> from <code>src/styles</code> directory and go to <code>src/pages/index.ts</code> and replace the existing code with the following:</p>
<pre><code class="lang-tsx">import type { NextPage } from "next";
import Head from "next/head";

const Home: NextPage = () =&gt; {
    return (
        &lt;div&gt;
            &lt;Head&gt;
                &lt;title&gt;All Products&lt;/title&gt;
                &lt;meta name="description" content="All Products" /&gt;
                &lt;link rel="icon" href="/favicon.ico" /&gt;
            &lt;/Head&gt;

            &lt;main className="container mx-auto"&gt;
                &lt;h1 className="h-1"&gt;Hello&lt;/h1&gt;
            &lt;/main&gt;
        &lt;/div&gt;
    );
};

export default Home;
</code></pre>
<p>When we run the <code>create-next-app</code> command to create the project, it adds some boilerplate code. Here we removed that in some instances while replacing <code>index.ts</code> with an <code>h1</code> and text that says "Hello".</p>
<p>It's time to run the website using the following command:</p>
<pre><code class="lang-shell">npm run dev
</code></pre>
<p>You'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.36.15-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Open <a target="_blank" href="http://localhost:3000/">http://localhost:3000</a> on your browser, and you'll get the following screen with a hello message:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.38.49-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Screen with Hello Message</em></p>
<p>Let's configure Prisma into our project using the following command:</p>
<pre><code class="lang-shell">npx prisma init
</code></pre>
<p>You'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.41.15-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Prisma Successfully Configured Message</em></p>
<p>Under <code>prisma/schema.prisma</code> replace the existing code with the following code:</p>
<pre><code class="lang-python">// This <span class="hljs-keyword">is</span> your Prisma schema file,
// learn more about it <span class="hljs-keyword">in</span> the docs: https://pris.ly/d/prisma-schema

generator client {
  provider        = <span class="hljs-string">"prisma-client-js"</span>
  previewFeatures = [<span class="hljs-string">"referentialIntegrity"</span>]
}

datasource db {
  provider             = <span class="hljs-string">"mysql"</span>
  url                  = env(<span class="hljs-string">"DATABASE_URL"</span>)
  referentialIntegrity = <span class="hljs-string">"prisma"</span>
}

model Category {
  id        String    @id @default(cuid())
  name      String    @unique
  createdAt DateTime  @default(now())
  products  Product[]
}

model Product {
  id          String    @id @default(cuid())
  title       String    @unique
  description String
  price       String
  quantity    Int
  image       String
  createdAt   DateTime  @default(now())
  category    Category? @relation(fields: [categoryId], references: [id])
  categoryId  String?
}
</code></pre>
<p>This file consists of our database source that is MySQL. We are using MySQL because PlanetScale supports MySQL only.</p>
<p>Also, we have created two models that are:</p>
<h3 id="heading-category">Category:</h3>
<ol>
<li><p>name: Every category will have a unique title.</p>
</li>
<li><p>createdAt: The date when a category is added.</p>
</li>
<li><p>products: A foreign relationship with the product model.</p>
</li>
</ol>
<h3 id="heading-product">Product:</h3>
<ol>
<li><p>title: Every product will have a unique title.</p>
</li>
<li><p>description: This is just information about the product.</p>
</li>
<li><p>price: It is of <code>String</code> type because it will hold a decimal value.</p>
</li>
<li><p>quantity: It is of <code>Int</code> type because it will hold a numerical value.</p>
</li>
<li><p>image: Representation of what the product will look like. We will use placeimg for the purpose of this tutorial.</p>
</li>
<li><p>createdAt: The date when a product is added.</p>
</li>
<li><p>category: A foreign relationship with the category model.</p>
</li>
</ol>
<p>We are using <code>cuid()</code> instead of <code>uuid()</code> for the id because they are better for horizontal scaling and sequential lookup performance. Prisma has inbuilt support for CUID. You can read more about it <a target="_blank" href="https://github.com/paralleldrive/cuid">here</a>.</p>
<p>Now time to update our <code>.env</code> file with the following:</p>
<pre><code class="lang-python">DATABASE_URL=

STRIPE_SECRET_KEY=

NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
</code></pre>
<p>You'll find the Stripe secret key and publishable key under the dashboard. The database URL is the one that we had copy-pasted earlier and kept in a safe location. Update this <code>.env</code> with those credentials.</p>
<p>Also, note that the <code>.gitignore</code> file created by NextJS doesn't ignore the <code>.env</code> file. It is configured to ignore the <code>.env.local</code> file. But Prisma requires <code>.env</code>, so we will replace the <code>.gitignore</code> file content with the following:</p>
<pre><code class="lang-python"><span class="hljs-comment"># See https://help.github.com/articles/ignoring-files/ for more about ignoring files.</span>

<span class="hljs-comment"># dependencies</span>
/node_modules
/.pnp
.pnp.js

<span class="hljs-comment"># testing</span>
/coverage

<span class="hljs-comment"># next.js</span>
/.next/
/out/

<span class="hljs-comment"># production</span>
/build

<span class="hljs-comment"># misc</span>
.DS_Store
*.pem

<span class="hljs-comment"># debug</span>
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

<span class="hljs-comment"># local env files</span>
.env
.env*.local

<span class="hljs-comment"># vercel</span>
.vercel

<span class="hljs-comment"># typescript</span>
*.tsbuildinfo
next-env.d.ts
</code></pre>
<p>Ideally, Prisma manages schema migration using the <code>prisma migrate</code> command. But as PlanetScale has its schema migration mechanism inbuilt, we will use that. Use the following command to push migration to our current main branch.</p>
<p>Note, our main branch is not yet promoted as a production branch.</p>
<pre><code class="lang-shell">npx prisma db push
</code></pre>
<p>Now let's generate the Prisma client using the following command:</p>
<pre><code class="lang-shell">npx prisma generate
</code></pre>
<p>Go to the PlanetScale Dashboard, and there you'll find two tables created:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-2.59.50-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Two Tables Created</em></p>
<p>Click on these tables, and you'll be redirected to the following page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-3.00.39-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Database Schema</em></p>
<h2 id="heading-how-to-implement-mock-data-category-products-api-and-all-category-single-category-ui">How to Implement Mock Data, Category-Products API, and All Category-Single Category UI.</h2>
<p>In this section, we'll implement the following functionality:</p>
<ol>
<li><p>Create mock data</p>
</li>
<li><p>Create a Category and Product API.</p>
</li>
<li><p>Create an All Category and Single Category UI.</p>
</li>
</ol>
<p>You can find the <strong>eCommerce website code</strong> implemented in this section at this <a target="_blank" href="https://github.com/Sharvin26/Ecommerce-Website-ReactJS-TailwindCSS-PlanetScale-Stripe/tree/18bfb1152cfdeb14ba1a554d88d2b766a319d66a">commit</a>.</p>
<h3 id="heading-how-to-create-the-mock-data">How to Create the Mock Data:</h3>
<p>Under the <code>prisma</code> directory, create a file named <code>seed.ts</code> and copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> {
    randBetweenDate,
    randNumber,
    randProduct,
    randProductAdjective,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@ngneat/falso"</span>;
<span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@prisma/client"</span>;

<span class="hljs-keyword">const</span> primsa = <span class="hljs-keyword">new</span> PrismaClient();

<span class="hljs-keyword">const</span> main = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> primsa.category.deleteMany();
        <span class="hljs-keyword">await</span> primsa.product.deleteMany();
        <span class="hljs-keyword">const</span> fakeProducts = randProduct({
            length: <span class="hljs-number">1000</span>,
        });
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> index = <span class="hljs-number">0</span>; index &lt; fakeProducts.length; index++) {
            <span class="hljs-keyword">const</span> product = fakeProducts[index];
            <span class="hljs-keyword">const</span> productAdjective = randProductAdjective();
            <span class="hljs-keyword">await</span> primsa.product.upsert({
                where: {
                    title: <span class="hljs-string">`<span class="hljs-subst">${productAdjective}</span> <span class="hljs-subst">${product.title}</span>`</span>,
                },
                create: {
                    title: <span class="hljs-string">`<span class="hljs-subst">${productAdjective}</span> <span class="hljs-subst">${product.title}</span>`</span>,
                    description: product.description,
                    price: product.price,
                    image: <span class="hljs-string">`<span class="hljs-subst">${product.image}</span>/tech`</span>,
                    quantity: randNumber({ min: <span class="hljs-number">10</span>, max: <span class="hljs-number">100</span> }),
                    category: {
                        connectOrCreate: {
                            where: {
                                name: product.category,
                            },
                            create: {
                                name: product.category,
                                createdAt: randBetweenDate({
                                    <span class="hljs-keyword">from</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-string">"10/06/2020"</span>),
                                    to: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
                                }),
                            },
                        },
                    },
                    createdAt: randBetweenDate({
                        <span class="hljs-keyword">from</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-string">"10/07/2020"</span>),
                        to: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
                    }),
                },
                update: {},
            });
        }
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">throw</span> error;
    }
};

main().catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Error While generating Seed: \n"</span>, err);
});
</code></pre>
<p>Here we are creating 1000 fake products and adding them to the database.</p>
<p>We are following these steps to add the products:</p>
<ol>
<li><p>Delete all the categories using the <code>deleteMany()</code> function.</p>
</li>
<li><p>Delete all the product using the <code>deleteMany()</code> function.</p>
</li>
<li><p>The above are optional steps, but it's always a good idea to rerun the seed script with a clean table.</p>
</li>
<li><p>As the <code>title</code> attribute from the <code>product</code> table has unique property associated with it we bind it with the <code>randProductAdjective</code> function output to make repetitions less likely.</p>
</li>
<li><p>But still, there is a probability that the title property created by the <code>falso</code> gets repeated. So we use the upsert method from <code>@prisma/client</code>.</p>
</li>
<li><p>We are also creating/associating the category when we create a product.</p>
</li>
</ol>
<p>Now go to <code>package.json</code> and update the following code below <code>scripts</code>:</p>
<pre><code class="lang-shell">"prisma": {
    "seed": "ts-node --compiler-options {\"module\":\"CommonJS\" prisma/seed.ts"
},
</code></pre>
<p>We will use the <code>ts-node</code> package to run our seed script command. The seed script is written in TypeScript while <code>ts-node</code> converts TypeScript code to JavaScript.</p>
<p>Use the following command to install the package:</p>
<pre><code class="lang-shell">npm i --save-dev ts-node
</code></pre>
<p>As the <code>ts-node</code> will convert the code to JavaScript, we can execute the following command to seed the tables with mock data:</p>
<pre><code class="lang-shell">npx prisma db seed
</code></pre>
<p>You'll get the following output that will show it has started running. It will take some time to seed the tables with mock data.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-3.18.56-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once the seed command is successful, you'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-3.20.09-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The benefit of Prisma is that it also has a studio, which can be used to view the database in a local development environment. Use the following command to run this studio:</p>
<pre><code class="lang-shell">npx prisma studio
</code></pre>
<p>Open <a target="_blank" href="http://localhost:5555">http://localhost:5555</a>, on your browser, and you'll get the following screen with all the tables:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-3.22.23-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Prisma Studio</em></p>
<p>The number of Products and Categories may vary on your side or be similar, as this is random data.</p>
<h3 id="heading-how-to-create-the-category-and-product-apis">How to Create the Category and Product APIs:</h3>
<p>Under the <code>src/pages/api</code> category you'll find a file named <code>hello.ts</code>. Remove this file and create two directories named <code>categories</code> and <code>products</code>.</p>
<p>Inside those categories, create a file named <code>index.ts</code> and copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> nc <span class="hljs-keyword">from</span> <span class="hljs-string">"next-connect"</span>;
<span class="hljs-keyword">import</span> { prisma } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../lib/prisma"</span>;
<span class="hljs-keyword">import</span> { TApiAllCategoriesResp, TApiErrorResp } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../types"</span>;

<span class="hljs-keyword">const</span> getCategories = <span class="hljs-keyword">async</span> (
    _req: NextApiRequest,
    res: NextApiResponse&lt;TApiAllCategoriesResp | TApiErrorResp&gt;
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> categories = <span class="hljs-keyword">await</span> prisma.category.findMany({
            select: {
                id: <span class="hljs-literal">true</span>,
                name: <span class="hljs-literal">true</span>,
                products: {
                    orderBy: {
                        createdAt: <span class="hljs-string">"desc"</span>,
                    },
                    take: <span class="hljs-number">8</span>,
                    select: {
                        title: <span class="hljs-literal">true</span>,
                        description: <span class="hljs-literal">true</span>,
                        image: <span class="hljs-literal">true</span>,
                        price: <span class="hljs-literal">true</span>,
                    },
                },
            },
            orderBy: {
                createdAt: <span class="hljs-string">"desc"</span>,
            },
        });
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({ categories });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({
            message: <span class="hljs-string">"Something went wrong!! Please try again after sometime"</span>,
        });
    }
};

<span class="hljs-keyword">const</span> handler = nc({ attachParams: <span class="hljs-literal">true</span> }).get(getCategories);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>In the above snippet, we are doing the following:</p>
<ol>
<li><p>When we create a file under the <code>pages&gt;api</code> directory, NextJS treats it as a Serverless API. So by creating a file named <code>categories/index.ts</code> we are informing Next that it needs to convert this to the <code>/api/categories</code> API.</p>
</li>
<li><p>Using the <code>next-connect</code> library we are making sure that only the <code>get</code> operation is allowed for the <code>getCategories</code> function.</p>
</li>
<li><p>Under this function, we are querying the database with an order as <code>desc</code> for the <code>createdAt</code> property and we only take the latest eight product rows for each category row. We also select a specific property from the product and category that are required by the front end.</p>
</li>
</ol>
<p>We don't query all the products for each category in this API, because it will slow down our response time.</p>
<p>You'll find that we have imported <code>prisma</code> and <code>types</code> files. Let's create two directories under <code>src</code> named <code>lib</code> and <code>types</code>.</p>
<p>Under the <code>lib</code> directory, create a file named <code>prisma.ts</code>, and under the types directory create a file named <code>index.ts</code>.</p>
<p>Let's create our global Prisma constant under <code>prisma.ts</code>. Copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@prisma/client"</span>;

<span class="hljs-keyword">declare</span> <span class="hljs-built_in">global</span> {
    <span class="hljs-keyword">var</span> prisma: PrismaClient;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> prisma =
    <span class="hljs-built_in">global</span>.prisma ||
    <span class="hljs-keyword">new</span> PrismaClient({
        log: [],
    });

<span class="hljs-keyword">if</span> (process.env.NODE_ENV !== <span class="hljs-string">"production"</span>) <span class="hljs-built_in">global</span>.prisma = prisma;
</code></pre>
<p>Here we are creating a global prisma variable which we can use across the project.</p>
<p>Let's add the types that we will use application-wide under <code>src/types/index.ts</code>.</p>
<p>Copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> TApiAllCategoriesResp = {
    categories: {
        id: <span class="hljs-built_in">string</span>;
        name: <span class="hljs-built_in">string</span>;
        products: {
            title: <span class="hljs-built_in">string</span>;
            description: <span class="hljs-built_in">string</span>;
            image: <span class="hljs-built_in">string</span>;
            price: <span class="hljs-built_in">string</span>;
        }[];
    }[];
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> TApiSingleCategoryWithProductResp = {
    category: {
        id: <span class="hljs-built_in">string</span>;
        name: <span class="hljs-built_in">string</span>;
        products: {
            id: <span class="hljs-built_in">string</span>;
            title: <span class="hljs-built_in">string</span>;
            description: <span class="hljs-built_in">string</span>;
            image: <span class="hljs-built_in">string</span>;
            price: <span class="hljs-built_in">string</span>;
            quantity: <span class="hljs-built_in">number</span>;
        }[];
        hasMore: <span class="hljs-built_in">boolean</span>;
    };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> TApiSingleProductResp = {
    product: {
        title: <span class="hljs-built_in">string</span>;
        description: <span class="hljs-built_in">string</span>;
        price: <span class="hljs-built_in">string</span>;
        quantity: <span class="hljs-built_in">number</span>;
        image: <span class="hljs-built_in">string</span>;
    };
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> TApiErrorResp = {
    message: <span class="hljs-built_in">string</span>;
};
</code></pre>
<p>Here we are creating four types which will be used across the project.</p>
<p>I'll be using Postman to test this API. Postman is a utility for developing APIs. You can call the APIs, and Postman will show the response based on how you structure it.</p>
<p>Just update the URL in Postman to:</p>
<pre><code class="lang-shell">http://localhost:3000/api/categories
</code></pre>
<p>And you'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-3.58.53-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>All Categories Resp</em></p>
<p>Now let's create an API to get a single category's information with its products.</p>
<p>Under the <code>src/pages/api/categories</code> directory create a file named <code>[id].ts</code> and copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> nc <span class="hljs-keyword">from</span> <span class="hljs-string">"next-connect"</span>;
<span class="hljs-keyword">import</span> { prisma } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../lib/prisma"</span>;
<span class="hljs-keyword">import</span> {
    TApiErrorResp,
    TApiSingleCategoryWithProductResp
} <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../types"</span>;

<span class="hljs-keyword">const</span> getSingleCategory = <span class="hljs-keyword">async</span> (
    req: NextApiRequest,
    res: NextApiResponse&lt;TApiSingleCategoryWithProductResp | TApiErrorResp&gt;
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> id = req.query.id <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
        <span class="hljs-keyword">const</span> cursorId = req.query.cursorId;
        <span class="hljs-keyword">if</span> (cursorId) {
            <span class="hljs-keyword">const</span> categoriesData = <span class="hljs-keyword">await</span> prisma.category.findUnique({
                where: {
                    id,
                },
                select: {
                    id: <span class="hljs-literal">true</span>,
                    name: <span class="hljs-literal">true</span>,
                    products: {
                        orderBy: {
                            createdAt: <span class="hljs-string">"desc"</span>,
                        },
                        take: <span class="hljs-number">12</span>,
                        skip: <span class="hljs-number">1</span>,
                        cursor: {
                            id: cursorId <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>,
                        },
                        select: {
                            id: <span class="hljs-literal">true</span>,
                            title: <span class="hljs-literal">true</span>,
                            description: <span class="hljs-literal">true</span>,
                            image: <span class="hljs-literal">true</span>,
                            price: <span class="hljs-literal">true</span>,
                            quantity: <span class="hljs-literal">true</span>,
                        },
                    },
                },
            });

            <span class="hljs-keyword">if</span> (!categoriesData) {
                <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ message: <span class="hljs-string">`Category not found`</span> });
            }

            <span class="hljs-keyword">let</span> hasMore = <span class="hljs-literal">true</span>;
            <span class="hljs-keyword">if</span> (categoriesData.products.length === <span class="hljs-number">0</span>) {
                hasMore = <span class="hljs-literal">false</span>;
            }

            <span class="hljs-keyword">return</span> res
                .status(<span class="hljs-number">200</span>)
                .json({ category: { ...categoriesData, hasMore } });
        }

        <span class="hljs-keyword">const</span> categoriesData = <span class="hljs-keyword">await</span> prisma.category.findUnique({
            where: {
                id,
            },
            select: {
                id: <span class="hljs-literal">true</span>,
                name: <span class="hljs-literal">true</span>,
                products: {
                    orderBy: {
                        createdAt: <span class="hljs-string">"desc"</span>,
                    },
                    take: <span class="hljs-number">12</span>,
                    select: {
                        id: <span class="hljs-literal">true</span>,
                        title: <span class="hljs-literal">true</span>,
                        description: <span class="hljs-literal">true</span>,
                        image: <span class="hljs-literal">true</span>,
                        price: <span class="hljs-literal">true</span>,
                        quantity: <span class="hljs-literal">true</span>,
                    },
                },
            },
        });
        <span class="hljs-keyword">if</span> (!categoriesData) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ message: <span class="hljs-string">`Category not found`</span> });
        }

        <span class="hljs-keyword">let</span> hasMore = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">if</span> (categoriesData.products.length === <span class="hljs-number">0</span>) {
            hasMore = <span class="hljs-literal">false</span>;
        }

        <span class="hljs-keyword">return</span> res
            .status(<span class="hljs-number">200</span>)
            .json({ category: { ...categoriesData, hasMore } });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({
            message: <span class="hljs-string">"Something went wrong!! Please try again after sometime"</span>,
        });
    }
};

<span class="hljs-keyword">const</span> handler = nc({ attachParams: <span class="hljs-literal">true</span> }).get(getSingleCategory);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>In the above snippet, we are doing the following:</p>
<ol>
<li><p>By creating a file named <code>[id].ts</code> under <code>src/pages/api/categories</code> we are telling NextJS to convert this to the <code>/api/categories/[id]</code> API.</p>
</li>
<li><p>The <code>[id]</code> is the category id from the category table.</p>
</li>
<li><p>Using the <code>next-connect</code> library we are making sure that only the <code>get</code> operation is allowed for the <code>getSingleCategory</code> function.</p>
</li>
<li><p>Under this function, we are querying the database with order as <code>desc</code> for the <code>createdAt</code> property and we only take the latest twelve product rows. We also select a specific property from the product that is required by the front end.</p>
</li>
</ol>
<p>In this API, you will find that we have implemented pagination also. It helps us get more products under one category.</p>
<p>There are two kinds of pagination. One is cursor based, and another is offset-based pagination.</p>
<p>So why did we choose cursor-based pagination instead of offset-based pagination?</p>
<p><a target="_blank" href="https://www.prisma.io/docs/concepts/components/prisma-client/pagination">According to the Prisma docs</a>,</p>
<blockquote>
<p>"Offset pagination does not scale at a database level. For example, if you skip 200,00 records and take the first 10, the database still has to traverse the first 200,00 records before returning the 10 that you asked for - this negatively affects performance."</p>
</blockquote>
<p>For more information read this <a target="_blank" href="https://www.prisma.io/docs/concepts/components/prisma-client/pagination">helpful guide</a>.</p>
<p>Update the URL in Postman to:</p>
<pre><code class="lang-shell">http://localhost:3000/api/categories/cl91683hp006d0mvlxlg5u176?cursorId=cl91685ht00b00mvllxjwzkqk
</code></pre>
<p>Our URL consists of two ids and you'll need to add <code>cl91683hp006d0mvlxlg5u176</code> from the previous all-category response. This <code>cl91685ht00b00mvllxjwzkqk</code> id is just the cursor of the product and you can add this as the last one you want.</p>
<p>You'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.07.44-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Single Category Resp</em></p>
<p>Now let's create an API to get single product information.</p>
<p>Under the <code>src/pages/api/products</code> directory create a file named <code>[title].ts</code> and copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> nc <span class="hljs-keyword">from</span> <span class="hljs-string">"next-connect"</span>;
<span class="hljs-keyword">import</span> { prisma } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../lib/prisma"</span>;
<span class="hljs-keyword">import</span> { TApiErrorResp, TApiSingleProductResp } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../../types"</span>;

<span class="hljs-keyword">const</span> getSingleProduct = <span class="hljs-keyword">async</span> (
    req: NextApiRequest,
    res: NextApiResponse&lt;TApiSingleProductResp | TApiErrorResp&gt;
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> title = req.query.title <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
        <span class="hljs-keyword">const</span> product = <span class="hljs-keyword">await</span> prisma.product.findUnique({
            where: {
                title,
            },
            select: {
                title: <span class="hljs-literal">true</span>,
                description: <span class="hljs-literal">true</span>,
                price: <span class="hljs-literal">true</span>,
                quantity: <span class="hljs-literal">true</span>,
                image: <span class="hljs-literal">true</span>,
            },
        });
        <span class="hljs-keyword">if</span> (!product) {
            <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ message: <span class="hljs-string">`Product not found`</span> });
        }
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({ product });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({
            message: <span class="hljs-string">"Something went wrong!! Please try again after sometime"</span>,
        });
    }
};

<span class="hljs-keyword">const</span> handler = nc({ attachParams: <span class="hljs-literal">true</span> }).get(getSingleProduct);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>In the above snippet, we are doing the following:</p>
<ol>
<li><p>By creating a file named <code>[title].ts</code> under <code>src/pages/api/products</code> we are informing NextJS to convert this to the <code>/api/products/[title]</code> API.</p>
</li>
<li><p>The <code>[title]</code> is the product title from the product table.</p>
</li>
<li><p>Using the <code>next-connect</code> library we are making sure that only the <code>get</code> operation is allowed for the <code>getSingleProduct</code> function.</p>
</li>
<li><p>Under this function, we are querying the database using the <code>findUnique</code> query based on the title.</p>
</li>
</ol>
<p>Update the URL in Postman to:</p>
<pre><code class="lang-shell">http://localhost:3000/api/products/Practical Gorgeous Fresh Shoes
</code></pre>
<p>Here <code>Practical Gorgeous Fresh Shoes</code> is the title of the product we want to get. You can replace it with any product title from your database.</p>
<p>You'll get the following response:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.12.35-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Single Product Resp</em></p>
<h3 id="heading-how-to-create-the-all-category-and-single-category-uis">How to Create the All Category and Single Category UIs:</h3>
<p>Under <code>src/pages/_app.tsx</code>, replace the existing code with the following:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { QueryClientProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> { ReactQueryDevtools } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query-devtools"</span>;
<span class="hljs-keyword">import</span> type { AppProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/app"</span>;
<span class="hljs-keyword">import</span> queryClient <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/query"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"../styles/globals.css"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{ Component, pageProps }: AppProps</span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">QueryClientProvider</span> <span class="hljs-attr">client</span>=<span class="hljs-string">{queryClient}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ReactQueryDevtools</span> <span class="hljs-attr">initialIsOpen</span>=<span class="hljs-string">{false}</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Component</span> {<span class="hljs-attr">...pageProps</span>} /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">QueryClientProvider</span>&gt;</span></span>
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp;
</code></pre>
<p>Here we are wrapping all our components with React QueryClient Provider. But we also need to pass in the Client Context.</p>
<p>Under the <code>src/lib</code> directory create a new file named <code>query.ts</code> and copy-paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { QueryClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;

<span class="hljs-keyword">const</span> queryClient = <span class="hljs-keyword">new</span> QueryClient();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> queryClient;
</code></pre>
<p>We are initiating a new <code>QueryClient</code> object and assigning it to the <code>queryClient</code> variable and exporting it as default. The reason we do this is that in this way we get to keep the <code>queryClient</code> object as a global context.</p>
<p>Under <code>src/pages/index.tsx</code>, replace the existing code with the following:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> type { NextPage } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Navbar"</span>;
<span class="hljs-keyword">import</span> ProductGrid <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/ProductGrid"</span>;
<span class="hljs-keyword">import</span> Skelton <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Skelton"</span>;

<span class="hljs-keyword">const</span> Home: NextPage = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> getAllCategories = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> respJSON = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/categories"</span>);
            <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> respJSON.json();
            <span class="hljs-keyword">return</span> resp;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">throw</span> error;
        }
    };

    <span class="hljs-keyword">const</span> { isLoading, data } = useQuery(
        [<span class="hljs-string">"AllCategoreiesWithProducts"</span>],
        getAllCategories
    );

    <span class="hljs-keyword">const</span> categories = data?.categories;

    <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">Head</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>All Products<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"All Products"</span> /&gt;</span>
                <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>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-auto"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>
                {isLoading ? (
                    <span class="hljs-tag">&lt;<span class="hljs-name">Skelton</span> /&gt;</span>
                ) : (
                    <span class="hljs-tag">&lt;&gt;</span>
                        {categories &amp;&amp; categories?.length &gt; 0 &amp;&amp; (
                            <span class="hljs-tag">&lt;<span class="hljs-name">ProductGrid</span>
                                <span class="hljs-attr">showLink</span>=<span class="hljs-string">{true}</span>
                                <span class="hljs-attr">categories</span>=<span class="hljs-string">{categories}</span>
                            /&gt;</span>
                        )}
                    <span class="hljs-tag">&lt;/&gt;</span>
                )}
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
        &lt;/div&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home;
</code></pre>
<p>Let's understand our code.</p>
<p>Here we are fetching the data from the <code>/api/categories</code> endpoint that we wrote earlier. We are using <code>useQuery</code> to cache this data with the key <code>AllCategoreiesWithProducts</code>.</p>
<p>But there are three components that we haven't created yet. Let's create those and understand each one.</p>
<p>Under the <code>src</code> directory, create a <code>components</code> directory. Under the newly created <code>components</code> directory, create three files named <code>Navbar.tsx</code>, <code>ProductGrid.tsx</code> and <code>Skelton.tsx</code>.</p>
<p>Under <code>Navbar.tsx</code> copy-paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> NextLink <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative bg-white mx-6"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-between pt-6 md:justify-start md:space-x-10"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex justify-start lg:w-0 lg:flex-1"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-2xl"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">NextLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"cursor-pointer"</span>&gt;</span>
                            Ecomm App
                        <span class="hljs-tag">&lt;/<span class="hljs-name">NextLink</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
                <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<p>Here we have created an <code>h1</code> with the text as Ecomm App. We have wrapped this text around <code>NextLink</code> and set the location as <code>/</code>. So when user clicks on this, they will be redirected to the home page.</p>
<p>Under <code>ProductGrid.tsx</code> copy-paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> NextImage <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-keyword">import</span> NextLink <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { AiOutlineRight } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/ai"</span>;
<span class="hljs-keyword">import</span> { useInView } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-intersection-observer"</span>;
<span class="hljs-keyword">import</span> { TApiAllCategoriesResp } <span class="hljs-keyword">from</span> <span class="hljs-string">"../types"</span>;

interface IProductGrid <span class="hljs-keyword">extends</span> TApiAllCategoriesResp {
    <span class="hljs-attr">showLink</span>: boolean;
    hasMore?: boolean;
    loadMoreFun?: <span class="hljs-built_in">Function</span>;
}

<span class="hljs-keyword">const</span> ProductGrid = <span class="hljs-function">(<span class="hljs-params">props: IProductGrid</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> { categories, showLink, loadMoreFun, hasMore } = props;
    <span class="hljs-keyword">const</span> { ref, inView } = useInView();

    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">if</span> (inView) {
            <span class="hljs-keyword">if</span> (loadMoreFun) loadMoreFun();
        }
    }, [inView, loadMoreFun]);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white"</span>&gt;</span>
            {categories.map((category) =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-12  p-6"</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{category.name}</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row justify-between"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"inline-flex items-center rounded-md bg-sky-800 px-8 py-2 text-md font-medium text-white"</span>&gt;</span>
                            {category.name}
                        <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                        {showLink &amp;&amp; (
                            <span class="hljs-tag">&lt;<span class="hljs-name">NextLink</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">category</span>/${<span class="hljs-attr">category.id</span>}`}&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row gap-2 underline hover:cursor-pointer items-center"</span>&gt;</span>
                                    View More
                                    <span class="hljs-tag">&lt;<span class="hljs-name">AiOutlineRight</span> /&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">NextLink</span>&gt;</span>
                        )}
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-6  grid grid-cols-1 gap-y-10 gap-x-6 xl:gap-x-8 sm:grid-cols-2 lg:grid-cols-4"</span>&gt;</span>
                        {category?.products.map((product) =&gt; (
                            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
                                <span class="hljs-attr">className</span>=<span class="hljs-string">"p-6 group rounded-lg border border-gray-200 bg-neutral-200"</span>
                                <span class="hljs-attr">key</span>=<span class="hljs-string">{product.title}</span>
                            &gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"min-h-80 w-full overflow-hidden rounded-md group-hover:opacity-75 lg:aspect-none lg:h-80"</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">NextImage</span>
                                        <span class="hljs-attr">priority</span>=<span class="hljs-string">{true}</span>
                                        <span class="hljs-attr">layout</span>=<span class="hljs-string">"responsive"</span>
                                        <span class="hljs-attr">width</span>=<span class="hljs-string">"25"</span>
                                        <span class="hljs-attr">height</span>=<span class="hljs-string">"25"</span>
                                        <span class="hljs-attr">src</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">product.image</span>}`}
                                        <span class="hljs-attr">alt</span>=<span class="hljs-string">{product.title}</span>
                                        <span class="hljs-attr">className</span>=<span class="hljs-string">"h-full w-full object-cover object-center lg:h-full lg:w-full"</span>
                                    /&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative mt-2"</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm font-medium text-gray-900"</span>&gt;</span>
                                        {product.title}
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-1 text-sm text-gray-500"</span>&gt;</span>
                                        {product.price}
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-6"</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">NextLink</span>
                                        <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">product</span>/${<span class="hljs-attr">product.title</span>}`}
                                    &gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative flex items-center justify-center rounded-md border border-transparent bg-sky-800 py-2 px-8 text-sm font-medium text-white hover:bg-sky-900 hover:cursor-pointer"</span>&gt;</span>
                                            View More Details
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">NextLink</span>&gt;</span>
                                <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    {!showLink &amp;&amp; (
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex items-center justify-center mt-8"</span>&gt;</span>
                            {hasMore ? (
                                <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                                    <span class="hljs-attr">ref</span>=<span class="hljs-string">{ref}</span>
                                    <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
                                    <span class="hljs-attr">className</span>=<span class="hljs-string">"inline-flex items-center rounded-md border border-transparent bg-sky-800 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-900"</span>
                                &gt;</span>
                                    Loading...
                                <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                            ) : (
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"border-l-4 border-yellow-400 bg-yellow-50 p-4 w-full"</span>&gt;</span>
                                    <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>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"ml-3"</span>&gt;</span>
                                            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-yellow-700"</span>&gt;</span>
                                                You have viewed all the Products
                                                under this category.
                                            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                                        <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 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>
                    )}
                    {showLink &amp;&amp; (
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full border-b border-gray-300 mt-24"</span> /&gt;</span>
                    )}
                <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>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ProductGrid;
</code></pre>
<p>Here we have created a grid that will show 1 column for the base screen. For the sm screen it will show 2 columns and for the lg screen it will show 4 columns.</p>
<p>Under this, we have a single card which has a Title, Price, and View More Details button. The View More Details button redirects the user to a single product page which will create a bit later.</p>
<p>Apart from that, we are using the <code>useInView</code> hook from the <code>react-intersection-observer</code> library to find the user's cursor on the screen. This ref is attached to a <code>Loading...</code> button and once user is near it then we execute the <code>loadMoreFn</code> function.</p>
<p>It makes an API call to the server to get the next twelve rows from the last cursor.</p>
<p>Under <code>Skelton.tsx</code> copy-paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> Skelton = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-12 h-8 w-40 rounded-lg bg-gray-200"</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-6 grid grid-cols-1 gap-y-10 gap-x-6 sm:grid-cols-2 lg:grid-cols-4 xl:gap-x-8"</span>&gt;</span>
                {Array(16)
                    .fill(0)
                    .map((_val, index) =&gt; (
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"rounded-2xl bg-black/5 p-4"</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"h-60 rounded-lg bg-gray-200"</span> /&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"space-y-4 mt-6 mb-4"</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"h-3 w-3/5 rounded-lg bg-gray-200"</span> /&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"h-3 w-4/5 rounded-lg bg-gray-200"</span> /&gt;</span>
                            <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Skelton;
</code></pre>
<p>We are using <code>placeimg</code> to get fake images for our product and we are using the Next Image component which requires that it be mentioned under <code>next.config.js</code>.</p>
<p>Replace the existing code in <code>next.config.js</code> with the following code:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('next').NextConfig}</span> </span>*/</span>
<span class="hljs-keyword">const</span> nextConfig = {
    <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">swcMinify</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">images</span>: {
        <span class="hljs-attr">domains</span>: [<span class="hljs-string">"placeimg.com"</span>],
    },
};

<span class="hljs-built_in">module</span>.exports = nextConfig
</code></pre>
<p>We will need to restart our server. Use the following command to start your development server again:</p>
<pre><code class="lang-shell">npm run dev
</code></pre>
<p>Open <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> and you'll see the following UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.31.29-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>All Products Page</em></p>
<p>Now let's create a single category page that the user can go to using a <code>View More</code> link.</p>
<p>Under <code>src/pages</code> create a directory named <code>category</code>. Under this directory create a file named <code>[id].tsx</code> and copy paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { useInfiniteQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/Navbar"</span>;
<span class="hljs-keyword">import</span> ProductGrid <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/ProductGrid"</span>;
<span class="hljs-keyword">import</span> Skelton <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/Skelton"</span>;

<span class="hljs-keyword">const</span> SingleCategory = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> getSingleCategory = <span class="hljs-keyword">async</span> ({ pageParam = <span class="hljs-literal">null</span> }) =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">let</span> url = <span class="hljs-string">`/api/categories/<span class="hljs-subst">${router.query.id}</span>`</span>;
            <span class="hljs-keyword">if</span> (pageParam) {
                url += <span class="hljs-string">`?cursorId=<span class="hljs-subst">${pageParam}</span>`</span>;
            }
            <span class="hljs-keyword">const</span> respJSON = <span class="hljs-keyword">await</span> fetch(url);
            <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> respJSON.json();
            <span class="hljs-keyword">return</span> resp;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">throw</span> error;
        }
    };

    <span class="hljs-keyword">const</span> { isLoading, data, fetchNextPage, isError } = useInfiniteQuery(
        [<span class="hljs-string">`singleCategory <span class="hljs-subst">${router.query.id <span class="hljs-keyword">as</span> string}</span>`</span>],
        getSingleCategory,
        {
            <span class="hljs-attr">enabled</span>: !!router.query.id,
            <span class="hljs-attr">getNextPageParam</span>: <span class="hljs-function">(<span class="hljs-params">lastPage</span>) =&gt;</span> {
                <span class="hljs-keyword">const</span> nextCursor =
                    lastPage?.category?.products[
                        lastPage?.category?.products?.length - <span class="hljs-number">1</span>
                    ]?.id;
                <span class="hljs-keyword">return</span> nextCursor;
            },
        }
    );

    <span class="hljs-keyword">const</span> allProductsWithCategory: any = {
        <span class="hljs-attr">name</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">products</span>: [],
        <span class="hljs-attr">hasMore</span>: <span class="hljs-literal">true</span>,
    };

    data?.pages.map(<span class="hljs-function">(<span class="hljs-params">page</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (page?.category) {
            <span class="hljs-keyword">if</span> (page.category?.name) {
                allProductsWithCategory.name = page.category?.name;
            }
            <span class="hljs-keyword">if</span> (page.category?.products &amp;&amp; page.category?.products.length &gt; <span class="hljs-number">0</span>) {
                allProductsWithCategory.products.push(
                    ...page.category?.products
                );
            }
        }
        <span class="hljs-keyword">return</span> page?.category;
    });

    <span class="hljs-keyword">if</span> (data?.pages[data?.pages.length - <span class="hljs-number">1</span>]?.category?.products.length === <span class="hljs-number">0</span>) {
        allProductsWithCategory.hasMore = <span class="hljs-literal">false</span>;
    }

    <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">Head</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>
                    {isLoading
                        ? "Loading..."
                        : `All ${allProductsWithCategory?.name} Product`}
                <span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
                    <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
                    <span class="hljs-attr">content</span>=<span class="hljs-string">"Generated by create next app"</span>
                /&gt;</span>
                <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>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-auto"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>
                {isLoading ? (
                    <span class="hljs-tag">&lt;<span class="hljs-name">Skelton</span> /&gt;</span>
                ) : (
                    <span class="hljs-tag">&lt;&gt;</span>
                        {allProductsWithCategory &amp;&amp;
                            allProductsWithCategory.products.length &gt; 0 &amp;&amp; (
                                <span class="hljs-tag">&lt;<span class="hljs-name">ProductGrid</span>
                                    <span class="hljs-attr">hasMore</span>=<span class="hljs-string">{allProductsWithCategory.hasMore}</span>
                                    <span class="hljs-attr">showLink</span>=<span class="hljs-string">{false}</span>
                                    <span class="hljs-attr">categories</span>=<span class="hljs-string">{[allProductsWithCategory]}</span>
                                    <span class="hljs-attr">loadMoreFun</span>=<span class="hljs-string">{fetchNextPage}</span>
                                /&gt;</span>
                            )}
                    <span class="hljs-tag">&lt;/&gt;</span>
                )}
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
        &lt;/div&gt;
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SingleCategory;
</code></pre>
<p>Here we are calling the <code>/api/categories/[id]</code> API to get the latest twelve products for that category id.</p>
<p>We are using the <code>useInfiniteQuery</code> hook from <code>react query</code> to fetch the data. This hook is useful for cursor-based pagination. We will be using the <code>ProductGrid</code> component that we created earlier.</p>
<p>Open <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a>, click on the View More link for any of the category, and you'll see the following UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.38.25-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Single Category Page</em></p>
<p>The difference between the previous UI and the current is that we now don't have the View More Link in the top right corner. Also, when you scroll below, you'll get more products for that category.</p>
<p>Once we scroll through all the products in that category we will see the following warning alert:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.39.44-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-implement-single-product-ui-and-stripe-checkout">How to Implement Single Product UI and Stripe Checkout.</h2>
<p>In this section, we'll implement the following functionality:</p>
<ol>
<li><p>Create Single Product UI</p>
</li>
<li><p>Create Stripe Checkout</p>
</li>
</ol>
<p>You can find the <strong>eCommerce</strong> website <strong>code</strong> implemented in this section at this <a target="_blank" href="https://github.com/Sharvin26/Ecommerce-Website-ReactJS-TailwindCSS-PlanetScale-Stripe/tree/e4b5426423358479a5bbe91ba17b3febacd5e4a3">commit</a>.</p>
<h3 id="heading-how-to-create-the-single-product-ui">How to Create the Single Product UI:</h3>
<p>Under the <code>src/pages</code> directory create a directory named <code>product</code>.</p>
<p>Under this directory create a file named <code>[title].tsx</code> and copy-paste the following code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { loadStripe, Stripe } <span class="hljs-keyword">from</span> <span class="hljs-string">"@stripe/stripe-js"</span>;
<span class="hljs-keyword">import</span> { useMutation, useQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"@tanstack/react-query"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> NextImage <span class="hljs-keyword">from</span> <span class="hljs-string">"next/image"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/Navbar"</span>;
<span class="hljs-keyword">import</span> Skelton <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/Skelton"</span>;

<span class="hljs-keyword">const</span> stripePromiseclientSide = loadStripe(
    process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!
);

<span class="hljs-keyword">const</span> SingleProduct = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> router = useRouter();

    <span class="hljs-keyword">const</span> getSingleProduct = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> title = router?.query?.title;

            <span class="hljs-keyword">const</span> respJSON = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/products/<span class="hljs-subst">${title}</span>`</span>);
            <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> respJSON.json();
            <span class="hljs-keyword">return</span> resp;
        } <span class="hljs-keyword">catch</span> (error) {
            <span class="hljs-keyword">throw</span> error;
        }
    };

    <span class="hljs-keyword">const</span> { mutate, <span class="hljs-attr">isLoading</span>: mutationIsLoading } = useMutation(
        <span class="hljs-keyword">async</span> (body: any) =&gt; {
            <span class="hljs-keyword">try</span> {
                <span class="hljs-keyword">const</span> respJSON = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/create-checkout-session"</span>, {
                    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
                    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(body),
                });
                <span class="hljs-keyword">const</span> resp = <span class="hljs-keyword">await</span> respJSON.json();
                <span class="hljs-keyword">const</span> stripe = (<span class="hljs-keyword">await</span> stripePromiseclientSide) <span class="hljs-keyword">as</span> Stripe;
                <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> stripe.redirectToCheckout({
                    <span class="hljs-attr">sessionId</span>: resp.id,
                });
                <span class="hljs-keyword">return</span> result;
            } <span class="hljs-keyword">catch</span> (error) {
                <span class="hljs-keyword">throw</span> error;
            }
        }
    );

    <span class="hljs-keyword">const</span> { data, isLoading } = useQuery(
        [<span class="hljs-string">`singleProduct, <span class="hljs-subst">${router?.query?.title}</span>`</span>],
        getSingleProduct,
        {
            <span class="hljs-attr">enabled</span>: !!router?.query?.title,
        }
    );

    <span class="hljs-keyword">const</span> product = data?.product;

    <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">Head</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{isLoading ? "Loading..." : `${product?.title}`}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
                    <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span>
                    <span class="hljs-attr">content</span>=<span class="hljs-string">"Generated by create next app"</span>
                /&gt;</span>
                <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>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-6 md:mx-auto"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>
                {isLoading ? (
                    <span class="hljs-tag">&lt;<span class="hljs-name">Skelton</span> /&gt;</span>
                ) : (
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-white"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-6 pb-16 sm:pb-24"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mx-auto mt-8"</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-col md:flex-row gap-x-8"</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"min-h-80 w-full overflow-hidden rounded-md group-hover:opacity-75 lg:aspect-none lg:h-80"</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">NextImage</span>
                                            <span class="hljs-attr">layout</span>=<span class="hljs-string">"responsive"</span>
                                            <span class="hljs-attr">width</span>=<span class="hljs-string">"25"</span>
                                            <span class="hljs-attr">height</span>=<span class="hljs-string">"25"</span>
                                            <span class="hljs-attr">src</span>=<span class="hljs-string">{</span>`${<span class="hljs-attr">product.image</span>}`}
                                            <span class="hljs-attr">alt</span>=<span class="hljs-string">{product.title}</span>
                                            <span class="hljs-attr">className</span>=<span class="hljs-string">"h-full w-full object-cover object-center lg:h-full lg:w-full"</span>
                                        /&gt;</span>
                                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"lg:col-span-5 lg:col-start-8 mt-8 md:mt-0"</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl font-medium text-gray-900 "</span>&gt;</span>
                                            {product.title}
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl font-light text-gray-700 mt-4"</span>&gt;</span>
                                            {product.description}
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-xl font-normal text-gray-500 mt-4"</span>&gt;</span>
                                            USD {product.price}
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                                        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                                            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span>
                                                mutate({
                                                    title: product.title,
                                                    image: product.image,
                                                    description:
                                                        product.description,
                                                    price: product.price,
                                                })
                                            }
                                            disabled={mutationIsLoading}
                                            type="button"
                                            className="inline-flex items-center rounded-md border border-transparent bg-sky-800 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-900  mt-4"
                                        &gt;
                                            Buy Now
                                        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                                    <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 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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                )}
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SingleProduct;
</code></pre>
<p>Here we are calling <code>/api/products/title</code> API to get the latest product. We have also created Stripe Interface for creating a checkout method once a user clicks the Buy Now button.</p>
<p>Once the user clicks the Buy Now button, we make an API call to <code>/api/create-checkout-session</code> using the <code>useMutation</code> hook. On a successful response, we redirect the user to the Stripe default checkout page.</p>
<p>Open <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> and click on View More Details button for any product.</p>
<p>You'll see the following UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-5.54.40-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Single Product Page</em></p>
<p>You can also visit this page by clicking on the View More link and then clicking on the View More Details button for any product.</p>
<h3 id="heading-how-to-set-up-stripe-checkout">How to Set Up Stripe Checkout:</h3>
<p>To set up Stripe Checkout, we need to add a new file under the lib directory.</p>
<p>Create a new file named <code>stripe.ts</code> under the lib directory and copy paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> Stripe <span class="hljs-keyword">from</span> <span class="hljs-string">"stripe"</span>;

<span class="hljs-keyword">const</span> stripeServerSide = <span class="hljs-keyword">new</span> Stripe(process.env.STRIPE_SECRET_KEY!, {
    apiVersion: <span class="hljs-string">"2022-08-01"</span>,
});

<span class="hljs-keyword">export</span> { stripeServerSide };
</code></pre>
<p>Here we are creating server-side instances of Stripe. Now under the <code>pages/api</code> directory, create a new file named <code>create-checkout-session.ts</code> and copy-paste the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> currency <span class="hljs-keyword">from</span> <span class="hljs-string">"currency.js"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { NextApiRequest, NextApiResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> nc <span class="hljs-keyword">from</span> <span class="hljs-string">"next-connect"</span>;
<span class="hljs-keyword">import</span> { stripeServerSide } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/stripe"</span>;
<span class="hljs-keyword">import</span> { TApiErrorResp } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../types"</span>;

<span class="hljs-keyword">const</span> checkoutSession = <span class="hljs-keyword">async</span> (
    req: NextApiRequest,
    res: NextApiResponse&lt;<span class="hljs-built_in">any</span> | TApiErrorResp&gt;
) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> host = req.headers.origin;
        <span class="hljs-keyword">const</span> referer = req.headers.referer;
        <span class="hljs-keyword">const</span> body = <span class="hljs-built_in">JSON</span>.parse(req.body);
        <span class="hljs-keyword">const</span> formatedPrice = currency(body.price, {
            precision: <span class="hljs-number">2</span>,
            symbol: <span class="hljs-string">""</span>,
        }).multiply(<span class="hljs-number">100</span>);
        <span class="hljs-keyword">const</span> session = <span class="hljs-keyword">await</span> stripeServerSide.checkout.sessions.create({
            mode: <span class="hljs-string">"payment"</span>,
            payment_method_types: [<span class="hljs-string">"card"</span>],
            line_items: [
                {
                    price_data: {
                        currency: <span class="hljs-string">"usd"</span>,
                        product_data: {
                            name: body?.title,
                            images: [body.image],
                            description: body?.description,
                        },
                        unit_amount_decimal: formatedPrice.toString(),
                    },
                    quantity: <span class="hljs-number">1</span>,
                },
            ],
            success_url: <span class="hljs-string">`<span class="hljs-subst">${host}</span>/thank-you`</span>,
            cancel_url: <span class="hljs-string">`<span class="hljs-subst">${referer}</span>?status=cancel`</span>,
        });
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">200</span>).json({ id: session.id });
    } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">500</span>).json({
            message: <span class="hljs-string">"Something went wrong!! Please try again after sometime"</span>,
        });
    }
};

<span class="hljs-keyword">const</span> handler = nc({ attachParams: <span class="hljs-literal">true</span> }).post(checkoutSession);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>In the snippet above we are doing the following:</p>
<ol>
<li><p>Formatting the price with precision as two and multiplying it by 100 as Stripe expects the unit_amount in cents by default.</p>
</li>
<li><p>We create the session and pass the id as the response.</p>
</li>
</ol>
<p>Now we need to create another page named <code>thank-you.tsx</code> under the <code>src/pages</code> directory. Once the product purchase is successful, Stripe Checkout will redirect to this page.</p>
<p>Copy-paste the following code under this file:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;
<span class="hljs-keyword">import</span> { HiCheckCircle } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/hi"</span>;
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/Navbar"</span>;

<span class="hljs-keyword">const</span> ThankYou = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> router = useRouter();
    <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">Head</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Thank You<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"All Products"</span> /&gt;</span>
                <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>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Head</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container mx-auto"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"rounded-md bg-green-50 p-4 mt-8"</span>&gt;</span>
                    <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>
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex-shrink-0"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">HiCheckCircle</span>
                                <span class="hljs-attr">className</span>=<span class="hljs-string">"h-5 w-5 text-green-400"</span>
                                <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>
                            /&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"ml-3"</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm font-medium text-green-800"</span>&gt;</span>
                                Order Placed
                            <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"mt-2 text-sm text-green-700"</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
                                    Thank you for your Order. We have placed the
                                    order and your email will recieve further
                                    details.
                                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
                                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> router.push("/")}
                                type="button"
                                className="inline-flex items-center rounded-md border border-transparent bg-sky-800 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-sky-900 mt-4"
                            &gt;
                                Continue Shopping
                            <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
                        <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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ThankYou;
</code></pre>
<p>Open <a target="_blank" href="http://localhost:3000/">http://localhost:3000/</a> and click on the View More Details button of any product.</p>
<p>Click on the Buy Now button and you'll be redirected to the following page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-6.09.52-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Add all the details for the test card. You can use any card from this <a target="_blank" href="https://stripe.com/docs/testing?numbers-or-method-or-token=card-numbers#cards">link</a>. Stripe provides various test cards which work only during Test Mode. Once you click on Pay and payment processing happens, Stripe will redirect you to the success page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-09-at-6.14.51-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Thank You Page</em></p>
<h2 id="heading-how-to-deploy-the-website-to-production">How to Deploy the Website to Production</h2>
<p>In this section, we'll implement the following functionality:</p>
<ol>
<li><p>Promote our PlanetScale branch to Main.</p>
</li>
<li><p>Deploy the app on Vercel.</p>
</li>
</ol>
<h3 id="heading-how-to-promote-the-planetscale-branch-to-main">How to Promote the PlanetScale Branch to Main:</h3>
<p>To promote the branch to main, we can do it either via the terminal or dashboard. I'll use the dashboard for this tutorial.</p>
<p>Go to your project on PlanetScale and you'll find the following message on the dashboard:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.19.14-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>PlanetScale Database Promotion</em></p>
<p>Let's click on the Promote a branch to production button and you'll get a confirmation model. Click on the Promote branch button. Once done you'll get a toast with a success message.</p>
<h3 id="heading-how-to-deploy-to-vercel">How to Deploy to Vercel:</h3>
<p>If you don't have an account on Vercel, you can create one <a target="_blank" href="https://vercel.com/signup">here</a>.</p>
<p>You can create a project on GitHub and push it to the Main branch. If you don't know how, you can check out <a target="_blank" href="https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-locally-hosted-code-to-github#adding-a-local-repository-to-github-using-git">this tutorial</a>.</p>
<p>Once the project is pushed on GitHub, go to Vercel and create an Add New button and select Project from the drop down.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.26.04-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Add New Project Vercel</em></p>
<p>You'll get the following the UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.26.39-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Select Git Provider Vercel</em></p>
<p>As we have pushed the code on GitHub, let's click on the Continue with GitHub button. You'll get the following UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.28.03-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Select Git Repository Vercel</em></p>
<p>Click on Import and you'll get the following UI:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.29.39-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Configure Project Vercel</em></p>
<p>Click on the Environment Variables and add these three there:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.31.02-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Add NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_SECRET_KEY, and DATABASE_URL</em></p>
<p>Once done click the Deploy button. You'll get the following UI once the deployment starts:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.31.52-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Deploying Vercel</em></p>
<p>Once deployed, Vercel will give you a Unique URL.</p>
<p>Visit this URL and you'll find its failing. Let's go to the deployment &gt; functions and you'll see the following error:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/10/Screenshot-2022-10-10-at-4.38.08-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Prisma generate Fails</em></p>
<p>We need to update our build command in <code>package.json</code> as follows:</p>
<pre><code class="lang-shell">"build": "npx prisma generate &amp;&amp; next build",
</code></pre>
<p>Push the code again to the Git repository and you'll find that Vercel starts redeploying your project.</p>
<p>Once the deployment is done, you can visit your application URL and you'll find it shows all your products.</p>
<p>With this, we have created our production-ready eCommerce application. If you have built the website along with the tutorial, then a very big congratulations to you on this achievement.</p>
<h2 id="heading-thank-you-for-reading"><strong>Thank you for reading!</strong></h2>
<p>Feel free to connect with me on <a target="_blank" href="https://twitter.com/sharvinshah26">Twitter</a> and <a target="_blank" href="https://github.com/Sharvin26">Github</a>.</p>
<p>If you want any project to be developed or want to consult with me, you can DM me on my Twitter (<a target="_blank" href="https://twitter.com/sharvinshah26">@sharvinshah26</a> ).</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Shopify Email Automation – How to Automatically Send Emails ]]>
                </title>
                <description>
                    <![CDATA[ Email automation is not something new in the e-commerce space. And it's a pretty simple and straightforward concept. Email automation is all about sending emails to customers based on events that occur on your website, like abandoned cart emails, ord... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/shopify-email-automation/</link>
                <guid isPermaLink="false">66d45e46d14641365a0508b6</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ email marketing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shopify ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Edan Ben-Atar ]]>
                </dc:creator>
                <pubDate>Thu, 31 Mar 2022 17:00:18 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/Shopify-Email-Automation.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Email automation is not something new in the e-commerce space. And it's a pretty simple and straightforward concept.</p>
<p>Email automation is all about sending emails to customers based on events that occur on your website, like abandoned cart emails, order confirmation emails, shipping confirmation emails, and so on.</p>
<p>The result? You save time by not having to do this work manually.</p>
<h2 id="heading-why-email-automation-is-important">Why Email Automation is Important</h2>
<p>Email is still one of the most effective marketing channels. Email marketing has a higher ROI than any other form of <strong>digital advertising</strong>, and even with all the new tools available to us, it’s still the most cost-effective channel.</p>
<p>Adding automation to your emails can take them even further. Emails are automatically sent based on triggers you set up so that they reach your customers when they’re most likely to convert. Automation also allows you to send messages at scale without any manual work.</p>
<p>Email also provides a direct line of communication with your customers and, if done correctly, can be something people look forward to opening.</p>
<p>It’s a powerful tool, but it’s not just about sending more emails — it’s about sending better ones.</p>
<p><a target="_blank" href="https://www.weblime.com/stories/shopify-automation/">Shopify email automation</a> lets you send personalized emails to each customer as they interact with your business individually and receive follow-up messaging based on their purchase behavior, browsing activity, and even gives them recommendations based on their buying history.</p>
<p>This post will cover different types of email automation, what their purpose is, and how it can benefit the customers and e-commerce stores.</p>
<h2 id="heading-why-is-shopify-email-automation-essential">Why is Shopify email automation essential?</h2>
<p>There are many reasons for implementing email automation on your Shopify store. Here are just a few:</p>
<ul>
<li><p>It saves time.</p>
</li>
<li><p>It increases engagement.</p>
</li>
<li><p>It improves conversion rates.</p>
</li>
<li><p>It enhances the customer experience.</p>
</li>
<li><p>It saves time.</p>
</li>
</ul>
<h3 id="heading-saving-time">Saving time</h3>
<p>Email automation can save you a lot of time by automating tedious, repetitive tasks that would otherwise need to be done manually and individually to each recipient.</p>
<p>This is especially useful when sending a large number of emails in a short amount of time, such as in an event invitation or email marketing campaign.</p>
<h3 id="heading-establishing-trust">Establishing trust</h3>
<p>You can use email automation to establish trust with your customers, which will help increase engagement with your brand and business.</p>
<p>You can also use it to build relationships with your subscribers and increase the likelihood that they'll make a purchase or recommend you to their friends and family.</p>
<h3 id="heading-improving-conversion-rates">Improving conversion rates</h3>
<p>As a business owner, you have to think about how you can improve your conversion rate constantly. This is especially true if you're trying to grow your business.</p>
<p>One of the biggest benefits of using <strong>email marketing automation</strong> software is that it can improve your conversion rate.</p>
<p>When used correctly, email automation can help you reach more people in less time. It can also help you get them interested enough about what you have to offer so that they actually buy from you instead of just browsing around aimlessly on your website.</p>
<h2 id="heading-shopify-email-marketing-automation-software">Shopify email marketing automation software</h2>
<h3 id="heading-klavyio">Klavyio</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/image-162.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Klavyio is a tool that you can use to set up automated email workflows. It’s a third-party tool (not built by Shopify) that integrates with your store and helps you build stronger relationships with your customers, as well as increase sales.</p>
<p>Using Klavyio, you can create emails that are sent automatically when a customer purchases a product from your store or when they abandon their cart. You can also set up emails to be sent at different points after someone spends time browsing your site, or even if no one has visited for a while.</p>
<p>Not sure how you would use Klavyio in your store? Here are some ideas:</p>
<ul>
<li><p>Send an abandoned cart recovery email after someone leaves items in their cart but doesn't purchase them. You can use the personalization features in Klavyio to show the specific products they left behind, so they're more likely to come back and buy.</p>
</li>
<li><p>Send a newsletter about new products or store promotions to your existing customers. This is a great way to let people know about things happening in your store.</p>
</li>
<li><p>Start building a segment of customers who have purchased from you before. Then send them exclusive offers that aren't available to anyone else.</p>
</li>
</ul>
<p>Here's how it works:</p>
<ul>
<li><p>Klaviyo takes all your customer data and allows you to build experience across email, owned channels and measure the results in revenue.</p>
</li>
<li><p>Klaviyo uses machine learning to analyze and group your customers into segments that are most likely to buy.</p>
</li>
<li><p>Klaviyo automatically creates personalized emails based on what each segment is most interested in.</p>
</li>
</ul>
<p>That way, you know exactly what to send and when to send it.</p>
<p>If a customer adds a product to their cart but doesn’t check out, the abandoned cart email is triggered after some time. This mail includes a list of products that were left behind in the shopping cart. This is when customers return to their carts and complete the transaction in many cases.</p>
<p>Someone who adds a product to their cart is a qualified lead. You can use the abandoned cart email automation process to bring them back with their cart and complete their transaction.</p>
<p>Abandoned carts are triggered after a set period of time, usually between 60 minutes and 24 hours. The product list in the mail reminds them what they were doing and leads them back to the checkout page.</p>
<h3 id="heading-zapier">Zapier</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/03/image-161.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Zapier is a web-based service that allows you to connect your favorite web apps and automate workflows—without the headache of learning how to code. It’s a great way to extend the functionality of existing apps.</p>
<p>Zapier uses a trigger-action model. A trigger starts a workflow, and an action ends it.</p>
<p>Zapier moves info between your web apps automatically so that you can focus on your most important work.</p>
<p>To use Zapier, you'll first need to select the app(s) you want to create an automation between. You can either search for your app (Shopify, Gmail, and so on) or scroll through the list of popular apps.</p>
<p>Here’s how Zapier can help you:</p>
<ol>
<li><p>Add new leads to a drip campaign</p>
</li>
<li><p>Trigger follow-up emails after a purchase</p>
</li>
<li><p>Send predefined email responses</p>
</li>
<li><p>Forward contact form submissions to your inbox</p>
</li>
<li><p>Add new contacts to your email list</p>
</li>
</ol>
<p>The best part? You don’t need any technical skills or a big budget. If you can point, click, and type, Zapier can help you automate your email marketing—and more.</p>
<p>For email marketing services, this means Zapier can listen for events and then trigger actions in other apps.</p>
<p>For example, when someone subscribes to your mailing list or when someone purchases something from your Shopify store, Zapier can be instructed to send the user's information to your CRM.</p>
<p>Or, if a new Google Sheets row is created with customer/user data—names, emails, phone numbers—Zapier can add that person as a subscriber to your mailing list automatically.</p>
<h3 id="heading-integromat">Integromat</h3>
<p>Integromat is a powerful workflow automation platform with a focus on scalability, reliability, and flexibility. It enables you to connect Shopify triggers and actions to other apps, web services, or API endpoints.</p>
<p>The platform offers a range of capabilities such as complete control over data flow, API creation, and management tools for developers, as well as scenario builder for non-technical users.</p>
<p>Integromat also allows you to automate complex processes that would otherwise require hiring experienced developers.</p>
<p>Here are some of the things you can do using this integration:</p>
<ul>
<li><p>Send out welcome emails, confirmations, and reviews as soon as your customers sign up to your store or place an order on it</p>
</li>
<li><p>Use different templates based on the product bought by each customer</p>
</li>
<li><p>Segment your subscribers based on their activity, location, and purchase history.</p>
</li>
</ul>
<h2 id="heading-takeaways">Takeaways</h2>
<p>Shopify email automation can be your sales partner if you implement it wisely.</p>
<p>Email automation will probably become your best friend in the long run, especially when it comes to marketing. And if you're having a hard time deciding which strategy to go for, then think of this: email automation is designed to help you improve your sales experience and, ultimately, increase sales.</p>
<p>And it's not just about marketing. Email automation can be used throughout the customer journey to improve their experience as well.</p>
<p>Now that you know more about <strong>Shopify email automation</strong>, there's no reason not to research and learn more about it – because there is definitely a lot for you out there.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Choose a CMS – WordPress vs Ghost vs Shopify ]]>
                </title>
                <description>
                    <![CDATA[ WordPress, Shopify, and Ghost are some of the most powerful and popular CMS platforms out there. And you might wonder why so many people use them. Well, it's because they're easy to use, cost-effective, and highly efficient. With WordPress, Shopify, ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/chose-a-cms-wordpress-vs-shopify-vs-ghost/</link>
                <guid isPermaLink="false">66d45e3f3a8352b6c5a2aa32</guid>
                
                    <category>
                        <![CDATA[ blog ]]>
                    </category>
                
                    <category>
                        <![CDATA[ cms ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Ghost ]]>
                    </category>
                
                    <category>
                        <![CDATA[ shopify ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WordPress ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Edan Ben-Atar ]]>
                </dc:creator>
                <pubDate>Wed, 02 Feb 2022 17:05:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/01/WordPress-vs-Ghost-vs-Shopify.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>WordPress, Shopify, and Ghost are some of the most powerful and popular CMS platforms out there.</p>
<p>And you might wonder why so many people use them. Well, it's because they're easy to use, cost-effective, and highly efficient.</p>
<p>With WordPress, Shopify, and Ghost, you can create a website from scratch in a matter of minutes.</p>
<p>Tailored to your needs, these <a target="_blank" href="https://www.weblime.com/stories/top-website-builders">powerful website builders</a> can help you achieve anything from building an e-commerce website to starting your own blog or showcasing your portfolio.</p>
<p>It's no wonder why so many businesses choose these platforms. But which one should you choose?</p>
<p>We can't simply say that one is better than the other, because there's no single solution that will work in every situation.</p>
<p>So in this article, we'll cover the three platforms and look at their strengths and weaknesses so that you can figure out what will work best for your particular situation.</p>
<h2 id="heading-if-you-want-to-build-a-blog-from-scratch-use-ghost">If you Want to Build a Blog From Scratch – Use Ghost</h2>
<p>If you are a blogger who is looking for an open-source platform, <a target="_blank" href="https://ghost.org/">Ghost</a> might be the perfect choice for you.</p>
<p>The fact that it’s open-source enables you to alter the code of the platform, customize it, and make it more suitable to your writing needs.</p>
<p>And even if you’re not a coder, you can get help from other users who are willing to contribute to the community by fixing bugs or adding new features.</p>
<p>Tutorials on how to install Ghost on your own web server are available online. You can also choose to go with a hosted plan, which allows you to use themes created by other users instead of relying on the default theme.</p>
<p>Ghost is a platform that is based on Node.js, and it runs using Node.js servers. It comes with a rich plugin system, allowing you to customize your blog to suit your own needs.</p>
<p>Ghost lets you to use Markdown instead of HTML for writing. This makes it easy for you to write directly in the browser window.</p>
<p>If you don’t know about Markdown, it is a lightweight markup language that allows you to write in rich text but with simpler code, as opposed to HTML.</p>
<p>In fact, one of the benefits of Ghost over other platforms is that your posts are written in Markdown rather than HTML or text files, which results in much cleaner code.</p>
<h3 id="heading-you-can-use-ghost-as-a-headless-cms">You Can Use Ghost as a Headless CMS</h3>
<p>Ghost has a flexible architecture, which means it can be used as a headless CMS. In fact, it's one of the most popular headless content management systems in the JAMStack.</p>
<p>A <a target="_blank" href="https://www.freecodecamp.org/news/what-is-headless-cms-explained/">headless CMS</a> allows you to build a front-end website or application, such as a mobile app, with an API that calls the CMS for its data. You can deploy the API and your website or app to separate servers or environments without having to change anything within your content.</p>
<p>Since the API delivers data from the CMS, you will have full control over that data, including permissions, tags, and categories. If you need to make changes to your content, you will only have to make those changes on one end rather than both.</p>
<p>Headless CMSs are often used for building websites and applications with JavaScript frameworks such as AngularJS and React.js​. However, if you need to render a dynamic site with multiple pages using a single API call, this type of CMS can be beneficial as well.</p>
<p>To use a headless CMS, you will need to learn how it works and how to utilize it through its API. This may require additional development resources, depending on your skill level.</p>
<p>This approach is gaining popularity among startups and agencies who want more control over the display of content while still retaining the ability to make updates through a central interface.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/ghost-screenshot-admin.png" alt="Screenshot of Ghost admin interface" width="600" height="400" loading="lazy"></p>
<h3 id="heading-advantages-of-using-ghost">Advantages of Using Ghost</h3>
<p>Ghost is a free, easy-to-use CMS that doesn’t require any kind of coding skills.</p>
<p>Along with simplicity, Ghost CMS comes with lots of out-of-the-box features which make it really appealing to bloggers and small businesses.</p>
<p>You can add multiple authors, set up collaborative writing, track analytics, create polls, customer support forums, manage templates, and much more using drag and drop features.</p>
<p>Things get interesting as you start using third-party apps on Ghost. There are numerous integrations available for installing apps like Google Analytics and Disqus comments.</p>
<p>Some other pros include:</p>
<ul>
<li><p><strong>It has a clean, minimalistic design</strong>. If you want your blog to be beautiful, then Ghost CMS is great for that.</p>
</li>
<li><p><strong>Complete tools</strong>. You get all the tools that are required in creating a blog, starting from the installation to setting up the theme with the drag and drop features.</p>
</li>
<li><p><strong>Customizable themes</strong>. The themes can be customized using the built-in theme editor that makes it really easy for anyone to modify them as per their needs.</p>
</li>
<li><p><strong>Ghost community</strong>. If you’re stuck, you can find help from other Ghost users by going over their forums and asking questions.</p>
</li>
</ul>
<h2 id="heading-if-you-want-to-take-your-website-to-the-next-level-use-wordpress">If You Want to Take Your Website to the Next Level – Use WordPress</h2>
<p>You can use <a target="_blank" href="https://wordpress.org/">WordPress</a> as a simple blogging platform, but it is also a fully-featured content management system (CMS). It allows you to build custom websites and blogs if you want to take your business to the next level.</p>
<p>Because of its popularity, there is an active support community and lots of free training materials.</p>
<p>Tutorials for the basic functionality and more advanced features are widely available online.</p>
<p>WordPress is free, but if you need help installing it or customizing it for your needs, you can purchase web hosting from a reputable company. Hosting costs vary depending on the type of hosting service you choose.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/how-to-get-started-with-wordpress/">You can install WordPress yourself</a> or pay someone to do it for you. The WordPress website provides instructions on how to install the software on different server platforms.</p>
<p>Once installed on your server, WordPress will allow you to create your own blog page. You have complete control over the look and feel of your site through the use of themes, which are pre-built designs for the layout of your pages.</p>
<p>Themes are available for purchase or free download from the WordPress website. You can also <a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-create-your-own-wordpress-theme-from-scratch/">design your own custom theme</a> if you're up to the challenge.</p>
<p>You might want a more complicated site than what is provided by the basic WordPress installation. In that case, you can hire a designer to build you a customized theme or using an existing theme as a framework and add your own custom features through plugins. Plugins extend the features of themes with special design elements and programming functions.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/WordPress-screen-themes.png" alt="Screenshot of WordPress themes in the admin UI" width="600" height="400" loading="lazy"></p>
<h3 id="heading-advantages-of-using-wordpress">Advantages of using WordPress</h3>
<p>WordPress is by far the most customizable option in this list. It features the most comprehensive library of themes and plugins, allows you to edit the source code quite easily, and even write your own custom functionalities.</p>
<p>You can think of WordPress as the CMS that falls between Ghost and Shopify, as it allows you to run an eCommerce store and a blog at the same time by simply installing a compatible theme.</p>
<p>WordPress lets you take your blog to the next level and scale your eCommerce store as your business grows.</p>
<p>The downside is that sooner or later you will need a developer’s help. This is because the more plugins you install, the slower your website will get, and users hate slow websites (not to mention search engines).</p>
<p>Some other pros include:</p>
<ul>
<li><p><strong>Extremely cheap to get started with</strong>. You can get a WordPress website up and running for free, and basic hosting plans start as low as $18.96 per year.</p>
</li>
<li><p><strong>Ability to assign user roles</strong>. At some point during your business growth cycle, you might need to bring in extra people to help you create, edit and publish content. WordPress comes with built-in user roles which will make your life a whole lot easier.</p>
</li>
<li><p><strong>Best marketing plugins on the market</strong>. You can install one of the many SEO plugins available in the plugin directory. Some examples include Yoast or Rank Math. These plugins help you optimize your content (like articles, pages, and even product pages) and increase your chances of outranking your competitors.</p>
</li>
<li><p><strong>A vast array of integrations</strong>. Chances are you will be using 3rd party SaaS products like Mailchimp or ActiveCampaign. Regardless of the service, you will find it extremely easy to integrate them with your website as almost all have created a WordPress plugin.</p>
</li>
<li><p><strong>The biggest online library of resources</strong>. The WordPress community is by far the largest which makes it easy to find answers to all your questions regarding your installation. There are tens of thousands of websites and forums focused solely on WordPress websites featuring tons of information.</p>
</li>
</ul>
<h2 id="heading-if-you-want-the-fastest-way-to-start-an-ecommerce-business-use-shopify">If You Want the Fastest Way to Start an eCommerce Business – Use Shopify</h2>
<p><a target="_blank" href="https://www.shopify.com/">Shopify</a> started in 2006 as an online store for snowboarding equipment. Back then, the CMS ecosystem was lacking a robust and easy-to-use solution, so the Shopify creators decided to code their own.</p>
<p>Needless to say, this was a hit. In the years that followed more and more stores have started to use the Shopify CMS. As of May 2021, more than 1.7 million businesses from some 175 countries use Shopify to run their businesses.</p>
<p>By far the simplest and fastest way to start an eCommerce business, Shopify is mainly used by nontechnical people that want to bring their brick and mortar stores online. In no more than a few minutes you can have your own website up and running and start accepting online payments.</p>
<p>But Shopify is also used by some of the biggest online stores on the Internet, which makes it a great option if you want to scale your business to the next level, and have the funds to do so.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/01/Shopify-screenshot.png" alt="Screenshot of Shopify UI" width="600" height="400" loading="lazy"></p>
<p>Some of Shopify’s advantages:</p>
<ul>
<li><p><strong>Extremely easy to get started with</strong>. The CMS was built with ease of use in mind. Anyone can get a store up and running in no time. You don’t have to know how to code or even how to connect a domain to your website as Shopify does all that for you.</p>
</li>
<li><p><strong>Compatible with most major third-party services</strong>. Shopify connects out of the box with the biggest third-party services, like Klaviyo for email marketing or Facebook Ads for paid advertising.</p>
</li>
<li><p><strong>Plenty of apps in the app store</strong>. If you feel that your store needs additional functionality, like bundle discounts, for example, you will be happy to hear that there are tons of apps that can help you achieve this. Just be careful and look at their cost as they can go up rather quickly.</p>
</li>
</ul>
<p>Some of Shopify’s disadvantages:</p>
<ul>
<li><p><strong>Can get pretty expensive</strong>. Shopify is extremely easy to start with, but that comes with a cost. The cheapest plan (Basic) starts at $29 per month, while the most expensive one (Advanced) reaches $299 per month. The main difference is the commission applied by Shopify on payments.</p>
</li>
<li><p><strong>High payment fees</strong>. You have to take into account that Shopify charges up to a 2% fee on your transactions (on the Basic plan). This commission decreases with the plan to a minimum of 0.5% (on the Advance plan). In comparison, WooCommerce doesn’t charge you a percentage of your sales.</p>
</li>
<li><p><strong>Shopify developers are expensive</strong>. If you want to customize your store beyond basic functionalities you will need the help of a developer, and they are not cheap. However, there are a multitude of apps available that can help you achieve your goals.</p>
</li>
<li><p><strong>Most Shopify Apps have a monthly cost</strong>. You can find free apps in the app store, but most of them have a monthly cost (compared to a fixed one-time fee charged by the vast majority of WordPress plugins).</p>
</li>
<li><p><strong>The blog functionality isn't great</strong>. Shopify is an eCommerce-centric platform so there’s no wonder that they haven’t spent too much time optimizing their blog functionality. Big stores choose to host their blogs on WordPress while keeping their storefront on Shopify.</p>
</li>
</ul>
<p>In conclusion, you can use Ghost to start your blog almost instantly, or Shopify to configure a fast and secure online store. And, if you want to get the best out of both worlds, you might want to go with WordPress.</p>
<p><a target="_blank" href="https://www.weblime.com/stories/the-ultimate-wordpress-guide">The ultimate WordPress guide</a> is a free resource that will help you understand the basics and help you launch your new website in no time, so be sure to give it a read before you make the big decision of choosing a specific platform.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up a Payment Gateway in Next.js and React with Razorpay and TailwindCSS ]]>
                </title>
                <description>
                    <![CDATA[ By Manu Arora If you have an e-commerce application, a payment gateway lets you process payments on your website on the fly.  With all the modern payment gateway solutions available these days, there are many ways you can integrate payments and charg... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/integrate-a-payment-gateway-in-next-js-and-react-with-razorpay-and-tailwindcss/</link>
                <guid isPermaLink="false">66d4601fa326133d12440a33</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ payments ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tailwind ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 21 Dec 2021 18:16:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/12/Blue-and-White-Modern-Corporate-Travel-YouTube-Thumbnail.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Manu Arora</p>
<p>If you have an e-commerce application, a payment gateway lets you process payments on your website on the fly. </p>
<p>With all the modern payment gateway solutions available these days, there are many ways you can integrate payments and charge your users for your product or services.  </p>
<p>In this tutorial, we are going to build a landing page that lets the end user purchase products from a web application. The page looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-20-at-10.54.31-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Live Demo: <a target="_blank" href="https://integrate-payments.vercel.app">Integrate Payments</a>
Source Code: <a target="_blank" href="https://github.com/manuarora700/integrate-payments.git">Integrate Payments Source Code</a></p>
<p>Some of the popular payment gateways that are available are:</p>
<ul>
<li><a target="_blank" href="https://stripe.com">Stripe</a></li>
<li><a target="_blank" href="https://gumroad.com">Gumroad</a></li>
<li><a target="_blank" href="https://paypal.com">PayPal</a></li>
<li><a target="_blank" href="https://razorpay.com">Razorpay</a></li>
</ul>
<p>Today, we are going to learn how to integrate Razorpay with a Next.js (React) application and understand how the flow actually works.</p>
<h2 id="heading-tech-stack"><strong>Tech Stack</strong></h2>
<p>For our Stack, we are going to use the following technologies:</p>
<ul>
<li><a target="_blank" href="https://nextjs.org">Next.js</a> - A framework for React that gives access to serverless functions and React architecture.</li>
<li><a target="_blank" href="https://tailwindcss.com">TailwindCSS</a> - A utility-based CSS framework for easy styling</li>
<li><a target="_blank" href="https://razorpay.com">Razorpay</a> - A payment gateway system that lets users access payments.</li>
<li><a target="_blank" href="https://vercel.com">Vercel</a> - For hosting our Next.js application (if not already hosted)</li>
<li><a target="_blank" href="https://tailwindmasterkit.com">Tailwind Master Kit</a> - For easily accessible Tailwind Components</li>
</ul>
<h2 id="heading-project-setup"><strong>Project Setup</strong></h2>
<p>If you already have a project, then you can directly skip to the integration part of the article. If not, let's get started by creating a Git repository and hosting our project on Vercel.</p>
<h3 id="heading-how-to-set-up-a-nextjs-repository-and-website"><strong>How to Set Up a Next.js Repository and Website</strong></h3>
<p>First, head over to <a target="_blank" href="https://vercel.com">Vercel</a> and create a hobby account for yourself. (If you're going to use it for a commercial project, make sure you buy their plan. Hobby accounts are just for testing and creating playgrounds.)</p>
<p>Once the account is created, click on <code>New Project</code>
<img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-11.56.47-AM.png" alt="Screenshot-2021-12-21-at-11.56.47-AM" width="600" height="400" loading="lazy"></p>
<p>Then, select <code>Next.js</code> from the available options and create a Git Repository on the platform itself.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-11.57.31-AM.png" alt="Screenshot-2021-12-21-at-11.57.31-AM" width="600" height="400" loading="lazy"></p>
<p>Your site will be deployed within seconds and you will get a URL for the live website.</p>
<h3 id="heading-how-to-set-up-tailwindcss"><strong>How to Set Up TailwindCSS</strong></h3>
<p>Now since the website is setup, you can directly go to <a target="_blank" href="https://github.com">GitHub</a>, clone the repository, to run it in your local environment. For that, follow these simple steps:</p>
<ul>
<li>Go to <a target="_blank" href="https://github.com">GitHub</a> and find your newly created repository</li>
<li>Click on the <code>code</code> section and copy the repository URL.</li>
<li>Open your terminal on the desktop and write <code>git clone &lt;repo_name&gt;</code>. This will clone the repository in your local environment so that you can start working.</li>
<li>Once the repository is cloned/copied in your local environment, open the project in your favourite code editor (VSCode is the best in my opinion).</li>
<li>In the terminal, open the location of the application and write <code>npm install</code>. This will install all the related node modules.</li>
<li>You can start the local development server by writing <code>npm run dev</code>.</li>
</ul>
<p>Now the project is up and running in your local environment. To access your website locally, open <code>localhost:3000</code> in your browser and you will be able to see the boilerplate website already there for you.  </p>
<p>Setting up tailwind is very simple. Their <a target="_blank" href="https://tailwindcss.com/docs/guides/nextjs">documentation</a> makes it even simpler. Check out their docs for reference and more on TailwindCSS as a framework.  </p>
<p>To setup Tailwind on your local environment, follow the below steps:</p>
<ul>
<li><code>npm install -D tailwindcss postcss autoprefixer</code> - This will install TailwindCSS along with other important dependencies for compiling and running your Tailwind code.</li>
<li><code>npx tailwindcss init -p</code> - This will initialize a <code>tailwind.config.js</code> file that is just an object which can be manipulated according to the user's needs.</li>
<li>In the <code>tailwind.config.js</code> file, paste the below code which basically tells Tailwind to compile the code present in the <code>/pages</code> and <code>/components</code> directories.</li>
</ul>
<pre><code class="lang-js"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">content</span>: [
    <span class="hljs-string">"./pages/**/*.{js,ts,jsx,tsx}"</span>,
    <span class="hljs-string">"./components/**/*.{js,ts,jsx,tsx}"</span>,
  ],
  <span class="hljs-attr">theme</span>: {
    <span class="hljs-attr">extend</span>: {},
  },
  <span class="hljs-attr">plugins</span>: [],
}
</code></pre>
<ul>
<li>Open the <code>globals.css</code> file present in the <code>/styles</code> directory and paste the following code. These code snippets import all the Tailwind related setup code:<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;
<span class="hljs-keyword">@tailwind</span> components;
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
</li>
<li>Restart your website by quitting the terminal and writing <code>npm run dev</code> on the terminal. Now you're ready to harness the power of TailwindCSS.</li>
</ul>
<p>Now that Tailwind and our website are setup, let's jump right into developing the page and integrating payments.</p>
<h2 id="heading-landing-page-development"><strong>Landing Page Development</strong></h2>
<p>The landing page that we are going to use is directly taken from the <a target="_blank" href="https://tailwindmasterkit.com">Tailwind Master Kit</a> that lets you access components built with TailwindCSS.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-12.13.56-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Let's break down the code and understand it better.</p>
<h3 id="heading-navbarjs"><strong>Navbar.js</strong></h3>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <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 flex-row items-center  justify-between px-20 py-10"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row items-center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"font-bold italic text-2xl text-white mr-10"</span>&gt;</span>Payments<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row space-x-10"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 text-sm tracking-wide font-light"</span>
            &gt;</span>
              Pricing
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 text-sm tracking-wide font-light"</span>
            &gt;</span>
              Product
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 text-sm tracking-wide font-light"</span>
            &gt;</span>
              Team
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span>
              <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 text-sm tracking-wide font-light"</span>
            &gt;</span>
              Sales
            <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"flex flex-row space-x-10 items-center"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-gray-400 text-sm tracking-wide font-light"</span>&gt;</span>
          Sales
        <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-[#272A30] text-gray-300 px-8 text-sm py-2 rounded-md shadow-xl drop-shadow-2xl"</span>&gt;</span>
          Sign in
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <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>
  );
};
</code></pre>
<p>Building the Navbar is simple. It's a Flexbox container with links and unordered list items aligned in a <code>row</code>. </p>
<p>The button, however, is interesting. It used the new TailwindCSS drop shadow class which drops a background shadow. (We can also use colored shadows in TailwindCSS 3.0+ versions - pretty cool.)</p>
<h3 id="heading-herojs"><strong>Hero.js</strong></h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Hero = <span class="hljs-function">(<span class="hljs-params">{ onClick }</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"relative z-10 flex flex-col md:flex-row mt-10 items-center  max-w-6xl justify-evenly mx-auto"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"md:w-1/3 mb-20 md:mb-0 mx-10"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">" text-white font-bold text-5xl mb-10"</span>&gt;</span>
          Integrate{" "}
          <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-clip-text text-transparent bg-gradient-to-r from-pink-500 to-violet-500"</span>&gt;</span>
            payments
          <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>{" "}
          in less than 10 minutes.
        <span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-gray-300 font-light tracking-wide w-[300px] mb-10"</span>&gt;</span>
          Learn how to integrate a Payment Gateway with your Next.js and React
          application.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gradient-to-r from-[#3e4044] to-[#1D2328] p-[1px] rounded-md mb-4"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onClick}</span>
            <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gradient-to-r from-[#2E3137] to-[#1D2328] rounded-md w-full py-4 shadow-xl drop-shadow-2xl text-gray-300 font-bold"</span>
          &gt;</span>
            Purchase Now!
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gradient-to-r from-[#3e4044] to-[#1D2328] p-[1px] rounded-md"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-gradient-to-r from-[#1D2328] to-[#1D2328] rounded-md w-full py-4 shadow-sm drop-shadow-sm text-gray-400 font-light"</span>&gt;</span>
            Read Blog
          <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
        <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 class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-2/3 bg-white flex-shrink-0  relative"</span>&gt;</span> */}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span>
        <span class="hljs-attr">className</span>=<span class="hljs-string">"w-full md:w-[36rem] h-full"</span>
        <span class="hljs-attr">alt</span>=<span class="hljs-string">"stripe payment from undraw"</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">"/payments.svg"</span>
      /&gt;</span>
      {/* <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>
  );
};
</code></pre>
<p>The hero section contains our <code>Purchase Now</code> button which will initialise the payments for us (we'll look at the implementation in the next section).</p>
<p>The layout contains two sections: the <code>Left section</code> contains all the text and the <code>Right Section</code> contains a large image (taken from Undraw, a free and open source illustrations website). </p>
<p>The <code>onClick</code> action on the button is important since it is responsible for triggering the action that will initialise the payments. The <code>onClick</code> is nothing but a <code>callback</code> that calls the function which is passed down as a prop to the component.  </p>
<p>That's pretty much it for the UI part. Let's jump into the payments section and understand how to setup a developer account on Razorpay and use their SDK to make payments on our website.</p>
<h2 id="heading-how-to-set-up-a-razorpay-account-and-retrieve-api-keys">How to Set Up a Razorpay Account and Retrieve API Keys</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-12.25.43-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>For integrating payments (that is, receiving payments on our website), we need to have two things:</p>
<ol>
<li>A Razorpay account</li>
<li>A set of API Keys that lets us access their services.</li>
</ol>
<p>Let's create an account and retrieve the API keys.</p>
<ul>
<li>Head over to <a target="_blank" href="https://razorpay.com">Razorpay</a> and sign up for an account</li>
<li>After signing up you can access the <a target="_blank" href="https://dashboard.razorpay.com/app/dashboard">Dashboard</a> where you will find all the necessary details that are required for integrating payments.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-12.28.44-PM.png" alt="Screenshot-2021-12-21-at-12.28.44-PM" width="600" height="400" loading="lazy"></li>
<li>For now, we will be in Test mode so that we can test our payments before we actually go live.</li>
<li>In the left panel, scroll down to <code>Settings</code> - There you will find the API keys section along with the configurations you can make to your payments UI.
<img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-12.30.10-PM.png" alt="Screenshot-2021-12-21-at-12.30.10-PM" width="600" height="400" loading="lazy"></li>
<li>Since you will be doing it for the first time, click on <code>Generate API Keys</code> and the download will automatically start. The downloaded file contains <code>Razorpay API Key</code> and <code>Razorpay API Secret</code>.</li>
</ul>
<p>Now you're all set with the API keys and setting up the platform. Let's jump directly into how to actually trigger the Razorpay API and make payments.</p>
<h2 id="heading-how-to-integrate-payments-with-razorpay"><strong>How to Integrate Payments with Razorpay</strong></h2>
<p>For our payments to be integrated, we need a button click that actually initializes the Razorpay <code>checkout</code> module. For this, we already have a button <code>Purchase Now</code> the calls a function <code>onClick</code> that is nothing but a callback. Let's see the actual implementation and understand the code behind it.</p>
<p>To initialise a payment, we need to add Razorpay's <code>checkout</code> script into our code. In React, we can simply do it using the <code>document.body.appendChild(script)</code> code.</p>
<h3 id="heading-initializerazorpay"><strong>initializeRazorpay()</strong></h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> initializeRazorpay = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {
      <span class="hljs-keyword">const</span> script = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">"script"</span>);
      script.src = <span class="hljs-string">"https://checkout.razorpay.com/v1/checkout.js"</span>;

      script.onload = <span class="hljs-function">() =&gt;</span> {
        resolve(<span class="hljs-literal">true</span>);
      };
      script.onerror = <span class="hljs-function">() =&gt;</span> {
        resolve(<span class="hljs-literal">false</span>);
      };

      <span class="hljs-built_in">document</span>.body.appendChild(script);
    });
  };
</code></pre>
<p>Now, we are using a promise to achieve this task. We do this because later on, we are going to use the <code>initializeRazorpay()</code> in such a way that every time <code>Purchase Now</code> is clicked, the payments are initialised. We simply have to <code>await</code> this function to create and append a script into the DOM.  </p>
<p>Let's look at the main function which is responsible for creating and initializing payments on the page.</p>
<h3 id="heading-makepayment-function"><strong>makePayment() function</strong></h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> makePayment = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> initializeRazorpay();

    <span class="hljs-keyword">if</span> (!res) {
      alert(<span class="hljs-string">"Razorpay SDK Failed to load"</span>);
      <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-comment">// Make API call to the serverless API</span>
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/razorpay"</span>, { <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span> }).then(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span>
      t.json()
    );
    <span class="hljs-built_in">console</span>.log(data);
    <span class="hljs-keyword">var</span> options = {
      <span class="hljs-attr">key</span>: process.env.RAZORPAY_KEY, <span class="hljs-comment">// Enter the Key ID generated from the Dashboard</span>
      <span class="hljs-attr">name</span>: <span class="hljs-string">"Manu Arora Pvt Ltd"</span>,
      <span class="hljs-attr">currency</span>: data.currency,
      <span class="hljs-attr">amount</span>: data.amount,
      <span class="hljs-attr">order_id</span>: data.id,
      <span class="hljs-attr">description</span>: <span class="hljs-string">"Thankyou for your test donation"</span>,
      <span class="hljs-attr">image</span>: <span class="hljs-string">"https://manuarora.in/logo.png"</span>,
      <span class="hljs-attr">handler</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
        <span class="hljs-comment">// Validate payment at server - using webhooks is a better idea.</span>
        alert(response.razorpay_payment_id);
        alert(response.razorpay_order_id);
        alert(response.razorpay_signature);
      },
      <span class="hljs-attr">prefill</span>: {
        <span class="hljs-attr">name</span>: <span class="hljs-string">"Manu Arora"</span>,
        <span class="hljs-attr">email</span>: <span class="hljs-string">"manuarorawork@gmail.com"</span>,
        <span class="hljs-attr">contact</span>: <span class="hljs-string">"9999999999"</span>,
      },
    };

    <span class="hljs-keyword">const</span> paymentObject = <span class="hljs-keyword">new</span> <span class="hljs-built_in">window</span>.Razorpay(options);
    paymentObject.open();
  };
</code></pre>
<p>The <code>makePayment()</code> method is responsible for initialising and opening the Razorpay popup.</p>
<p>The <code>makePayment()</code> function does the following operations:</p>
<ol>
<li>Initializes the Razorpay Checkout script and appends it to the body. This was handled by the <code>initializeRazorpay</code> method as we saw earlier.</li>
<li>Makes a call to the <code>/api/razorpay.js</code> serverless function. (This we will talk about in a minute).</li>
<li><p>Creates an Object which has 4 important keys:</p>
<ol>
<li><code>currency</code> - The currency in which we want the transaction to happen</li>
<li><code>amount</code> - The amount in which the transaction has to happen. Note that it has to be the smallest denomination. Example if you're from the USA, then the amount will be in cents.</li>
<li><code>order_id</code> - This will be generated from the serverless API which we are going to talk about in a minute.</li>
<li><code>handler</code> -  When the payments are successful, this callback function is called.</li>
</ol>
</li>
<li><p>Finally, a <code>paymentObject</code> is created with the <code>options</code> passed down as the parameters to the <code>window.Razorpay</code> method. This is available to us because of the <code>checkout</code> script that we looked at before.</p>
</li>
</ol>
<p>We looked at the above <code>makePayment()</code> method and saw a line of code which is:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/razorpay"</span>, { <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span> }).then(<span class="hljs-function">(<span class="hljs-params">t</span>) =&gt;</span>
      t.json()
    );
</code></pre>
<p>But what does it mean?</p>
<p>Next.js allows us to access serverless functions with the help of <code>apis</code> that are available to us in the <code>api</code> folder within Next.js.</p>
<p>The serverless APIs are nothing but <code>Lambda Functions</code> that act as a back-end for our JAMStack applications. Here, we can write our back-end related code easily without having to create a separate back-end.</p>
<p>Here, we need serverless because the <code>order_id</code> that we saw in the <code>makePayments()</code> code is unique and has to be generated at the backend. Not only this but the <code>amount</code> and <code>currency</code> also comes from the backend. This is to ensure that no one can manipulate the amount and the currency and the portal is secure for payments.</p>
<p>Let's have a look at the serverless API code and understand it better.</p>
<h3 id="heading-apirazorpayjs"><strong>/api/razorpay.js</strong></h3>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> Razorpay = <span class="hljs-built_in">require</span>(<span class="hljs-string">"razorpay"</span>);
<span class="hljs-keyword">const</span> shortid = <span class="hljs-built_in">require</span>(<span class="hljs-string">"shortid"</span>);

<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>{
  <span class="hljs-keyword">if</span> (req.method === <span class="hljs-string">"POST"</span>) {
    <span class="hljs-comment">// Initialize razorpay object</span>
    <span class="hljs-keyword">const</span> razorpay = <span class="hljs-keyword">new</span> Razorpay({
      <span class="hljs-attr">key_id</span>: process.env.RAZORPAY_KEY,
      <span class="hljs-attr">key_secret</span>: process.env.RAZORPAY_SECRET,
    });

    <span class="hljs-comment">// Create an order -&gt; generate the OrderID -&gt; Send it to the Front-end</span>
    <span class="hljs-keyword">const</span> payment_capture = <span class="hljs-number">1</span>;
    <span class="hljs-keyword">const</span> amount = <span class="hljs-number">499</span>;
    <span class="hljs-keyword">const</span> currency = <span class="hljs-string">"INR"</span>;
    <span class="hljs-keyword">const</span> options = {
      <span class="hljs-attr">amount</span>: (amount * <span class="hljs-number">100</span>).toString(),
      currency,
      <span class="hljs-attr">receipt</span>: shortid.generate(),
      payment_capture,
    };

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> razorpay.orders.create(options);
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">id</span>: response.id,
        <span class="hljs-attr">currency</span>: response.currency,
        <span class="hljs-attr">amount</span>: response.amount,
      });
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.log(err);
      res.status(<span class="hljs-number">400</span>).json(err);
    }
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// Handle any other HTTP method</span>
  }
}
</code></pre>
<p>This of <code>razorpay.js</code> as your route which leads to <code>/api/razorpay</code>. Every file you create in the API folder becomes a serverless route. Just like we create APIs in the back-end, we create files here in the APIs folder which becomes a route for us.  </p>
<p>For example: let's say you create a file in the <code>/api</code> folder named <code>posts.js</code>. So the route will become <code>/api/posts</code> which can return anything you want depending upon the use case.  </p>
<p>For our case, we need to make a <code>POST</code> request to our back-end that will create an <code>order_id</code> for us along with <code>amount</code> and <code>currency</code> that can be returned to the front-end for making payments.  </p>
<p>Let's understand the flow for this API.</p>
<ol>
<li>First we need to install the <code>razorpay</code> module along with <code>shortid</code> for generating short unique ids. To do that, head over to your terminal and write <code>npm install razorpay</code> and <code>npm install shortid</code></li>
<li>Now, to access a <code>POST</code> request, we check the request object and access the method by using the below snippet:</li>
</ol>
<pre><code class="lang-js"><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>{
  <span class="hljs-keyword">if</span> (req.method === <span class="hljs-string">"POST"</span>) {
    <span class="hljs-comment">// Initialize razorpay object</span>
    <span class="hljs-keyword">const</span> razorpay = <span class="hljs-keyword">new</span> Razorpay({
      <span class="hljs-attr">key_id</span>: process.env.RAZORPAY_KEY,
      <span class="hljs-attr">key_secret</span>: process.env.RAZORPAY_SECRET,
    });

    <span class="hljs-comment">// rest of the code...</span>
}
</code></pre>
<ol start="3">
<li><p>Here, <code>request.method</code> checks for the method. If the method is <code>POST</code> we go ahead and initialize the Razorpay object.</p>
</li>
<li><p>The Razorpay object takes in 2 parameters: <code>key_id</code> and <code>key_secret</code>. Remember when we downloaded the keys from Razorpay dashboard? Let's put them to use.</p>
</li>
<li><p>Open/create the <code>.env</code> file in your folder structure's root and paste the following code:</p>
</li>
</ol>
<pre><code class="lang-js">RAZORPAY_KEY=YOUR_KEY_HERE
RAZORPAY_SECRET=YOUR_SECRET_HERE
</code></pre>
<p>Here, you can plug in your API key and secret and you will be good to go.</p>
<p>Note: Make sure you restart your development server – otherwise the changes won't be reflected.</p>
<p>Once the <code>razorpay</code> object is setup, it takes in three important options: <code>receipt</code>, <code>amount</code> and <code>currency</code>.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> payment_capture = <span class="hljs-number">1</span>;
    <span class="hljs-keyword">const</span> amount = <span class="hljs-number">499</span>;
    <span class="hljs-keyword">const</span> currency = <span class="hljs-string">"INR"</span>;
<span class="hljs-keyword">const</span> options = {
      <span class="hljs-attr">amount</span>: (amount * <span class="hljs-number">100</span>).toString(),
      currency,
      <span class="hljs-attr">receipt</span>: shortid.generate(),
    };
</code></pre>
<p>Note that amount and currency are being declared in our <code>back-end</code> so that there's no way for attackers to tamper with it.</p>
<p>Once the options are setup, we can create orders with Razorpay's <code>_razorpay_._orders_.create(options)</code> method.</p>
<pre><code class="lang-js"><span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> razorpay.orders.create(options);
      res.status(<span class="hljs-number">200</span>).json({
        <span class="hljs-attr">id</span>: response.id,
        <span class="hljs-attr">currency</span>: response.currency,
        <span class="hljs-attr">amount</span>: response.amount,
      });
    } <span class="hljs-keyword">catch</span> (err) {
      <span class="hljs-built_in">console</span>.log(err);
      res.status(<span class="hljs-number">400</span>).json(err);
    }
</code></pre>
<p>Here, we simply <code>await</code> the <code>create()</code> method provided by Razorpay. When the create method is successful, we get an <code>id</code> which is nothing but the <code>order_id</code> that we need to supply to the front-end in order to generate unique payments.  </p>
<p>Once everything is successful, we send a <code>200 response</code> with <code>id</code>, <code>currency</code> and <code>amount</code> fields. This is all what is required by the front-end to process payments.</p>
<h2 id="heading-how-to-make-payments-with-razorpay"><strong>How to Make Payments with Razorpay</strong></h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/12/Screenshot-2021-12-21-at-1.14.41-PM.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once everything is integrated and is in place, we can start using Razorpay's charging methods – there are various options available. With this, you can start charging for your services and products by simply accepting payments on your website.</p>
<p>The whole popup is customisable and can be edited directly from Razorpay's dashboard portal.  </p>
<p>Since you're in Test mode, to start using their services in production, you need to complete their Identification process by submitting your proof documents and simply toggle between <code>test mode</code> and <code>live mode</code>. </p>
<p>That's all you need to do from the coding side to make the transition from test to live.</p>
<h2 id="heading-environment-variables"><strong>Environment Variables</strong></h2>
<p>To make sure that our changes are reflected in our live production website, we need to add the same environment variables that we added in the code on the Vercel platform as well.  </p>
<p>For that:</p>
<ol>
<li>Head over to Vercel and open your project</li>
<li>Click on <code>settings</code></li>
<li>Click on <code>environment variables</code>.</li>
<li>You will get 2 input fields - Name and Value.</li>
<li>First, enter <code>RAZORPAY_KEY</code> and add the API key</li>
<li>Second, enter <code>RAZORPAY_SECRET</code> and add the secret value</li>
<li>Redeploy the website and you will be able to make payments in the live environment as well.</li>
</ol>
<h2 id="heading-live-demo-and-source-code"><strong>Live Demo and Source Code</strong></h2>
<p>The entire source code for the application can be found <a target="_blank" href="https://github.com/manuarora700/integrate-payments">here</a>.</p>
<p>The live demo of the website is <a target="_blank" href="https://integrate-payments.vercel.app/">here</a>.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>Integrating payments is easy, thanks to Razorpay's excellent documentation that is easy to understand.</p>
<p>I enjoyed coding this website and integrating payments. You can also see a snippet of the code at my website: <a target="_blank" href="https://manuarora.in/snippets">Manu Arora's Code Snippets</a></p>
<p>If you liked this blog, try implementing it in your own website so you can reach out to your end-users and make payments an easy task for yourself.</p>
<p>If you'd like to give any feedback, reach out to me at my <a target="_blank" href="https://twitter.com/mannupaaji">Twitter handle</a> or visit my <a target="_blank" href="https://manuarora.in/">Website</a></p>
<p>Also thanks to <a target="_blank" href="https://tailwindmasterkit.com/">Tailwind Master Kit</a> for the beautiful Landing Page UI.  </p>
<p>Happy Coding. :)</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create an Excel File that Pulls Customer Data with WooCommerce and Python ]]>
                </title>
                <description>
                    <![CDATA[ “Hey, could you please send me our customers' emails? We’re about to launch a new marketing campaign and…we need them ASAP.” If you work in the Web department on a corporate level, you've probably heard this sentence hundreds of times. So, today I wa... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-an-excel-file-with-customers-data-with-woocommerce-and-python/</link>
                <guid isPermaLink="false">66bdff5c7cdf166e835fce67</guid>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ excel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spreadsheets ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Marco Venturi ]]>
                </dc:creator>
                <pubDate>Fri, 08 Oct 2021 15:15:28 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/10/cover-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>“Hey, could you please send me our customers' emails? We’re about to launch a new marketing campaign and…we need them ASAP.”</p>
<p>If you work in the Web department on a corporate level, you've probably heard this sentence hundreds of times. So, today I want to share how I solved this issue for a company I worked at.</p>
<h2 id="heading-what-was-the-request">What Was the Request?</h2>
<p>We had an e-commerce app built with WooCommerce, that got thousands of orders every day worldwide. To register, users had to add their first name, last name, and email address. </p>
<p>Our marketing department asked my team to provide them with the email address and first names of all our customers to launch a new campaign. </p>
<p>I expected them to ask for a CSV file to upload contacts massively to their marketing platform. Instead, they told me they needed an Excel file since they had to edit it before going ahead with the campaign launch. </p>
<p>Once they assigned me the Jira issue, I was ready to start.</p>
<h2 id="heading-analyzing-the-problem">Analyzing the Problem</h2>
<p>WooCommerce provides users with the export functionality to get customers' data but it doesn’t generate an Excel file. It generates a CSV or an XML file.</p>
<p>When you work with a tool like WordPress, the first option you think about is using a plugin and get what you need. </p>
<p>I found a few options on the WordPress plugin directory, but there is a lot to consider every time you install a new plugin in your instance. You have to think about maintenance costs (consider every time a developer works on it, they log hours that affect your budget), security vulnerabilities, and – last but not the least – purchase approval can take a long time.  </p>
<p>I also considered a second option: downloading the CSV via the user interface and turning it into an Excel file by using one of the thousand services you can find online. </p>
<p>But using third parties services could break critical security and privacy policies and the final result is not always reliable. So I decided to go no further in this direction.</p>
<p>In the end, I thought developing a script was the best and fastest option to solve this task.  </p>
<p>WordPress provides developers with APIs. Many other plugins from the WordPress system provide APIs as well, and WooCommerce is not an exception. The <a target="_blank" href="https://docs.woocommerce.com/document/woocommerce-rest-api/">documentation</a> is robust and it offers links to libraries for the most used languages such as Node.js, Python, PHP, and Ruby.</p>
<p>I decided to go with Python and use this technology to develop a script that generates an Excel file with the following structure:</p>
<ul>
<li>Two columns: first name and email</li>
<li>One row per customer</li>
</ul>
<p>I chose Python for several reasons: there are hundreds of libraries that can help you get get the job done, it is flexible, and it is very useful when it comes to handling data.</p>
<p>I also decided to use Pandas and Openpyxl to handle data and create the Excel file.</p>
<h2 id="heading-before-coding-lets-get-what-we-need">Before coding, let’s get what we need</h2>
<p>To work with the WooCommerce API, I need to generate the APIs keys to work with. You can get more info on WooCommerce official <a target="_blank" href="https://docs.woocommerce.com/document/woocommerce-rest-api/">documentation</a>. </p>
<p>First, you need to log into your WooCommerce instance, and then go to WooCommerce &gt; Settings &gt; Advanced &gt; REST API.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/0-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then I'll give it a brief description, choose the user I want to generate the API for, choose the permissions granted (Read/Write or both), and then hit “Generate API key”.</p>
<p>Next, I’ll get the Consumer Key and the Consumer Secret:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/screencapture-marcoventuritest-it-wooTest-wp-admin-admin-php-2021-10-06-17_19_38.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-prepare-the-environment">How to Prepare the Environment</h2>
<p>The very first thing I did was add the necessary libraries to develop my script. I started with the Python library for WooCommerce. To install it, I ran this command:</p>
<pre><code class="lang-python">pip install woocommerce
</code></pre>
<p>Good. Now I'll go ahead and install Pandas, a data analysis and manipulation tool for Python:</p>
<pre><code class="lang-python">pip install pandas
</code></pre>
<p>After that, I installed Openpyxl, a Python library to read and write Excel files:</p>
<pre><code class="lang-python">pip install openpyxl
</code></pre>
<h2 id="heading-lets-code">Let’s code</h2>
<p>I made the API call using the API function provided by the WooCommerce Python library and I stored it in a variable. Then I passed the function the base URL of my WooCommerce website, the consumer key, the consumer secret, and the version.</p>
<pre><code class="lang-python">wcdata = API(
    url=<span class="hljs-string">'&lt;BASE_URL&gt;'</span>,
    consumer_key=<span class="hljs-string">'ck_XXXXXXXXXXXXXXXXXXXX'</span>,
    consumer_secret=<span class="hljs-string">'cs_XXXXXXXXXXXXXXXXXXXX'</span>,
    version=<span class="hljs-string">'wc/v3'</span>
)
</code></pre>
<p>Then I used the GET function to call the "customers" endpoint and I created locally a JSON file (“contacts.json”) with the data I got from the endpoint I called right before:</p>
<pre><code class="lang-python">newJson = wcdata.get(<span class="hljs-string">'customers'</span>).json()
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'contacts.json'</span>, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> f:
    json.dump(newJson, f, ensure_ascii=<span class="hljs-literal">False</span>, indent=<span class="hljs-number">4</span>)
</code></pre>
<p>I converted it to a Pandas object and stored it in the "df_json" variable:</p>
<pre><code class="lang-python">df_json = pd.read_json(<span class="hljs-string">'contacts.json'</span>)
</code></pre>
<p>I used the <code>to_excel()</code> function to turn the object into an Excel file. I passed the function three arguments:</p>
<ul>
<li>The name of the file I was about to create</li>
<li>The index, set to “false” since I didn't want the record id to be printed on my file</li>
<li>The columns I wanted to print on my file (first_name and email)</li>
</ul>
<pre><code class="lang-python">df_json.to_excel(<span class="hljs-string">'customers_contacts.xlsx'</span>, index=<span class="hljs-literal">False</span>, columns=(<span class="hljs-string">'first_name'</span>, <span class="hljs-string">'email'</span>))
</code></pre>
<p>I ran the script and I got this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/10/6bis.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That’s it. This is how I created an Excel file with customers' emails and first names with WooCommerce APIs and Python in less than 20 lines of code. </p>
<p>Of course, it is a script you can run via the command line when needed or you can also automate it to regularly generate reports about the e-commerce you and your team are running.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>I also wanted to share some other content I found on the web while studying to develop this script. </p>
<p>The first one is a Stack Overflow <a target="_blank" href="https://stackoverflow.com/questions/12309269/how-do-i-write-json-data-to-a-file">question</a>. It helped me optimize my code while creating the JSON file. I really appreciated the chosen question, especially when it suggested how you can write a "nicer" JSON file on a modern system. </p>
<p>The second one is about Pandas. If you write Python code, one day or another, you'll have to deal with data and their manipulation. This <a target="_blank" href="https://www.marsja.se/how-to-convert-json-to-excel-python-pandas/">article</a> by Erik Marsja explains really well how you can convert your JSON file to Excel with Pandas. It provides readers with several tips on how they can use this powerful library to display the data they want in an efficient and effective way.</p>
<p>Feel free to share this post if you found it useful! You can also find the full code on this Github <a target="_blank" href="https://github.com/mventuri/-woocommerce-to-excel-python">repo</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Develop a Reusable eCommerce Platform ]]>
                </title>
                <description>
                    <![CDATA[ By Ramón Morcillo This is a story about my team’s hard work developing not a single eCommerce platform, but a reusable one for different owners. We kept the same codebase, look, and feel, and made it highly customizable.  I will conclude with what we... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/develop-a-reusable-ecommerce-platform/</link>
                <guid isPermaLink="false">66d460c54bc8f441cb6df81f</guid>
                
                    <category>
                        <![CDATA[ architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GraphQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ teamwork ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 30 Dec 2020 19:48:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/12/plants.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ramón Morcillo</p>
<p>This is a story about my team’s hard work developing not a single eCommerce platform, but a reusable one for different owners. We kept the same codebase, look, and feel, and made it highly customizable. </p>
<p>I will conclude with what we learned from the process. I think our takeaways will be a useful learning resource for other software developers out there (and for ourselves in future projects, as well).</p>
<p>I will try to focus on the relevant parts as much as possible to make it easier to understand. Having said this, a bit of background is needed to go through this article.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li>Context of the project</li>
<li>The first MVP</li>
<li>Implementing GraphQL</li>
<li>Architecture and Tech Stack</li>
<li>The second MVP</li>
<li>Conclusion and lessons learned</li>
<li>Final thoughts</li>
</ul>
<h2 id="heading-context-of-the-project">Context of the project</h2>
<p>The client we were developing the platform for was an eLearning company which was composed of 3 main sub-companies.</p>
<p>For the past few years, the sub-companies had been operating mostly independently. But now they were trying to create a standardized way of doing things, so they could grow together in the best way.</p>
<p>The project was an ambitious one. Creating an ecommerce platform that would work for all the sub-companies wasn’t easy to design or to implement. There were a large number of unsolved questions they had, which made it very hard to estimate.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/doubts-2.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-the-first-mvp">The first MVP</h2>
<p>To tackle this difficult challenge, we started from the bottom with one of the 3 sub-companies – let’s call it sub-company H. In fact, it wasn’t one of the main sub-companies, it was a sub-company from a sub-company. </p>
<p>To explain it better, if we name the 3 main sub-companies L, N, and P, then H was a sub-company of N.</p>
<p>Being a sub-sub-company didn’t mean the platform would be simpler to develop. It was quite the opposite, actually, given all the features proposed for the MVP.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/main_company_structure-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Main company structure</em></p>
<p>While the main goal was for a user to be able to purchase a product (seems quite obvious), there were too many dependencies with other services to accomplish this seemingly simple MVP.</p>
<p>Part of the product and order information came from another team’s domain, the Integrations team (I will call it team <em>In</em>). They communicated with <a target="_blank" href="https://www.swell.is/">Swell</a> and Klopotek, an ecommerce system where we stored the product's information along with the order’s status.</p>
<p>The discounts were also provided by team <em>In’s</em>, to which we had to subscribe and then calculate the final product price according to the user info and privileges before displaying it.</p>
<p>To make product content like images or descriptions accessible and customizable for the client we retrieved it through <a target="_blank" href="https://www.contentful.com/">Contentful</a>, a content platform where clients were able to manage it in an easy way.</p>
<p>We managed the payment with <a target="_blank" href="https://stripe.com/">Stripe</a>, a payments service, and then we communicated with team <em>In</em> to update the order status on Swell.</p>
<p>The service available for the user to authenticate should be agnostic to the owner and reusable on all sub-companies. It had to be provided by another team, yet in the end, we actually developed it ourselves.</p>
<p>And to put the icing on the cake, we also had to implement the user tracking with <a target="_blank" href="https://segment.com/">Segment</a>, a popular service to collect user events from web and mobile apps.</p>
<p>Here is a simple diagram of what I have been describing which might make it easier to understand. I have grouped the microservices architecture in just <em>Backend</em> and <em>Frontend</em> to keep it simple.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/mvp_architecture_overview-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>MVP Architecture overview</em></p>
<h2 id="heading-why-we-chose-graphql">Why We Chose GraphQL</h2>
<p>To accomplish our goals for the project, we had to <em>provide the frontend with a unique source of truth</em> of the product’s core information from the backend. </p>
<p>Therefore the only thing that we aimed to have different from one store’s frontend to another would be the designs and the content from Contentful. </p>
<p>Regarding these designs and their implementations on React, we planned to use a shared-components-library.</p>
<p>Therefore, what is GraphQL doing here and why did we decide to go for it? </p>
<p>Well, in case you don’t know how GraphQL works, essentially it lets you define a Schema with all the properties and queries that could be made to your product. Then it lets you serve it to the frontend to let it decide what to request without the backend having to create an endpoint for each of those requests (like in REST services). </p>
<p>To learn more about it, check out this tutorial <a target="_blank" href="https://www.ramonmorcillo.com/getting-started-with-graphql-and-nodejs/">I wrote to explain it</a>. It teaches you how to use it with Node.js. Also, <a target="_blank" href="https://graphql.org/learn/">their docs</a> are worth checking out.</p>
<p>This meant that each of the shops would request the data they needed from the product just by looking at the Schema, the source of truth. </p>
<p>Thanks to this fact we would not have to implement different sources of data  in the backend for each shop. This gave the frontend the power and responsibility (the first involves the second 🕷) to request the product data needed to display at each interface.</p>
<blockquote>
<p>With great power comes great responsibility.
— Stan Lee</p>
</blockquote>
<p>Just to be clear, if we had decided to go with REST we would have needed to create different endpoints for each of the shops. Or we would have had to make the frontend retrieve all the product data in each shop and then decide which properties to display. This means it would've had to store unnecessary data in the frontend that would only add noise. </p>
<p>Or even worse, we would have had all the shops’ backend services deployed for each one of the frontend shops. This would've used unnecessary resources and increased the cost considerably. </p>
<p>Here is why we took this initial approach. The worst part, in my opinion, would've been the waste of time from maintaining and hardly refactoring all the mess that we would have created.</p>
<p>Furthermore, by making a single request on-demand, the payload was lighter, and therefore, the performance over the network was improved.</p>
<p>Anyway, as with every problem, there were other approaches we could've taken on the way to developing this project and its architecture. But at that moment this one seemed to us the best one.</p>
<h2 id="heading-architecture-and-tech-stack">Architecture and Tech Stack</h2>
<p>The microservices architecture mainly consisted of Node.js services hosted on Azure K8s clusters. Depending on their needs and the data they worked with, they did or didn't have a MongoDB, PostgreSQL, or Redis database associated.</p>
<p>The asynchronous communication between them was handled mainly with <a target="_blank" href="https://azure.microsoft.com/en-us/services/service-bus/">Azure Service Bus</a> topics and subscriptions through a publish/subscribe messaging communication model. </p>
<p>The main difference with common messaging queues is that you can have more than one receiver, so you do not have multiple queues to receive messages in more than one service.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/azure_service_bus_messaging_queues-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Azure Service Bus messaging Queues. <a target="_blank" href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview#queues">Source</a></em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/azure_service_bus_messaging_topics-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Azure Service Bus messaging Topics. <a target="_blank" href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview#topics">Source</a></em></p>
<p>On the frontend part, the sites were developed with React. Sometimes we used <a target="_blank" href="https://github.com/vercel/next.js/">Next</a>, and other ones we built from scratch with <a target="_blank" href="https://github.com/facebook/create-react-app">Create React App</a>, depending on the complexity and the requirements of each. </p>
<p>We moved from Redux, used in previous projects, to the official <a target="_blank" href="https://reactjs.org/docs/context.html">Context API</a> to manage most of the state.</p>
<p>Here are the main services and their functionalities for the first MVP architecture: </p>
<ul>
<li><strong>shop-web-app:</strong> The client shop application.</li>
<li><strong>gateway-api-service:</strong> Proxy service to receive requests from the client and redirect them to the corresponding services.</li>
<li><strong>cms-api-service:</strong> Service to retrieve and serve the content from Contentful</li>
<li><strong>catalog-api-service:</strong> Service that subscribes to team In messages and persists the product core data to serve it later through GraphQL.</li>
<li><strong>orders-api-service.</strong> Service that handles all the payment business logic</li>
<li><strong>auth-api-service:</strong> Provisional service to implement the user authentication to be able to buy products.</li>
<li><strong>auth-web-app:</strong> The client for the auth service.</li>
<li><strong>integrations-ecommerce-api-service:</strong> service from the integrations domain that handles the payments. Although this service was not in our domain we developed it together to increase the delivery speed and free them from extra work.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/first_mvp_arquitecture-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>First MVP architecture</em></p>
<p>To deploy and update the resources needed on Azure we used <a target="_blank" href="https://www.terraform.io/">Terraform</a>, which let us define infrastructure as code and manage their life cycles on the K8s clusters. We also worked with Azure DevOps as our CI &amp; CD system.</p>
<p>On the services, we used <a target="_blank" href="https://github.com/guidesmiths/systemic">Systemic</a>, a Node.js framework for minimal dependency injection that lets you create components and their dependencies in a system. Each component handles a separate object from the domain such as the routing, controller, services, database, and so on in an agnostic way from the others.</p>
<p><a target="_blank" href="https://www.apollographql.com/">Apollo</a> was our choice to implement GraphQL. It provided us with a data graph layer to easily connect both frontend and backend. </p>
<p>Again, to learn more about it check out <a target="_blank" href="https://www.apollographql.com/docs/">their docs</a> or <a target="_blank" href="https://www.ramonmorcillo.com/getting-started-with-graphql-and-nodejs/">this tutorial.</a> </p>
<p>Finally, we hosted the code on <a target="_blank" href="https://github.com/">GitHub</a> to make use of features like Pull Requests to review our code properly before implementing it.</p>
<h2 id="heading-the-second-mvp">The second MVP</h2>
<p>An MVP (Minimum Viable Product) is the first prototype you create and deliver in a project. This means that there is usually just one, and when you create it you start implementing new features on it. </p>
<p>So, why did we focus on a second MVP for the same project? Well, when we reached a “stable” version of the first one, the client realized that we needed to start with the main sub-company stores. They decided to stop the sub-sub-company H store development to focus on the development of the new ones. </p>
<p>This was mainly because to some services ended their support for the sub-companies in the coming months, meaning that their stores had to be developed first.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/how_we_felt_with_second_mvp-1.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Graphical representation of how we felt with the second MVP</em></p>
<p>Although we tried to make a proper estimation for the first MVP, we surpassed its deadline since some unplanned issues appeared along the way. Thus, when we were told that the new deadline would be even sooner, we decided to take a different approach to reach it on time. </p>
<p>We decided to develop more than one store at the same time, which was a double-edged sword approach. </p>
<p>On one hand, we would see on the go how well the reusability aspect of our platform worked while refactoring it. We would also end up with more than one store in the end. </p>
<p>On the other hand, we would have to set up and maintain the environments and resources of multiple shops. Plus we would have to implement their designs which would slow us down, meaning we might not reach the deadline on time, again.</p>
<p>We saw this MVP as an opportunity to start over and improve our codebase. So we added TypesScript and Styled-Components to our React application. </p>
<p>I have to admit that I was very happy when we made these choices because I had been working with that stack on <a target="_blank" href="https://github.com/reymon359?tab=repositories&amp;q=&amp;type=source&amp;language=typescript">my own projects</a>. So now I was able to learn more and get even better at it.</p>
<p>Fortunateluy, we were able to reuse most of the code from the previous MVP for the React apps and the backend services. But not everything was a bed of roses. </p>
<p>Not all of us were used to working with this new stack and it slowed us down at the beginning. Furthermore, with the same stack, we started developing a React components library for all the platforms, which, even though it was planned for the first MVP, never saw the light.</p>
<p>By that time, the team in charge of the user authentication service started working on it so we stopped its development and just implemented it on the site. </p>
<p>In addition, we started the development of a products search service (<strong>search-api-service</strong>) with <a target="_blank" href="https://azure.microsoft.com/en-us/services/search/">Azure Cognitive Search</a>.</p>
<p>After all the changes mentioned above the architecture evolved this way.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/second_mvp_arquitecture.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Second MVP architecture</em></p>
<h2 id="heading-conclusion-and-lessons-learned">Conclusion and lessons learned</h2>
<p>As I am writing this, the platform isn’t finished yet. But it has been a great challenge to get where we are. </p>
<p>We have learned some valuable lessons that can be useful to others, not just about the stack and architecture described above but about the way we worked as a team.</p>
<h3 id="heading-innovate-the-stack">Innovate the stack</h3>
<p>Working with new technologies can be risky and less comfortable than sticking with old and well-known ones. But innovation and adaptability is the right way to go so you don't get left behind in Software Development. </p>
<p>One of the most important points when you upgrade your stack or adopt a new one, apart from checking the proper way to do so following standards, is <em>being sure the team is comfortable working with it,</em>. Not just at the beginning, either, but during the whole process to make the transition easier.</p>
<h3 id="heading-dont-underestimate-promise-less-deliver-more">Don’t underestimate, promise less, deliver more</h3>
<p>We happily estimated the first MVP and agreed to deliver a great number of features. We ended up needing more time because of all the issues that appeared on the way and _had to learn to say “no” <em>sometimes.</em> </p>
<p>On the second MVP, we didn’t estimate that far ahead in time and didn’t commit ourselves to features we weren’t sure we could deliver within the time expected. </p>
<p>Because of this, we were able to work less stressed, have a better mood, deliver better code, and improve the client's feelings about the project since they weren’t disappointed with the progress.</p>
<h3 id="heading-teamwork-inside-the-team">Teamwork inside the team.</h3>
<p>We realized that the best way to progress and develop was to feel comfortable – not just with the technologies but, most importantly, our teammates. Some of the measures that improved our relationship and teamwork were:</p>
<h4 id="heading-team-democracy">Team democracy.</h4>
<p>No matter the work we were doing at the moment, <em>all of us had the same voice and our opinion counted the same</em> when making a choice. This was key when we discussed the adoption of the new stack and the practices we would follow.</p>
<h4 id="heading-reviewing-code">Reviewing code.</h4>
<p>Feedback is one of the best ways to improve not only the code itself but the way you write it too. That's why we decided to work with GitHub Pull Requests to implement most of the features. </p>
<p><em>Working with them not only improved our code base but also made us aware of how the features were being implemented in other areas, avoiding catchup meetings and helping us keep track of the full project scope</em>. </p>
<p>We refined this system little by little with features like a <a target="_blank" href="https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/about-required-reviews-for-pull-requests">minimum number of reviewers to merge them</a> or <a target="_blank" href="https://github.com/integrations/slack">subscribing to them through slack.</a></p>
<h4 id="heading-helping-and-asking-for-help">Helping and asking for help.</h4>
<p>In my opinion, this one is a must. <em>The team must lose the fear of asking for help if they get stuck. At the same time, they must be willing to help others when they ask for it.</em> </p>
<p>I am happy to say that we were able to reach this balance and our work improved in many ways. The next point, pairing, was key in losing the fear of asking for help and getting to know each other better.</p>
<h4 id="heading-pairing-as-much-as-possible">Pairing as much as possible.</h4>
<p>At this point in software development, the advantages of doing pair programming are quite well known. We paired not just to deliver the features in a faster and better way of doing things, but to learn from each other's way of coding. </p>
<p>Each week, we decided the pairing tasks and teammates to implement them. But if someone needed or wanted to pair, we just asked for it and moments after a teammate offered to help.</p>
<h4 id="heading-paying-attention-to-feedback">Paying attention to feedback.</h4>
<p>The sprint retrospectives were the perfect moment to review all the things that went well, those that went wrong, to propose changes, and to look forward to improvement. Therefore the more we shared our opinions the more issues we could approach and solve.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/teamwork_makes_the_dream_work.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-teamwork-with-other-partners">Teamwork with other partners.</h4>
<p>We were dependent on other teams' work – so having a good relationship with them was also an important point in our development process. </p>
<p><em>Communication was the key point: the more we communicated the more we improved,</em>. And thanks to this, our goal was to be one whole team. Here are some actions we followed in order to enhance this communication:</p>
<ul>
<li><strong>Have a private place just for us.</strong> We created a separate channel to talk about the progress and solve any questions or doubts as soon as possible without extra meetings.</li>
<li><strong>Quick meetings.</strong> A meeting once a week worked great to check the progress on the main issues. But we did not always wait for this one meeting, and had a quick call whenever an issue needed to be discussed.</li>
<li><strong>Stay updated on the overall progress.</strong> We had a teammate from our team attending their daily standups and one of them at ours who updated the rest of the team if needed.</li>
</ul>
<p>Here is some actual footage of us and the Integrations Team:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/source--1-.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-make-the-client-feel-like-part-of-the-team">Make the client feel like part of the team.</h4>
<p>At the beginning of the first MVP, there were too many clues and too little communication to clarify them so we were blocked sometimes or had to set up time-wasting meetings for these issues. </p>
<p>The core of the problem, like most problems in life, was a lack of communication. So we solved it by increasing our communication, asking questions directly to the client, inviting them to retrospectives, daily standups, and other meetings even when they were not required.</p>
<p>This helped keep the client updated as much as possible. In the end, the more we communicated the more we made them feel part of the team, and the better we worked together.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>I want to first thank my teammates. It has been a pleasure to work with them, starting each day eager to have fun together developing the project.</p>
<p>On the same level, thank you to the teammates from other teams who always gave a helping hand when requested.</p>
<p>I'm also thankful for the opportunity to participate in the full end-to-end implementation of the project from which I learned so much. I solved issues on Front, Back, and DevOps such as setting up environments, pipelines, messaging between services, persisting and retrieving data, serving it to the frontend, and implementing the interfaces to display it.</p>
<p>Finally, I am thankful for having the chance to work and get better at technologies that I was using on side-projects like GraphQL or TypeScript.</p>
<p>I hope you enjoyed this article. You can read <a target="_blank" href="https://ramonmorcillo.com/developing-a-reusable-ecommerce-platform/">it too on my site</a> along with others! If you've got any questions, suggestions, or feedback in general, don't hesitate to reach out on any of the social networks from <a target="_blank" href="https://ramonmorcillo.com/">my site</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ E-commerce APIs Explained – An Overview of API Integration and its Benefits ]]>
                </title>
                <description>
                    <![CDATA[ By Rashmi Sharma Technology is fast-paced and continually shapes and affects the eCommerce industry.  With the rise of technologies such as the internet of things (IoT), artificial intelligence (AI), augmented reality/virtual reality (AR/VR), and blo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-guide-to-ecommerce-apis/</link>
                <guid isPermaLink="false">66d460c537bd2215d1e245b9</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 20 Nov 2020 21:42:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/11/photo-1454779132693-e5cd0a216ed3-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Rashmi Sharma</p>
<p>Technology is fast-paced and continually shapes and affects the eCommerce industry. </p>
<p>With the rise of technologies such as the internet of things (IoT), artificial intelligence (AI), augmented reality/virtual reality (AR/VR), and blockchain, the e-Commerce world has seen many advancements. </p>
<p>But among the dozens of E-commerce-related technologies, there is one that connects them all. APIs are gradually improving eCommerce applications and websites by connecting them with each other. </p>
<p>eCommerce APIs help developers transfer information from one software to another and then use this data in a single user interface. </p>
<p>APIs work as an extensible platform that integrates with various solutions so companies can take advantage of their existing functionality without building features from scratch.</p>
<h2 id="heading-the-benefits-of-api-integration-for-an-e-commerce-platform">The Benefits of API Integration for an E-commerce Platform</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/09ff1a71704495.5bceaafda095b.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>APIs help integrate several platforms with one another. For example, you can integrate your E-commerce website with a shipping provider's account and import your data for orders and shipments. This way, you can streamline your shipping operations on one platform.</p>
<p>E-commerce fulfillment APIs can help businesses automate, coordinate, manage, and streamline their operations with features such as order fulfillment, courier management, labeling, invoicing, printing shipment information, tracking, and confirmation notifications. </p>
<p>So if you want to establish a solid platform for your E-commerce business, you will need to integrate an API in your shipping and logistics software solution. </p>
<h3 id="heading-its-extensible">It's Extensible</h3>
<p>APIs connect different software components and enable them to interact with each other by receiving requests and sending responses. </p>
<p>It extends the functionality of an application or website, helping you get work done faster. It also simplifies information sharing.</p>
<h3 id="heading-increased-security">Increased Security</h3>
<p>APIs enhance the security of software systems by letting users request data from your systems. The API protects your E-commerce data with security measures such as encryption.</p>
<h3 id="heading-reusability">Reusability</h3>
<p>APIs can be reused across platforms and applications because they are built using a service-oriented approach. </p>
<p>Reusable programs reduce the time and effort developers must spend on the project which in turn increases their productivity.</p>
<h3 id="heading-scalability">Scalability</h3>
<p>APIs enable you to connect to any new system without making any changes in the original system, program, or hardware. </p>
<p>It just takes a few lines of code to connect with any new system and offers scalability to handle more complex transactions.</p>
<h3 id="heading-synchronization">Synchronization</h3>
<p>Due to API integration, you can pick and choose elements of your online store from other applications and programs to create a platform according to your specific business needs. This way you can synchronize your operations workflow in a logical manner.</p>
<p>Through integrated systems, one company can connect to other companies’ software and applications to manage their data, shipping channels, products and services which best meet their needs. </p>
<p>So as you can see the API revolution involves all of us. </p>
<p>Now let's learn about three common types of API models that get data from systems, turn data into processes, or create an experience.</p>
<h2 id="heading-3-common-types-of-api-models">3 Common Types of API Models</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/AAEAAQAAAAAAAAUXAAAAJDA2ZjdkZDQ3LTM1ZjUtNGIzMS1hZDA3LWRjZjZkMTA2OTk5OQ.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image Source: <a target="_blank" href="https://blogs.mulesoft.com/dev/anypoint-platform-dev/api-templates-reusable-system-process-apis/">MuleSoft</a></em></p>
<p>When you use API-led integration, you can improve the way users interact with new devices and technology shifts. </p>
<p>You can improve the E-commerce fulfillment processes through multichannel selling, inventory management, shipping, or creating personalized experiences across different channels. And there are three types of models you can use to build these APIs. </p>
<h3 id="heading-experience-apis">Experience APIs</h3>
<p>These APIs are utilized for data reconfiguration so that the same data can be presented in multiple formats based on users’ needs. </p>
<p>Experience APIs create a common data source for each channel instead of setting up separate integrations for each channel.</p>
<h3 id="heading-process-apis">Process APIs</h3>
<p>These APIs are implemented when integrating different systems of an IT ecosystem. They help create independent data source points to avoid data silos within a single system or across systems. </p>
<p>They don’t rely on other systems where the data is to be delivered. This means that Process APIs help businesses looking to expand their current IT infrastructure. </p>
<h3 id="heading-system-apis">System APIs</h3>
<p>System APIs hide the complexity of the core systems such as FTP servers, ERP, CRM, or legacy systems. These types of APIs typically provide a way to access systems for data and record integration present in the business.</p>
<p>Now I will discuss some E-commerce APIs that can enhance your site’s functionality and improve your customers' experience.</p>
<h2 id="heading-the-different-types-of-e-commerce-apis"><strong>The Different Types of E-commerce APIs</strong></h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/related-tags6.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image Source: <a target="_blank" href="https://www.programmableweb.com/news/71-ecommerce-apis-seatwave-playme-and-ebay/2012/06/06">ProgrammableWeb</a></em></p>
<p>API integration lets developers integrate various functionality of their systems directly into online stores. This helps streamline the order fulfillment, shipping, and delivery processes. </p>
<p>There are hundreds of E-commerce APIs for developers to browse through. Some of them are available for adding orders, shipping, tracking, getting products, viewing, buying and selling, and even creating marketing segments, and plenty of other tasks. </p>
<p>Here I'll highlight some of the most popular and useful.</p>
<h3 id="heading-product-information-api">Product information API</h3>
<p>A product information API allows you to extract product details from a database of products that includes information about product descriptions, product ID, product titles, product specs, product images, pricing, the number of products, and more.</p>
<h3 id="heading-orders-api">Orders API</h3>
<p>Orders APIs include details about orders that are created from your channel. This API syncs order import and export status and displays a list of all created and available orders in your account. </p>
<p>A user can sort and filter the order data according to the date of packaging and shipping. </p>
<p>You can also cancel orders, return orders, create bulk import orders, and add inventory for an ordered product. Further parameters can be added as required.</p>
<h3 id="heading-inventory-api">Inventory API</h3>
<p>You can use this API to sort and filter inventory data and check the inventory details of a product's SKU. With this API you can also update product inventory details for a specified product.</p>
<h3 id="heading-shipping-api">Shipping API</h3>
<p>This API lets you automate the process of shipping. It allows you to connect with the logistics providers and you can track details from the product sale through the package being delivered to a customer. It can also automate the process of creating packaging invoices and labels.  </p>
<p>The Shipping API allows you to choose from multiple carriers based on their delivery time, rates, and location. Shipment tracking is also possible with this type of API. It enables you to integrate order tracking information into a mobile application or website or via an email or a text message. </p>
<h3 id="heading-couriers-api">Couriers API</h3>
<p>Use this API to check for courier options and serviceability in a particular region and create a request for the pickup of your order. </p>
<p>You can also use this API to assign a unique tracking number to your shipment that helps you track the shipment and get details about it.</p>
<h3 id="heading-wrapper-api">Wrapper API</h3>
<p>You can use this all in one API to create an order, ship an order, and generate labels and invoices for the same order. With this API you can do multiple tasks in one go.</p>
<h3 id="heading-channels-api">Channels API</h3>
<p>The channel API gives you details about all the integrated channels in your account (for example Shopify, Magento, Opencart, WooCommerce, or Amazon marketplace). </p>
<p>The API assigns a unique channel id at the time of order creation that can be used to select or specify a custom channel for your account. This API also shows a list of all the channels already integrated into your store’s account.</p>
<h3 id="heading-catalog-api">Catalog API</h3>
<p>With this API you can create, edit, and manage your product catalog within a few minutes. It also supports integration with other key systems of your business such as order fulfillment, inventory management, and reporting &amp; analysis.</p>
<h3 id="heading-authenticationlogin-api">Authentication/Login API</h3>
<p>The authentication or login API is the access authorization that you need to access the resources of a shipping provider's site. This login API helps the channel owner validate that you are a valid user.</p>
<h3 id="heading-payment-api">Payment API</h3>
<p>With the payment API you can create an application for the checkout process that accepts payment through credit card or debit card. The Payment API can be programmed according to your existing payment gateway.</p>
<h3 id="heading-validation-api">Validation API</h3>
<p>The Validation API helps verify the address and other customer details that they enter on the website. </p>
<p>The API verifies the address and identifies a new customer or existing customer and decides the possible deliverability issues. This API automates the validation and correction of address issues and also eliminates the hassle of verifying documents.</p>
<h3 id="heading-marketing-api">Marketing API</h3>
<p>A useful API to automate email marketing functions and segmentation based on the customer'ss buying behaviors, purchase history, location, and other criteria.</p>
<h2 id="heading-how-do-e-commerce-apis-work">How Do E-commerce APIs Work?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/11/protocols7.png" alt="Image" width="600" height="400" loading="lazy">
<em>Image Source: <a target="_blank" href="https://www.programmableweb.com/news/71-ecommerce-apis-seatwave-playme-and-ebay/2012/06/06">ProgrammableWeb</a></em></p>
<p>As mentioned above, APIs allow you to interact with your site and other systems under the hood and display this information to customers. </p>
<p>These APIs are useful for E-commerce websites because there are tons of requests, responses, and logic that you need to handle at the same time. </p>
<blockquote>
<p>These API's are commonly based on REST architecture and use the HTTP request methods to make a request for information to the server. </p>
</blockquote>
<p>REST APIs use a simple URL and are lightweight to implement with almost any tool. Developers get a set of search and query tools with the REST API that helps them gain deeper access to a website's data. </p>
<p>REST APIs also support OAuth 2.0 authentication for security purposes (which is also used in popular apps such as Facebook and Google). Additionally, the REST API supports cross-platform development and a wider range of programming languages. </p>
<p>Developers can integrate E-commerce APIs into a website by using the following information.</p>
<ul>
<li>Resources refers to an object that determines the relationship to other resources and methods that operate on it.</li>
<li>Endpoints are touchpoints of interaction of APIs with another system.</li>
<li>HTTP Methods are the allowed interactions that include GET for retrieving resources, POST for creating resources, and PUT for changing or replacing resources.</li>
<li>Parameters are criteria that have a name, value type, and description to determine the type of action you want to take on the resource.</li>
<li>The Sample Request and Response Objects identify inputs and outputs of a resource.</li>
</ul>
<h2 id="heading-end-note">End Note</h2>
<p>E-commerce shipping and order fulfillment can be much easier if you use API integration and the right technology. </p>
<p>At the end of the day, every decision you make about your E-commerce business should help you achieve your goals. As far as shipping and order fulfillment is concerned, using E-commerce APIs achieves both of these objectives. </p>
<p>So what are you waiting for? Get started using E-commerce APIs now!</p>
<p>Need some help? You can get in touch with the <a target="_blank" href="https://www.shiprocket.in/">Shiprocket</a> team to learn more about possible solutions.</p>
<p><em>Thank you for reading!</em>   </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How I Built a Mobile App for Online Shopping Amid the COVID-19 Lockdown ]]>
                </title>
                <description>
                    <![CDATA[ By Ogbeta Ovokerie When many stores that sell nonessential items were ordered to close in March by government decrees, consumers moved their shopping online. U.S. online sales increased 49% in April over the prior year, according to Adobe Analytics. ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-i-built-a-mobile-app-for-online-shopping-amid-covid-19-lock-down/</link>
                <guid isPermaLink="false">66d460897df3a1f32ee7f87f</guid>
                
                    <category>
                        <![CDATA[ android app development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ecommerce ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Java ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mobile app development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jun 2020 12:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/06/ecommerce-blog-1.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Ogbeta Ovokerie</p>
<p>When many stores that sell nonessential items were ordered to close in March by government decrees, consumers moved their shopping online. U.S. online sales increased 49% in April over the prior year, according to <a target="_blank" href="https://www.adobe.com/analytics/abobe-analytics.html">Adobe Analytics</a>. </p>
<p>The increase in sales was not particularly significant on desktop. But mobile shopping apps such as Instacart <a target="_blank" href="https://www.visualcapitalist.com/covid-19-impact-on-popularity/">saw an increase of about 650% (wow!)</a> in new mobile app users (in the U.S alone) between March and April. </p>
<p>So, in order for e-Commerce stores to take full advantage of the increase in online shoppers during the Coronavirus pandemic, they should have a native mobile app. </p>
<p>In this tutorial, I will be developing a native mobile Android app using the Woocommerce theme. I will also explain why I went for a native mobile app (instead of a hybrid app or a progressive web app) and the Woocommerce theme. Even better, I will start with some research data to show just how important it is for an e-Commerce store to have a mobile app.</p>
<h2 id="heading-traffic-to-brick-and-mortar-stores-has-almost-vanished">Traffic to brick-and-mortar stores has almost vanished</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Is-there-anybody-out-there.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Is there anybody out there?</em></p>
<p>Consumer surveys suggest that the shift to online shopping will continue as long as COVID-19 remains a threat. A <a target="_blank" href="https://www.rsrresearch.com/research">survey of 1,200 consumers in late March</a> 2020 found that 90% of shoppers were hesitant to shop in stores because of the Coronavirus pandemic. And 45% expected online shopping would be a necessity for them during the crisis. </p>
<p>A <a target="_blank" href="https://bizrateinsights.com/">separate survey in April</a> found that 55% of online consumers said they were ordering more online than they were before the virus hit, up from 26% in March. And 22% said in April they were ordering a lot more online, as opposed to only 6% in March. </p>
<p>If the pandemic continues to linger, there could be a big shift to online shopping during retailers’ two biggest seasons of the year – back-to-school and holiday shopping. 23% of retailers <a target="_blank" href="https://go.forrester.com/research/">surveyed in March</a> planned to shift resources as a result of the Coronavirus outbreak.</p>
<p>Retail has been forced to make some major changes. As all non-essential businesses were urged to shut down, and foot traffic into brick-and-mortar retail stores <a target="_blank" href="https://qz.com/1829531/covid-19-has-caused-us-retail-traffic-to-almost-entirely-vanish/">all but vanished</a>. Even with staggered re-openings, the importance of having e-Commerce functionality is clearer than ever. </p>
<p>The pandemic proved the need for businesses of all types to be flexible and ready for anything, and the need for them to take the digital leap. e-Commerce will be front and center for the new normal.</p>
<h2 id="heading-but-my-client-has-a-responsive-website">But my client has a responsive website!</h2>
<p>A number of trends are converging that make website owners want a native mobile app. The main goal of every website owner is to add value to the business by reaching more users. I think it will come as no surprise that mobile phone usage is on the rise and more people interact with their mobile phones than with desktops. </p>
<p>In order to tap into this market of mobile phone users, we need to know which types of apps these mobile phone users interact with the most (progressive web apps, hybrid apps, responsive mobile websites, or native apps). Luckily for us the people at <a target="_blank" href="https://www.go-globe.com/mobile-apps-usage">Go-Globe</a> did an analysis: a whopping 85% of mobile users interact with native mobile apps the most.</p>
<p>There are a number of other reasons why companies should have a native mobile app to meet the needs of the growing number of online shoppers amid the coronavirus pandemic. Let's explore them now a bit more.</p>
<h3 id="heading-they-offer-a-better-experience-to-loyal-website-visitors">They offer a better experience to loyal website visitors</h3>
<p>Mobile sites are great for discovery. But loyal users – those that come back more often – want to have an app. People use apps more than they use search on mobile devices. A brand (that is, an app) on a user’s home screen is a constant reminder of a site and its content.</p>
<h3 id="heading-they-connect-directly-with-website-visitors">They connect directly with website visitors</h3>
<p>A brand can be on all social channels, but only a fraction of users will ever see its message. Emails can also be sent, but with a 25% open rate, only a fraction of the audience will be reached.</p>
<p>A branded mobile app gives a direct line to users, ultimately retaining users and turning casual visitors into loyal users.</p>
<h3 id="heading-they-make-use-of-mobile-device-features">They make use of mobile device features</h3>
<p>Native mobile apps have the advantage of utilizing features of a mobile phone like the camera, contact list, GPS, phone calls, accelerometer, compass, and etc. Such device features, when used within an app, can make the user experience interactive and fun. </p>
<p>Moreover, these features can also reduce the efforts users would have to make otherwise. For instance, users that would like to know the location of a business can use the GPS/navigation to easily find it (especially useful in food apps). </p>
<p>These features can also significantly shorten the time users take to perform a certain task in an app, and can even boost conversions. </p>
<p>Mobile websites, PWAs, and hybrid apps can also use some of the device's features. Still, there are technological constraints or limits in utilizing all the features of a device which native mobile apps can use easily.</p>
<h3 id="heading-they-give-you-the-ability-to-work-offline">They give you the ability to work offline</h3>
<p>This is probably the most fundamental difference between a mobile website and an app. Although native mobile apps might require internet connectivity to perform most of their tasks, they can still offer basic content and functionality to users in offline mode. </p>
<p>Let’s take the example of an e-Commerce website – the app can provide features like tax and installment calculation, and determine a user's spending limit. These features can work even without the help of an internet connection.</p>
<p>Even though mobile websites, PWAs, and hybrid mobile apps can use caching to load web pages without an internet connection, they can only offer limited functions.</p>
<h3 id="heading-they-help-increase-brand-presence">They help increase brand presence</h3>
<p>Users spend a substantial amount of their time on mobile devices. It’s safe to say that many users see the apps they’ve installed on their devices almost every day.</p>
<p>This regular encounter can be viewed as a branding opportunity for the apps. Even when users are not actively using a mobile app, they are still reminded of the brand associated with the app. The app's icon acts like a mini-advertisement for the brand.  </p>
<p>The presence of an app on a user’s device helps influence their perception about a brand subconsciously. This behavior can be linked to the Signal Detection Theory, which suggests that users still process ads they’ve ignored at some level subconsciously.</p>
<h3 id="heading-apps-can-work-faster-than-websites">Apps can work faster than websites</h3>
<p>A well-designed mobile app can perform actions much quicker than a mobile website.<br>Apps usually store their data locally on mobile devices, in contrast to websites that generally use web servers. For this reason, data retrieval happens swiftly in mobile apps. </p>
<p>Apps can further save users’ time by storing their preferences and using them to take proactive actions on their behalf.</p>
<p>There is also a technical justification as to why mobile apps can work faster. Mobile websites use JavaScript code to perform most of their functions. And some of the frameworks that mobile apps use can run almost five times faster than JavaScript! </p>
<p>While all this is happening in the background, users get to complete actions more quickly on the front end of mobile apps, again contributing to a delightful user experience.</p>
<h3 id="heading-they-increase-seo-potential-for-a-website">They increase SEO potential for a website</h3>
<p>Mobile apps can be advantageous in two ways – for in-app content and website content as synonymous words will be used in the content for products and services. Google these days rank in-app content too and you can modify your content in your application to help you with your website SEO.</p>
<h3 id="heading-mobile-app-spending-is-expected-to-double-by-2024-despite-the-economic-impacts-of-covid-19">Mobile app spending is expected to double by 2024, despite the economic impacts of COVID-19</h3>
<p><a target="_blank" href="https://sensortower.com/blog/sensor-tower-app-market-forecast-2024">According to a revised 2020-2024 market forecast</a>, worldwide consumer spending in mobile apps is projected to reach $171 billion by 2024. This is more than double the $85 billion from 2019. </p>
<p>This total, however, is about $3 billion (or 2%) less than the forecast the firm had released prior to the COVID-19 outbreak.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/mobile-app-spending.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mobile App Spending</em></p>
<p>Still, it's notable that even the slowest-growing regions on both Apple's App Store and Google Play will see revenue that's over 80% higher than their 2019 levels by the year 2024. The app stores will also hit several milestones during the next five years. </p>
<p>For starters, global spending in mobile apps will surpass $100 billion for the first time in 2020, growing at approximately 20% year-over-year to hit $102 billion. This indicates that website owners do not just get more customers, but more paying customers that are willing to spend.</p>
<h3 id="heading-they-reach-out-to-a-younger-audience">They reach out to a younger audience</h3>
<p>The stats indicate that 18-24 years olds are the most active mobile app users.</p>
<p>Still not convinced? Let these statistics do the talking:</p>
<ol>
<li>77% of Americans own a smartphone.</li>
<li>Over 230 million U.S. consumers own smartphones.</li>
<li>Around 100 million U.S. consumers own tablets.</li>
<li>79% of smartphone users have made a purchase online using their mobile device in the last 6 months.</li>
<li>e-Commerce dollars now comprise 10% of <em>all</em> retail revenue.</li>
<li>80% of shoppers used a mobile phone inside of a physical store to either look up product reviews, compare prices, or find alternative store locations.</li>
<li>An estimated 10 billion mobile connected devices are currently in use.</li>
<li>Mobile app users spend an average 201.8 minutes per month shopping, compared to 10.9 minutes/month for website users.</li>
<li>58% of millennials mentioned that they preferred purchasing through apps.</li>
</ol>
<p>Ignoring these trends in mobile e-Commerce (referred to as m-Commerce in the industry) evolution means potentially missing out on more and more profit as these trends continue.</p>
<h2 id="heading-enough-talk-lets-write-some-code">Enough talk! Let’s write some code</h2>
<p>All native mobile apps are just a bundle of code written in Java, Kotlin, Objective-C, or Swift that manipulate data and resources (.png, .xml files). The manipulated data can be retrieved from the mobile device’s sensors such as the screen, camera, storage memory, GPS, speakers, accelerometer, compass, or from a server. </p>
<p>In this tutorial we will be using the following tools:</p>
<ul>
<li><strong>JSoup (a</strong> J<strong>ava library):</strong> JSoup is an HTML parser which can directly parse a URL, HTML text content, and provides a set of very convenient API interfaces to manipulate data.</li>
<li><strong>Android Studio:</strong> This is the official tool for writing and compiling Android apps in Java or Kotlin and produces ready to install .apk files. All code in this tutorial will be written in Java.</li>
<li><strong>Woocommerce Plugin:</strong> This <a target="_blank" href="https://trends.builtwith.com/shop/WooCommerce">is the most popular e-Commerce plugin for WordPress</a>. So building a mobile app for the most popular e-Commerce plugin seems like a good idea.</li>
</ul>
<h2 id="heading-getting-and-manipulating-data-from-the-server">Getting and Manipulating Data from the Server</h2>
<p>Data will be gotten from the server using a <a target="_blank" href="https://medium.com/extend/what-is-rest-a-simple-explanation-for-beginners-part-1-introduction-b4a072f8740f">RESTful API</a>. WooCommerce (version 2.6+) is fully integrated with the WordPress REST API. This allows data to be created, read, updated, and deleted using requests in JSON format. It uses WordPress REST API Authentication methods and standard HTTP verbs which are understood by most HTTP clients. </p>
<p>I will be using the API version 2, v2 which is available for Woocommerce version 3.0.x or later and WordPress version 4.4 or later.</p>
<p>The default response format is JSON. Successful requests will return a <code>200 OK</code> HTTP status. Some key information about responses are:</p>
<ul>
<li>Dates are returned in ISO8601 format: <code>YYYY-MM-DDTHH:MM:SS</code></li>
<li>Resource IDs are returned as integers.</li>
<li>Any decimal monetary amount, such as prices or totals, will be returned as strings with two decimal places.</li>
<li>Other amounts, such as item counts, are returned as integers.</li>
<li>Blank fields are generally included as <code>null</code> or empty string instead of being omitted.</li>
</ul>
<p>Most requests made to the Woocommerce REST API have to be authenticated using pre-generated keys (consumer key and consumer secret). New keys are generated through the WordPress admin interface. Just go to WooCommerce &gt; Settings &gt; API &gt; Keys/Apps.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/woocommerce-api-keys-settings.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click the "Add Key" button. In the next screen, add a description and select the WordPress user you would like to generate the key for. Then click the "Generate API Key" button and WooCommerce will generate REST API keys for the selected user.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/woocommerce-creating-api-keys.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now that keys have been generated, you should see two new keys. These two keys are your Consumer Key and Consumer Secret.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/woocommerce-api-key-generated.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We then create a URL which will include the business website and the API endpoint from which the data in JSON format will be returned. </p>
<p>Using the Jsoup library, we connect to the URL (website + API endpoint) and add the API keys (consumer key and consumer secret) with other parameters of the API endpoint. Almost all endpoints accept optional parameters which can be passed as a HTTP query string parameter:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> JSOUP_CONNECTION_TIMEOUT = <span class="hljs-number">100000</span>;

String websiteUrl = <span class="hljs-string">"www.freecodecamp.shop"</span>; <span class="hljs-comment">//example website doesn't exist</span>
<span class="hljs-comment">//This API lets you retrieve all product categories</span>
String apiExtension = <span class="hljs-string">"/wp-json/wc/v2/products/categories"</span>;
<span class="hljs-comment">//Map to store our parameters in a key value format</span>
HashMap&lt;String, String&gt; data = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
data.put(<span class="hljs-string">"page"</span>, String.valueOf(page)); <span class="hljs-comment">// API parameter to get the current page of the collection. Default is 1.</span>
<span class="hljs-comment">//add API keys for authentication</span>
data.put(<span class="hljs-string">"consumer_key"</span>, getKey());
data.put(<span class="hljs-string">"consumer_secret"</span>, getSecret());

<span class="hljs-comment">//concatenate both websiteUrl and apiExtension to form the requestUrl</span>
String requestUrl = websiteUrl + apiExtension

<span class="hljs-keyword">try</span> {
    Connection.Response response = Jsoup.connect(requestUrl).timeout(JSOUP_CONNECTION_TIMEOUT).followRedirects(<span class="hljs-keyword">true</span>)
        .ignoreContentType(<span class="hljs-keyword">true</span>)
        .data(data)
        .execute();

    String json = response.body();
    <span class="hljs-comment">//parse json string to get needed data.</span>

} <span class="hljs-keyword">catch</span> (Exception e){
    <span class="hljs-comment">//catch both JSONException and IOException</span>
}
</code></pre>
<p>The  <code>getKey()</code> and <code>getSecret()</code> methods just return the API keys:</p>
<pre><code class="lang-java">
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">getKey</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"ck_a89d59d7441f027df0d91f01c9e2dcaxxxxxxxxx"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">getSecret</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"cs_c3f8fe620bd5b1cb3567712eb843609xxxxxxxxx"</span>;
    }
</code></pre>
<p>But wait...for some websites, when I run this code it gives me the error <code>401 Unauthorized</code>. This is an authentication or permission error, due to incorrect API keys...so what gives?</p>
<p>Sure, the above code only works for secured websites with the HTTPS protocol, whereas unsecured websites which use the HTTP protocol will need to encrypt the API keys before sending them. </p>
<p>What does that mean, exactly? HTTPS is HTTP with encryption, as HTTPS uses TLS (SSL) to encrypt normal HTTP requests and responses. This makes HTTPS far more secure than HTTP. A website that uses HTTP has http:// in its URL, while a website that uses HTTPS has https://.</p>
<p>You must use OAuth 1.0a "one-legged" authentication to ensure your REST API credentials cannot be intercepted by an attacker. The required parameters are _oauth_consumer_key, oauth_timestamp, oauth_nonce, oauth<em>signature</em> and _oauth_signature<em>method.</em></p>
<p>We will create a method that will get these encrypted parameters as a HashMap. The method, <code>getAuthenticationPrams(String url, HashMap&lt;String, String&gt; mData)</code> will accept a request URL (website URL + API extension) and any parameters that we might want to add to the API extension. </p>
<p>Here we collect and normalize our parameters, which includes all <em>oauth</em>*_ parameters except for the _oauth<em>signature</em> itself.</p>
<pre><code class="lang-java"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> HashMap&lt;String, String&gt; <span class="hljs-title">getAuthenticationParams</span><span class="hljs-params">(String url, <span class="hljs-meta">@Nullable</span> HashMap&lt;String, String&gt; mData)</span></span>{
    HashMap&lt;String, String&gt; data = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
    <span class="hljs-keyword">if</span>(url.startsWith(<span class="hljs-string">"http://"</span>)){
        String nonce = <span class="hljs-keyword">new</span> TimestampService().getNonce();
        String timestamp = <span class="hljs-keyword">new</span> TimestampService().getTimestampInSeconds();

        data.put(<span class="hljs-string">"oauth_consumer_key"</span>, getKey());
        data.put(<span class="hljs-string">"oauth_signature_method"</span>, <span class="hljs-string">"HMAC-SHA1"</span>);
        data.put(<span class="hljs-string">"oauth_version"</span>, <span class="hljs-string">"1.0"</span>);
        data.put(<span class="hljs-string">"oauth_nonce"</span>, nonce);
        data.put(<span class="hljs-string">"oauth_timestamp"</span>, timestamp);

        <span class="hljs-keyword">if</span>(mData != <span class="hljs-keyword">null</span>)
            data.putAll(mData);

        String firstBaseString = <span class="hljs-string">"GET&amp;"</span> + urlEncoded(url);
        String generatedBaseString = formatQuery(data);

        ParametersList result = <span class="hljs-keyword">new</span> ParametersList();
        result.addQuerystring(generatedBaseString);
        generatedBaseString = result.sort().asOauthBaseString();
        String secondBaseString = <span class="hljs-string">"&amp;"</span> + generatedBaseString;

        <span class="hljs-keyword">if</span> (firstBaseString.contains(<span class="hljs-string">"%3F"</span>)) {
            secondBaseString = <span class="hljs-string">"%26"</span> + urlEncoded(generatedBaseString);
        }
        String baseString = firstBaseString + secondBaseString;
        String signature = <span class="hljs-keyword">new</span> HmacSha1SignatureService().getSignature(baseString, getSecret(), <span class="hljs-string">""</span>);
        data.put(<span class="hljs-string">"oauth_signature"</span>, signature);

    } <span class="hljs-keyword">else</span>{
        data.put(<span class="hljs-string">"consumer_key"</span>, getKey());
        data.put(<span class="hljs-string">"consumer_secret"</span>, getSecret());

        data.putAll(mData);
    }

    <span class="hljs-keyword">return</span> data;
}
</code></pre>
<p>The <code>TimestampService</code> class generates a timestamp and nonce for the _oauth<em>nonce</em> and _oauth<em>timestamp</em> parameters.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.util.Random;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TimestampService</span> </span>{
    <span class="hljs-keyword">private</span> Timer timer;

    <span class="hljs-comment">/**
     * Default constructor.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TimestampService</span><span class="hljs-params">()</span> </span>{
        timer = <span class="hljs-keyword">new</span> Timer();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getNonce</span><span class="hljs-params">()</span> </span>{
        Long ts = getTs();
        <span class="hljs-keyword">return</span> String.valueOf(ts + timer.getRandomInteger());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getTimestampInSeconds</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> String.valueOf(getTs());
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> Long <span class="hljs-title">getTs</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> timer.getMilis() / <span class="hljs-number">1000</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">setTimer</span><span class="hljs-params">(Timer timer)</span> </span>{
        <span class="hljs-keyword">this</span>.timer = timer;
    }

    <span class="hljs-comment">/**
     * Inner class that uses {<span class="hljs-doctag">@link</span> System} for generating the timestamps.
     *
     */</span>
    <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Timer</span> </span>{
        <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Random rand = <span class="hljs-keyword">new</span> Random();
        <span class="hljs-function">Long <span class="hljs-title">getMilis</span><span class="hljs-params">()</span> </span>{
            <span class="hljs-keyword">return</span> System.currentTimeMillis();
        }

        <span class="hljs-function">Integer <span class="hljs-title">getRandomInteger</span><span class="hljs-params">()</span> </span>{
            <span class="hljs-keyword">return</span> rand.nextInt();
        }
    }

}
</code></pre>
<p>The  <code>formatQuery(HashMap&lt;String, String&gt; mData)</code> method formats the parameters into query parameters which are a set of parameters attached to the end of a url. The <code>urlEncoded(String url)</code> method translates the given string into an application/x-www-form-urlencoded format using a specific encoding scheme. This method uses the supplied encoding scheme to obtain the bytes for unsafe characters.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.io.UnsupportedEncodingException;
<span class="hljs-keyword">import</span> java.net.URLEncoder;

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">formatQuery</span><span class="hljs-params">(HashMap&lt;String, String&gt; mData)</span></span>{
        <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>;
        StringBuilder param = <span class="hljs-keyword">new</span> StringBuilder();
        <span class="hljs-keyword">for</span>(String key : mData.keySet()){
            <span class="hljs-keyword">if</span>(i &gt; <span class="hljs-number">0</span>){
                param.append(<span class="hljs-string">"&amp;"</span>);
            }
            param.append(key);
            param.append(<span class="hljs-string">"="</span>);
            param.append(mData.get(key));
            i++;
        }

        <span class="hljs-keyword">return</span> param.toString();
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">urlEncoded</span><span class="hljs-params">(String url)</span> </span>{
        String encodedurl = <span class="hljs-string">""</span>;
        <span class="hljs-keyword">try</span> {
            encodedurl = URLEncoder.encode(url, <span class="hljs-string">"UTF-8"</span>);
        } <span class="hljs-keyword">catch</span> (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        <span class="hljs-keyword">return</span> encodedurl;
    }
</code></pre>
<p>The <code>ParametersList</code> class takes the formatted query and then encodes it using the <code>OAuthEncoder</code> class. The values that start with <em>oauth</em>*_ need to be encoded into a string which will be used later on. The process to build the string is very specific:</p>
<ol>
<li>Percent encode every key and value that will be signed.</li>
<li>Sort the list of parameters alphabetically by encoded key.</li>
<li><p>For each key/value pair: </p>
</li>
<li><p>Append the encoded key to the output string</p>
</li>
<li>Append the <code>=</code> character to the output string</li>
<li>Append the encoded value to the output string</li>
<li>If there are more key/value pairs remaining, append an <code>&amp;</code> character to the output string.</li>
</ol>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.util.ArrayList;
<span class="hljs-keyword">import</span> java.util.Collections;
<span class="hljs-keyword">import</span> java.util.List;
<span class="hljs-keyword">import</span> java.util.Map;


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ParametersList</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">char</span> QUERY_STRING_SEPARATOR = <span class="hljs-string">'?'</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String PARAM_SEPARATOR = <span class="hljs-string">"&amp;"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String PAIR_SEPARATOR = <span class="hljs-string">"="</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String EMPTY_STRING = <span class="hljs-string">""</span>;

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> List&lt;Parameter&gt; params;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">ParametersList</span><span class="hljs-params">()</span> </span>{
        params = <span class="hljs-keyword">new</span> ArrayList&lt;Parameter&gt;();
    }

    ParametersList(List&lt;Parameter&gt; params) {
        <span class="hljs-keyword">this</span>.params = <span class="hljs-keyword">new</span> ArrayList&lt;Parameter&gt;(params);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">add</span><span class="hljs-params">(String key, String value)</span> </span>{
        params.add(<span class="hljs-keyword">new</span> Parameter(key, value));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">asOauthBaseString</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> OAuthEncoder.encode(asFormUrlEncodedString());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">asFormUrlEncodedString</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">if</span> (params.size() == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> EMPTY_STRING;

        StringBuilder builder = <span class="hljs-keyword">new</span> StringBuilder();
        <span class="hljs-keyword">for</span>(Parameter p : params) {
            builder.append(<span class="hljs-string">'&amp;'</span>).append(p.asUrlEncodedPair());
        }
        <span class="hljs-keyword">return</span> builder.toString().substring(<span class="hljs-number">1</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addAll</span><span class="hljs-params">(ParametersList other)</span> </span>{
        params.addAll(other.params);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">addQuerystring</span><span class="hljs-params">(String queryString)</span> </span>{
        <span class="hljs-keyword">if</span> (queryString != <span class="hljs-keyword">null</span> &amp;&amp; queryString.length() &gt; <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">for</span> (String param : queryString.split(PARAM_SEPARATOR)) {
                String pair[] = param.split(PAIR_SEPARATOR);
                String key = OAuthEncoder.decode(pair[<span class="hljs-number">0</span>]);
                String value = pair.length &gt; <span class="hljs-number">1</span> ? OAuthEncoder.decode(pair[<span class="hljs-number">1</span>]) : EMPTY_STRING;
                params.add(<span class="hljs-keyword">new</span> Parameter(key, value));
            }
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">contains</span><span class="hljs-params">(Parameter param)</span> </span>{
        <span class="hljs-keyword">return</span> params.contains(param);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> params.size();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> ParametersList <span class="hljs-title">sort</span><span class="hljs-params">()</span> </span>{
        ParametersList sorted = <span class="hljs-keyword">new</span> ParametersList(params);

        Collections.sort(sorted.params);
        <span class="hljs-keyword">return</span> sorted;
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Parameter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Comparable</span>&lt;<span class="hljs-title">Parameter</span>&gt; </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String key;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String value;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">Parameter</span><span class="hljs-params">(String key, String value)</span> </span>{
        <span class="hljs-keyword">this</span>.key = key;
        <span class="hljs-keyword">this</span>.value = value;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">asUrlEncodedPair</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> OAuthEncoder.encode(key).concat(<span class="hljs-string">"="</span>).concat(OAuthEncoder.encode(value));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">equals</span><span class="hljs-params">(Object other)</span> </span>{
        <span class="hljs-keyword">if</span>(other == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
        <span class="hljs-keyword">if</span>(other == <span class="hljs-keyword">this</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
        <span class="hljs-keyword">if</span>(!(other <span class="hljs-keyword">instanceof</span> Parameter)) <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;

        Parameter otherParam = (Parameter) other;
        <span class="hljs-keyword">return</span> otherParam.key.equals(key) &amp;&amp; otherParam.value.equals(value);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">hashCode</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> key.hashCode() + value.hashCode();
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">compareTo</span><span class="hljs-params">(Parameter parameter)</span> </span>{
        <span class="hljs-keyword">int</span> keyDiff = key.compareTo(parameter.key);

        <span class="hljs-keyword">return</span> keyDiff != <span class="hljs-number">0</span> ? keyDiff : value.compareTo(parameter.value);
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> java.io.UnsupportedEncodingException;
<span class="hljs-keyword">import</span> java.net.URLDecoder;
<span class="hljs-keyword">import</span> java.net.URLEncoder;
<span class="hljs-keyword">import</span> java.util.Collections;
<span class="hljs-keyword">import</span> java.util.HashMap;
<span class="hljs-keyword">import</span> java.util.Map;
<span class="hljs-keyword">import</span> java.util.regex.Pattern;


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OAuthEncoder</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String CHARSET = <span class="hljs-string">"UTF-8"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Map&lt;String, String&gt; ENCODING_RULES;

    <span class="hljs-keyword">static</span> {
        Map&lt;String, String&gt; rules = <span class="hljs-keyword">new</span> HashMap&lt;String, String&gt;();
        rules.put(<span class="hljs-string">"*"</span>, <span class="hljs-string">"%2A"</span>);
        rules.put(<span class="hljs-string">"+"</span>, <span class="hljs-string">"%20"</span>);
        rules.put(<span class="hljs-string">"%7E"</span>, <span class="hljs-string">"~"</span>);
        ENCODING_RULES = Collections.unmodifiableMap(rules);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">OAuthEncoder</span><span class="hljs-params">()</span></span>{}

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">encode</span><span class="hljs-params">(String plain)</span> </span>{
        String encoded = <span class="hljs-string">""</span>;
        <span class="hljs-keyword">try</span> {
            encoded = URLEncoder.encode(plain, CHARSET);
        }
        <span class="hljs-keyword">catch</span> (UnsupportedEncodingException uee) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> OAuthException(<span class="hljs-string">"Charset not found while encoding string: "</span> + CHARSET, uee);
        }
        <span class="hljs-keyword">for</span>(Map.Entry&lt;String, String&gt; rule : ENCODING_RULES.entrySet()) {
            encoded = applyRule(encoded, rule.getKey(), rule.getValue());
        }
        <span class="hljs-keyword">return</span> encoded;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">applyRule</span><span class="hljs-params">(String encoded, String toReplace, String replacement)</span> </span>{
        <span class="hljs-keyword">return</span> encoded.replaceAll(Pattern.quote(toReplace), replacement);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> String <span class="hljs-title">decode</span><span class="hljs-params">(String encoded)</span> </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> URLDecoder.decode(encoded, CHARSET);
        }
        <span class="hljs-keyword">catch</span>(UnsupportedEncodingException uee) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> OAuthException(<span class="hljs-string">"Charset not found while decoding string: "</span> + CHARSET, uee);
        }
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OAuthConstants</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-title">OAuthConstants</span><span class="hljs-params">()</span></span>{}

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String OUT_OF_BAND = <span class="hljs-string">"oob"</span>;
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OAuthException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">RuntimeException</span> </span>{

    <span class="hljs-comment">/**
     * Default constructor
     * <span class="hljs-doctag">@param</span> message message explaining what went wrong
     * <span class="hljs-doctag">@param</span> e original exception
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OAuthException</span><span class="hljs-params">(String message, Exception e)</span> </span>{
        <span class="hljs-keyword">super</span>(message, e);
    }

    <span class="hljs-comment">/**
     * No-exception constructor. Used when there is no original exception
     *
     * <span class="hljs-doctag">@param</span> message message explaining what went wrong
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OAuthException</span><span class="hljs-params">(String message)</span> </span>{
        <span class="hljs-keyword">super</span>(message, <span class="hljs-keyword">null</span>);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> serialVersionUID = <span class="hljs-number">1L</span>;
}
</code></pre>
<p>The collected values (<em>oauth</em>*_ parameters + API extension parameters) must be joined to make a single string, from which the signature will be generated. This is called the signature base string in the OAuth specification. </p>
<p>To encode the HTTP method, request URL, and parameter string into a single string:</p>
<ol>
<li>Set the output string equal to the uppercase HTTP Method (GET in this example).</li>
<li>Append the <code>&amp;</code> character to the output string.</li>
<li>Percent encode the URL and append it to the output string.</li>
<li>Append the <code>&amp;</code> character to the output string</li>
<li>Percent encode the parameter strng and append it to the output string.</li>
</ol>
<p>The <code>HmacSha1SignatureService</code> class generates the signature using the signature base string and your consumer secret key with an <code>&amp;</code> character with the HMAC-SHA1 hashing algorithm.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> android.util.Base64;

<span class="hljs-keyword">import</span> javax.crypto.Mac;
<span class="hljs-keyword">import</span> javax.crypto.spec.SecretKeySpec;


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HmacSha1SignatureService</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String EMPTY_STRING = <span class="hljs-string">""</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String CARRIAGE_RETURN = <span class="hljs-string">"\r\n"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String UTF8 = <span class="hljs-string">"UTF-8"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String HMAC_SHA1 = <span class="hljs-string">"HmacSHA1"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String METHOD = <span class="hljs-string">"HMAC-SHA1"</span>;

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getSignature</span><span class="hljs-params">(String baseString, String apiSecret, String tokenSecret)</span> </span>{
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">return</span> doSign(baseString, OAuthEncoder.encode(apiSecret) + <span class="hljs-string">'&amp;'</span> + OAuthEncoder.encode(tokenSecret));
        }
        <span class="hljs-keyword">catch</span> (Exception e) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> OAuthSignatureException(baseString, e);
        }
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> String <span class="hljs-title">doSign</span><span class="hljs-params">(String toSign, String keyString)</span> <span class="hljs-keyword">throws</span> Exception </span>{

        SecretKeySpec key = <span class="hljs-keyword">new</span> SecretKeySpec((keyString).getBytes(UTF8), HMAC_SHA1);
        Mac mac = Mac.getInstance(HMAC_SHA1);
        mac.init(key);
        <span class="hljs-keyword">byte</span>[] bytes = mac.doFinal(toSign.getBytes(UTF8));
        <span class="hljs-keyword">return</span> bytesToBase64String(bytes).replace(CARRIAGE_RETURN, EMPTY_STRING);
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> String <span class="hljs-title">bytesToBase64String</span><span class="hljs-params">(<span class="hljs-keyword">byte</span>[] bytes)</span> </span>{
        <span class="hljs-keyword">return</span> Base64.encodeToString(bytes,Base64.NO_WRAP);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getSignatureMethod</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> METHOD;
    }
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-comment">/**
 * Specialized exception that represents a problem in the signature
  */</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OAuthSignatureException</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">OAuthException</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> serialVersionUID = <span class="hljs-number">1L</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String MSG = <span class="hljs-string">"Error while signing string: %s"</span>;

    <span class="hljs-comment">/**
     * Default constructor
     *
     * <span class="hljs-doctag">@param</span> stringToSign plain string that gets signed (HMAC-SHA, etc)
     * <span class="hljs-doctag">@param</span> e original exception
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">OAuthSignatureException</span><span class="hljs-params">(String stringToSign, Exception e)</span> </span>{
        <span class="hljs-keyword">super</span>(String.format(MSG, stringToSign), e);
    }

}
</code></pre>
<p>That's it! Now you can connect to any e-Commerce website that uses the Woocommerce plugin without installing any other plugin. Using the WordPress REST API you can get/manipulate the data you want using its API endpoints. The endpoints I used are:</p>
<ol>
<li><code>/wp-json/wc/v2/customers</code> - lets you create a new customer after you have verified them through the signin/login screen of the app.</li>
<li><code>/wp-json/wc/v2/payment_gateways</code> - lets you retrieve and view all the available payment gateways</li>
<li><code>/wp-json/wc/v2/products</code> - helps you view all the products that have been sold on the website</li>
<li><code>/wp-json/wc/v2/shipping/zones</code> - helps you create a new shipping zone</li>
<li><code>/wp-json/wc/v2/settings/general/woocommerce_currency</code> - gets the currency used</li>
<li><code>/wp-json/wc/v2/orders</code> - helps you create a new order</li>
<li><code>/wp-json/wc/v2/products/categories</code> - lets you retrieve all product categories</li>
<li><code>/wp-json/wc/v2/coupons</code> - helps you list all the coupons that have been created by the website administrator</li>
</ol>
<p>Here's my final mobile app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/06/Woostroid_Optimized.gif" alt="Image" width="600" height="400" loading="lazy">
<em>My Final Product</em></p>
<h2 id="heading-resources-that-makes-the-app-look-pretty">Resources that Makes the App Look Pretty</h2>
<p>In finalizing your native mobile app creation, you will need a UI/UX which the user will interact with to manipulate the data gotten from the WordPress server. Luckily, the people at <a target="_blank" href="https://wsdesign.in/freebies/">Wsdesign</a> have some free and ready to use templates that you can download.</p>
<p>I hope that you found this article useful and it was able to help you learn and build an awesome app today. If you really liked it, please do share it on all social media platforms.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
