<?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" version="2.0"><channel><title><![CDATA[Osada's Blog]]></title><description><![CDATA[Osada's Blog]]></description><link>https://blog.osadavidath.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 19:03:03 GMT</lastBuildDate><atom:link href="https://blog.osadavidath.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How to build an eCommerce website and integrating Email notification only using open source tools]]></title><description><![CDATA[TL;DR
In this tutorial, you’ll learn how to build an eCommerce store that you can fully customize and even prepare the ground for sending transactional notifications and messages to buyers.

As a developer, knowing how to build an eCommerce website w...]]></description><link>https://blog.osadavidath.com/how-to-build-an-ecommerce-website-and-integrating-email-notification-only-using-open-source-tools</link><guid isPermaLink="true">https://blog.osadavidath.com/how-to-build-an-ecommerce-website-and-integrating-email-notification-only-using-open-source-tools</guid><category><![CDATA[ecommerce]]></category><category><![CDATA[medusa]]></category><category><![CDATA[novu]]></category><category><![CDATA[email]]></category><category><![CDATA[push notifications]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Thu, 01 Feb 2024 15:58:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802195139/4e45b3a9-e763-4724-bfef-94ee9dd3021b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-tldr">TL;DR</h2>
<p>In this tutorial, you’ll learn how to build an eCommerce store that you can fully customize and even prepare the ground for sending transactional notifications and messages to buyers.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802622773/fd785d7b-4f15-4dab-a495-3bd3780a371f.png" alt class="image--center mx-auto" /></p>
<p>As a developer, knowing how to build an eCommerce website with code is a practical and valuable skill. It opens up job opportunities, lets you work on real projects, and adds versatility to your skill set. Plus, in the world of web development, eCommerce is a stable and growing industry. So, whether you're into building your own projects, collaborating with others, or just want to stay in the loop with tech trends, diving into eCommerce coding can be a smart move.</p>
<p>1st rule in software development: <strong>Don't reinvent the wheel unless you really have to</strong>. It's fine to recreate something for learning or business purposes, BUT if something is not your core competency, pick a ready-to-use solution. This saves nerves, time, and money.</p>
<p>Open source is the developer’s way of giving back to humanity, so in this tutorial, we are going to use it. Everyone loves simple food recipes that don’t require tons of different ingredients, and in this meal, we are going to include ONLY TWO because that’s all we need.</p>
<p>Medusa is an open source, headless ecommerce engine that prides itself as an alternative to Shopify. It’s built on Node.js with modification in mind, Medusa is composed of three parts:</p>
<p>Headless commerce: The repository that exposes the REST API</p>
<p>Admin panel: Developers can use the Medusa admin panel to customize and manage their store functionalities</p>
<p>Frontend: You can develop the frontend with Next.js, a static site generator like Gatsby, or any other framework</p>
<p>You can learn more about Medusa by checking their <a target="_blank" href="https://github.com/medusajs/medusa">GitHub repository</a>.</p>
<p>The BIGGEST MISTAKE developers make is <strong>not thinking far ahead and plan for future potential needs</strong>. That is the main difference between software developers and software engineers. Building notifications infrastructure / systems is not even a real thought initially. It always starts with something small. Sending an email to confirm the purchase, sending a push notification about a sale going live, or even just an SMS with a confirmation code to authenticate the user in the registration stage. Very soon, it becomes a nightmare to build, maintain and oversee, and once the store takes off and requires to handle higher scale, the real pain come knocking on the door (It calls “Refactoring”) which in polite terminology means: “The code is crap and cannot be maintained so we need to rewrite the whole thing”.</p>
<p>So I came across a DevTool that does just that! An open-source READY-TO-INTEGRATE notifications infrastructure!</p>
<p>Honestly, I feel that sharing such a tool is almost like sharing a cheat code and competitive advantage, but they just won Producthunt’s Golden Kitty Award, so anyway, they are going to be on everyone's radar soon, so there is no real point in gatekeeping this. Enjoy!</p>
<p>Novu is the one tool you need for ALL of your notification needs in the present and the future.</p>
<p>All that is required from you is:</p>
<ol>
<li><p>Create an account</p>
</li>
<li><p>Pick the channels you wish to communicate with your customers (Email, SMS, WhatsApp, etc.)</p>
</li>
<li><p>Connect your providers (SendGrid, Twillio, FCM, etc.)</p>
</li>
<li><p>Build your tailored notification workflow (Or just use their pre-made templates)</p>
</li>
<li><p>Add the code snippet to your Backend to call Novu’s API once an event accrued</p>
</li>
</ol>
<p>And that’s it!</p>
<p>Anyway, at the end of this guide, you will have a clear idea about how to use Novu and how to implement it with your Medusa eCommerce store.</p>
<p>Are you not really a tutorial person? Or maybe just want a shortcut? I've got you covered.</p>
<p>You can obtain the source code for this tutorial on this <a target="_blank" href="https://github.com/osadavc/medusa-novu-tutorial"><strong>GitHub</strong></a> repository.****</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You’re going to use TypeScript throughout this project, for that you need to have Node JS installed on your local computer. If you don’t already have it, you can download it from the <a target="_blank" href="https://nodejs.org/en/">official Node JS website</a>.</p>
<p>It is recommended to have a preliminary idea about basic JavaScript concepts to make navigating through this tutorial easier but feel free to follow along even if you don’t have much experience.</p>
<h2 id="heading-configuring-databases">Configuring Databases</h2>
<p>To properly use Medusa and its notification features, you need to utilize two databases, namely PostgreSQL and Redis. Let’s discuss about installing each of those databases in this section.</p>
<h3 id="heading-configuring-redis">Configuring Redis</h3>
<p>If you’re using Linux or Mac, the process is fairly easier, you can just install Redis in your local computer following the instructions from <a target="_blank" href="https://redis.io/docs/install/install-redis/">Redis official documentation</a>.</p>
<p>But if you are using windows, things are little more completed, you need to have WSL2 installed in order to use Redis locally. You can find instructions for that <a target="_blank" href="https://redis.io/docs/install/install-redis/install-redis-on-windows/">here</a>.</p>
<p>Also if you don’t want to install Redis locally, you can deploy a small but free Redis instance in their free <a target="_blank" href="https://redis.com/try-free/">managed cloud platform</a> as well.</p>
<h3 id="heading-configuring-postgresql">Configuring PostgreSQL</h3>
<p>PostgreSQL is the main database that is used by Medusa. Installing Postgres is fairly straightforward. You can download the relevant installer that matches your OS from their <a target="_blank" href="https://www.postgresql.org/download/">official website</a>.</p>
<p>Once both of the databases are correctly configured, you can move on to the next step.</p>
<h2 id="heading-configuring-medusa">Configuring Medusa</h2>
<h3 id="heading-installing-medusa">Installing Medusa</h3>
<p>To test Novu integration, you need a Medusa instance up and running on your local computer. A Medusa instance consists of 3 parts, you can install and setup all three of them all at once with one single command by using the “create-medusa-app” utility provided by Medusa.</p>
<p>To create a Medusa store, open your terminal at the location of your choice and run the following command.</p>
<pre><code class="lang-bash">npx create-medusa-app@latest
</code></pre>
<p>Once you run this command on your terminal, you will be presented with a series of questions, make sure to enter your PostgreSQL database’s connection information correctly and choose to install the Next.js starter storefront (You won’t be touching its code but it would be needed for testing the integration)</p>
<p>You can find more information about “create-medusa-app” in <a target="_blank" href="https://docs.medusajs.com/create-medusa-app">their documentation</a>.</p>
<p>Now you can move into the main folder and the storefront folder and run <code>yarn dev</code> separately to start up the backend and frontend both in development mode.</p>
<aside>
💡 To test out the integration that you’re going to build, you need some products in the storefront. Make sure to create some test ones yourself or insert some dummy data to the database by following the instructions <a target="_blank" href="https://docs.medusajs.com/development/backend/install#seed-data">here</a>.
</aside>

<h3 id="heading-connecting-redis">Connecting Redis</h3>
<p>Now that you have a Medusa server up and running, you need to connect your Redis instance that you created. To do this, open the Medusa server folder (not the storefront) in your text editor, open the <code>.env</code> file and add the following line at the end of it. (Make sure to add your actual Redis URL at the end)</p>
<pre><code class="lang-sql">REDIS_URL=
</code></pre>
<p>Next, open the <code>medusa-config.js</code> file and inside the <code>modules</code> constant which is already defined, include the following object.</p>
<pre><code class="lang-tsx">eventBus: {
  resolve: "@medusajs/event-bus-redis",
  options: {
    redisUrl: REDIS_URL,
  },
},
</code></pre>
<p>Right below, in the <code>projectConfig</code> object, uncomment the line with the <code>redis_url</code> to enable Redis.</p>
<p>Congratulations ! You have successfully connected Redis to your Medusa instance. Make sure to restart the Medusa server because a restart might be necessary for these changes to reflect.</p>
<h2 id="heading-configuring-novu">Configuring Novu</h2>
<p>Now that you have a Medusa instance up and running, you have to configure Novu.</p>
<h3 id="heading-creating-a-novu-account">Creating a Novu Account</h3>
<p>To create a Novu account, head on over to <a target="_blank" href="https://hubs.ly/Q02jlYb50">Novu Signup page</a> and either use GitHub login or email and password to create yourself a brand new Novu account.</p>
<p>Now, before you go ahead and create a Novu workflow, there are a few things that you should take note of.</p>
<p>First, go to the Integration Store from the left menu and see the integrations that are currently created. You can use the Novu test providers for now but in production, you need to make use of a third party provider like SendGrid.</p>
<p>Next, head on over to the Settings section from the left hand side menu and copy the API Key. You are going to need that just in a bit inside your code.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802636089/c1e82b29-43ba-4746-b7c3-8e5e9c14291f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-creating-a-novu-workflow-for-email-notifications">Creating a Novu Workflow for Email Notifications</h3>
<p>Basically a workflow is how you send notifications in Novu, you simply create a workflow from the dashboard and trigger that workflow from the backend of your code.</p>
<p>To start creating a workflow, go to the workflow tab and click on <code>Add a workflow</code> , then choose a Blank workflow for now. On the top left of the page, you can name your workflow, I am going to name mine as “Medusa Novu Integration”. Naming the workflow will be important because you will be calling this workflow by its name.</p>
<p>In the middle of the page, you can see the area you will build your workflow on. Click the plus button below the Workflow trigger block and choose Email because you are going to be sending just Email notifications for now.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802664525/daad112d-a0f4-4626-82a8-5b733c4b8443.png" alt class="image--center mx-auto" /></p>
<p>Next, click on the pencil icon to start editing your Email notification.</p>
<p>Start by setting the Sender name and Subject per your preference and move on to the Editor section. This Editor is where you will craft your email. Now you can style, add images and do all sorts of things to the email that you’re going to be sending in a bit, but for the sake of simplicity, copy and paste the below text in your editor.</p>
<pre><code class="lang-sql">Hey {{name}},

Thank you for placing your order <span class="hljs-keyword">with</span> us.

<span class="hljs-keyword">Order</span> <span class="hljs-keyword">ID</span> - {{orderId}}
Amount - {{amount}}
</code></pre>
<p>Notice that there are some variables inside the double curly braces. These are variables that you can define from the backend when you’re sending an email.</p>
<p>Now that you’ve finished the work on Novu dashboard, click on the Update button on the top right.</p>
<h2 id="heading-handling-store-events-with-novu">Handling Store Events with Novu</h2>
<p>There is an extensive array of events in Medusa that you can use to notify users. You can take a look at the whole list of events in the <a target="_blank" href="https://docs.medusajs.com/development/events/events-list">Medusa documentation</a>.</p>
<p>But for the sake of this guide, you are going to be using the <code>order.placed</code> event mainly. As the name suggests, this event will be called when a user places an order from the frontend.</p>
<p>To listen to these events and send notifications, you can create a Notification Provider. You can find more information about that in <a target="_blank" href="https://docs.medusajs.com/development/notification/overview">their documentation</a>.</p>
<p>For the Notification Provider to properly work, we need to set up two things inside of the Medusa codebase. Namely,</p>
<ul>
<li><p>Service</p>
</li>
<li><p>Loader</p>
</li>
</ul>
<h3 id="heading-creating-the-notification-provider-service">Creating the Notification Provider Service</h3>
<p>Start by creating a new TypeScript file inside the <code>service</code> directory inside the <code>src</code> directory. Make sure to use kebab-case when naming the file. As an example, I used <code>novu-notifications.ts</code></p>
<p>Then copy and paste the following code inside that file, I will explain how each code block works in a bit.</p>
<pre><code class="lang-tsx">import { AbstractNotificationService, OrderService } from "@medusajs/medusa";
import { Novu } from "@novu/node";

class NovuNotificationsService extends AbstractNotificationService {
  static identifier = "novu-notifications";

  protected orderService: OrderService;

  protected novu: Novu;

  constructor(container) {
    super(container);

    this.orderService = container.orderService;
    this.novu = new Novu(process.env.NOVU_API_KEY);
  }

  async sendNotification(
    event: string,
    data: any,
    attachmentGenerator: unknown
  ): Promise&lt;{
    to: string;
    status: string;
    data: Record&lt;string, unknown&gt;;
  }&gt; {
    if (event === "order.placed") {
      const order = await this.orderService.retrieve(data.id, {
        relations: ["customer", "shipping_address", "payments"],
      });

      const orderData = {
        orderId: order.id.split("_")[1],
        name: `${order.shipping_address.first_name} ${order.shipping_address.last_name}`,
        amount: (order.payments[0].amount / 100).toLocaleString("en-US", {
          style: "currency",
          currency: "USD",
        }),
      };

      await this.novu.trigger("medusa-store-notifications", {
        to: {
          subscriberId: order.email,
          email: order.email,
        },
        payload: orderData,
      });

      return {
        to: order.email,
        status: "done",
        data: orderData,
      };
    }
  }

  async resendNotification(
    notification: any,
    config: any,
    attachmentGenerator: unknown
  ): Promise&lt;{
    to: string;
    status: string;
    data: Record&lt;string, unknown&gt;;
  }&gt; {
    const to: string = config.to ? config.to : notification.to;

    await this.novu.trigger("medusa-store-notifications", {
      to: {
        subscriberId: to,
        email: to,
      },
      payload: notification.data,
    });

    return {
      to,
      status: "done",
      data: notification.data,
    };
  }
}

export default NovuNotificationsService;
</code></pre>
<ul>
<li><p>Notice the imports. The <code>AbstractNotificationService</code> is the class that has extended the notification service class with. <code>OrderService</code> is used to access the order information like customer’s name and amount to include in the notification.</p>
</li>
<li><p>The <code>Novu</code> class is imported from <code>@novu/node</code> in order to make requests to the Novu API.</p>
<p>  💡 Make sure to install the `@novu/node`package by running `npm i @node/novu` inside the Medusa server</p>
</li>
<li><p>Next, you have the class named <code>NovuNotificationService</code> in this case and it requires a property named identifier. This will be used later inside the loader to call this specific notification service.</p>
</li>
<li><p>There are two variables defined for the Medusa services and the Novu and all of those two variables are initialized inside the constructor.</p>
<p>  💡 Make sure to include the Novu API key you copied earlier inside the Novu constructor like this or via an environment variable. `this.novu = new Novu("MY_NOVU_API_KEY");`</p>
</li>
</ul>
<p>Next, there are the two most important functions, <code>sendNotification</code> and <code>resendNotification</code>. The first function will be called when any of the events we have defined are triggered and the resend function is called when a resend notification is triggered from the admin panel.</p>
<p>Inside the <code>sendNotification</code> function,</p>
<ul>
<li><p>The event is first checked, in this case, it only proceeds further if the event is <code>order.placed</code> which means a new order is placed.</p>
</li>
<li><p>Then all the details are accessed from the database with the call to <code>orderService</code> like this.</p>
<pre><code class="lang-tsx">  const order = await this.orderService.retrieve(data.id, {
    relations: ["customer", "shipping_address", "payments"],
  });
</code></pre>
</li>
<li><p>Then you can see the data from the database call is organized in to a new object that matches what Novu requires. Notice that the keys here are the same as what you defined inside the Novu dashboard.</p>
<pre><code class="lang-tsx">  const orderData = {
    orderId: order.id.split("_")[1],
    name: `${order.shipping_address.first_name} ${order.shipping_address.last_name}`,
    amount: (order.payments[0].amount / 100).toLocaleString("en-US", {
      style: "currency",
      currency: "USD",
    }),
  };
</code></pre>
</li>
<li><p>Finally, the Novu workflow you initially created is called with the necessary details to trigger an email notification like this. Notice that the email is passed into <code>subscriberId</code>and <code>email</code> fields.</p>
<p>  I was having a problem here regarding sending emails to users without subscribing them to an email list. I was able to get immediate support in Novu’s Discord server. I highly recommend you to leave a message on the Discord server if you need some immediate support.</p>
<pre><code class="lang-tsx">  await this.novu.trigger("medusa-store-notifications", {
    to: {
      subscriberId: order.email,
      email: order.email,
    },
    payload: orderData,
  });
</code></pre>
</li>
</ul>
<p>Inside the <code>resendNotification</code> function,</p>
<ul>
<li>The previously sent notification details are passed into the function. So you can just take them and resend the notification as shown in the code.</li>
</ul>
<p>That’s basically everything about the Notification Provider. The next important part is the loader function that will help this service class to be loaded in the Medusa environment.</p>
<h3 id="heading-creating-the-notification-provider-loader">Creating the Notification Provider Loader</h3>
<p>Start by creating a new file named <code>notifications.ts</code> inside the <code>src/loaders</code> folder inside Medusa server with the following content.</p>
<pre><code class="lang-tsx">import { MedusaContainer, NotificationService } from "@medusajs/medusa";

export default async (container: MedusaContainer): Promise&lt;void&gt; =&gt; {
  const notificationService = container.resolve&lt;NotificationService&gt;(
    "notificationService"
  );

  notificationService.subscribe("order.placed", "novu-notifications");
};
</code></pre>
<p>Notice in the last line, the service called <code>novu-notifications</code> is subscribed to the <code>order.placed</code> event. The service name here is the one that you defined as the <code>identifier</code> in the service file. Make sure that both the names across the files are matching.</p>
<h3 id="heading-handling-more-events">Handling More Events</h3>
<p>If you want to handle more events all you have to do is add another subscribe statement in the loader file like this.</p>
<pre><code class="lang-tsx">notificationService.subscribe("order.refund_created", "novu-notifications");
</code></pre>
<p>In this case, this is being called when a refund is triggered.</p>
<p>And then send the notification with the related parameters from the service file. As it is mentioned earlier you can find all the events with their related data payloads are mentioned in the <a target="_blank" href="https://docs.medusajs.com/development/events/events-list">Medusa documentation</a>.</p>
<p>That’s basically it. Now you should be ready to test this thing out!</p>
<h2 id="heading-testing-notifications">Testing Notifications</h2>
<p>To test notifications, go to the Medusa storefront, which is usually hosted on <a target="_blank" href="http://localhost:8000">localhost:8000</a>, choose a product, add it to the cart and place an order with some dummy details.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802688915/52f72b5c-d22a-4e76-ae52-33e73749ecfe.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>You can choose from a set of example products if you chose to seed dummy products in an earlier step.</p>
</blockquote>
<p>If you have followed all the instructions and everything went well, you should now receive an email that looks something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706802720261/0c0ba0c0-184c-4c9a-b4c9-a81754c18c38.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you followed this tutorial, you should have a clear understanding of how to build an eCommerce store and even send a confirmation of purchase to the buyer!</p>
<p>Novu and Medusa have very responsive and helpful communities on Discord, so if you need help - just head there!</p>
<p>Thank you for reading so far! If you’ve found it helpful or would like me to cover something else, let me know down in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing Hasotion - Write Your Hashnode Articles Without Leaving Notion]]></title><description><![CDATA[💻
The following article is regarding the project in this GitHub repository.



💡
TL;DR Watch this video to understand how to get started with Hasotion.


Introduction 🤩
Are you someone like me who loves Hashnode as well as Notion? Well, then this ...]]></description><link>https://blog.osadavidath.com/introducing-hasotion-a-platform-to-write-your-hashnode-articles-from-notion</link><guid isPermaLink="true">https://blog.osadavidath.com/introducing-hasotion-a-platform-to-write-your-hashnode-articles-from-notion</guid><category><![CDATA[ NotionAPI]]></category><category><![CDATA[APIHackathon]]></category><category><![CDATA[hashnodeapi]]></category><category><![CDATA[hashnodeHackathon]]></category><category><![CDATA[notion]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Wed, 31 Jan 2024 18:54:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706781538362/d81d2a9c-f8f8-45ba-8190-b5c6b4bcfe6a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">💻</div>
<div data-node-type="callout-text">The following article is regarding the project in this <a target="_blank" href="https://github.com/osadavc/hasotion">GitHub repository</a>.</div>
</div>

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text"><strong>TL;DR</strong> Watch <a target="_blank" href="https://youtu.be/Z_OgY-KWM0Y?si=uBA-tKvl9mtQvdrK">this video</a> to understand how to get started with Hasotion.</div>
</div>

<h2 id="heading-introduction">Introduction 🤩</h2>
<p>Are you someone like me who loves Hashnode as well as Notion? Well, then this article is for you. Keep reading.</p>
<p>When I am writing my blog posts, most of the time I am used to writing the article in Notion and copying and pasting all of the content to Hashnode or any other blogging platform I use. You might be wondering why I do that because Hashnode already has an insanely good editor for writing your blog posts.</p>
<p>Yes, that is indeed true but writing it on Notion helps me back up my content and really helps me with publishing the same article to multiple places.</p>
<p>This product is simply a result of an attempt to streamline that process even more by making use of Hashnode GraphQL APIs and Notion APIs.</p>
<p>Also, I think it is worth mentioning that this Hackathon was quite a challenge for me because I don’t have much GraphQL experience and the very short time period I had to complete this project.</p>
<p>In case you’re curious about the weird name, Hasotion is derived from Hashnode + Notion 😉</p>
<h2 id="heading-how-hasotion-works">How Hasotion Works? 🤔</h2>
<p>For you to get a rough idea, let me go over the flow of this application in a few steps and explain them. You can also take a look at the explanation video I have linked below in the Demo section.</p>
<ol>
<li><p>Log in to the application with Google</p>
</li>
<li><p>Connect your Notion account and duplicate the provided database</p>
</li>
<li><p>Connect your Hashnode account by providing the Personal access token (PAT) and the Blog ID</p>
</li>
<li><p>Go to the Notion database. Write your article on a new page. Fill in all the details</p>
</li>
<li><p>Check the <em>Ready</em> checkbox.</p>
</li>
<li><p>Just click on the <strong>Update Article</strong> button under the title</p>
</li>
</ol>
<p>The whole process is that simple. Before going more into the notion page details (Step 4) and technical details (Step 6), let me start by introducing my tech stack, the APIs and the interesting packages I used throughout my project.</p>
<h2 id="heading-tech-stack">Tech Stack ⚙️</h2>
<ol>
<li><p>Next JS as the Javascript framework</p>
</li>
<li><p>Tailwind CSS as the CSS framework</p>
</li>
<li><p>PostgreSQL as the Database</p>
</li>
<li><p>Prisma ORM</p>
</li>
<li><p>Next Auth for Google Auth</p>
</li>
<li><p>Hashnode API to post the articles to Hashnode blog</p>
</li>
<li><p>Notion API to read content from the Notion pages. (Used this interesting package called <a target="_blank" href="https://www.npmjs.com/package/notion-to-md">notion-to-md</a> to convert notion pages to markdown easily)</p>
</li>
<li><p>Cloudinary to store the blog covers of each post.</p>
</li>
</ol>
<p>The Hashnode API was fairly easy to use. Even though I didn’t have much experience in GraphQL I was able to get it up and running by checking out the well-composed Hashnode API documentation.</p>
<p>I didn’t go for an advanced GraphQL library like apollo-client but kept it pretty simple by using the <a target="_blank" href="https://www.npmjs.com/package/graphql-request">graphql-request</a> package.</p>
<h2 id="heading-the-notion-database">The Notion Database📄</h2>
<p>Remember you cloned a Notion database to your account when you connected the Notion account to Hasotion. In this section, I am going to go over the Notion database and explain the ins and outs of that.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706887771236/446a5a6d-778e-49bc-b2d8-6ad2012260bf.png" alt class="image--center mx-auto" /></p>
<p>The database is pretty simple with just 6 columns that you need to interact with. The first five columns are pretty much self-explanatory if you have ever used Hashnode but I will go through each of them briefly.</p>
<ul>
<li><p>Title - Your blog article's title</p>
</li>
<li><p>Slug - The ending part of the URL (If kept empty, it will automatically be generated from the Title)</p>
</li>
<li><p>Subtitle - The subtitle of your blog article</p>
</li>
<li><p>Cover Image - The image that will be displayed alongside your blog post in Hashnode</p>
</li>
<li><p>Tags - The article tags (must be comma-separated to work correctly)</p>
</li>
</ul>
<p>Then you have the final column which is <strong><em>Ready (No. 2)*</em></strong>,<em> when you're done with writing your article and finally ready to publish the article, you check the ready box respective to the article and click on the **Update Articles </em>(No. 1)<em>*</em> button below the title. That will trigger an article update.</p>
<p>The next section will explain what will happen behind the scenes once the update is triggered.</p>
<h2 id="heading-the-main-functionality-posting-articles">The Main Functionality - Posting Articles 📤</h2>
<p>In this section, I will go over how the updating/posting article happens in the backend.</p>
<p>Firstly when you click on the <em>Update Article</em> button, a Next.JS API route will be triggered. You can find that particular API route with comments explaining each step <a target="_blank" href="https://github.com/osadavc/hasotion/blob/master/src/pages/api/article.ts">here</a>.</p>
<p>Inside that API route,</p>
<ol>
<li><p>It first checks if the user is properly authenticated and if all the API tokens and stuff are configured.</p>
</li>
<li><p>The ID of the database you cloned in the earlier step in obtained via Notion API. We can just do a search and get the first result because according to Notion login, our integration should only have access to one database.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> { results: notionResult } = <span class="hljs-keyword">await</span> notion.search({
   page_size: <span class="hljs-number">1</span>,
   filter: {
     property: <span class="hljs-string">"object"</span>,
     value: <span class="hljs-string">"database"</span>,
   },
 });

 <span class="hljs-keyword">const</span> databaseId = notionResult[<span class="hljs-number">0</span>].id;
</code></pre>
</li>
<li><p>Then the database is searched to retrieve all the pages (aka. posts) with Ready being true. A sort is also done to help choose the latest article</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> { results: databaseItems } = <span class="hljs-keyword">await</span> notion.databases.query({
   database_id: databaseId,
   sorts: [
     {
       timestamp: <span class="hljs-string">"last_edited_time"</span>,
       direction: <span class="hljs-string">"descending"</span>,
     },
   ],
   filter: {
     property: <span class="hljs-string">"Ready"</span>,
     checkbox: {
       equals: <span class="hljs-literal">true</span>,
     },
   },
 });
</code></pre>
</li>
<li><p>Then the content of that particular page is converted to Markdown by using the NPM package I mentioned earlier.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> notionString = n2m.toMarkdownString(
   <span class="hljs-keyword">await</span> n2m.pageToMarkdown(savingItem.id)
 );
</code></pre>
</li>
<li><p>Then, the data we obtained from the Notion page is fed into the Hashnode API with the GraphQL client.</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> gqlClient.request(publishNewPostQuery, {
   input: {
     title,
     subtitle,
     publicationId: loggedInUser.hashnodePublicationId,
     contentMarkdown: notionString.parent,
     slug,
     tags,
     coverImageOptions: {
       coverImageURL,
     },
   },
 });
</code></pre>
</li>
<li><p>Finally, we uncheck all the posts in the Notion database for future purposes and save the published Hashnode article ID to the Notion database. The article ID is saved to the Notion database because it is needed to update the article in future.</p>
<pre><code class="lang-typescript"> databaseItems.forEach(<span class="hljs-keyword">async</span> (item) =&gt; {
   <span class="hljs-keyword">await</span> notion.pages.update({
     page_id: item.id,
     properties: {
       Ready: {
         checkbox: <span class="hljs-literal">false</span>,
       },
     },
   });
 });

 <span class="hljs-keyword">await</span> notion.pages.update({
   page_id: savingItem.id,
   properties: {
     <span class="hljs-string">"Hashnode Post ID"</span>: {
       rich_text: [
         {
           text: { content: data.publishPost.post.id.toString() },
         },
       ],
     },
   },
 });
</code></pre>
</li>
<li><p>Finally, to wrap everything up, the user is redirected to a page that displays the article title and the link</p>
<pre><code class="lang-typescript"> <span class="hljs-keyword">return</span> res.redirect(
   <span class="hljs-string">`/notion?title=<span class="hljs-subst">${title}</span>&amp;url=<span class="hljs-subst">${
     data?.publishPost?.post.url || data?.updatePost?.post.url
   }</span>`</span>
 );
</code></pre>
</li>
</ol>
<p>That’s a rough technical explanation of the whole process.</p>
<h2 id="heading-struggles">Struggles 😶</h2>
<p>First and foremost with the short duration of the hackathon, I couldn’t waste time on the design. So I had to whip up a quick and simple landing page. Despite it being really simple, I think it does a good job at conveying the message to the user.</p>
<p>I also had some problems with getting the Hashnode API working but managed to solve them easily by referring to the <a target="_blank" href="https://apidocs.hashnode.com/">Hashnode Public API Docs</a>.</p>
<p>The final and most crucial challenge was the mechanism used to update posts on Hashnode after editing them on Notion. There is no <em>Notion button or Notion webhook</em> feature to make this process a lot simpler. So I had the idea to create a Chrome extension as I did in one of my earlier projects but doing that will add a barrier of entry to users. So I gave up on that and finally settled on adding a little link that updates the latest article that you wrote on Notion.</p>
<h2 id="heading-video-demo">Video Demo 📽️</h2>
<p>Here is a video showing how to get started with Hasotion in less than 5 minutes.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Z_OgY-KWM0Y">https://youtu.be/Z_OgY-KWM0Y</a></div>
<p> </p>
<h2 id="heading-conclusion">Conclusion 🏁</h2>
<p>Feel free to actually use Hasotion for your day-to-day needs because I plan to keep this project up and running forever for completely free and add more and more features down the line.</p>
<p>Also with the limited time I had, my goal was to make an application that does one single thing but that single thing at its best! I personally feel like I was able to stay up to my goals.</p>
<p>Finally, I thank Hashnode for this amazing opportunity to build something with Hashnode’s brand-new set of APIs.</p>
<h2 id="heading-final-note">Final Note 📝</h2>
<p>If you’re curious, yes this article is written and posted completely by using Hasotion. Feel free to take a look at my <a target="_blank" href="https://osadavc.notion.site/Introducing-Hasotion-Write-Your-Hashnode-Articles-Without-Leaving-Notion-77a57ffc17f745acad81d17f433a319a?pvs=4">Notion document</a>.</p>
<blockquote>
<p>⚠️ This is currently hosted on Render. So expect some cold-boots.</p>
</blockquote>
<h2 id="heading-links">Links 🔗</h2>
<ul>
<li><p><a target="_blank" href="https://hasotion.osadavidath.com/">Hasotion Website</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/osadavc/hasotion">GitHub Repository</a></p>
</li>
</ul>
<p>Thank you so much for reading this article. Hope you enjoyed it. Let me know your ideas by leaving a comment below.</p>
]]></content:encoded></item><item><title><![CDATA[My Experience in Building TextQuest, an AI-Powered Platform to Test Your Knowledge with MindsDB]]></title><description><![CDATA[💻
The following article is regarding the project in this GitHub repository.


Introduction
TextQuest is a platform powered by AI that aims to solve a common problem students, including myself, face. Before diving into the project details, let me exp...]]></description><link>https://blog.osadavidath.com/my-experience-in-building-textquest-with-mindsdb</link><guid isPermaLink="true">https://blog.osadavidath.com/my-experience-in-building-textquest-with-mindsdb</guid><category><![CDATA[MindsDBHackathon]]></category><category><![CDATA[mindsdb]]></category><category><![CDATA[studying]]></category><category><![CDATA[chatgpt]]></category><category><![CDATA[openai]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Fri, 12 Jan 2024 21:08:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1704898450253/4e4bab31-1d78-42f7-a26b-c2e0a06fe1ae.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div data-node-type="callout">
<div data-node-type="callout-emoji">💻</div>
<div data-node-type="callout-text">The following article is regarding the project in <a target="_blank" href="https://github.com/osadavc/textquest">this GitHub repository</a>.</div>
</div>

<h2 id="heading-introduction">Introduction</h2>
<p>TextQuest is a platform powered by AI that aims to solve a common problem students, including myself, face. Before diving into the project details, let me explain the problem and how this platform intends to provide a solution. As someone who prefers to tackle day-to-day issues through my projects, I believe TextQuest will be a helpful solution to all the students out there who struggle with studying.</p>
<p>If you're a student who spends most of your time studying loads and loads of textbooks, you might have noticed that you tend to forget things quickly. Now, there are many ways to minimize this problem, but in my opinion, one of the best ways is to make questions from the specific text and try to answer them yourself.</p>
<p>Well, even though this is one of the best ways to study your text material. This is an incredibly time-consuming process, you have to form questions from the text and try to answer them.</p>
<p>That whole process will be a lot easier with TextQuest because it generates the questions automatically with the help of MindsDB and OpenAI APIs by looking at your text.</p>
<h2 id="heading-how-textquest-works">How TextQuest Works</h2>
<p>Before we talk more about how this works, I highly suggest you watch the video below to get a rough understanding of TextQuest and how you can use it.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/TZ6lWoHJgTo">https://youtu.be/TZ6lWoHJgTo</a></div>
<p> </p>
<p>Once you have logged in to TextQuest via Google,</p>
<ol>
<li><p>Upload a textbook (or any PDF with text in it) and select the page which you want your questions to be generated from.</p>
</li>
<li><p>Choose the number of questions you need to generate.</p>
</li>
</ol>
<p>and boom, after a few seconds you will see all the questions that were just generated with AI.</p>
<p>Now you can select the answers and click on the <em>Check Answers</em> button to verify your answers.</p>
<p>Under the hood, this uses the GPT 3.5 turbo model via the OpenAI handler that MindsDB comes with. I will get into more detail about the MindsDB integration in a later section of this article.</p>
<p>With that clear idea about how TextQuest works, let's move on to more technical details.</p>
<h2 id="heading-my-tech-stack"><strong>My Tech Stack</strong></h2>
<p>As always, I used my go-to tech stack for this project which is</p>
<ul>
<li><p>NextJS as the frontend Javascript framework (with Typescript)</p>
</li>
<li><p>TailwindCSS for utility class-based CSS styling</p>
</li>
<li><p>PostgreSQL and Prisma for the database</p>
</li>
</ul>
<p>with the interesting addition of MindsDB to consume data from OpenAI APIs. Also, I used shadcn ui as the component library.</p>
<h2 id="heading-technical-walkthrough">Technical Walkthrough</h2>
<p>This section includes all the technical information of TextQuest and how you can set it up locally if you want to try it out yourself (The project is not hosted live as of now)</p>
<h3 id="heading-setting-up-mindsdb">Setting up MindsDB</h3>
<p>Since this project uses the facilities provided by MindsDB, I first had to install MindsDB locally on my computer. (unfortunately, the MindsDB cloud version is unavailable as of now)</p>
<p>I followed the instructions in the official <a target="_blank" href="https://docs.mindsdb.com/setup/self-hosted/pip/source">MindsDB documentation</a> to get MindsDB up and running. The <em>Troubleshooting</em> section was quite helpful in solving many issues that came up. (For some reason, I could only get it working with the <em>pip from source</em> installation method, so if you're facing some errors, maybe give it a try)</p>
<h3 id="heading-connecting-mindsdb-to-openai">Connecting MindsDB to OpenAI</h3>
<p>The next task on the list is getting access to OpenAI models from MindsDB, to do this I first had to install the OpenAI handler to MindsDB with the following command.</p>
<pre><code class="lang-bash">pip install .[openai]
</code></pre>
<p>Now that I had the OpenAI-related packages installed, I had to create the ML Engine and the Model from the MindsDB dashboard, to do this I opened the MindsDB dashboard which is hosted in <code>http://localhost:47334</code> and ran the following commands to create them.</p>
<h4 id="heading-creating-ml-engine-openai">Creating ML Engine (OpenAI)</h4>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> ML_ENGINE openai_engine
<span class="hljs-keyword">FROM</span> openai
<span class="hljs-keyword">USING</span>
    api_key = <span class="hljs-string">'YOUR_API_KEY'</span>;
</code></pre>
<p>This engine will be used when you make calls to OpenAI APIs in the future, make sure to replace <code>YOUR_API_KEY</code> with a proper OpenAI API key.</p>
<h4 id="heading-creating-model">Creating Model</h4>
<p>To start consuming data from the backend of our application, you need to create a model inside of MindsDB.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MODEL</span> mindsdb.question_generator
PREDICT questions
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">engine</span> = <span class="hljs-string">'openai_engine'</span>,
    model_name = <span class="hljs-string">'gpt-3.5-turbo'</span>,
    prompt_template = <span class="hljs-string">'Answer in JSON (json array named questions with each object having question, 4 answers and the actual answer. The key of actual answer should be "correct_answer"). All the questions SHOULD only have ONE CORRECT answer. Generate {{question_amount}} {{question_type}} questions (all the answers in mcq questions should be full) from the following text. IGNORE the sections {{exceptions}}. \n\n {{page_content}}. Ask questions other than {{ignore_questions}}. Only ask questions which are answerable by the given text'</span>,
    max_tokens = <span class="hljs-number">3000</span>;
</code></pre>
<ul>
<li><p>You can see that I've named this model as "question_generator" in the first line, this is how we will refer to this model in the future.</p>
</li>
<li><p>In the <em>engine</em> parameter, the ML Engine's name will be inputted.</p>
</li>
<li><p>In the <em>model_name</em> parameter, I have defined the OpenAI model that is being used. I am using the <em>gpt-3.5-turbo</em> model, while you can use any model such as a gpt4, I find that <em>gpt-3.5-turbo</em> is the perfect match considering cost and performance.</p>
</li>
<li><p>The most important part of this query is the <em>prompt_template</em> parameter, this is basically where you define all of your instructions to the model. If you have read through this, I have instructed the model exactly how to generate questions and also you can notice that I have specified 5 variables, let's go over each of them and explain how each of these works.</p>
<ol>
<li><p><strong>question_amount</strong> - Exactly as the name says, this is just the number of questions that I want to generate through a particular call.</p>
</li>
<li><p><strong>question_type</strong> - Right now, this variable only accepts the value <em>mcq</em>, this is kept as a variable to support more question types in the future (eg: long answer questions, short answer questions).</p>
</li>
<li><p><strong>exceptions</strong> - This variable supports an interesting addition for this application which is ignoring whole sections of the text for the question generation process.</p>
</li>
<li><p><strong>page_content</strong> - The content that is used for generating questions, it is the text in the textbook page in this scenario.</p>
</li>
<li><p><strong>ignore_questions</strong> - If you generate questions again from a page you have already generated questions from, the previously generated questions will be passed into this variable for the AI to not make the same questions again.</p>
</li>
</ol>
</li>
</ul>
<h3 id="heading-handling-pdfs-from-the-application">Handling PDFs from the Application</h3>
<p>Now that the MindsDB part is completely configured, all I had to do was create a frontend and backend to read PDFs and call the MindsDB model with the page data.</p>
<p>First of all, I used <a target="_blank" href="https://uploadthing.com/">Uploadthing</a> as my storage bucket, so every PDF that the users upload will be stored there.</p>
<p>Since it is not feasible to feed the whole book to the GPT model because of the cost and speed concerns, I decided that letting the user choose what page they need the questions generated from is the best solution for this.</p>
<p><img src="https://i.ibb.co/kGZ6k1h/select-a-page-textquest.gif" alt class="image--center mx-auto" /></p>
<p>So here is what I came up with, a sliding panel to display the PDF and let the user choose the page that they need to generate the questions from. I managed to display the PDF with some react-pdf magic. It was a little buggy but I managed to get it to work flawlessly by downgrading the react-pdf version to 6.2.2 (A little tip if you're planning to work with react-pdf in the future)</p>
<p>Then once the user hits the Generate question button with the number of questions that they need, in the backend I load the PDF file again to read the content in that specific page of the PDF file and format them properly. I had to create a function myself (using some of the open-source code I found on GitLab) to do this because none of the out-of-the-box solutions was working properly. You can find my custom utils in my GitHub project <a target="_blank" href="https://github.com/osadavc/textquest/blob/master/src/utils/pdfUtils.ts">here</a>.</p>
<h3 id="heading-feeding-the-data-to-openai-via-mindsdb">Feeding the Data to OpenAI via MindsDB</h3>
<p>Then, I used the <code>mindsdb-js-sdk</code> package from the NextJS API routes to call the previously created model like this (When a user requests a generation)</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> model = <span class="hljs-keyword">await</span> MindsDB.Models.getModel(<span class="hljs-string">"question_generator"</span>, <span class="hljs-string">"mindsdb"</span>);

<span class="hljs-keyword">const</span> queryOptions = {
  where: [
    <span class="hljs-string">`page_content = "<span class="hljs-subst">${pageContent}</span>"`</span>,
    <span class="hljs-string">`question_amount = "<span class="hljs-subst">${numberOfQuestions}</span>"`</span>,
    <span class="hljs-string">`question_type = "<span class="hljs-subst">${questionType}</span>"`</span>,
    <span class="hljs-string">`ignore_questions = "<span class="hljs-subst">${samePageQuestions}</span>"`</span>,
    <span class="hljs-string">`exceptions = "<span class="hljs-subst">${processedExceptions}</span>"`</span>,
  ],
};

<span class="hljs-keyword">const</span> questions = <span class="hljs-built_in">JSON</span>.parse(
  (<span class="hljs-keyword">await</span> model?.query(queryOptions))?.value.toString() ?? <span class="hljs-string">""</span>,
).questions.map(<span class="hljs-function">(<span class="hljs-params">item: QuestionItem</span>) =&gt;</span> ({
  ...item,
  <span class="hljs-comment">// To support answers like all of the above</span>
  answers: [
    ...shuffleArray(item.answers.slice(<span class="hljs-number">0</span>, <span class="hljs-number">3</span>)),
    ...item.answers.slice(<span class="hljs-number">3</span>, <span class="hljs-number">4</span>),
  ],
}));
</code></pre>
<p>After I have gotten the questions and answers from the GPT request, you can notice that I have done some processing before saving them to the database, basically what I have done here (with reasons) are,</p>
<ul>
<li><p>Shuffle the MCQ answers list - gpt3.5 tends to always make the correct answer the first answer in the list.</p>
</li>
<li><p>While making sure the last answer stays the same - To support some answers like "All of the above".</p>
</li>
</ul>
<p>After all of the processing is done, I save them to the database and send them back to the client for the user to see.</p>
<h3 id="heading-correcting-users-answers">Correcting User's Answers</h3>
<p>This is the final step in the process which is also an extremely trivial task. In a separate API route which is called when the user clicks on the <em>Check Answers</em> button, I have this code where it simply throws the user's answer into the database.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> prisma.$transaction(
  questionIdList.map(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span>
    prisma.question.update({
      where: {
        id: item,
      },
      data: {
        userMCQAnswerIndex: pageAnswers[item],
      },
    }),
  ),
);
</code></pre>
<p>Now, this may not look like much but since we have the correct answer's index saved in the database, from the frontend we can compare the two answers and render it according. (In this case green border for correct answers and red border for wrong answers)</p>
<blockquote>
<p><a target="_blank" href="https://github.com/osadavc/textquest/blob/master/src/components/Dashboard/SingleDashboard/SingleQuestion.tsx">Here</a> is the code relavant to correct and wrong answers being displayed.</p>
</blockquote>
<h2 id="heading-slack-bot">Slack Bot</h2>
<p>TextQuest is not limited to the web interface. At the same time, you can also access the main TextQuest feature through a Slack Bot as well. Yes, you heard that right. You can access it through a Slack bot.</p>
<p>This Slackbot works through a MindsDB Job. Let me go over how this works and how you can set it up locally.</p>
<h3 id="heading-initializing-slack-in-mindsdb">Initializing Slack In MindsDB</h3>
<p>First, we have to connect Slack to our MindsDB instance. You can find all the details on how to do that <a target="_blank" href="https://docs.mindsdb.com/integrations/app-integrations/slack">here</a> in their documentation. Make sure to follow the second method as that's how we will connect to Slack.</p>
<p>You can initiate the connection by running this SQL statement in the MindsDB editor.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> mindsdb_slack
<span class="hljs-keyword">WITH</span>
  <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">'slack'</span>,
  <span class="hljs-keyword">PARAMETERS</span> = {
      <span class="hljs-string">"token"</span>: <span class="hljs-string">"MY_SLACK_TOKEN"</span>
    };
</code></pre>
<p>Make sure to replace the Slack token according to the instructions in the documentation.</p>
<h3 id="heading-creating-openai-model">Creating OpenAI Model</h3>
<p>Now that we have our Slack instance ready, we can create the OpenAI model to do the actual generation of questions.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MODEL</span> mindsdb.slack_question_generator
PREDICT response
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">engine</span> = <span class="hljs-string">'openai_engine'</span>,
    model_name = <span class="hljs-string">'gpt-3.5-turbo'</span>,
    prompt_template = <span class="hljs-string">'Generate 5 MCQ Questions from the below text. Each question must have 4 choices and the correct answer. The correct answer list should be included at the end. Only ask questions which are answerable by the given text \n\n {{text}}'</span>,
    max_tokens=<span class="hljs-number">3000</span>;
</code></pre>
<p>This model is pretty similar to the model we created before when creating the web interface but this is much simpler because we are just requesting 5 questions from the text.</p>
<p>You can test it out by running the following SQL statement if you want.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> response <span class="hljs-keyword">FROM</span> mindsdb.slack_question_generator <span class="hljs-keyword">WHERE</span> <span class="hljs-built_in">text</span> = <span class="hljs-string">'Text to generate questions'</span>
</code></pre>
<h3 id="heading-creating-the-mindsdb-job">Creating the MindsDB Job</h3>
<p>Finally, we create a MindsDB job for this to keep running and check for new messages and respond every minute.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> JOB mindsdb.slack_question_generator_bot <span class="hljs-keyword">AS</span> (
  <span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> mindsdb_slack.channels(channel, <span class="hljs-built_in">text</span>)
    <span class="hljs-keyword">SELECT</span>
        t.channel <span class="hljs-keyword">as</span> channel,
        t.text <span class="hljs-keyword">as</span> input_text, 
        r.response <span class="hljs-keyword">as</span> <span class="hljs-built_in">text</span>
    <span class="hljs-keyword">FROM</span> mindsdb_slack.channels <span class="hljs-keyword">as</span> t
    <span class="hljs-keyword">JOIN</span> mindsdb.slack_question_generator <span class="hljs-keyword">as</span> r
    <span class="hljs-keyword">WHERE</span> t.channel = <span class="hljs-string">'general'</span> <span class="hljs-keyword">AND</span> t.created_at &gt; <span class="hljs-string">"2023-07-25 05:22:00"</span> <span class="hljs-keyword">AND</span> t.created_at &gt; <span class="hljs-string">"{{PREVIOUS_START_DATETIME}}"</span>
    <span class="hljs-keyword">LIMIT</span> <span class="hljs-number">1</span>;
)
EVERY minute;
</code></pre>
<p>If everything is correctly configured, once you send a text to the general chat (or any chat you have set up), it should respond with 5 MCQ questions and their answers at the bottom. Pretty efficient for a quick recap in my opinion.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706106135820/0ce94c26-f2f8-4bbc-ab5f-96d85457f1f4.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-slack-bot-for-administration">Slack Bot For Administration</h2>
<p>Before I wrap up this project, I wanted to play around with the MindsDB agents as well. So I built sort of an assistant for the Text Quest admin to monitor this application.</p>
<h3 id="heading-conversational-llm-model">Conversational LLM Model</h3>
<p>Before creating a MindsDB agent, a conversational model to access the LLM that we're going to be using should be created (Make sure to replace your gpt3.5 API key)</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> ML_ENGINE langchain
<span class="hljs-keyword">FROM</span> langchain

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MODEL</span> mindsdb.text_quest_db_inspection
PREDICT answer
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">engine</span> = <span class="hljs-string">'langchain'</span>,
    input_column = <span class="hljs-string">'question'</span>,
    api_key = <span class="hljs-string">'API_KEY_HERE'</span>, 
    model_name = <span class="hljs-string">'gpt-3.5-turbo'</span>,
    <span class="hljs-keyword">mode</span> = <span class="hljs-string">'conversational'</span>,
    user_column = <span class="hljs-string">'question'</span> ,
    assistant_column = <span class="hljs-string">'answer'</span>,
    prompt_template=<span class="hljs-string">'Answer the user input in a helpful way'</span>;
</code></pre>
<h3 id="heading-connect-database">Connect Database</h3>
<p>If you've already read the rest of the article, you know that I used PostgreSQL, so I am going to connect my PostgreSQL instance to MindsDB</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> agent_db
<span class="hljs-keyword">WITH</span> <span class="hljs-keyword">ENGINE</span> = <span class="hljs-string">"postgres"</span>,
<span class="hljs-keyword">PARAMETERS</span> = {
    <span class="hljs-string">"user"</span>: <span class="hljs-string">"postgres"</span>,
    <span class="hljs-string">"password"</span>: <span class="hljs-string">"PASSWORD_HERE"</span>,
    <span class="hljs-string">"host"</span>: <span class="hljs-string">"HOST_HERE"</span>,
    <span class="hljs-string">"port"</span>: <span class="hljs-string">"5432"</span>,
    <span class="hljs-string">"database"</span>: <span class="hljs-string">"postgres"</span>
};
</code></pre>
<h3 id="heading-creating-the-skill-and-agent">Creating the Skill and Agent</h3>
<p>Once you've created the model and connected the database, you can proceed to create the skill and the agent as follows.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> SKILL text_to_sql_skill
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">type</span> = <span class="hljs-string">'text_to_sql'</span>,
    <span class="hljs-keyword">database</span> = <span class="hljs-string">'agent_db'</span>,
    <span class="hljs-keyword">tables</span> = [<span class="hljs-string">'User'</span>, <span class="hljs-string">'Textbook'</span>, <span class="hljs-string">'Question'</span>];

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">AGENT</span> text_to_sql_agent
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">model</span> = <span class="hljs-string">'text_quest_db_inspection'</span>,
    skills = [<span class="hljs-string">'text_to_sql_skill'</span>];
</code></pre>
<p>Notice the <code>tables</code> property here, I am going to include the names of all of my tables so that they can be accessed from my chatbot.</p>
<h3 id="heading-creating-the-chatbot">Creating the Chatbot</h3>
<p>Now that everything is ready, all that's left to do is create the actual chatbot. You can follow the instructions in the <a target="_blank" href="https://docs.mindsdb.com/integrations/app-integrations/slack">MindsDB documentation</a> to connect Slack and the following query to create the chatbot.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> CHATBOT text_to_sql_chatbot
<span class="hljs-keyword">USING</span>
    <span class="hljs-keyword">database</span> = <span class="hljs-string">'mindsdb_slack'</span>, 
    <span class="hljs-keyword">agent</span> = <span class="hljs-string">'text_to_sql_agent'</span>,
    enable_dms = <span class="hljs-literal">true</span>,
    is_running = <span class="hljs-literal">true</span>;
</code></pre>
<p>Then inside the Slack DM with the bot, you can ask questions like "How many users are registered" and the bot will happily answer it for you!</p>
<h2 id="heading-linting-amp-code-quality">Linting &amp; Code Quality</h2>
<p>As always, I used ESLint and Prettier for linting and keeping the code nice and formatted. I also used the prettier plugin <code>@trivago/prettier-plugin-sort-imports</code> to keep the import statements ordered which I think looks very clean.</p>
<h2 id="heading-future-plans">Future Plans</h2>
<p>I built this project with upgradability in mind so I am planning to add a lot more question types to the application and make the experience more gamified.</p>
<h2 id="heading-self-deployment">Self Deployment</h2>
<p>Even though I would love to host this application and provide a link for you to try out, unfortunately, that's not possible due to MindsDB cloud being currently unavailable.</p>
<p>Feel free to take a look at the code in this repository - <a target="_blank" href="https://github.com/osadavc/textquest">https://github.com/osadavc/textquest</a> and contact me on Twitter (X) if you have any doubts regarding self-hosting, I will be very happy to guide you through the whole process.</p>
<p>Before wrapping up the article, I would like to thank Hashnode and MindsDB for providing this opportunity to build an amazing product.</p>
<p>Finally, thank you so much for reading the article, I hope this project will help you a lot if you're a student and if you have any comments I would love to hear them down below in the comments.</p>
]]></content:encoded></item><item><title><![CDATA[Implementing Authentication with Passkeys using Authsignal and Next.JS]]></title><description><![CDATA[There are numerous ways to authenticate users, ranging from the basic username and password to advanced methods like JWT authentication, magic link, OTP, TOTP, security keys, and many more.
Passkeys have become the latest authentication trend. This n...]]></description><link>https://blog.osadavidath.com/implementing-authentication-with-passkeys-using-authsignal-and-nextjs</link><guid isPermaLink="true">https://blog.osadavidath.com/implementing-authentication-with-passkeys-using-authsignal-and-nextjs</guid><category><![CDATA[authentication]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[passkeys]]></category><category><![CDATA[Passwordless]]></category><category><![CDATA[authsignal]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Thu, 05 Oct 2023 14:25:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515478279/86e6ffeb-2b13-4f12-b235-3ca93c782471.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are numerous ways to authenticate users, ranging from the basic username and password to advanced methods like JWT authentication, magic link, OTP, TOTP, security keys, and many more.</p>
<p>Passkeys have become the latest authentication trend. This new and convenient method eliminates the use of passwords or email codes for logging in. Passkeys allow users to access applications through their device's screen lock, such as fingerprint or face ID. Unlike passwords, passkeys are highly secure and protected from phishing attacks. They are also more secure than SMS OTPs, which can make users vulnerable to sim swapping.</p>
<p>Throughout this tutorial, you’re going to be learning about how you can implement passkeys authentication in your own app, with Authsignal which is an authentication as a service product which enables you to implement multi-factor passwordless authentication in the easiest ways possible.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Having a basic knowledge of JavaScript and React is recommended to follow this tutorial more easily.</p>
<h2 id="heading-configure-authsignal">Configure Authsignal</h2>
<p>Before proceeding with the tutorial, you need to create an Authsignal project. For this, you can go to <a target="_blank" href="http://authsignal.com">authsignal.com</a> and click on <em>Create Free Account.</em> Then you can go through their simple procedure and get your organization configured.</p>
<p>In the last step, you will create your first tenant - a project inside your Authsignal organization. Choose any name for your tenant and select the data storage region.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515764062/95acb425-ae2e-472e-975c-66574a6e1357.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-configure-authenticators">Configure Authenticators</h3>
<p>Now that you have your Authsignal account configured you need to configure the authenticators that you’re going to be using for your project. We are going to be using email magic links and passkeys as the authenticators in this project. To configure those, click on the configure button you can see on the dashboard page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515770000/25648d72-3720-447c-8ad7-60154bf8cb09.png" alt class="image--center mx-auto" /></p>
<p>and then you would see all the authenticators that you can use.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515777034/3eae0cde-8636-4bb0-9728-65182fa3fcc9.png" alt class="image--center mx-auto" /></p>
<p>First, let’s configure the email magic link. To do that, click on the <em>Set up Email Magic Link</em> button and select Authsignal as the email provider. Now you have successfully configured email magic link as an authenticator.</p>
<p>Configuring passkeys has a pretty similar process, click on the <em>Set up Passkey</em> button. Since we’re just testing on localhost for the time being, make sure to set the Relying Party ID as <code>localhost</code>. (Make sure to change this when you’re moving your application to production)</p>
<p>Now you have successfully configured both the passkey and email magic link as authenticators.</p>
<h3 id="heading-api-keys">API Keys</h3>
<p>To connect Authsignal to your Next.JS application you would need the API key and some more information. You can obtain this information by clicking on the <em>API Keys</em> section in the left sidebar.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515791685/1399f279-a3df-4463-b3d0-7cf326afaade.png" alt class="image--center mx-auto" /></p>
<p>Take note of these because you’re going to need them in future steps.</p>
<h2 id="heading-configuring-the-nextjs-project">Configuring the Next.JS Project</h2>
<p>Now that we have completed the Authsignal setup, we can now proceed with the coding phase. We will be utilizing the Next.JS framework for our application development. Although React is an option, it requires a separate backend setup, making Next.JS a more practical choice.</p>
<h3 id="heading-installing-nextjs">Installing Next.JS</h3>
<p>First, bootstrap a brand new Next.JS project by opening your CLI and running the following command.</p>
<pre><code class="lang-bash">npx create-next-app@latest
</code></pre>
<p>Then during the installation, you will see some prompts. I am going to use typescript and tailwind for this project but I am not going to use the Next.JS app router to keep this guide simple, also make sure to opt-in to creating a <code>src</code> folder to make following this tutorial easier.</p>
<h3 id="heading-installing-required-dependencies">Installing Required Dependencies</h3>
<p>Now that you have the Next.JS project configured, let’s install the required dependencies. To do this cd into the folder that your project is in and run the following the command</p>
<pre><code class="lang-bash">npm i @authsignal/browser @authsignal/node axios cookie jwt-decode uuid &amp;&amp; npm i -D @types/cookie @types/uuid
</code></pre>
<p>Two authsignal packages will be used to make communication with Authsignal servers easier, axios will be used to make HTTP requests to the backend from the frontend, uuid package will be used to generate a random identifier for new users, jwt-decode and cookie packages will be used to manage the user’s session inside the app.</p>
<h2 id="heading-set-up-magic-link-authentication-with-authsignal">Set up Magic Link Authentication with Authsignal</h2>
<p>Before setting up authentication, you need to feed your API keys and other information into the project. In order to do this, create a <code>.env.local</code> file in the root of your project and add the following.</p>
<pre><code class="lang-plaintext">AUTHSIGNAL_SECRET=
NEXT_PUBLIC_AUTHSIGNAL_URL=
NEXT_PUBLIC_AUTHSIGNAL_TENANT_ID=
NEXT_PUBLIC_APP_URL=http://localhost:3000
</code></pre>
<p>You need to fill in this information with the details you obtain from your Authsignal dashboard and for the <code>NEXT_PUBLIC_APP_URL</code> you have to include the URL that your app is running on, so if you deploy your app, you need to replace this with the deployed URL.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515806970/502a3341-c86d-49b2-a307-8a40f63735e6.png" alt class="image--center mx-auto" /></p>
<p>If you're unfamiliar with email magic links, they're a method of authentication that involves entering your email address and receiving a link in your inbox. Once you click on that link, you'll be automatically logged in to the app.</p>
<h3 id="heading-initializing-authsignal">Initializing Authsignal</h3>
<p>To communicate with the Authsignal servers, you need to initialize two Authsignal clients. one for the front end and the other for the back end.</p>
<p>We’re going to make use of a custom react hook to initialize the front-end client. To do this, create a new file called <code>useAuthsignalClient.ts</code> inside the <code>hooks</code> folder inside the <code>src</code> folder and then paste the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"@authsignal/browser"</span>;
<span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> useAuthsignalClient = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [client, setClient] = useState&lt;Authsignal | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> authsignalClient = <span class="hljs-keyword">new</span> Authsignal({
      tenantId: process.env.NEXT_PUBLIC_AUTHSIGNAL_TENANT_ID!,
      baseUrl: process.env.NEXT_PUBLIC_AUTHSIGNAL_API_BASE_URL!,
    });

    setClient(authsignalClient);
  }, []);

  <span class="hljs-keyword">return</span> client;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> useAuthsignalClient;
</code></pre>
<p>Here, we’re basically initializing a new object from the <code>Authsignal</code> class passing the tenantId and baserUrl to the constructor and we’re returning that from the hook so that we can use our <code>authsignalClient</code> without repeating this code snippet all over again.</p>
<p>Now to create our back-end client, create a file called <code>authsignal.ts</code> inside the <code>lib</code> folder and paste the following code snippet.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"@authsignal/node"</span>;

<span class="hljs-keyword">const</span> secret = process.env.AUTHSIGNAL_SECRET;

<span class="hljs-keyword">if</span> (!secret) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"AUTHSIGNAL_SECRET is undefined"</span>);
}

<span class="hljs-keyword">const</span> apiBaseUrl = process.env.NEXT_PUBLIC_AUTHSIGNAL_API_BASE_URL;
<span class="hljs-keyword">const</span> redirectUrl = <span class="hljs-string">`<span class="hljs-subst">${process.env.NEXT_PUBLIC_APP_URL}</span>/api/callback`</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> authsignal = <span class="hljs-keyword">new</span> Authsignal({ secret, apiBaseUrl, redirectUrl });
</code></pre>
<p>Here also we’re simply initializing an <code>Authsignal</code> object with the secret, apiBaseUrl and redirectUrl. Notice that the redirectUrl points to a backend function called callback, we will create that callback function in a moment.</p>
<h3 id="heading-sign-up-backend-function">Sign Up Backend Function</h3>
<p>Authsignal SDK is designed in a way so that before doing anything in the front end, you first need to <em>track</em> that action through the backend. You can read more about this in the <a target="_blank" href="https://docs.authsignal.com/learn/tracking-actions/overview">Authsignal’s documentation</a>.</p>
<p>To do this, first, you have to set up an API route. In order to set up an API route, create a new file named <code>email.ts</code> inside the <code>pages/api</code> directory.</p>
<p>Then inside that <code>email.ts</code> file, paste this code.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/authsignal"</span>;
<span class="hljs-keyword">import</span> { NextApiHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> { v4 } <span class="hljs-keyword">from</span> <span class="hljs-string">"uuid"</span>;

<span class="hljs-keyword">const</span> handler: NextApiHandler = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { url } = <span class="hljs-keyword">await</span> authsignal.track({
    action: <span class="hljs-string">"signUp"</span>,
    userId: v4(),
  });

  res.status(<span class="hljs-number">200</span>).json({
    url,
  });
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>Let’s go through the above code now,</p>
<ul>
<li><p>First, we import the authsignal object that we exported earlier from the <code>lib</code> folder.</p>
</li>
<li><p>Then we’re going to track an action named <code>signUp</code> passing a random uuid that we can obtain with the <code>u4</code> function in the uuid library as the <code>userId</code></p>
</li>
<li><p>Finally, we’re sending the URL we get back from the Authsignal servers to the front end.</p>
</li>
</ul>
<h3 id="heading-homepage-frontend">Homepage Frontend</h3>
<p>Now we have the backend ready, let’s move on to the front end and hook our API route with a UI.</p>
<p>In the pages folder, you will see a file named <code>index.tsx</code>, which is where you write your front-end logic in Next.JS. Open that file and remove all the content in that file (Also open the <code>styles/globals.css</code> file and remove everything after the 3rd line to get similar styles)</p>
<p>First, let’s code a simple interface that looks something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515826430/1ccee541-c557-403d-a73b-87c869f14225.png" alt class="image--center mx-auto" /></p>
<p>Paste the following code in the <code>index.tsx</code> file. This should get a basic interface like the above screenshot up and running.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> HomePage = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"p-5 font-inter flex flex-col justify-center items-center"</span>&gt;
      &lt;h2 className=<span class="hljs-string">"text-center text-3xl font-bold"</span>&gt;
        Next JS + AuthSignal Passkeys Demo
      &lt;/h2&gt;

      &lt;div className=<span class="hljs-string">"border rounded-lg flex flex-col mt-16 w-fit p-16 min-w-[750px]"</span>&gt;
        &lt;button className=<span class="hljs-string">"border px-6 py-3 mt-2"</span>&gt;
          Sign Up / Sign In
        &lt;/button&gt;

        &lt;p className=<span class="hljs-string">"text-center my-5"</span>&gt;or&lt;/p&gt;

        &lt;button&gt;Sign <span class="hljs-keyword">in</span> <span class="hljs-keyword">with</span> an existing passkey&lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> HomePage;
</code></pre>
<p>To make everything work, we need to import some libraries. Add these imports at the top of your file.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">"axios"</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> useAuthsignalClient <span class="hljs-keyword">from</span> <span class="hljs-string">"@/hooks/useAuthsignalClient"</span>;
</code></pre>
<p>Then inside the HomePage function, we’re first going to initialize all of these hooks. Add the following lines right at the beginning of the <code>HomePage</code> function.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> router = useRouter();
<span class="hljs-keyword">const</span> authSignal = useAuthsignalClient();
</code></pre>
<p>Here we’re initializing the Next.JS router, this will be later used in order to redirect the user to the dashboard page after a successful login and then we initialize the hook that we made earlier to communicate with Authsignal from the frontend.</p>
<p>Now let’s create the <code>signUp</code> function to allow users to sign up for our application. To do this include the following snippet of code below where you initialized the hooks.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> signUp = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> { data } = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">"/api/email"</span>);
  <span class="hljs-built_in">window</span>.location.href = data.url;
};
</code></pre>
<p>Here what we basically do is redirect the user to the Authsignal hosted sign-up page in order to complete the signup process.</p>
<p>Before we can test this out, we have to take care of several other things.</p>
<h3 id="heading-callback-function">Callback Function</h3>
<p>First of all, we need to take care of the callback function. This is where the user comes after they get authenticated from authsignal. We have already specified our redirect URL as <code>/api/callback</code>. Create a new file, name it <code>callback</code> inside the <code>api</code> folder, and paste the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { serialize } <span class="hljs-keyword">from</span> <span class="hljs-string">"cookie"</span>;
<span class="hljs-keyword">import</span> jwtDecode <span class="hljs-keyword">from</span> <span class="hljs-string">"jwt-decode"</span>;
<span class="hljs-keyword">import</span> { NextApiHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> { authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/authsignal"</span>;

<span class="hljs-keyword">const</span> handler: NextApiHandler = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> token = req.query.token <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;

  <span class="hljs-keyword">const</span> decodedToken = jwtDecode&lt;<span class="hljs-built_in">any</span>&gt;(token);

  <span class="hljs-keyword">const</span> { state } = <span class="hljs-keyword">await</span> authsignal.validateChallenge({ token });

  <span class="hljs-keyword">if</span> (state === <span class="hljs-string">"CHALLENGE_SUCCEEDED"</span>) {
    <span class="hljs-keyword">const</span> cookie = serialize(<span class="hljs-string">"auth-session"</span>, token, {
      expires: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(decodedToken.exp * <span class="hljs-number">1000</span>),
      secure: process.env.NODE_ENV === <span class="hljs-string">"production"</span>,
      path: <span class="hljs-string">"/"</span>,
    });

    res.setHeader(<span class="hljs-string">"Set-Cookie"</span>, cookie);
  }

  res.redirect(<span class="hljs-string">"/dashboard"</span>);
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>You can find this code snippet in the <a target="_blank" href="https://github.com/authsignal/nextjs-example/blob/with-email-hosted-ui/pages/api/callback.ts">example repository of Authsignal</a>. What this basically does is take the returning auth token from authsignal and validate if that token is properly authenticated and if that token is properly authenticated, set a cookie named <code>auth-session</code> with that auth token and redirect the user to <code>/dashboard</code> page.</p>
<h3 id="heading-authsignal-rules">Authsignal Rules</h3>
<p>Now since we have configured both the email and passkey providers inside of Authsignal, when you click on the sign up button, both the providers will be displayed but we don’t want that. We specifically want only the email provider to be displayed.</p>
<p>To do this, go to your Authsignal dashboard, choose the right project if you haven’t already, and then click on the <em>actions</em> menu from the top bar. Click on the new action button and create a new action with the name <code>signUp</code>.</p>
<p>Then go to the <strong><em>rules</em></strong> menu in the sidebar and click on create rule. Then you can give any name to the rule, choose <em>challenge</em> from the outcome menu, untick <em>passkey</em> and then save the rule.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515844247/a5f32d4a-ed84-46fe-b15b-a18be92b7bb1.png" alt class="image--center mx-auto" /></p>
<p>Now you should be good to go ahead and test it. Click on the Sign-Up button and after following the on-screen instructions of entering the email and clicking on the link that you receive to <code>the</code> email, you should be redirected to <code>/dashboard</code>. Because we haven’t yet created a dashboard page, you will be redirected to a 404 page if everything goes well.</p>
<h3 id="heading-dashboard-page">Dashboard Page</h3>
<p>Now that we successfully get redirected to the dashboard page, let’s code a simple UI to display our email and a logout button that looks something like this</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1696515854768/0630a4c2-f1ac-4a29-a491-6caeada0c44d.png" alt class="image--center mx-auto" /></p>
<p>First, create a <code>dashboard.tsx</code> page inside the <code>pages</code> folder and paste the following code, then let’s go over the code line by line.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { GetServerSideProps } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</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> { authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/authsignal"</span>;
<span class="hljs-keyword">import</span> jwtDecode <span class="hljs-keyword">from</span> <span class="hljs-string">"jwt-decode"</span>;
<span class="hljs-keyword">import</span> { VerificationMethod } <span class="hljs-keyword">from</span> <span class="hljs-string">"@authsignal/node"</span>;
<span class="hljs-keyword">import</span> useAuthsignalClient <span class="hljs-keyword">from</span> <span class="hljs-string">"@/hooks/useAuthsignalClient"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getServerSideProps: GetServerSideProps = <span class="hljs-keyword">async</span> (context) =&gt; {
  <span class="hljs-keyword">const</span> token = context.req.cookies[<span class="hljs-string">"auth-session"</span>];

  <span class="hljs-keyword">if</span> (!token) {
    <span class="hljs-keyword">return</span> {
      redirect: {
        destination: <span class="hljs-string">"/"</span>,
        permanent: <span class="hljs-literal">false</span>,
      },
    };
  }

  <span class="hljs-keyword">const</span> decodedToken = jwtDecode&lt;<span class="hljs-built_in">any</span>&gt;(token);
  <span class="hljs-keyword">const</span> userId = decodedToken.sub;

  <span class="hljs-keyword">if</span> (!userId) {
    <span class="hljs-keyword">return</span> {
      redirect: {
        destination: <span class="hljs-string">"/"</span>,
        permanent: <span class="hljs-literal">false</span>,
      },
    };
  }

  <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> authsignal.getUser({ userId });

  <span class="hljs-keyword">return</span> {
    props: {
      token,
      email: user.email,
      hasEnrolledPasskey: user.enrolledVerificationMethods?.includes(
        <span class="hljs-string">"PASSKEY"</span> <span class="hljs-keyword">as</span> VerificationMethod
      )!!,
    },
  };
};

<span class="hljs-keyword">const</span> Dashboard = <span class="hljs-function">(<span class="hljs-params">{
  email,
  hasEnrolledPasskey,
  token,
}: {
  email: <span class="hljs-built_in">string</span>;
  hasEnrolledPasskey: <span class="hljs-built_in">boolean</span>;
  token: <span class="hljs-built_in">string</span>;
}</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> router = useRouter();

  <span class="hljs-keyword">const</span> logOut = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">document</span>.cookie = <span class="hljs-string">`auth-session=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT`</span>;
    router.push(<span class="hljs-string">"/"</span>);
  };

  <span class="hljs-keyword">const</span> authSignal = useAuthsignalClient();

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"p-5 font-inter flex flex-col justify-center items-center"</span>&gt;
      &lt;h2 className=<span class="hljs-string">"text-center text-3xl font-bold"</span>&gt;
        Next JS + AuthSignal Passkeys Demo
      &lt;/h2&gt;

      &lt;div className=<span class="hljs-string">"border rounded-lg flex flex-col mt-16 w-fit p-16 text-center"</span>&gt;
        &lt;h3&gt;You are logged <span class="hljs-keyword">in</span>&lt;/h3&gt;
        &lt;p className=<span class="hljs-string">"mt-2"</span>&gt;Your email is {email}&lt;/p&gt;

        {!hasEnrolledPasskey &amp;&amp; (
          &lt;span
            className=<span class="hljs-string">"text-blue-600 cursor-pointer mt-5"</span>
          &gt;
            Save the passkey
          &lt;/span&gt;
        )}

        &lt;button className=<span class="hljs-string">"border px-6 py-3 mt-10"</span> onClick={logOut}&gt;
          Sign Out
        &lt;/button&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Dashboard;
</code></pre>
<p>We first have a <code>getServerSideProps</code> function on the top of the file. This function runs on the server before the page is rendered to the client. You can read more about this in the <a target="_blank" href="https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props">Next.JS documentation</a>.</p>
<p>Inside the <code>getServerSideProps</code> function,</p>
<ul>
<li><p>First, we read the cookie that we set before from the callback function and take it to the constant <code>token</code>.</p>
</li>
<li><p>If there is no token, we simply redirect the user to the home page.</p>
</li>
<li><p>If there is a token, we decode the token and take the <code>userId</code> from it.</p>
</li>
<li><p>If there is no <code>userId</code> which means the token is invalid, we redirect the user to the home page.</p>
</li>
<li><p>If there is a <code>userId</code> we pass that to the <code>getUser</code> function in authsignal and get back the user information.</p>
</li>
<li><p>Then we pass some of that information back to the client ( notice the <code>hasEntrolledPasskey</code> value, if this is true that means the user has already configured passkeys and if that is false that means the user has not configured passkeys. You will see how this is useful in the future parts of the tutorial)</p>
</li>
</ul>
<p>Then inside the page, we simply render the information we got from the server side including a function called <code>logOut</code> that would clear the cookie and redirect the user back to the home page.</p>
<p>Also notice that if the user hasn’t enrolled passkeys, we render a text called <em>Save the passkey</em>. We will implement the functionality of this in the next section of the tutorial.</p>
<h2 id="heading-set-up-passkeys-authentication-with-authsignal">Set up Passkeys Authentication with Authsignal</h2>
<p>If you followed until now, you should have correctly configured magic links as an authentication provider. Now let’s shift our focus to implementing passkeys.</p>
<p>First of all, you need to create a new API route. Create a new file, name it <code>passkey.ts</code> inside the <code>api</code> folder, and paste the following code.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { authsignal } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/lib/authsignal"</span>;
<span class="hljs-keyword">import</span> { NextApiHandler } <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;

<span class="hljs-keyword">const</span> handler: NextApiHandler = <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { token } = <span class="hljs-keyword">await</span> authsignal.track({
    action: <span class="hljs-string">"passkey"</span>,
    userId: <span class="hljs-string">""</span>,
  });

  res.status(<span class="hljs-number">200</span>).json({
    token,
  });
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler;
</code></pre>
<p>Nothing really new here, we just track a new action called <code>passkey</code> and send the token we get back to the front end.</p>
<h3 id="heading-sign-up-for-the-passkey">Sign up for the passkey</h3>
<p>Now, move on to the <code>dashboard.tsx</code> and inside the <code>Dashboard</code> function, create a new function as follows.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> passkeySignUp = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> authSignal?.passkey.signUp({
      token,
      userName: email,
    });
    location.reload();
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
};
</code></pre>
<p>What this does is sign up that user with a passkey and reload the page if it is successful, or if it was unsuccessful, we just console.log the error.</p>
<p>In the markup, under the <code>onClick</code> handler of <strong><em>Save the passkey</em></strong> element, call this function like this.</p>
<pre><code class="lang-typescript">{!hasEnrolledPasskey &amp;&amp; (
  &lt;span
    className=<span class="hljs-string">"text-blue-600 cursor-pointer mt-5"</span>
    onClick={passkeySignUp}
  &gt;
    Save the passkey
  &lt;/span&gt;
)}
</code></pre>
<p>Now, if you go to the <code>/dashboard</code> page and click on the <strong><em>Save the passkey</em></strong> button, your browser will prompt confirm your screen lock and the passkey will be saved. When the page refreshes, the <strong><em>Save the passkey</em></strong> should disappear.</p>
<h3 id="heading-sign-in-with-the-passkey">Sign in with the passkey</h3>
<p>To implement the login side of passkeys, go to <code>index.tsx</code> file, make sure <code>axios</code> is imported and add a new function like follows.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> passkeyLogin = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> {
    data: { token },
  } = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">"/api/passkey"</span>);

  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> authSignal?.passkey.signIn({
    token,
  });

  <span class="hljs-keyword">if</span> (result) {
    <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">`/api/callback?token=<span class="hljs-subst">${result}</span>`</span>;
    router.push(<span class="hljs-string">"/dashboard"</span>);
  }
};
</code></pre>
<p>Inside this function, we first get the token from the API function we created before and call the passkey sign-in function with that token.</p>
<p>Then if it returns a new token, we pass that token to our callback function to complete the login.</p>
<p>Now if you go to the homepage and click on the <em>Sign in with an existing passkey</em> button, you should be able to log in with the passkey you previously saved.</p>
<aside>
💡 This article goes over implementing passkeys and using Authsignal in a very basic manner. To implement passkeys in a real-world project. You would need a database to keep track of the user details.
</aside>

<h3 id="heading-importance-of-passkeys-and-why-authsignal">Importance of Passkeys and Why Authsignal</h3>
<p>As Google has explained <a target="_blank" href="https://blog.google/technology/safety-security/the-beginning-of-the-end-of-the-password/">here</a>, passkeys are a very novel method of authentication and a very big step towards the passwordless future.</p>
<p>But it would be a very daunting task to implement passkeys on your own. This is where authentication as a service services comes in clutch. As we discussed throughout this article, you can set up passkeys to your own application in a few lines of code using Authsignal.</p>
<p>If you followed right until the end, now you know how to use Authsignal and more specifically how to implement passkeys without going through much trouble.</p>
<p>If you have any problems or if you want to take a look at the final code, you can check the <a target="_blank" href="https://github.com/osadavc/authsignal-passkeys-tutorial">GitHub repository with the completed code</a>.</p>
<p>To learn more about Authsignal, <a target="_blank" href="https://docs.authsignal.com/">visit the Authsignal documentation</a></p>
]]></content:encoded></item><item><title><![CDATA[Introducing GitJournal, The BEST Way To Start Writing Your Own Journal 📕]]></title><description><![CDATA[Backstory 💡
Hey folks, this article is dedicated to the project I built for the 1Password Hackathon. I have participated in many hackathons here at Hashnode. I believe this is the 5th hackathon I have participated in within the last 2 years, but the...]]></description><link>https://blog.osadavidath.com/introducing-gitjournal</link><guid isPermaLink="true">https://blog.osadavidath.com/introducing-gitjournal</guid><category><![CDATA[1password]]></category><category><![CDATA[BuildWith1Password]]></category><category><![CDATA[journal]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[Passage]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Thu, 29 Jun 2023 20:50:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1688070943051/db4a5473-0dfe-4993-b94d-018fcb578b60.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-backstory">Backstory 💡</h2>
<p>Hey folks, this article is dedicated to the project I built for the <a target="_blank" href="https://1password.com/">1Password</a> Hackathon. I have participated in many hackathons here at <a target="_blank" href="https://hashnode.com/">Hashnode</a>. I believe this is the 5th hackathon I have participated in within the last 2 years, but the project I worked on this time is nothing like any other thing I have built.</p>
<p>Let me tell you exactly why I am saying so.</p>
<p>For a long time, I've been wanting to write a journal to document my life story but I never really got the motivation to kick-start it. Now I already hear you asking why. Well, one of the main reasons was where I was supposed to write my journal. In a paper book? No way. So I did what all of you would probably do. I went on Google and searched for the best platforms to write a journal. Well, yeah. There were plenty of platforms to write journals but in summary, they had quite several issues in common.</p>
<ol>
<li><p>What if some human could read my journal entries (Oh no, that'll be so embarrassing)</p>
</li>
<li><p>What if that journaling service suddenly closes down their service without any notice (am I gonna lose everything I wrote about my life for the past decade ?)</p>
</li>
<li><p>Some features are paid (I ain't paying 10 bucks a month to write a journal)</p>
</li>
</ol>
<p>So, those were some issues that I had with the current journaling software market. Simply, there were no viable services for me to journal with. You exactly guessed what I did. I did what every developer would do. I started to build my own 😂.</p>
<p>Well, let me give you a little spoiler before we start with the article. You have no idea how 1Password's <a target="_blank" href="https://passage.id/">Passage</a> made my idea even more user-friendly, especially for an application like a journal.</p>
<h2 id="heading-introduction">Introduction 📕</h2>
<p>And that was how GitJournal was born. GitJournal is a journaling software that is nothing like the competitors in the market right now. The truly unique thing is the way that we store your journal entries. For now, just keep the phrase "Your Life, Your Journal". I will explain how we store the journal entries as we progress through the article.</p>
<h2 id="heading-why-gitjournal">Why GitJournal 🤔</h2>
<p>Behind every product, there should be the important question "Why ?". Why on earth would someone want to use GitJournal or even better, switch from another journaling platform to GitJournal?</p>
<p>Well, to answer that question most simply, it provides a solution to every issue that I have mentioned above.</p>
<ol>
<li><p>No human could read your journal entries. (All of your entries are protected by AES-256 military-grade protection with a key <strong>THAT YOU HOLD YOURSELF</strong>)</p>
</li>
<li><p>No problem, you have the key and all your entries in your GitHub repository (unless GitHub closes down which is unlikely to ever happen)</p>
</li>
<li><p>It is completely free. You don't have to spend a penny.</p>
</li>
</ol>
<h2 id="heading-how-gitjournal-works">How GitJournal Works 🧑🏻‍💻</h2>
<p>Now that you know why you should potentially use GitJournal, let me tell you a bit about how GitJournal Works.</p>
<p>This flow chart depicts what would happen behind the scenes once you create your account on GitJournal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1687795950186/fd0926b0-3111-4ac0-b1eb-765128788e70.png" alt class="image--center mx-auto" /></p>
<p>Now let me go over each segment of this flow chart.</p>
<ol>
<li><p>The first thing that you have to do is sign up for GitJournal via Passage. Passage utilizes amazing authentication powered by passkeys. You will see how this authentication system plays a huge role throughout the project.</p>
</li>
<li><p>Once you're logged in to the application, you will have to connect your GitHub account. Why? you may ask. Well, we store all of your journal entries in your own GitHub repo. (You might think because this uses GitHub, it is only for developers. Actually no, it is not. Yes, it uses GitHub but you don't need to know anything basically, you just need to have a GitHub account)</p>
</li>
<li><p>This is where it gets a little interesting. Now you have two options in front of you. You can either store the secret key yourself or let GitJournal store it. Both of these options have their pros and cons.</p>
<p> eg: If you store the key in the database, your journal entries can be read by the administrators of the GitJournal site but you have no risk of losing your journal entries. On the other hand, if you store the keys yourself, you have no risk of your entries being read by the site administrators but you will have to keep the secret keys secure, if you lose them, then your journal entries are gone forever.</p>
</li>
</ol>
<h2 id="heading-my-tech-stack"><strong>My Tech Stack ⚙️</strong></h2>
<p>Now let's go a little bit into the technical side of my project. So, for this project, I used,</p>
<ul>
<li><p>NextJS as my frontend framework</p>
</li>
<li><p>Tailwind CSS for utility-based CSS Styling</p>
</li>
<li><p><a target="_blank" href="https://passage.id/">Passage</a> by 1Password for authentication</p>
</li>
<li><p>PostgreSQL as the database</p>
</li>
<li><p>and I used a pretty cool library called "wired-elements" for those hand-drawn-looking elements</p>
</li>
</ul>
<h2 id="heading-demo"><strong>Demo 🪧</strong></h2>
<p>Now, let me walk you through the whole app step by step and help you understand what exactly is GitJournal.</p>
<p>First and foremost we have a simple yet beautiful home page designed in the hand-written journal style including some cheesy ChatGPT-made journal entries 😅</p>
<p><img src="https://api.pikwy.com/web/649d3ef22ec72f09e907f0d2.jpg" alt /></p>
<p>You can now click on the <em>Create Your Journal</em> button or the user icon on the top right-hand corner to go to the authentication page which looks something like this initially.</p>
<p><img src="https://api.pikwy.com/web/649d415bdfe35874c03d55f6.jpg" alt /></p>
<p>This authentication page is of course powered by Passage. Because of Passage's authentication based on passkeys, it is super easy for you to log in to the application.</p>
<p>First, you need to put your email and confirm the OTP you will receive in your mailbox. Then you can confirm your Windows Hello login method or the way you use to log in to your Mac. Now, on subsequent logins, instead of using an OTP or password, you can directly log in to GitJournal with your PC's main login method. (In this case, I use my windows password because I don't have a fingerprint sensor for my PC but if you do, you will have that option)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688037736717/ddd699a2-9f69-46c8-80de-0a43a27960e4.gif" alt class="image--center mx-auto" /></p>
<p>Now, the wild ride will start. The first thing that you will have to do is connect your GitHub account (make sure to give the repo access, so that GitJournal can read and write to your repos)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688043585275/1f45a84b-d49a-4bf5-b906-33d0f81be3f6.gif" alt class="image--center mx-auto" /></p>
<p>After connecting the repository, you will have to choose if you want to store the key in the cloud or locally. Here is a brief comparison between both of those options</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Local</td><td>Cloud</td></tr>
</thead>
<tbody>
<tr>
<td>Your data is 100% secure, nobody can read them without a key.</td><td>Still, the same protection is applied but we have the key, so anybody who has access to the database could potentially read your content</td></tr>
<tr>
<td>All your data will be gone if you lose the key</td><td>No suck issues because your key will be stored in the cloud database.</td></tr>
</tbody>
</table>
</div><p>According to your personal choice, you can choose either and continue. I am going to choose the local option because is the most secure one.</p>
<p>Immediately after your repository is created, you will be greeted with a notice like this. <strong>You must click on the Download Backup button and download the file because that is what you will need to unlock your journal entries if you log in again on a different browser.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688044141133/349e1a5e-a1f2-40f4-bf46-ee7cf45d7bf6.png" alt class="image--center mx-auto" /></p>
<p>For a little surprise, check your GitHub 😉. You will see a brand new repository with a single file being created. <strong>Make sure to not do anything with this repository.</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688046546618/5c799bb9-69b5-42ce-aa5c-07da0ec006c1.png" alt class="image--center mx-auto" /></p>
<p>Let's hop back into our app. Now you should see a very simple editor which is powered by TipTap with a calendar by side to write your journal.</p>
<h3 id="heading-writing-a-journal-entry">Writing a Journal Entry ✍🏻</h3>
<p>First, select the date on the calendar (today's date is automatically selected). Then write your secrets in the editor and format them as you please and finally click the Save button.</p>
<p>That's it, all your secrets are encrypted and stored in GitHub for you 🥳. Now if you check your repository, you will see a new file in a new folder like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688046466028/f1c4331c-0fc3-48ae-95c9-925193e87677.png" alt class="image--center mx-auto" /></p>
<p>Now, this file contains your whole journal entry's content. For example, in this example, this whole thing means "Hey there." How amazing that is. <em>This is encrypted with AES-256 encryption.</em></p>
<h2 id="heading-video-demo">Video Demo 📽️</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/l1LPskJPOZQ">https://youtu.be/l1LPskJPOZQ</a></div>
<p> </p>
<h2 id="heading-role-of-passage">Role of Passage 🔒</h2>
<p>Passage's amazing passkey authentication method is how you log in to GitJournal.</p>
<p>Now let me tell you how that really shines. Imagine you wrote a journal entry for today and wanted to go away so obviously, you have to log out, otherwise, someone could access your secrets. Now when you come back, you don't have to remember any crazy password or open your email to find an OTP, all you have to do is just gently keep your finger on the fingerprint sensor 😅 and you're IN.</p>
<p>Overall, I am really happy with Passage and will definitely use it in my future projects.</p>
<h3 id="heading-struggles">Struggles <strong>😩</strong></h3>
<ul>
<li><p>I had some problems getting the "wired-elements" library to get it working with NextJS but after some tinkering, I figure out a <a target="_blank" href="https://github.com/osadavc/git-journal/blob/8ad42cceafbe6d2ce1bf7e3773da92224891a204/src/components/Common/WiredElements.tsx#L3">little hack</a> to make that work.</p>
</li>
<li><p>I also had a problem with displaying this little dialog box when the user hasn't saved the entry but tries to close the browser tab while fixing that, <a target="_blank" href="https://github.com/osadavc/git-journal/blob/8ad42cceafbe6d2ce1bf7e3773da92224891a204/src/components/Journal/JournalEditor.tsx#L72">I learnt that</a> it is good to use <code>useCallback</code> hook when adding even listeners.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1688048643371/3d69a423-0542-4283-aaf7-35264758791c.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<h3 id="heading-future-plans"><strong>Future Plans 🔮</strong></h3>
<ul>
<li><p>I want to create a GitHub action that would allow users to download their journal as a ZIP with all the entries decrypted.</p>
</li>
<li><p>Making this an actual SaaS with a paid plan including many more features such as even more security features and releasing this as a competitor to the current online journal market</p>
</li>
</ul>
<h2 id="heading-important-links">Important Links 🔗</h2>
<ul>
<li><p><a target="_blank" href="https://gitjournal.osadavidath.com/">GitJournal</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/osadavc/git-journal">GitHub Repository</a></p>
</li>
</ul>
<p>Finally, my thanks to <a target="_blank" href="https://hashnode.com/">Hashnode</a> and <a target="_blank" href="https://1password.com/">1Password</a> for giving me this awesome opportunity to build the project that I've been wanting to build for so long.</p>
<p>And thank you for reading all the way to the end. Let me know the good, bad and ugly about my project and I would be really happy to see you using this as your daily journal.</p>
<blockquote>
<p>PS: I am starting to journal from today 😜</p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Dev Retro 2022 - End Of Another Chapter Of Life]]></title><description><![CDATA[Introduction
2021, one of the best years of my life just ended and I was all in for a brand new adventure. 2021 was more of a learning year, I learnt a lot of things including mobile development, web development and much more.
I've thought myself how...]]></description><link>https://blog.osadavidath.com/dev-retro-2022-end-of-another-chapter-of-life</link><guid isPermaLink="true">https://blog.osadavidath.com/dev-retro-2022-end-of-another-chapter-of-life</guid><category><![CDATA[#DevRetro2022]]></category><category><![CDATA[hackathon]]></category><category><![CDATA[developer-journey]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[full stack]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Sun, 25 Dec 2022 14:19:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671975781583/Hp73eRDxe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>2021, one of the best years of my life just ended and I was all in for a brand new adventure. 2021 was more of a learning year, I learnt a lot of things including mobile development, web development and much more.</p>
<p>I've thought myself how to code all through self study. After a couple of udemy courses and a million different YouTube videos, I knew the very basics of full stack development and could actually do something myself.</p>
<h2 id="heading-the-beginning-january-month">The Beginning (January Month)</h2>
<p>Starting out, January was actually a really special month for me. After winning a competition held by <a target="_blank" href="https://www.youtube.com/@SonnySangha">Sonny Sangha</a> last Decemeber, I won one of the prizes and got in to his <a target="_blank" href="https://www.papareact.com/">Zero To Full Stack Hero</a> course for completely free. I was super excited about this one and also I wanted to join this course for a while, so I spend the full month going through different course lessons and coaching calls.</p>
<h2 id="heading-the-turning-point-february-month">The Turning Point (February Month)</h2>
<p>February was the most amazing month of year. I think I can even consider February as one turning point for my life. Hashnode started February with a bang. Hashnode was hosting a <a target="_blank" href="https://townhall.hashnode.com/netlify-hackathon">new hackathon</a> partnering with Netlify. I couldn't be more excited, I saw the introduction post and thought to myself, I am going to build something and possibly win it as well.</p>
<p>I had just turned 15 last year and 2000$ was life changing money for me. So, that was how I built <a target="_blank" href="https://blog.osadavidath.com/introducing-wordable-the-better-wordle">Wordable</a>. I spent my entire month building, every night I did nothing else but coding. So after about 3 weeks I had completed the whole project. I wrote my first blog post and it was up. The article was even featured on Hashnode but inside my mind, I was always thinking if I will win or not.</p>
<p><img src="https://blog.osadavidath.com/_next/image?url=https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1646940148356%2F8zgl6HZ5Z.png%3Fw%3D1600%26h%3D840%26fit%3Dcrop%26crop%3Dentropy%26auto%3Dcompress%2Cformat%26format%3Dwebp&amp;w=3840&amp;q=75" alt="Introducing Wordable - The Better Wordle 🟩🟨⬛" /></p>
<h2 id="heading-the-big-win-march-month">The Big Win (March Month)</h2>
<p>It was already March and I was busy refreshing the Hashnode townhall page like 100 times a day hoping that the winners would be announced at any time.</p>
<p>Well, that day finally came. On March 18th 2022 the results were up and I was a Winner. I seriously couldn't be more happier. That was one of the biggest wins and turning points in my life so far.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671820081909/cWvahIrxp.png" alt class="image--center mx-auto" /></p>
<p>Also I built something for Hasura hackathon on March as well but I didn't have much hopes on it because I had exams that month and I had to do the project in a rush.</p>
<h2 id="heading-another-turning-point-april-month">Another Turning Point ? (April Month)</h2>
<p>This month was honestly a roller-coaster of events. Lot of amazing things happened this month. First of all I won the Hasura Hackathon. I couldn't believe my eyes, this was my second hackathon win in a row ! Meanwhile Astro announced their <a target="_blank" href="https://astro.build/blog/astro-1-hackathon/">first ever hackathon</a> which I was very excited about so I started building something. At the end of the month, I had built <a target="_blank" href="https://github.com/astro-community/astro-auth">Astro Auth</a> which was a complete auth solution for Astro.</p>
<p>At the end of the month, something interesting happened. Without me even knowing I had got in to freelancing.</p>
<h2 id="heading-the-other-big-win-may-month">The Other Big Win (May Month)</h2>
<p>On May, I was continuing my freelance work participating some more hackathons and also I won the Astro Hackathon. This was another MASSIVE win for me.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1671913298648/MtoWlksKM.png" alt class="image--center mx-auto" /></p>
<p>Nothing really interesting happened in rest of the year, I participated to almost every Hashnode hackathon after that but I didn't unfortunately win any, but I kept doing more freelancing projects and I wrote some articles to <a target="_blank" href="https://medusajs.hashnode.dev/advanced-ecommerce-analytics-with-medusa-and-matomo-analytics#comments-list">Medusa</a>.</p>
<h2 id="heading-what-about-2023">What About 2023 ?</h2>
<p>For me personally, I had an awesome 2022 and I am planning to keep that up in 2023 as well. Here are some of my plans for 2023.</p>
<ul>
<li><p>Launch a brand new SaaS product.</p>
</li>
<li><p>Scale up my freelancing business through fiverr and other platforms.</p>
</li>
<li><p>Participate in Hackathons.</p>
</li>
<li><p>Write for more guest writing programs and write more articles in general.</p>
</li>
</ul>
<h2 id="heading-final-notes">Final Notes</h2>
<p>Finally, I am thankful to everyone who supported me in any way and especially Hashnode since Hashnode is probably the reason I've came a long way this year.</p>
]]></content:encoded></item><item><title><![CDATA[How I Built Twotion The Easiest & Most Amazing Way To Write Twitter Threads 🐦]]></title><description><![CDATA[Hey folks, I assume most of you have used Twitter and have read amazing threads written by some amazing people or maybe even you're a content creator on Twitter and you write threads regularly.
But how do you write twitter threads? You can either wri...]]></description><link>https://blog.osadavidath.com/how-i-built-twotion</link><guid isPermaLink="true">https://blog.osadavidath.com/how-i-built-twotion</guid><category><![CDATA[PlanetScale]]></category><category><![CDATA[PlanetScaleHackathon]]></category><category><![CDATA[notion]]></category><category><![CDATA[Twitter]]></category><category><![CDATA[SaaS]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Sun, 31 Jul 2022 18:55:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1659248101148/yJViE1W0U.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey folks, I assume most of you have used Twitter and have read amazing threads written by some amazing people or maybe even you're a content creator on Twitter and you write threads regularly.</p>
<p>But how do you write twitter threads? You can either write a Twitter thread inside of Twitter itself which I wouldn't call a good idea. Or you can use a service like <a target="_blank" href="https://typefully.com/">typefully</a> to write the tweet and tweet it from inside of typefully. This option is great and I myself use this option whenever I want to write a thread but is there a better option?</p>
<p><img src="https://c.tenor.com/yNkQLuSKxNkAAAAC/there-might-be-a-better-way-odafin-tutuola.gif" alt="Better way" /></p>
<h2 id="heading-introduction">Introduction 🐦</h2>
<p>When I did my research before building this project, I saw that many people are writing their threads inside of Notion (I assume you know what is Notion, if you don't know what notion is, it is a note-taking tool that is loved among many people) and then copy and paste them to twitter or typefully.</p>
<p>Well, you don't have to do that anymore.</p>
<p>Twotion (Twitter + Notion) will allow you to tweet threads / single tweets from inside of Notion. You only need to visit the Twotion website to set up that, after the initial setup, you don't have to leave notion to tweet threads 🤯.</p>
<h2 id="heading-but-how">But How 🤔</h2>
<p>Well, you might be thinking how did I do that because there is no Notion SDK that allows me to add a plugin or something similar to Notion. This is where browser extensions come into play. I created a browser extension that supports all the chromium-based browsers (Chrome, Brave, Edge, Opera and many more). With this extension, you can post your threads without leaving Notion at all.</p>
<h2 id="heading-tech-stack">Tech Stack ⚙️</h2>
<ol>
<li><p>Next JS</p>
</li>
<li><p>Tailwind CSS</p>
</li>
<li><p>Planetscale Database</p>
</li>
<li><p>Prisma</p>
</li>
<li><p>Next Auth</p>
</li>
<li><p>Notion API</p>
</li>
</ol>
<p>I used Next JS and Tailwind CSS as always as the UI framework and the CSS framework. I used Planetscale to store all the data and Notion API to work with the Notion Pages.</p>
<h2 id="heading-how-twotion-works-amp-walk-through">How Twotion Works &amp; Walk-through 🔧</h2>
<p>First, when a user visits the webpage, they will see a landing page that looks something like this. This actually turned out pretty well when I was building and I am pretty sure this is one of the best landing pages I've ever built. Here, I have included how to use Twotion and little bit of information about that.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659270988675/Rkm39mawa.png" alt="image.png" /></p>
<p>When the user initially sign-up to the website with Twitter, the user will be prompted to connect their Notion.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659255695030/OO_uzHo1T.png" alt="image.png" /></p>
<p>When the user is connecting to Notion, the user should give Twotion access to a single Notion database to write their tweets and store them inside of Notion. (The user won't be able to tweet from pages outside of this database)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659255929143/cI7SEPH-S.png" alt="image.png" /></p>
<p>Now before proceeding further, you need to have the browser extension installed.</p>
<h3 id="heading-installing-browser-extension">Installing Browser Extension</h3>
<p>There are a few ways to install the browser extension.</p>
<ol>
<li><p>Install Chrome Web Store - Work is in progress to publish the extension in webstore.</p>
</li>
<li><p>Install from source.</p>
</li>
</ol>
<ul>
<li><p>Download the latest zip file from <a target="_blank" href="https://github.com/osadavc/twotion/releases">GitHub</a></p>
</li>
<li><p>Extract Files</p>
</li>
<li><p>Turn on developer mode in your chromium-based browser.</p>
</li>
<li><p>Click on <code>Load Unpacked</code> and select the folder you got after extracting the zip file.</p>
</li>
</ul>
<h3 id="heading-writing-a-tweet-twitter-thread">Writing A Tweet / Twitter Thread</h3>
<p>Now we're coming to the fun part. All you have to do to tweet something is open up a new page in the notion database that you previously created and type out whatever you want😎.</p>
<p>As an example, see the following Notion page</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659271502955/ZhT5r7Vig.png" alt="image.png" /></p>
<p>Here every boxed thing is a single notion block. Every notion block counts as a single tweet. If you want to have multiple paragraphs in a single tweet, you have to include both of those paragraphs inside a single block.</p>
<p>If there is an image somewhere on the Notion page, the image will be automatically attached to the tweet that is right above it.</p>
<p>Also if you want to add hashtags or tag people, just type it out ⚡</p>
<p>One more thing, we all know that each tweet has a word limit of 280 characters, so if any of your tweets exceeds that character limit, they will be highlighted in red colour. (Done by the extension 🔥, You need be logged in to the website in order to use the extension)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659271827496/Ux3wo2ppY.png" alt="image.png" /></p>
<p>Now, to tweet your thread, all you gotta do is, click on the Twotion extension on your extension bar while you're on the Notion page you want to be tweeted and click on the Tweet button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659272032579/0bY2PJeoO.png" alt="image.png" /></p>
<p>and then once you've done it should be tweeted in a matter of seconds just like this. <a target="_blank" href="https://twitter.com/devCoder123/status/1553744352387555328?s=20&amp;t=UOm1BhAPVt11aQA3ui6EYQ">Here</a> is the thread that we just tweeted.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659272646118/tJeXTA18B.png" alt="image.png" /></p>
<h3 id="heading-the-dashboard">The Dashboard</h3>
<p>If you go to the dashboard of the website now by clicking on <code>Go To Dashboard</code> on the landing page, you will see all of the tweets you have posted so far like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659277100274/z1IFFHrU8.png" alt="image.png" /></p>
<p>From the two buttons in the far right corner of each of the thread cards, you can either open the tweet/thread on Twitter or preview it as an embed like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1659277213426/qu2ZQOhCZ.png" alt="image.png" /></p>
<h2 id="heading-what-i-learned-from-this-project">What I Learned From This Project 🤓</h2>
<p>First of all, I learned some more advanced concepts of Prisma and relational databases while working with Planetscale. Then of course I learned a lot about how to build chrome extensions. I've built a couple of simple ones in the past but not an advanced one like this with TS.</p>
<p>Also, I learned a lot of things about Notion and Twitter APIs.</p>
<h2 id="heading-role-of-planetscale">Role Of PlanetScale 🌎</h2>
<p>This is my first time working with PlanetScale and I'd have to say that I really enjoyed it. I love how PlanetScale has branches to manage local stuff and production stuff. What I love the most about PlanetScale is the speed. I have used a couple of these database services in the past and they were not as fast as Planetscale, to be honest. And the other awesome thing about PlanetScale is the really generous free tier.</p>
<p>I have pretty much used PlanetScale for every aspect of this application. All the user data, Notion OAuth API keys and most importantly previous tweets are stored inside of PlanetScale.</p>
<h2 id="heading-struggles">Struggles 😫</h2>
<p>There weren't as many struggles in this one but I had quite a few headaches. The first one is syncing the login state between the extension and the website. So I want the user to be able to log in to the website and use the extension without login into the extension again. I managed to fix it by accessing the cookies of the website from the extension and making calls to the backend with those cookies.</p>
<p>I also had a little issue with Prisma, it was actually a silly issue but I couldn't properly debug because I couldn't get much information about the error. However, after debugging for a couple of hours, I managed to fix it.</p>
<p>The next one was deployment, the issue was when a user is tweeting something, the backend call takes more than 10 seconds (if their thread is long) which is the limit of most serverless deployment solutions like Vercel and Netlify so I ended up deploying this in a VPS.</p>
<h2 id="heading-linting-amp-code-quality">Linting &amp; Code Quality 🧑🏻‍💻</h2>
<p>I used ESLint and Prettier to maintain the code quality as always and dropped a nice little emoji in front of every commit to make them look a bit consistent and nice.</p>
<h2 id="heading-future-plans">Future Plans 🔮</h2>
<p>I have some cool ideas like scheduled tweets and more to implement. I would like to implement them in my free time in the future.</p>
<blockquote>
<p>Also, the extension will be live on Chrome Webstore in the near future and till then please install the extension with the source. If you need any support installing the extension or something else, feel free to leave a comment or contact me directly, I will be more than happy to help you.</p>
</blockquote>
<p>Finally, thank you to PlanetScale and Hashnode for making this project a reality by hosting this Hackathon.</p>
<h2 id="heading-links">Links 🔗</h2>
<ul>
<li><p><a target="_blank" href="https://github.com/osadavc/twotion">GItHub Repository</a></p>
</li>
<li><p><a target="_blank" href="https://github.com/osadavc/twotion/releases/tag/1.0">Chrome Extension (Manual Install)</a></p>
</li>
</ul>
<h2 id="heading-video-demo">Video Demo 📽️</h2>
<p>Here is a video showing how to get started with Twotion in 3 minutes 😎</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Y87UNwZTnEc">https://youtu.be/Y87UNwZTnEc</a></div>
<p> </p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://twitter.com/hashnode/status/1553840049350815744">https://twitter.com/hashnode/status/1553840049350815744</a></div>
<p> </p>
<p>Thank you so much for reading this article. Hope you enjoyed it. Let me know your ideas by leaving a comment below.</p>
]]></content:encoded></item><item><title><![CDATA[Introducing NFT Dapper - The Easiest Way To Kick Off Your NFT Drop Without Code ⚡]]></title><description><![CDATA[Introduction 🤩
Hey folks 👋🏻,
Here I am again with a project for the Linode X Hashnode hackathon. I got to know about this Hackathon immediately when it went live but I didn't have a solid idea at that time. I first looked at Linode API to see if I...]]></description><link>https://blog.osadavidath.com/introducing-nft-dapper-the-easiest-way-to-kick-off-your-nft-drop-without-code</link><guid isPermaLink="true">https://blog.osadavidath.com/introducing-nft-dapper-the-easiest-way-to-kick-off-your-nft-drop-without-code</guid><category><![CDATA[Linode]]></category><category><![CDATA[Linode Hackathon]]></category><category><![CDATA[NFT]]></category><category><![CDATA[dapps]]></category><category><![CDATA[Sass]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Tue, 28 Jun 2022 21:55:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1656452331275/5nM2U2XoE.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction 🤩</h3>
<p>Hey folks 👋🏻,</p>
<p>Here I am again with a project for the Linode X Hashnode hackathon. I got to know about this Hackathon immediately when it went live but I didn't have a solid idea at that time. I first looked at Linode API to see if I can do something cool with that but I didn't get an idea to do something with that. But after playing around for a little while I got the perfect idea. I thought to make a platform that people can utilize to launch their own NFT projects without using a single line of code. So, completely no code friendly.</p>
<h3 id="heading-introducing-nft-dapperhttpsnftdapperxyz">Introducing <a target="_blank" href="https://nftdapper.xyz">NFT Dapper</a> 🧑🏻‍💻</h3>
<p>So, the main idea and concept behind NFT Dapper are allowing its users to create their own completely white-labelled NFT drop. So whoever is gonna mint an NFT from an NFT drop that is made with the use of NFT Dapper won't know that the NFT drop is made with a third-party tool. It is just like you made it yourself. And there are more exciting stuff, I will come to those in a bit.</p>
<h3 id="heading-tech-stack">Tech Stack ⚙️</h3>
<ol>
<li>Next JS</li>
<li>TailwindCSS</li>
<li>PostgresSQL with Prisma</li>
<li>Solidity</li>
</ol>
<p>Next JS and TailwindCSS are pretty much my life defaults, so nothing more to talk about it. This time I tried to use PostgresSQL with Prisma instead of my usual MongoDB with Mongoose and it was pretty fine but I still think I kind of prefer MongoDB. And then of course Solidity for smart contract.</p>
<p>All of these are deployed on Linode 😉. (Psst... Linode was awesome, read why in the Role of Linode section).</p>
<h3 id="heading-demo">Demo 🪧</h3>
<h4 id="heading-home-page">Home Page</h4>
<p>Now, I will take you through the whole application and show you how it really works.</p>
<p>First, when you go to <a target="_blank" href="https://nftdapper.xyz">NFT Dapper</a>, you will see a simple, yet modern home page that looks like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656361029805/VWU2hidcM.png" alt="homepage.png" /></p>
<p>Here, I have listed some facts about NFT Dapper with a huge headline and a login button. You can click on Login With Metamask and make sure to choose the correct wallet if you have multiple wallets in your Metamask because this wallet will be later used to deploy our Smart Contract.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656361257881/vYEDpi1op.png" alt="image.png" /></p>
<p>Once you're in, you can create a new project by clicking on the plus button. Put a name, and description and choose the chain that you're willing to deploy your project on. If this is your first time, I suggest going with a test network to test everything out.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1657093697429/JTQB4Y8nG.png" alt="image.png" /></p>
<p>Once you click on the <code>Create Project</code> button, you will see a new project is being created and it'll appear on your project list. Just click on that and if the project's chain and the chain that is connected to metamask doesn't match, it'll show you a popup saying to switch the chain, just click on that and switch the chain to the correct chain.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656361556361/8wA3fEu4h.png" alt="image.png" /></p>
<h4 id="heading-project-dashboard">Project Dashboard</h4>
<p>Now, you'll see a little dashboard with some information and some Tabs like this. You need to go through these tabs one by one and set up your NFT collection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656361621558/MHmXyCDEY.png" alt="image.png" /></p>
<h4 id="heading-the-smart-contract-tab">The Smart Contract Tab</h4>
<p>When you go inside the smart contract tab, on the left side you'll see a bunch of options to choose from and on the right side, you can see some inputs that it requires to generate your contract on demand. </p>
<blockquote>
<p>Remember, unlike every competitor for this platform, this generates a smart contract on demand based on the features you choose. If you didn't choose a certain feature, then that'll not be in the code of the actual smart contract.</p>
</blockquote>
<p>Now, I will give you a little explanation of what all these features are and how to use them, </p>
<ol>
<li><p>Pausable Smart Contract - With this option enabled, you can pause the smart contract at any time by clicking on the pause button that will be displayed on the dashboard. Pausing smart contracts will stop users from minting NFTs from the contract.</p>
</li>
<li><p>Set A Public Sale Starting Time - When you enable this option, you'll see a new input field has appeared on the right-side panel called <code>Public Sale Starting Time</code>. You can choose any time that you want here and people won't be able to mint from your contract until that particular time.</p>
</li>
<li><p>Allow To Mint Multiple NFTs - When this option is enabled, one wallet can mint up to whatever number of tokens you specify in the right-side panel. By default, a wallet only can mint a single token per transaction and unlimited tokens with as much as transactions as they need.</p>
</li>
<li><p>Charge A Mint Fee From Users - This option will allow you to charge a fee from minters, they will have to pay this amount to mint a single NFT in the minting process.</p>
</li>
</ol>
<p>Apart from all of these options you have some inputs that should be filled.</p>
<ol>
<li><p>Collection Name - This will be the name of your collection and the name of the smart contract.</p>
</li>
<li><p>Collection Symbol - Ethereum needs a collection symbol in-order to have an NFT on the blockchain, so you have to put a symbol there (eg: BAYC)</p>
</li>
<li><p>Total Supply - You need to put how many NFTs you need that particular collection to have.</p>
</li>
</ol>
<p>once you have filled in all the necessary information, click the <code>Deploy Contract</code> button, wait for a few seconds to generate the smart contract on demand and then confirm the transaction on Metamask and wait for the transaction to complete. Once everything is done as expected, you will see a new view to control your contract, there you can change different properties on the smart contract like the public sale starting time, pause the contract and most importantly withdraw funds from your contract, for example, the funds that are collected by the mint.</p>
<p>Once you have completed the deploy step, you will see the <code>Images and Metadata</code> tab is enabled for you.</p>
<h4 id="heading-images-and-metadata-tab">Images and Metadata Tab</h4>
<p>Here is the place where you will set your images and metadata. I am not gonna go too deep into how to set up metadata and what structure should they be in. But brief instructions on how to set up the images and metadata are shown on the page itself. Once you have uploaded your images and metadata to IPFS, you've gotta put to <strong>Metadata IPFS</strong> Hash to the input shown. You do need to enter the IPFS Hash of your images because the images are linked to the metadata files.</p>
<p>Once you put an IPFS Hash, the application will check if it is correct, if it is correct, it'll show a preview of the 1st image on the left side panel. If it shows an error, try checking the box called <code>Add .json suffix to each of the metadata files</code>. If it still errors out, then there is something wrong with your IPFS location or your metadata is not accessible.</p>
<p>Once everything is done, you can click on the <code>Save Metadata URL</code>. It will take some seconds and you will have to confirm two different transactions.</p>
<p>After doing this, you will see that the <code>Minting Page</code> tab is now available to click on.</p>
<h4 id="heading-minting-page">Minting Page</h4>
<p><em>Wait, wait, this is too interesting to be discussed now, I will explain this after explaining the other two tabs.</em></p>
<h4 id="heading-code-tab">Code Tab</h4>
<p>If you want to build a custom landing page for your NFT drop and have some experience with frontend, you can use the information given here to use the contract from your front. For example, here you can find the contract address and ABI for the contract and some information about the functions that you need to be aware of.</p>
<h4 id="heading-settings-tab">Settings Tab</h4>
<p>Nothing fancy here, just a general settings tab to change the name, and description. You can change the chain if you still haven't deployed your contract.</p>
<h4 id="heading-minting-page-the-cool-part">Minting Page (The COOL Part) 🦄</h4>
<p>I personally think this is the most interesting part of this application and where Linode really shines compares to services like Vercel or Netlify.</p>
<p>First, you could open up the Mint Page and it asks whether you want to use a subdomain of the <code>nftdapper.xyz</code> or a custom domain. Yes, you read that right, you could add a custom domain to this, just like hashnode does. Pretty amazing, right? I mean if you were to do this on Vercel, you have to use their pro plan and the same applies to Netlify. But with Linode, I can literally do that for 5$ a month (Well, I used the 30$ a month plan but you get the idea)</p>
<p>If you choose the subdomain, then you could put a name basically, for example just <code>mycollection</code> and then, you're minting page will appear in <code>mycollection.nftdapper.xyz</code>. You can check the sample page <a target="_blank" href="https://mycollection.nftdapper.xyz/">here</a></p>
<p>If you choose a custom domain, then you can put your custom domain there and add a CNAME record called <code>@</code> if you're not deploying on a subdomain of yourself and the subdomain if you're deploying on a subdomain that is owned by you pointing to the <code>cname.nftdapper.xyz</code>.</p>
<p>Well, you will see the instructions in real-time down there.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1656450612428/WKxopjGAy.png" alt="image.png" /></p>
<p>Once you've properly done that, you can click on the <code>Configure Minting Page</code> button. It might take up to 48 hours for the domain to be actively serving your page because of the delays in DNS propagation but as per my experience, it takes no longer than a few seconds if you're managing your DNS with Cloudflare.</p>
<blockquote>
<p>Have to mention that this is possible because of the awesomeness in <a target="_blank" href="https://caddyserver.com/">Caddy</a> and I didn't find clear information about doing this, so in my GitHub repo, you can see the caddy configuration that I used. If you want to build a multi-tenant application just like this, then there you go. Also while going through some caddy forums and stuff, I found out that Hashnode also used the same method before migrating to Vercel. 🤯</p>
</blockquote>
<p>That's pretty much it for the functionality of the whole application.</p>
<h3 id="heading-video-demo">Video Demo 📽️</h3>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://vimeo.com/724643677">https://vimeo.com/724643677</a></div>
<h3 id="heading-role-of-linode">Role of Linode 🙃</h3>
<p>Basically, every component of the application is hosted on Linode, including the Next JS application, database and everything you could think of. What I've noticed is it is so damn fast. Like really fast. Also because of Linode's generous 100$ free tier, I got a dedicated VPS and everything is running smoothly.</p>
<p>Also if it wasn't for linode, configuring a multi-tenant (allowing users to use subdomains and custom domains) application will be a bit difficult.</p>
<h3 id="heading-linting-andamp-code-quality">Linting &amp; Code Quality 🧑🏻‍💻</h3>
<p>Nothing much to say that differs from my other previous projects. Just used ESLint, Prettier and used Emojis for every commit to keep the commit list looking clean.</p>
<h3 id="heading-struggles">Struggles 😶</h3>
<p>There weren't many struggles building this but I found myself stumbling upon setting up a multi-tenant environment with Caddy but finally solved it. If you're struggling to build something similar my <code>Caddyfile</code> is in the Github repo.</p>
<h3 id="heading-future-plans">Future Plans🔮</h3>
<p>I'd really like to make this a real SaaS if users like this and let me know your thoughts on that in the comments as well. Also, I am planning to add much more features like Whitelists, Delayed Reveals etc.</p>
<h3 id="heading-important-links">Important Links 🔗</h3>
<ul>
<li><a target="_blank" href="http://nftdapper.xyz/">NFT Dapper</a></li>
<li><a target="_blank" href="https://github.com/osadavc/nftdapper.xyz">GitHub Repository</a></li>
<li><a target="_blank" href="https://mycollection.nftdapper.xyz/">Example Minting Page</a></li>
</ul>
<p>Finally, as always thank you so much to Linode and Hashnode for hosting this hackathon, I indeed learnt a lot about Linux because this is my first time deploying an application on a cloud Linux server. Also, let me know if y'all have any suggestions or improvements that I could make.</p>
<blockquote>
<p>UPDATE: Fixed the Metamask connect bug. <a target="_blank" href="https://github.com/osadavc/nftdapper.xyz/commit/391512bf13a7149ff6eaf2e00e3cc74a5b704097">COMMIT</a></p>
<p>UPDATE: Added more instructions on local development. <a target="_blank" href="https://github.com/osadavc/nftdapper.xyz/commit/ae5fc8128dcefb3fd839d2778cac32c5c7f7a4f5">COMMIT</a></p>
</blockquote>
]]></content:encoded></item><item><title><![CDATA[Quickstart Guide To Building NFT Minting DAPPs On The Ethereum Chain]]></title><description><![CDATA[Hey folks 👋🏻, it's been a while since I wrote my last article and I thought why not write another article. In this tutorial, we'll discuss how to generate NFTs, create a minting DAPP, set up a delayed reveal and many more. So, let's just talk about...]]></description><link>https://blog.osadavidath.com/quick-start-guide-to-build-a-minting-dapp</link><guid isPermaLink="true">https://blog.osadavidath.com/quick-start-guide-to-build-a-minting-dapp</guid><category><![CDATA[THW Web3]]></category><category><![CDATA[NFT]]></category><category><![CDATA[Solidity]]></category><category><![CDATA[Ethereum]]></category><category><![CDATA[Smart Contracts]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Mon, 09 May 2022 14:47:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1652123588783/4XFnFKPP3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hey folks 👋🏻, it's been a while since I wrote my last article and I thought why not write another article. In this tutorial, we'll discuss how to generate NFTs, create a minting DAPP, set up a delayed reveal and many more. So, let's just talk about the background story for a little bit and hop into the actual smart contract.</p>
<h3 id="heading-what-on-the-earth-are-nfts">What On The Earth Are NFTs?</h3>
<p>If you haven't been living under a rock for the past year or so you should know what are NFTs at this point but if you don't know, NFTs are just digital assets that are most importantly stored in the blockchain. (Well, not exactly <strong>stored in the blockchain</strong> but kind of like that) NFTs are not just sitting in the blockchain doing nothing. They are bought and sold online, not for 5$ but for millions of dollars 🤑. You can go to <a target="_blank" href="http://opensea.io/">opensea</a> and scroll through a bunch of awesome NFTs that costs millions of dollars. </p>
<p>NFTs are unique, as the name suggests. (Non-Fungible Tokens) So they can't be replaced with something else. For example crypto is fungible, you can trade 1ETH for 1ETH which are exactly the same but in NFTs, you can't do that. If you traded an NFT for a different NFT you will have a completely different NFT.</p>
<p>Also, I must say that they are a TON of hate going on for NFTs. <a target="_blank" href="https://www.reddit.com/r/OutOfTheLoop/comments/rho91b/whats_up_with_the_nft_hate/">Read more about that</a>. But I don't care because,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651774835867/AshkU2Srz.png" alt="NFT Meme.png" /></p>
<p>So without any further ado, let's jump into the <strong>REAL STUFF</strong>.</p>
<h3 id="heading-how-we-can-create-a-nft-collection">How We Can Create A NFT Collection?</h3>
<p>Well, there are many methods for you to create NFT collections, you can create them with code, low code or even no code. But in this article, we focus on the method with code to mint our NFTs. But we'll just go over the other method briefly.</p>
<ul>
<li>No Code</li>
</ul>
<p>So the easiest method of all of them is Open Sea Lazy Mint. You can just go to opensea and click that create button and enter the details manually and <strong>save</strong> the NFT. I'll actually be minted when someone bought your NFT. That's why they call it Lazy Minting.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651776975086/qmZzOf7nr.png" alt="image.png" /></p>
<ul>
<li>Low Code</li>
</ul>
<p>You can use services like <a target="_blank" href="https://thirdweb.com/">Thirdweb</a> or <a target="_blank" href="https://www.nftport.xyz/">NFT Port</a> to mint your NFTs and you don't actually have to write a single line of solidity. It might be a good way as well if you think about it.</p>
<p>These are definitely <strong>not</strong> No Code. You will have to write some code in order to work with these. But you are free from writing blockchain specific languages like Solidity. So it might be a great fit for your choice. Especially Thirdweb, I find Thirdweb really interesting so make sure to give Thirdweb a go as well.</p>
<h3 id="heading-lets-start-building-our-nft-collection">Let's start Building Our NFT Collection</h3>
<h4 id="heading-getting-the-base-character-and-layers-ready">Getting The Base Character And Layers Ready</h4>
<p>Before writing any smart contract, DAPPs or anything like that, we need some digital thing to mint as NFTs, right? So far the most popular thing is little artworks. So let's build our project with some artwork as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651779965734/sdFKgmsLR.png" alt="image.png" /></p>
<p>But most of them are not just a piece of art. If you take a look at popular NFT collections like Bored Ape Yacht Club. You can see a similarity between all of those. It is basically like there is a base figure, in this case, an ape and on that ape, there are different things that are called traits. You have to design those base characters and traits separately, to learn how to properly do that, you can check out this <a target="_blank" href="https://youtu.be/k72aCI1VyKM">amazing video by codestackr</a>. I am not gonna explain it too much because I feel like it is out of the scope of this article.</p>
<p>You can generate all the layers you need and come back to this article to proceed further.</p>
<h4 id="heading-generating-the-actual-nft-arts">Generating The Actual NFT Arts</h4>
<p>So this is where the interesting things start happening. Now you will need an Art Generator to merge all of your layers and create a single PNG file. There are many ways you could achieve that but in this case, I am gonna use something called HashLips. I have made a customized fork of the official HashLips art generator on my GitHub profile.</p>
<p>You will need to clone the premade repository by me to proceed with this tutorial. In that repository, you will be able to find starter codes for all of the things that we are gonna be doing. To clone the repository, use this command.</p>
<pre><code class="lang-shell">git clone -b starter https://github.com/osadavc/build-a-minting-dapp.git
</code></pre>
<p>once you have it cloned, cd into the <code>art-generator</code> directory. This is where all the magic happens. If you have generated your layers according to the video I suggested I have before, you should have a folder structure like this or something similar to this structure</p>
<pre><code>images<span class="hljs-operator">/</span>
├── Background<span class="hljs-operator">/</span>
│   ├── GreenBackground.png
│   └── BlueBackground.png
├── Base<span class="hljs-operator">/</span>
│   └── MainBase.png
├── Eyes<span class="hljs-operator">/</span>
│   ├── SunGlasses.png
│   ├── Eyes.png
│   └── EyebrowRaised.png
└── Hair<span class="hljs-operator">/</span>
    ├── StraightHair.png
    └── CurlyHair.png
</code></pre><p>What you have to do is remove everything inside the <code>art-generator/layers</code> that you cloned earlier and copy all of the folders that you have generated and put them inside that <code>art-generator/layers</code> directory.</p>
<p>Then open the file called <code>art-generator/src/config.js</code> and look from lines 6 to 23. Which looks something like this.</p>
<pre><code><span class="hljs-keyword">const</span> namePrefix = <span class="hljs-string">"Your Collection"</span>;
<span class="hljs-keyword">const</span> description = <span class="hljs-string">"Remember to replace this description"</span>;
<span class="hljs-keyword">const</span> baseUri = <span class="hljs-string">"ipfs://NewUriToReplace"</span>;

<span class="hljs-keyword">const</span> layerConfigurations = [
  {
    growEditionSizeTo: <span class="hljs-number">5</span>,
    layersOrder: [
      { name: <span class="hljs-string">"Background"</span> },
      { name: <span class="hljs-string">"Eyeball"</span> },
      { name: <span class="hljs-string">"Eye color"</span> },
      { name: <span class="hljs-string">"Iris"</span> },
      { name: <span class="hljs-string">"Shine"</span> },
      { name: <span class="hljs-string">"Bottom lid"</span> },
      { name: <span class="hljs-string">"Top lid"</span> },
    ],
  },
];
</code></pre><p>Here you have a number of things to change here. First, change the <code>namePrefix</code> variable to the name of your NFT Collection and make sure to add a little description to the <code>description</code> variable as well. We'll have to do some work before changing the base URL, so we'll look into that really soon. Then you have the most important part.</p>
<pre><code><span class="hljs-string">const</span> <span class="hljs-string">layerConfigurations</span> <span class="hljs-string">=</span> [
  {
    <span class="hljs-attr">growEditionSizeTo:</span> <span class="hljs-number">5</span>,
    <span class="hljs-attr">layersOrder:</span> [
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Background"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Eyeball"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Eye color"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Iris"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Shine"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Bottom lid"</span> },
      { <span class="hljs-attr">name:</span> <span class="hljs-string">"Top lid"</span> },
    ],
  },
]<span class="hljs-string">;</span>
</code></pre><p>First, change the property called <code>growEditionSizeTo</code> to the maximum number of arts that you need and then you have the layer order, so you have to put all of your folder names in the order of the real layer order, so the topmost object in the array <code>layersOrder</code> should be the layer that sits behind all of the other layers in the actual artwork. Most probably this will be the background and you have to put all of your traits in this array for the art generator to work properly.</p>
<p>Then you've come to the satisfying part. Make sure you're in the <code>art-generator</code> directory in your terminal and run </p>
<pre><code class="lang-shell">yarn generate

or

npm run generate
</code></pre>
<p>Once you've done that you'll see a directory called <code>build</code> with all of your images and metadata, if you take a look at one of these images, you will see that the image is composed of all of the layers that you defined previously.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651822628826/sLkKIc6Rz.png" alt="image.png" /></p>
<h4 id="heading-uploading-images-to-ipfs">Uploading Images To IPFS</h4>
<p>Now that you have all the images that you need, you have to upload them somewhere in order for the other people to see them. You can store them in many places but the most recommended way is to store them in IPFS (InterPlanetary File System). We can't store them in the blockchain itself because the images are too big to be stored in the blockchain. This is the reason why I said NFTs are not really stored in the blockchain. We only have a record of them in the blockchain. </p>
<p>To upload the images to IPFS, you'll need a service that'll allow you to store files in IPFS. We are gonna use <a target="_blank" href="https://www.pinata.cloud/">Pinata</a>. Go ahead and create an account in pinata.</p>
<p>Now, you can select your images folder and upload them to pinata like this,
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651841020301/xncQnCWGJ.gif" alt="Animation1.gif" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651841614683/llTUfAYOl.png" alt="image.png" /></p>
<p>Now, if you take a closer look at the newly created row, you will see something called CID, make sure to copy that and replace it like this in the config file in the art generator.</p>
<pre><code><span class="hljs-keyword">const</span> namePrefix = <span class="hljs-string">"Beautiful Eyes"</span>;
<span class="hljs-keyword">const</span> description = <span class="hljs-string">"A NFT Collection That Contains 50 Beautiful Eyes"</span>;
<span class="hljs-comment">// Update The CID</span>
<span class="hljs-keyword">const</span> baseUri = <span class="hljs-string">"ipfs://QmPAgyCjcMQLag5HM2rJN71Zkt1TFtgLdyqcG8qzum73vh"</span>;
</code></pre><p>Now that you have updated the config file, you need to update the metadata files you previously generated as well. Obviously doing it manually will be a tedious task, so make sure you are at the root level of the <code>art-generator</code> and run the below command which will update the metadata.</p>
<pre><code class="lang-shell">yarn update_info

or

npm run update_info
</code></pre>
<p>Once it is successfully completed, you can check a metadata file and you'll see that the information is correctly updated. Now you have to follow the same steps that we did previously for images and upload these metadata files to Pinata as well.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1651861542537/C_R6V_79Q.png" alt="image.png" /></p>
<p>Congratulations 🎊.  Now you have your artwork generated and hosted in a decentralized manner.</p>
<h4 id="heading-writing-solidity-smart-contract">Writing Solidity Smart Contract</h4>
<p>Now that the artworks are hosted on IPFS, it is time to write the smart contract to allow users to mint NFTs. We are going to use the Hardhat toolchain to be able to compile and deploy Solidity code. You usually have to set up hardhat with their CLI tool but to save time I have created a starter for you. If you would like to learn how to set up Hardhat by yourself, read <a target="_blank" href="http://hardhat.org/">Hardhat documentation</a>, they have explained how to set up a new project very clearly.</p>
<p>I am gonna show you how to build the smart contract but if you need to learn solidity from zero to hero, I highly suggest taking a look at <a target="_blank" href="https://www.youtube.com/playlist?list=PLvfQp12V0hS2PQd9-X-E2AjmXj1o05WOo">Hashlips Solidity playlist</a>. </p>
<p>The starter code for the smart contract is available in the <code>smart-contract</code> directory in the previously cloned repository.</p>
<p>There are a few folders in the <code>smart-contracts</code> folder. All of your smart contracts will go to <code>contracts</code> and all of the scripts that you define will go inside the <code>scripts</code> directory. We will later see to what we use these scripts.</p>
<h5 id="heading-creating-a-basic-nft-minting-contract"><strong>Creating A Basic NFT Minting Contract</strong></h5>
<p>We are going to use the ERC721 industry standard which is suitable to build Non Fungible tokens to build our smart contract.</p>
<p>To make our lives a lot easier and writing smart contracts so easier and more secure, we are going to use a tool called <a target="_blank" href="https://openzeppelin.com/">OpenZeppelin</a>. Before doing anything you need to install OpenZeppelin, in-order to do that run one of the following commands <strong>inside</strong> the <code>smart-contract</code> directory.</p>
<pre><code class="lang-shell">yarn add @openzeppelin/contracts

or

npm i @openzeppelin/contracts
</code></pre>
<p>and to get the boilerplate for our contract, we are going to use the OpenZeppelin tool, they have an awesome tool called <a target="_blank" href="https://docs.openzeppelin.com/contracts/4.x/wizard">OpenZeppelin Wizard</a>. Open their wizard and select the following options.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652004309058/lU_ZVDokq.png" alt="image.png" /></p>
<ol>
<li><p>First we are gonna choose ERC721 because that is the standard that we are planning to use.</p>
</li>
<li><p>Then, you need to choose a Name and Symbol for your NFT Contract.</p>
</li>
<li><p>From features, choose Mintable, Auto Increment Ids and URI Storage</p>
</li>
<li><p>Choose Ownable because the contract is owned by only one user.</p>
</li>
</ol>
<p>Once the code is generated, click on the <code>Copy To Clipboard</code> button and paste the copied code to the contract you have created.</p>
<p>Next, we need to add a couple of functions to customize the behaviour of our smart contract.</p>
<ul>
<li>First, we are gonna add a new <code>mint</code> function to allow users to mint </li>
</ul>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
   <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.005</span> <span class="hljs-literal">ether</span>, <span class="hljs-string">"Not Enough ETH"</span>);

   _tokenIdCounter.increment();
   <span class="hljs-keyword">uint256</span> newItemId <span class="hljs-operator">=</span> _tokenIdCounter.current();

   _mint(to, newItemId);
}
</code></pre><p>With this mint function, we are allowing users to mint an NFT by paying 0.005ETH, if they haven't paid the correct amount we are aborting the function.</p>
<ul>
<li>Base URI Method</li>
</ul>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/"</span>;
}
</code></pre><p>This is the URL where our <strong>METADATA</strong> is hosted, make sure to change it to your own IPFS content id and keep a slash after the id.</p>
<ul>
<li>Token URI Function</li>
</ul>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _tokenId</span>)
    <span class="hljs-title"><span class="hljs-keyword">public</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)
</span>{
    <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> currentBaseURI <span class="hljs-operator">=</span> _baseURI();
    <span class="hljs-keyword">return</span>
        <span class="hljs-keyword">bytes</span>(currentBaseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>
            ? <span class="hljs-keyword">string</span>(
                <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                    currentBaseURI,
                    uintToString(_tokenId),
                    <span class="hljs-string">".json"</span>
                )
            )
            : <span class="hljs-string">""</span>;
}
</code></pre><p>What we are basically doing here is when a marketplace requests for the metadata of the NFT, we get the token id and return the actual metadata URL. Notice here we are using a utility function called <code>uintToString</code> to convert a <code>uint256</code> to <code>string</code>. That function is given below. </p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uintToString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _i</span>)
    <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
    <span class="hljs-title"><span class="hljs-keyword">pure</span></span>
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> str</span>)
</span>{
    <span class="hljs-keyword">if</span> (_i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
    }
    <span class="hljs-keyword">uint256</span> j <span class="hljs-operator">=</span> _i;
    <span class="hljs-keyword">uint256</span> length;
    <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
        length<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
        j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
    }
    <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bstr <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(length);
    <span class="hljs-keyword">uint256</span> k <span class="hljs-operator">=</span> length;
    j <span class="hljs-operator">=</span> _i;
    <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
        bstr[<span class="hljs-operator">-</span><span class="hljs-operator">-</span>k] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> (j <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
        j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
    }
    str <span class="hljs-operator">=</span> <span class="hljs-keyword">string</span>(bstr);
}
</code></pre><p>Once you have made all these changes your contract should look something like this,</p>
<pre><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/ERC721.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/Counters.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BeautifulEyesNFT</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC721</span>, <span class="hljs-title">ERC721URIStorage</span>, <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">Counters</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">Counters</span>.<span class="hljs-title">Counter</span>;

    Counters.Counter <span class="hljs-keyword">private</span> _tokenIdCounter;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) <span class="hljs-title">ERC721</span>(<span class="hljs-params"><span class="hljs-string">"BeautifulEyesNFT"</span>, <span class="hljs-string">"BEN"</span></span>) </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.005</span> <span class="hljs-literal">ether</span>, <span class="hljs-string">"Not Enough ETH"</span>);

        _tokenIdCounter.increment();
        <span class="hljs-keyword">uint256</span> newItemId <span class="hljs-operator">=</span> _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeMint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> uri</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_burn</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
    </span>{
        <span class="hljs-built_in">super</span>._burn(tokenId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">public</span></span>
        <span class="hljs-title"><span class="hljs-keyword">view</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)
    </span>{
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> currentBaseURI <span class="hljs-operator">=</span> _baseURI();
        <span class="hljs-keyword">return</span>
            <span class="hljs-keyword">bytes</span>(currentBaseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>
                ? <span class="hljs-keyword">string</span>(
                    <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                        currentBaseURI,
                        uintToString(_tokenId),
                        <span class="hljs-string">".json"</span>
                    )
                )
                : <span class="hljs-string">""</span>;
    }

    <span class="hljs-comment">// utils</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uintToString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _i</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">pure</span></span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> str</span>)
    </span>{
        <span class="hljs-keyword">if</span> (_i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
        }
        <span class="hljs-keyword">uint256</span> j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">uint256</span> length;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            length<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bstr <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(length);
        <span class="hljs-keyword">uint256</span> k <span class="hljs-operator">=</span> length;
        j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            bstr[<span class="hljs-operator">-</span><span class="hljs-operator">-</span>k] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> (j <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        str <span class="hljs-operator">=</span> <span class="hljs-keyword">string</span>(bstr);
    }
}
</code></pre><p>With that in place, the most basic NFT minting contract is done 🙌🏻. If you really want to test this Contract as it is now, go to the very bottom and read the Deployment section but for now, I am not gonna do it.</p>
<h5 id="heading-adding-a-sale-time"><strong>Adding A Sale Time</strong></h5>
<p>Right now a user can mint tokens at any time that they need, but you might need to restrict this to a specific time, let's just say the time that you launch your project. So let's stop users from minting at any time that they want.</p>
<p>First, go to your contract and create a new variable of the type uint256 to store the sale starting time like the following,</p>
<pre><code><span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> saleTimeStamp;
</code></pre><p>Then in the constructor, get a new argument to change the <code>saleTimeStamp</code> when the contract is deployed like the following, </p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title">ERC721</span>(<span class="hljs-params"><span class="hljs-string">"BeautifulEyesNFT"</span>, <span class="hljs-string">"BEN"</span></span>) </span>{
    saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
}
</code></pre><p>You might need to change the sale time later, so you will also need a function to change the value stored in the <code>save timestamp</code> variable.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setSaleTimeStamp</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
}
</code></pre><p>Now, when a user is trying to mint we need to check if the time stored in <code>saleTimeStamp</code> is lower than the time now meaning the sale is already going on. To do that add another <code>require</code> statement to the <code>mint</code> function.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-comment">// Compare the time now and the saved timestamp</span>
    <span class="hljs-built_in">require</span>(saleTimeStamp <span class="hljs-operator">&lt;</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>, <span class="hljs-string">"Sale has not started yet"</span>);
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.005</span> <span class="hljs-literal">ether</span>, <span class="hljs-string">"Not Enough ETH"</span>);

    _tokenIdCounter.increment();
    <span class="hljs-keyword">uint256</span> newItemId <span class="hljs-operator">=</span> _tokenIdCounter.current();

    _mint(to, newItemId);
}
</code></pre><p>after necessary changes are made, the contract should look like this</p>
<pre><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/ERC721.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/Counters.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BeautifulEyesNFT</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC721</span>, <span class="hljs-title">ERC721URIStorage</span>, <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">Counters</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">Counters</span>.<span class="hljs-title">Counter</span>;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> saleTimeStamp;

    Counters.Counter <span class="hljs-keyword">private</span> _tokenIdCounter;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title">ERC721</span>(<span class="hljs-params"><span class="hljs-string">"BeautifulEyesNFT"</span>, <span class="hljs-string">"BEN"</span></span>) </span>{
        saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(saleTimeStamp <span class="hljs-operator">&lt;</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>, <span class="hljs-string">"Sale has not started yet"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.005</span> <span class="hljs-literal">ether</span>, <span class="hljs-string">"Not Enough ETH"</span>);

        _tokenIdCounter.increment();
        <span class="hljs-keyword">uint256</span> newItemId <span class="hljs-operator">=</span> _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeMint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> uri</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_burn</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
    </span>{
        <span class="hljs-built_in">super</span>._burn(tokenId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">public</span></span>
        <span class="hljs-title"><span class="hljs-keyword">view</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)
    </span>{
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> currentBaseURI <span class="hljs-operator">=</span> _baseURI();
        <span class="hljs-keyword">return</span>
            <span class="hljs-keyword">bytes</span>(currentBaseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>
                ? <span class="hljs-keyword">string</span>(
                    <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                        currentBaseURI,
                        uintToString(_tokenId),
                        <span class="hljs-string">".json"</span>
                    )
                )
                : <span class="hljs-string">""</span>;
    }

    <span class="hljs-comment">// utils</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uintToString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _i</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">pure</span></span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> str</span>)
    </span>{
        <span class="hljs-keyword">if</span> (_i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
        }
        <span class="hljs-keyword">uint256</span> j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">uint256</span> length;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            length<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bstr <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(length);
        <span class="hljs-keyword">uint256</span> k <span class="hljs-operator">=</span> length;
        j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            bstr[<span class="hljs-operator">-</span><span class="hljs-operator">-</span>k] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> (j <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        str <span class="hljs-operator">=</span> <span class="hljs-keyword">string</span>(bstr);
    }

    <span class="hljs-comment">// setters</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setSaleTimeStamp</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
    }
}
</code></pre><p>Now, users should not be able to mint beyond the time limit that we've specified. Again, if you want to deploy this and check, you can do that.</p>
<h5 id="heading-reveal-nft-metadata-after-drop"><strong>Reveal NFT Metadata After Drop</strong></h5>
<p>Normally when someone mints your NFT, they will see the actual metadata at the particular moment. But to create hype around the collection what we can do is just show users a placeholder image and at a fixed date we can reveal the collection to display the real images, metadata etc.</p>
<p>In order to do this, you need to prepare the unrevealed metadata, I am gonna use this as unrevealed metadata.</p>
<pre><code>{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Beautiful Eyes Not Revealed"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"A NFT Collection That Contains 50 Beautiful Eyes"</span>,
  <span class="hljs-attr">"image"</span>: <span class="hljs-string">"ipfs://QmPAgyCjcMQLag5HM2rJN71Zkt1TFtgLdyqcG8qzum73vh/20.png"</span>,
  <span class="hljs-attr">"dna"</span>: <span class="hljs-string">"?"</span>,
  <span class="hljs-attr">"edition"</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">"date"</span>: <span class="hljs-number">1652033960576</span>,
  <span class="hljs-attr">"attributes"</span>: [
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Background"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Eyeball"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Eye color"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Iris"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Shine"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Bottom lid"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    },
    {
      <span class="hljs-attr">"trait_type"</span>: <span class="hljs-string">"Top lid"</span>,
      <span class="hljs-attr">"value"</span>: <span class="hljs-string">"?"</span>
    }
  ]
}
</code></pre><p>As you can see I have used <code>?</code> to represent the unrevealed metadata and a static image, so before revealing, every NFT will have the same image. You need to upload this to IPFS as well.</p>
<p>First, you need a couple of different variables to get this working, add the following. The <code>notRevealedMetadata</code> should contain the metadata URL for NFTs that are not revealed and the <code>isReveal</code> variable is what we toggle to reveal or unreveal NFTs.</p>
<pre><code><span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> notRevealedMetadata <span class="hljs-operator">=</span>
    <span class="hljs-string">"ipfs://QmWX1bxTYxMtgsnZQA1PwWkVZtM9ZYGfJMsLUBgzPsp1MK"</span>;
<span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> isRevealed <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;
</code></pre><p>Then you need to add a function to toggle the <code>isReveal</code> variable accordingly,</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setIsRevealed</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> _isRevealed</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
    isRevealed <span class="hljs-operator">=</span> _isRevealed;
}
</code></pre><p>Then what you need to do is modify the <code>tokenURI</code> function so that it'll return the hidden metadata URL if the metadata is not revealed.</p>
<pre><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _tokenId</span>)
    <span class="hljs-title"><span class="hljs-keyword">public</span></span>
    <span class="hljs-title"><span class="hljs-keyword">view</span></span>
    <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
    <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)
</span>{
   <span class="hljs-comment">// Add if check here to check if the metadata is not revealed.</span>
    <span class="hljs-keyword">if</span> (isRevealed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-literal">false</span>) {
        <span class="hljs-keyword">return</span> notRevealedMetadata;
    }

    <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> currentBaseURI <span class="hljs-operator">=</span> _baseURI();
    <span class="hljs-keyword">return</span>
        <span class="hljs-keyword">bytes</span>(currentBaseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>
            ? <span class="hljs-keyword">string</span>(
                <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                    currentBaseURI,
                    uintToString(_tokenId),
                    <span class="hljs-string">".json"</span>
                )
            )
            : <span class="hljs-string">""</span>;
}
</code></pre><p>Once you have made those changes, your contract should look like this,</p>
<pre><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.4;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/ERC721.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/utils/Counters.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">BeautifulEyesNFT</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC721</span>, <span class="hljs-title">ERC721URIStorage</span>, <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-keyword">using</span> <span class="hljs-title">Counters</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title">Counters</span>.<span class="hljs-title">Counter</span>;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> saleTimeStamp;

    <span class="hljs-keyword">string</span> <span class="hljs-keyword">public</span> notRevealedMetadata <span class="hljs-operator">=</span>
        <span class="hljs-string">"ipfs://QmWX1bxTYxMtgsnZQA1PwWkVZtM9ZYGfJMsLUBgzPsp1MK"</span>;
    <span class="hljs-keyword">bool</span> <span class="hljs-keyword">public</span> isRevealed <span class="hljs-operator">=</span> <span class="hljs-literal">false</span>;

    Counters.Counter <span class="hljs-keyword">private</span> _tokenIdCounter;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title">ERC721</span>(<span class="hljs-params"><span class="hljs-string">"BeautifulEyesNFT"</span>, <span class="hljs-string">"BEN"</span></span>) </span>{
        saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_baseURI</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>) </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"ipfs://QmZ5WxKWrL7fMFnQpbHcDDGhSA42PPwQiMXL7guB4v6wJd/"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        <span class="hljs-built_in">require</span>(saleTimeStamp <span class="hljs-operator">&lt;</span> <span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>, <span class="hljs-string">"Sale has not started yet"</span>);
        <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">&gt;</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span><span class="hljs-number">.005</span> <span class="hljs-literal">ether</span>, <span class="hljs-string">"Not Enough ETH"</span>);

        _tokenIdCounter.increment();
        <span class="hljs-keyword">uint256</span> newItemId <span class="hljs-operator">=</span> _tokenIdCounter.current();

        _mint(to, newItemId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">safeMint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> uri</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        <span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_burn</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
    </span>{
        <span class="hljs-built_in">super</span>._burn(tokenId);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tokenURI</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _tokenId</span>)
        <span class="hljs-title"><span class="hljs-keyword">public</span></span>
        <span class="hljs-title"><span class="hljs-keyword">view</span></span>
        <span class="hljs-title"><span class="hljs-keyword">override</span></span>(<span class="hljs-params">ERC721, ERC721URIStorage</span>)
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span></span>)
    </span>{
        <span class="hljs-keyword">if</span> (isRevealed <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-literal">false</span>) {
            <span class="hljs-keyword">return</span> notRevealedMetadata;
        }

        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> currentBaseURI <span class="hljs-operator">=</span> _baseURI();
        <span class="hljs-keyword">return</span>
            <span class="hljs-keyword">bytes</span>(currentBaseURI).<span class="hljs-built_in">length</span> <span class="hljs-operator">&gt;</span> <span class="hljs-number">0</span>
                ? <span class="hljs-keyword">string</span>(
                    <span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(
                        currentBaseURI,
                        uintToString(_tokenId),
                        <span class="hljs-string">".json"</span>
                    )
                )
                : <span class="hljs-string">""</span>;
    }

    <span class="hljs-comment">// utils</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">uintToString</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _i</span>)
        <span class="hljs-title"><span class="hljs-keyword">internal</span></span>
        <span class="hljs-title"><span class="hljs-keyword">pure</span></span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> str</span>)
    </span>{
        <span class="hljs-keyword">if</span> (_i <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"0"</span>;
        }
        <span class="hljs-keyword">uint256</span> j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">uint256</span> length;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            length<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        <span class="hljs-keyword">bytes</span> <span class="hljs-keyword">memory</span> bstr <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">bytes</span>(length);
        <span class="hljs-keyword">uint256</span> k <span class="hljs-operator">=</span> length;
        j <span class="hljs-operator">=</span> _i;
        <span class="hljs-keyword">while</span> (j <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
            bstr[<span class="hljs-operator">-</span><span class="hljs-operator">-</span>k] <span class="hljs-operator">=</span> <span class="hljs-keyword">bytes1</span>(<span class="hljs-keyword">uint8</span>(<span class="hljs-number">48</span> <span class="hljs-operator">+</span> (j <span class="hljs-operator">%</span> <span class="hljs-number">10</span>)));
            j <span class="hljs-operator">/</span><span class="hljs-operator">=</span> <span class="hljs-number">10</span>;
        }
        str <span class="hljs-operator">=</span> <span class="hljs-keyword">string</span>(bstr);
    }

    <span class="hljs-comment">// setters</span>

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setSaleTimeStamp</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> _saleTimeStamp</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        saleTimeStamp <span class="hljs-operator">=</span> _saleTimeStamp;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">setIsRevealed</span>(<span class="hljs-params"><span class="hljs-keyword">bool</span> _isRevealed</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        isRevealed <span class="hljs-operator">=</span> _isRevealed;
    }
}
</code></pre><h5 id="heading-deploying-the-smart-contract"><strong>Deploying The Smart Contract</strong></h5>
<p>Now that the smart contract is done, we need to deploy it to actually use it, when you're moving your app to production you have to deploy your contract on Main Network but in Ethereum, deploying on Main Network costs huge gas fees, so for sake of this tutorial we are gonna deploy on a test net called <code>Rinkeby</code>.</p>
<p>We will need something called Ethereum node to deploy a smart contract, there are multiple node providers like Alchemy and Infura, for this tutorial we are going to use Alchemy. First, go ahead and create an account in <a target="_blank" href="https://www.alchemy.com/">Alchemy</a>.</p>
<p>Then you can create an application like this,</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652038977540/PftAWzwAd.gif" alt="Animation1.gif" /></p>
<p>Then create a new <code>.env</code> file in your <code>smart-contract</code> directory and add the following variables.</p>
<pre><code><span class="hljs-attr">RINKEBY_URL</span>=https://eth-rinkeby.alchemyapi.io/v2/XXX-XXXXXXXXXXXXXXXXXXXXXXXXX
<span class="hljs-attr">PRIVATE_KEY</span>=XXXXXXXXXX
</code></pre><p>You need to set the HTTP URL from Alchemy to the <code>RINKEBY_URL</code> and you need to get your wallet private key and set it to <code>PRIVATE_KEY</code> (You will need some Rinkeby in your wallet, use <a target="_blank" href="https://faucets.chain.link/">this faucet</a> to get it if you don't have already).</p>
<p>Read <a target="_blank" href="https://metamask.zendesk.com/hc/en-us/articles/360015289632-How-to-Export-an-Account-Private-Key">this</a> to learn how to export the private key from your wallet.</p>
<p>Now open the hardhat config file in your <code>smart-contract</code> directory and in the exporting object, add a new key called <code>networks</code> and modify it like this.</p>
<pre><code>module.exports <span class="hljs-operator">=</span> {
  solidity: <span class="hljs-string">"0.8.4"</span>,
  networks: {
    rinkeby: {
      url: process.env.RINKEBY_URL <span class="hljs-operator">|</span><span class="hljs-operator">|</span> <span class="hljs-string">""</span>,
      accounts:
        process.env.PRIVATE_KEY <span class="hljs-operator">!</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> undefined ? [process.env.PRIVATE_KEY] : [],
    },
  },
};
</code></pre><p>Now let's move on to the <code>scripts</code> directory, you will see a file called <code>deploy.js</code>. Open that file, here is how we deploy the contract. The main thing you should pay attention to is this line.</p>
<pre><code><span class="hljs-attribute">const</span> beautifulE<span class="hljs-literal">yes</span>NFT = await BeautifulE<span class="hljs-literal">yes</span>NFT.deploy(<span class="hljs-number">1652017663</span>);
</code></pre><p>Here, as parameters, we need to pass everything that we expect from the contract's constructor. In this case the sale starting time. Once you have prepared that open your terminal and navigate to the <code>smart-contract</code> directory and run the following command.</p>
<pre><code>npx hardhat run scripts<span class="hljs-operator">/</span>deploy.js <span class="hljs-operator">-</span><span class="hljs-operator">-</span>network rinkeby
</code></pre><p>If everything went okay you should see the contract address in the terminal like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1652041138895/GuyRuOyEW.gif" alt="Animation.gif" /></p>
<p>Congratulations 🎊.  Now you have deployed your smart contract to the Rinkeby test network.</p>
<h4 id="heading-creating-a-dapp-to-mint-nfts">Creating A DAPP To Mint NFTs</h4>
<p>Now that the contract is deployed, all we have to do is build a frontend to allow the user to mint the NFTs.</p>
<p>As always the starter for the frontend is available inside the <code>client</code> folder in the Github repo. In the starter, I have implemented the design and installed the dependencies that we are gonna use.</p>
<p>For this tutorial, I am going to use a really interesting library that I found recently called <code>wagmi</code>. It is essentially a library to interact with the blockchain from a react application.</p>
<p>First, go ahead and open <code>index.tsx</code> in the pages directory, here we will have to do a bunch of imports from the <code>wagmi</code> library. You can add these lines to the very top of the file.</p>
<pre><code><span class="hljs-keyword">import</span> {
  <span class="hljs-title">useAccount</span>,
  <span class="hljs-title">useConnect</span>,
  <span class="hljs-title">useNetwork</span>,
  <span class="hljs-title">useContractWrite</span>,
  <span class="hljs-title">chain</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"wagmi"</span>;

<span class="hljs-keyword">import</span> { <span class="hljs-title">InjectedConnector</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"wagmi/connectors/injected"</span>;

<span class="hljs-comment">// Make sure to import ethers as well</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"ethers"</span>;
</code></pre><p>You will also need the artifacts from the solidity contract. Once you have deployed your contract a folder called artifacts will be generated. Find the JSON file with the name of the contract and copy that and put that somewhere in the <code>client</code> folder. Then make sure to import it in the <code>index.tsx</code> like this.</p>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-title">contractInterface</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../lib/BeautifulEyesNFT.json"</span>;
</code></pre><p>Then we'll initialize these hooks</p>
<pre><code><span class="hljs-comment">// Used to change the button text dynamically</span>
const [buttonMessage, setButtonMessage] <span class="hljs-operator">=</span> useState(<span class="hljs-string">"Mint NFT"</span>);

<span class="hljs-comment">// Used to connect to the wallet</span>
const { connect } <span class="hljs-operator">=</span> useConnect({
  connector: <span class="hljs-keyword">new</span> InjectedConnector(),
});

<span class="hljs-comment">// Used to connect to the correct network</span>
const { activeChain, switchNetworkAsync } <span class="hljs-operator">=</span> useNetwork();

<span class="hljs-comment">// Used to get wallet information</span>
const { data: account } <span class="hljs-operator">=</span> useAccount();

<span class="hljs-comment">// Call the minting function in the contract with 0.005 ETH</span>
const { writeAsync } <span class="hljs-operator">=</span> useContractWrite(
  {
   <span class="hljs-comment">// Update your contract address and pass the `abi` object in the artifact to contract interface.</span>
    addressOrName: <span class="hljs-string">"0x40ac75b32e02bcb48e24c7a2061b399446157f14"</span>,
    contractInterface: contractInterface.abi,
  },
  <span class="hljs-string">"mint"</span>,
  {
    args: [account?.<span class="hljs-built_in">address</span>],
    overrides: {
      gasLimit: <span class="hljs-number">3000000</span>,
      <span class="hljs-built_in">value</span>: ethers.utils.parseEther(<span class="hljs-string">"0.005"</span>),
    },
  }
);
</code></pre><p>Then we'll need some lifecycle hooks to keep the wallet connected to the network we need.</p>
<pre><code>useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  switchNetworks();
}, [account]);

useEffect(() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  switchNetworks();
}, [activeChain?.id]);

const switchNetworks <span class="hljs-operator">=</span> async () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
  <span class="hljs-keyword">if</span> (account <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> activeChain?.id <span class="hljs-operator">!</span><span class="hljs-operator">=</span> chain.rinkeby.id <span class="hljs-operator">&amp;</span><span class="hljs-operator">&amp;</span> switchNetworkAsync) {
    setButtonMessage(<span class="hljs-string">"Switching Network..."</span>);
    await switchNetworkAsync(chain.rinkeby.id);
    setButtonMessage(<span class="hljs-string">"Mint NFT"</span>);
  }
};
</code></pre><p>What we are essentially doing here, is checking what is the network and switching according to every time the user manually changes the network or when they connect to metamask.</p>
<p>Then we'll render the button conditionally according to the minting status/wallet connected status</p>
<pre><code>{<span class="hljs-operator">!</span>account ? (
  <span class="hljs-operator">&lt;</span>button
    className<span class="hljs-operator">=</span><span class="hljs-string">"w-full bg-[#a04edf] rounded-md mt-5 text-white py-3"</span>
    onClick<span class="hljs-operator">=</span>{() <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
      connect();
    }}
  <span class="hljs-operator">&gt;</span>
    Connect Wallet
  <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>button<span class="hljs-operator">&gt;</span>
) : (
  <span class="hljs-operator">&lt;</span>button
    className<span class="hljs-operator">=</span><span class="hljs-string">"w-full bg-[#a04edf] rounded-md mt-5 text-white py-3 animate-pulse disabled:bg-[#a04edf]/30 disabled:animate-none disabled:cursor-none"</span>
    disabled<span class="hljs-operator">=</span>{buttonMessage <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-string">"Mint NFT"</span>}
    onClick<span class="hljs-operator">=</span>{async () <span class="hljs-operator">=</span><span class="hljs-operator">&gt;</span> {
      setButtonMessage(<span class="hljs-string">"Minting..."</span>);
      await writeAsync();
      setButtonMessage(<span class="hljs-string">"Check Your NFT On Opensea 🎉"</span>);
    }}
  <span class="hljs-operator">&gt;</span>
    {buttonMessage}
  <span class="hljs-operator">&lt;</span><span class="hljs-operator">/</span>button<span class="hljs-operator">&gt;</span>
)}
</code></pre><blockquote>
<p>Make sure to add your contract address and the ABI in the correct places as mentioned above.</p>
</blockquote>
<p>If you put a past time when deploying, you should be able to mint now.</p>
<h4 id="heading-change-variables-in-the-smart-contract">Change Variables In The Smart Contract</h4>
<p>If you want to change the information in the smart contract like <code>saleTimeStamp</code> or <code>isRevealed</code> there are a few ways you can do that, for now, we're gonna use Remix IDE to do that.</p>
<ol>
<li>Open the remix IDE and create a new contract.</li>
<li>Copy and paste all your smart contract code to remix.</li>
<li>Compile the contract.</li>
<li>Selected <code>Injected Web3</code> as the provider and connect Metamask.</li>
<li>Paste your smart contract address and interact with the contract.</li>
</ol>
<p>Here is how you do it in detail,</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/54ca03ffefdc4e568c7e53cda18a1710">https://www.loom.com/share/54ca03ffefdc4e568c7e53cda18a1710</a></div>
<h4 id="heading-revealing-the-nfts">Revealing the NFTs</h4>
<p>Once it is time for NFT reveal, you can follow the steps mentioned above and call the function called <code>setIsRevealed</code> with <code>true</code> as an argument. Once you do that opensea won't automatically pick up the new metadata, you either have to update them manually from opensea or you can use a service such as https://www.nftrefreshmetadata.com/ (only work on mainnet) to refresh all the metadata and show the real image.</p>
<p>So, that is pretty much for the article and I hope you enjoyed it. The completed code is available <a target="_blank" href="https://github.com/osadavc/build-a-minting-dapp">here</a> at the final branch - https://github.com/osadavc/build-a-minting-dapp</p>
<p>Let me know what you think in the comments 🙌🏻.</p>
]]></content:encoded></item><item><title><![CDATA[How I Build NFT Pen The Platform To Mint Interactive NFTs 🖊️]]></title><description><![CDATA[Introduction 🤩
Hey folks 👋🏻,
Here I am once again with my submission for Hasura X Hashnode. Even though I got to know about the hackathon on the first day of the month, due to an exam I couldn't even touch it for the first two weeks. Anyways I sta...]]></description><link>https://blog.osadavidath.com/how-i-build-nft-pen</link><guid isPermaLink="true">https://blog.osadavidath.com/how-i-build-nft-pen</guid><category><![CDATA[Hasura Hackathon]]></category><category><![CDATA[Hasura]]></category><category><![CDATA[NFT]]></category><category><![CDATA[Hashnode]]></category><category><![CDATA[Web3]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Wed, 30 Mar 2022 17:37:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1648592762892/C6jm8TaOe.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction 🤩</h3>
<p>Hey folks 👋🏻,</p>
<p>Here I am once again with my submission for Hasura X Hashnode. Even though I got to know about the hackathon on the first day of the month, due to an exam I couldn't even touch it for the first two weeks. Anyways I started to build something at the end of the second week. Also, this was very challenging for me because I haven't ever used many technologies that I used to build this. I had to completely learn GraphQL, Hasura, Solidity from the very beginning and it was actually awesome!</p>
<h3 id="heading-introducing-nft-penhttpsnft-penherokuappcom">Introducing <a target="_blank" href="https://nft-pen.herokuapp.com/">NFT Pen</a> 🖊️</h3>
<p>So the idea behind NFT Pen is to allow people to mint interactive NFTs with ease instead of the boring image NFTs. The first inspiration for this was from this NFT game called <a target="_blank" href="https://opensea.io/collection/punksvsapes">Punks Vs Apes</a>. If you take a look at one of their NFTs, you will see that there are interactive, that is not just an image. It is a fully functional but simple web page.</p>
<h3 id="heading-what-nft-pen-does">What NFT Pen Does 🤔</h3>
<p>First, you have to feed NFT Pen a codepen link. Then NFT Pen will scrape that, get all the information and mint a proper interactive NFT. You will see more about this process in the demo section.</p>
<h3 id="heading-tech-stack">Tech Stack ⚙️</h3>
<p>Talking of the tech stack that I used, I used all my day to day stuff and some additions</p>
<ol>
<li>Next JS</li>
<li>Tailwind CSS </li>
<li>Hasura</li>
<li>Solidity</li>
</ol>
<p>For authentication, I used NextAuth.js as always and for deployment, I had to use Heroku, to know more about why I used Heroku see the struggles section.</p>
<h3 id="heading-demo">Demo 🪧</h3>
<p>Now I will walk you through the whole application, show you how it works and give you an idea about how NFT Pen really works.</p>
<p>First, we have a modern home page like this. This time I followed a proper workflow. Instead of jumping straight into the code. I first designed it roughly and start to code it. I think it really improved the final output.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648633973930/qW_wyJGQE.png" alt="image.png" /></p>
<p>Here is what I designed with Figma. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648633913770/pdAbUe6Rm.png" alt="image.png" /></p>
<p>You can click on the Login With Google button to authenticate NFT Pen with your google account. I decided to use Google OAuth even I don't need to access any Google APIs because it is kind of a hassle to log in by typing emails and passwords.</p>
<p>Once you properly logged in you will see a popup to authenticate with metamask. This will be beneficial when you are gonna mint your own NFTs. After going through all that, you will see a screen like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648642081684/fHDDgo4aN.png" alt="image.png" /></p>
<h4 id="heading-minting-the-nft">Minting The NFT 🦄</h4>
<p>Before you start minting the NFT, you need to prepare the code that you are gonna mint. For that, you need to create a <a target="_blank" href="https://codepen.io/">codepen</a>. You can create your own codepen or you can use a public codepen that is available. For this example, I am gonna use <a target="_blank" href="https://codepen.io/osadavc/pen/MWrvbrw">this codepen</a>. And don't forget, Javascript is also working inside the NFT if you have JavaScript 🤯. Also, this will be really cool if you are gonna mint CSS arts.</p>
<p>But there are few constraints with the codepen. Make sure to pay attention to them as well.</p>
<ul>
<li>Make sure it is responsive for the size 500px by 500px otherwise your NFT will not look so good.</li>
<li>Make your codepen public</li>
<li>If you are minting a CSS art it is better if you can avoid scrollbars at 500px by 500px size.</li>
</ul>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codepen.io/osadavc/pen/MWrvbrw">https://codepen.io/osadavc/pen/MWrvbrw</a></div>
<p>Awesome, now we have our codepen ready to go. Now what you have to ultimately do is go to NFT Pen and paste your codepen link in the input box. and click on the Fetch button. Make sure to keep your URL exactly in this format <code>https://codepen.io/osadavc/pen/MWrvbrw</code>. Otherwise, it'll not be able to mint the NFT properly. Once you clicked on the fetch button you will see the preview of your codepen like this. And you can see the JavaScript is working exactly as it should work 🚀. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648644039780/3lecbYgQR.png" alt="image.png" /></p>
<p>Now just click on the Mint NFT button and give a name for your NFT, then you can click on the Mint NFT button on the popup.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648644402387/58p2_Xwjk.png" alt="image.png" /></p>
<p>And you can see it goes through a series of stuff and finally metamask popup will be opened. You can click on Confirm button to start the mint (Make sure you have a bit of test matic in your wallet of the Mumbai Polygon network, I added a little minting fee just for fun, it won't be an issue since this is in a test network)</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648644915751/yMpE_wm03.png" alt="image.png" /></p>
<p>In a few seconds, your transaction will be completed. After that, you will see that the popup says the mint is completed. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648645023971/fttn_L8r-.png" alt="image.png" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648645488883/OwxBhw9qT.png" alt="image.png" /></p>
<p>Now just click on that View The NFT Button and Voila, <a target="_blank" href="https://testnets.opensea.io/assets/mumbai/0xa5556506109266E48b355406cbD82fF271598231/3">here is</a> your freshly minted interactive NFT. </p>
<p>If in any case, opensea says that the NFT is not loaded or something just click the refresh button in opensea and wait for a few minutes. </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648645175310/x_o9EQIOO.png" alt="image.png" /></p>
<p>If you go to the opensea page, you will see the NFT with the JavaScript working 🎉.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648645967336/Nat9g0UeA.gif" alt="Animation.gif" /></p>
<p>And one more thing, you can see all of your previously minted NFTs, if you click the Minted Pens button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648646127599/e2Itj0HZx.png" alt="image.png" /></p>
<h3 id="heading-role-of-hasura">Role of Hasura 🙃</h3>
<p>I am managing all user data and minted NFT data with Hasura and must say as someone who has never used GraphQL before, Hasura and GraphQL are awesome. Especially because of the DX. Automatic type generation is probably one of the best things and with the eco-system around it, the DX is even better. With Apollo client, I could get an experience that I could never get with REST Apis.</p>
<h3 id="heading-linting-andamp-code-quality">Linting &amp; Code Quality 🧑🏻‍💻</h3>
<p>This time I thought about it a bit more, in addition to ESLint and Prettier, I tried to keep my commits consistent with emojis. And I think that actually looks cool! I will probably do that in every new project of mine.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648647074307/xdeOK90xr.png" alt="image.png" /></p>
<h3 id="heading-struggles">Struggles 😶</h3>
<p>There were not many struggles that I had to go through while building this but I faced a major issue with the deployment. With vercel and netlify I couldn't deploy it because they have a timeout in their free plans. It took a while to upload all those stuff to IPFS and take a screenshot with puppeteer. So I had to use Heroku, it is fine but the problem is with Heroku putting the app to sleep every 30 minutes. Really sorry if you are experiencing a little high load times.</p>
<h3 id="heading-future-plans">Future Plans🔮</h3>
<p>If people start loving this app and the idea, I am planning to add an in-app NFT builder to easily mint interactive NFTs 🙌🏻.</p>
<h3 id="heading-important-links">Important Links 🔗</h3>
<ul>
<li><a target="_blank" href="https://nft-pen.herokuapp.com">Deployed App</a></li>
<li><a target="_blank" href="https://github.com/osadavc/nft-pen">GitHub Repository</a></li>
<li><a target="_blank" href="https://testnets.opensea.io/collection/codepennft">NFT Collection</a></li>
</ul>
<p>Finally, Thank You Hasura and Hashnode for hosting the hackathon. Learnt a load of new tech and enjoyed them very much. Looking forward to seeing your comments about <a target="_blank" href="https://nft-pen.herokuapp.com">NFT Pen</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Schedule Cron Jobs Easily And Conveniently With Agenda]]></title><description><![CDATA[When it comes to building serious applications, at some point you'll need to schedule some cron jobs. It might be to send a scheduled email, schedule to tweet something or even schedule to post something.
There is a handful of ways you can achieve so...]]></description><link>https://blog.osadavidath.com/schedule-cron-jobs-with-agenda</link><guid isPermaLink="true">https://blog.osadavidath.com/schedule-cron-jobs-with-agenda</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express]]></category><category><![CDATA[MongoDB]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Sat, 26 Mar 2022 08:03:06 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1648281522161/n_80MEra_.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When it comes to building serious applications, at some point you'll need to schedule some cron jobs. It might be to send a scheduled email, schedule to tweet something or even schedule to post something.</p>
<p>There is a handful of ways you can achieve something like that but today we are gonna take a look at my favourite which is the <a target="_blank" href="https://www.npmjs.com/package/agenda">Agenda</a> library.</p>
<h3 id="heading-introduction">Introduction 🎉</h3>
<p>Agenda is a lightweight job scheduling library for Node.js as they say on their ReadMe. By using agenda you can create, manage cron jobs. It also allows you to use a MongoDB database to store your jobs so that your cron jobs will be persisted even if you restart the server.</p>
<h3 id="heading-getting-started">Getting Started ✨</h3>
<p>First, initialize a node project with <code>npm init -y</code> or <code>yarn init -y</code>. Then you will need to install a couple of dependencies. We will need Express, Agenda and DayJS. I will explain what each of them is for later. For now, just install them with <code>npm install express agenda dayjs</code> or <code>yarn add express agenda dayjs</code>.</p>
<blockquote>
<p>I am gonna use import syntax for the rest of the tutorial. If you also need to use important statements make sure to add <code>"type": "module"</code> to your package.json file</p>
</blockquote>
<p>In the index.js file, you can import all of your modules and initialize an express app like follows.</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> Agenda <span class="hljs-keyword">from</span> <span class="hljs-string">"agenda"</span>;
<span class="hljs-keyword">import</span> dayjs <span class="hljs-keyword">from</span> <span class="hljs-string">"dayjs"</span>;

<span class="hljs-keyword">const</span> app = express();
</code></pre>
<p>Make sure to listen to a specific port at the bottom of your file. In this case, I am gonna listen on port 3000.</p>
<pre><code class="lang-js">app.listen(<span class="hljs-number">3000</span>);
</code></pre>
<p>Then you can initialize an Agenda instance and pass the database connection URL and the collection name. I am gonna pass my local MongoDB database URL and the collection name is <code>agendaJobs</code>. You can choose whatever name you want.</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> agenda = <span class="hljs-keyword">new</span> Agenda({
  <span class="hljs-attr">db</span>: {
    <span class="hljs-attr">address</span>: <span class="hljs-string">"mongodb://localhost:27017/agenda-test"</span>,
    <span class="hljs-attr">collection</span>: <span class="hljs-string">"agendaJobs"</span>,
  },
});
</code></pre>
<p>Then you need to start the Agenda instance. To do that update your express listen statement like this.</p>
<pre><code class="lang-js">app.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> {
  agenda.start();
});
</code></pre>
<p>It will start the agenda instance once express initialized and it is listening on the specified port. Now express and agenda are fully configured and ready to go.</p>
<h3 id="heading-scheduling-jobs">Scheduling Jobs ⚒️</h3>
<p>Before scheduling a job, you need to define a job. This definition includes what the job does when it is called. You can define a job as follows.</p>
<pre><code class="lang-js">agenda.define(<span class="hljs-string">"doSomething"</span>, <span class="hljs-function">(<span class="hljs-params">job</span>) =&gt;</span> {}
</code></pre>
<p>You can give any name to your job. This name will be important when you want to schedule it. For now, I am gonna console log something when the job runs.</p>
<pre><code class="lang-js">agenda.define(<span class="hljs-string">"doSomething"</span>, <span class="hljs-function">(<span class="hljs-params">job</span>) =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Job Ran"</span>);
});
</code></pre>
<p>Now you need a way to test this out. For this, I am gonna create an express endpoint.</p>
<pre><code class="lang-js">app.get(<span class="hljs-string">"/"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> agenda.schedule(
    dayjs().add(<span class="hljs-number">5</span>, <span class="hljs-string">"seconds"</span>).format(),
    <span class="hljs-string">"doSomething"</span>
  );

  res.json(data);
});
</code></pre>
<p>What we're doing here is scheduling a job to run in 5 seconds. We are using dayjs to add 5 seconds to the current time. You can see that the job is scheduled and the job details are returned.</p>
<h3 id="heading-time-to-test">Time To Test 🧪</h3>
<p>If everything went fine, we should see,</p>
<ol>
<li>Job Details in the response.</li>
<li>A brand new record in the database.</li>
<li>Terminal output in 5 seconds.</li>
</ol>
<p>First, run your app with <code>nodemon</code> command. Then you can make a get request to <code>http://localhost:3000/</code> with any method you want. In this case, I am gonna use <a target="_blank" href="https://hoppscotch.io/">Hoppscotch</a>.</p>
<p>Once you have sent the request, if everything is successful, you will receive a response with the job details like this.</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"doSomething"</span>,
  <span class="hljs-attr">"data"</span>: {},
  <span class="hljs-attr">"priority"</span>: <span class="hljs-number">0</span>,
  <span class="hljs-attr">"type"</span>: <span class="hljs-string">"normal"</span>,
  <span class="hljs-attr">"nextRunAt"</span>: <span class="hljs-string">"2022-03-26T07:38:21.009+00:00"</span>,
  <span class="hljs-attr">"_id"</span>: <span class="hljs-string">"623ec2e8fdced2c3bd29695b"</span>
}
</code></pre>
<p>And if you check your database you will see the created job's record like this
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648280764386/rea8IhXfE.png" alt="image.png" /></p>
<p>After exactly 5 seconds, you should see the following output in the terminal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1648279491625/a21MzDY1Z.png?auto=compress" alt="Terminal Output" /></p>
<p>Most importantly the job will persist even if you restart the server. So that is it for the basic Agenda tutorial. I hope you learnt something and I am also planning to do a more in-depth tutorial on how to use Agenda. Let me know what do you think.</p>
<h3 id="heading-links">Links 🔗</h3>
<ul>
<li><a target="_blank" href="https://npm.im/agenda">Agenda Package</a></li>
<li><a target="_blank" href="https://github.com/osadavc/agenda-basics">Source Code</a></li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Introducing Wordable - The Better Wordle 🟩🟨⬛]]></title><description><![CDATA[Introduction 🟩🟨⬛
Hey folks 👋🏻,
I heard about the Netlify X Hashnode hackathon a few weeks ago and thought this is the best way to spend my February holiday. So here is what I built during that time.
Introducing Wordable - The Better Wordle 😉
You...]]></description><link>https://blog.osadavidath.com/introducing-wordable-the-better-wordle</link><guid isPermaLink="true">https://blog.osadavidath.com/introducing-wordable-the-better-wordle</guid><category><![CDATA[Netlify]]></category><category><![CDATA[NetlifyHackathon]]></category><category><![CDATA[Games]]></category><category><![CDATA[thirdweb]]></category><category><![CDATA[NFT]]></category><dc:creator><![CDATA[Osada Vidath]]></dc:creator><pubDate>Tue, 22 Feb 2022 20:00:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1646940148356/8zgl6HZ5Z.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-introduction">Introduction 🟩🟨⬛</h3>
<p>Hey folks 👋🏻,</p>
<p>I heard about the Netlify X Hashnode hackathon a few weeks ago and thought this is the best way to spend my February holiday. So here is what I built during that time.</p>
<h3 id="heading-introducing-wordable-the-better-wordle">Introducing Wordable - The Better Wordle 😉</h3>
<p>You might be like, "wait what? what do you mean by better wordle", don't worry, I will explain it from the beginning of this story. So as you all know <a target="_blank" href="https://www.nytimes.com/games/wordle/index.html">wordle</a> is trending on the internet right now, especially on Twitter. I don't think there is anybody who doesn't know how to cheat in the wordle game. So if you didn't know, it is as simple as this single line of code. Also, you can find all the words for the next few years if you take a look at the source code of wordle 😅. I'll leave that to you.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645544826869/6owz5Zbzt.png" alt="image.png" /></p>
<h3 id="heading-the-need-for-a-better-wordle">The Need For A Better Wordle 🤔</h3>
<p>After getting to know the problems with wordle. I told to myself that I need to fix this. So here I am with a web3 friendly better wordle called wordable 🟩🟨⬛. If you are thinking about what is the relation between web3 and wordable, I will come to that shortly. With wordable, I tried to solve all the problems I have with the current wordle.</p>
<h3 id="heading-what-wordable-really-can-do-more-than-wordle">What Wordable Really Can Do More Than Wordle ❓</h3>
<p>Well, there are a few things that wordable can do but wordle can't, let's see what are those.</p>
<ol>
<li>Modern user interface.</li>
<li>Share your game's grid with Twitter with a single click.</li>
<li>Mint a unique NFT containing your game's grid 🤯</li>
<li>Last but not least you <strong>can't</strong> cheat or find the word list of the next few years 😶‍🌫️</li>
</ol>
<h3 id="heading-my-tech-stack">My Tech Stack ⚙️</h3>
<p>Speaking of the tech stack, I tried to prioritize Netlify and choose others accordingly. Below are pretty much all the stuff I used to build Wordable</p>
<ol>
<li>Next JS</li>
<li>Tailwind CSS</li>
<li>Netlify Serverless</li>
<li>MongoDB</li>
<li>Thirdweb</li>
</ol>
<p>For authentication, I used NextAuth JS and for the NFT minting process, I used express.</p>
<h3 id="heading-demo">Demo 🪧</h3>
<p>Now I will walk you through the whole application and give you a little idea about what is Wordable.</p>
<p>First and foremost we have a modern homepage like this. The cool thing is the screenshot in the middle of the page changes according to the theme of the website and whether you are on a mobile device or a desktop. ☀️🌙
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645547523689/z9WPQ_BMH.png" alt="full_page.png" /></p>
<p>Currently, we only have a Twitter login and you will see why this is important in a bit. When you click on login with Twitter, you can authorize your Twitter account and go inside the app. Then you will see something like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645551380511/DITWsSFnJ.png" alt="image.png" /></p>
<p>In the left side panel, you can see the normal game board and in the right-side panel, there are all the games that you have already played.</p>
<p>You can play the game as if you were playing the classic wordle but notice that we are sending a request to the backend with the guess you made every time you submit the guess, this way we are able to validate the word without actually sending the word to the frontend.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645551935897/RnUOfpYT4.png" alt="image.png" /></p>
<h4 id="heading-the-best-part-sharing-your-result-with-the-world-using-twitter">The Best Part - Sharing Your Result With The World Using Twitter 😎</h4>
<p>Once you won the game, you will have a popup like this.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645552293783/12p1kkOe2.png" alt="image.png" /></p>
<p>Now this is where the interesting part and all of my struggles began (will explain what were those in a later part of the article)</p>
<p>If you click on Tweet your win button, you will see a little loading indicator and hopefully a success message, then if you check your Twitter account 😎, voila 
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645553666243/7-S161Nh5.png" alt="image.png" /></p>
<p>Also optionally if you want to customize your tweet you can click on Copy Result to copy the game grid to the clipboard.</p>
<h4 id="heading-the-best-of-the-best-part-minting-a-unique-nft">The Best Of The Best Part - Minting A Unique NFT 🦄</h4>
<p>Personally, I think this is the thing that is at the next level 🚀. If you click on the Generate an Exclusive NFT button. You will be first prompted to authenticate with Metamask. Please note that we only support the Rinkeby network right now so if you are in a different network the popup will show a button for you to click in order to change the network.</p>
<p>Once you have done all of that, you will see that your NFT is minting in progress and check your open-sea profile to see more info about that. (Unfortunately, I am not able to keep the HTTP connection till the NFT is minted)</p>
<p>Also if you check your right-side panel, you will see how the information is updated.
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645555858570/ExXPVyv0X.png" alt="image.png" /></p>
<p>All of the NFTs that are minted by users will appear at this collection in <a target="_blank" href="https://testnets.opensea.io/collection/wordable-wins">opensea</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645559644383/E-earJ2ub.png" alt="image.png" /></p>
<h3 id="heading-video-demo">Video Demo 📽️</h3>
<p>You can watch a video demo right here. Please note that the time to mint the NFT may vary, wait around 5 minutes and if the "See Your NFT On Opensea" button is displayed, you are good to go.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.loom.com/share/0750801c2a8a482a87c052a1aa33bbbd">https://www.loom.com/share/0750801c2a8a482a87c052a1aa33bbbd</a></div>
<h3 id="heading-what-if-i-lose-the-game">What If I lose The Game ☹️</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1645667948817/w2TYQhA2v.png" alt="image.png" /></p>
<p>Well, you will still be able to share your game grid to Twitter and copy it but you wouldn't be able to mint it as an NFT</p>
<p><em>So that is the whole functionality of this app.</em>😌</p>
<h3 id="heading-role-of-netlify">Role of Netlify 🙂</h3>
<p>Netlify is used in many parts of this application. Mainly the whole app is hosted on Netlify. Even though I could easily use Next JS API routes, I opt-in for vanilla Netlify serverless functions, because I wanted to try out Netlify functions for a long time and the experience, was absolutely amazing 🔥. </p>
<h3 id="heading-linting-andamp-code-quality">Linting &amp; Code Quality 🧑🏻‍💻</h3>
<p>I primarily used ESLint for linting and to keep the code consistent and formatted across the project I used prettier. </p>
<h3 id="heading-struggles">Struggles 😩</h3>
<p>In this whole process, I had to deal with 5 major struggles.</p>
<ul>
<li><p>Firstly I had to face issues with CSS. I spent countless hours trying to get the gameboard to a state where it looks good on mobile 📱.</p>
</li>
<li><p>Then I struggled a little bit to get the tweeting the game grid to work. Because I used Twitter OAuth 2, it didn't have many resources outside of the documentation. However, I finally got it to work.</p>
</li>
<li><p>The other problem came was when I was minting the NFT, the first problem was converting SVG to PNG, I tried with sharp at first, it worked but the fonts didn't work. So tried running a puppeteer instance and getting a screenshot, fonts worked but it didn't work on Netlify. I am not sure if I did something wrong but every example project on GitHub didn't work too. So I had to run it on a server environment (Heroku) for it to work.</p>
</li>
<li><p>Things got even weirder when I started minting the NFT because even though Heroku didn't have a 10 or 15-second timeout, we needed to send 1st byte within 30 seconds 😐. (That's the reason I can't keep the connection as I mentioned above)</p>
</li>
<li><p>Finally, I was having problems with netlify.app domain because Next Auth needed the app URL ahead of time. I fixed that by adding a little snippet of code that redirects netlify.app domain to my custom domain.</p>
</li>
</ul>
<p>Somehow I managed to fix all those issues and finish the whole project! Checkout wordable, contributions are highly appreciated and if there are any problems regarding the app, let me know.</p>
<h3 id="heading-future-plans">Future Plans 🔮</h3>
<ul>
<li>Post the first winner of the day to the official Twitter account.</li>
<li>Move from the test network to the main network.</li>
<li>Give users the ability to play custom and real-time games.</li>
</ul>
<h3 id="heading-useful-links">Useful Links 🔗</h3>
<ul>
<li><a target="_blank" href="https://wordable.weoffersolution.com/">Deployed App</a></li>
<li><a target="_blank" href="https://github.com/osadavc/wordable">GitHub Repository</a></li>
<li><a target="_blank" href="https://testnets.opensea.io/collection/wordable-wins">Opensea NFT Collection</a></li>
<li><a target="_blank" href="https://twitter.com/WordableW">Official Twitter Account</a></li>
</ul>
<p>Finally thank you to Netlify and Hashnode for hosting this amazing opportunity and that's it about Wordable, let me know what do you think about Wordable down below in the comments. 🟩🟨⬛</p>
<blockquote>
<p>UPDATE: Please try again if you couldn't mint the NFT before, Drastically improved the  UX with minting the NFT🚀.</p>
<p>UPDATE: Unfortunately, the live link will no longer work because I had to delete the twitter developer project to create another one. (Twitter allows only one project)</p>
</blockquote>
]]></content:encoded></item></channel></rss>