Setting up an NFT minting website with a timer

Setting up a mint page with a timer - thirdweb guides

This guide will show you how to build a custom mint page with a countdown timer using Next.js and the TypeScript SDK.

By the end, you'll have a Next.js site where users can know how long until the timer ends, they can claim their NFTs.

Let's get started!

Deploy the NFT Drop Contract

You can follow this guide to deploy an NFT Drop to Polygon, or any other network, by just selecting the network you want to deploy to:

Deploy a Smart Contract on Polygon (MATIC)
This guide will show you how to deploy any smart contract to the Polygon network. By the end, you’ll learn how to create a simple NFT Drop smart contract, deploy it to the Polygon Mumbai testnet, and mint an NFT on the smart contract. Let’s get started! What is Polygon?

Once you have deployed your NFT Drop, make sure to set the claim conditions.

Creating the Next.js App

Now let's create an NFT minting web app where users can:

  • Connect their wallet
  • Mint an NFT
  • See a timer for when the claim ends

Using the thirdweb CLI, create a new Next.js & TypeScript project with the React SDK preconfigured for you using the following command:

npx thirdweb create app --next --ts
💡
An API key is required to use thirdweb's infrastructure services, such as storage, RPCs, and Smart Wallet infrastructure from within the SDK. If you haven't created a key yet, you can do so for free from the thirdweb dashboard.

To use an API key with the React SDK, pass the clientId to the ThirdwebProvider. The template already comes with the clientId, so you can simply create a new .env file and add the client id with the respective name in the .env.example file.

Next, we need to update the chain that our app works on. Go into _app.tsx and change the activeChain variable to the chain that you deployed your contract to. In my case, it is mumbai:

// This is the chainId your dApp will work on.
const activeChain = "mumbai";

Rendering NFT Drop Metadata

We will show some simple metadata of the NFT Drop on our site. For that, we need to first get the metadata:

const { contract: nftDrop } = useContract("YOUR_DROP_ADDRESS");
const { data: contractMetadata } = useContractMetadata(nftDrop);
💡
Replace "YOUR_DROP_ADDRESS" with the contract address of your NFT Drop

Now have the contract metadata, we can use it to display the details:

<div>
  {/* Image Preview of NFTs */}
  <MediaRenderer
    src={contractMetadata?.image}
    alt={`${contractMetadata?.name} preview image`}
  />

  {/* Title of your NFT Collection */}
  <h1>{contractMetadata?.name}</h1>
  {/* Description of your NFT Collection */}
  <p>{contractMetadata?.description}</p>
</div>

Feel free to add in any styles you like but for the sake of this guide, I am going to keep it simple!

Now, we need to get the claim conditions using the useClaimConditions hook to check if the drop has started yet:

const { data: claimConditions } = useClaimConditions(nftDrop);

Now, let's check if the claim condition exists, and if so, render a timer component or a mint button depending on whether the drop has started:

{claimConditions &&
  claimConditions?.length > 0 &&
  claimConditions[0].startTime > new Date() ? (
    <div>
      <h2>Drop starts in:</h2>
      <Timer date={claimConditions[0].startTime} />
    </div>
  ) : (
    <Web3Button
      contractAddress={nftDrop?.getAddress() || ""}
      action={(cntr) => cntr.erc721.claim(1)}
      onError={(err) => {
        console.error(err);
        alert("Error claiming NFTs");
      }}
      onSuccess={() => {
        alert("Successfully claimed NFTs");
      }}
    >
      Claim NFT
    </Web3Button>
)}

Here we are checking if claim conditions exist, the length of claim conditions is at least 1, and the start time is greater than the current time. If it meets these criteria, we show a timer. Otherwise, the drop has started, and we show a simple Web3Button that allows people to claim an NFT. Here, I have hard-coded the number of NFTs to 1 but you can use a user input as well.

Creating the timer

Let's now finally make our Timer component that we rendered earlier. Create a new file in the components folder called Timer.tsx and add the following:

import { FC, useEffect, useState } from "react";
import styles from "../styles/Theme.module.css";

interface ITimeLeft {
  days: number;
  hours: number;
  minutes: number;
  seconds: number;
}

interface TimerProps {
  date: Date;
}

const Timer: FC<TimerProps> = ({ date }) => {
  const calculateTimeLeft = () => {
    const difference = Number(new Date(date)) - Number(new Date());
    let timeLeft = {};

    if (difference > 0) {
      timeLeft = {
        days: Math.floor(difference / (1000 * 60 * 60 * 24)),
        hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
        minutes: Math.floor((difference / 1000 / 60) % 60),
        seconds: Math.floor((difference / 1000) % 60),
      };
    }

    return timeLeft as ITimeLeft;
  };

  const [timeLeft, setTimeLeft] = useState<ITimeLeft>(calculateTimeLeft());
  const { days, hours, minutes, seconds } = timeLeft;

  useEffect(() => {
    setTimeout(() => {
      setTimeLeft(calculateTimeLeft());
    }, 1000);
  });

  const items = [
    { label: "Day", value: days },
    { label: "Hour", value: hours },
    { label: "Min", value: minutes },
    { label: "Sec", value: seconds },
  ];

  return (
    <div className={styles.timer}>
      {items.map(({ label, value }) => (
        <div key={label} className={styles.timerItem}>
          <p className={styles.timerValue}>
            {value < 10 ? `0${value}` : value}{" "}
          </p>
          <p className={styles.timerLabel}>
            {value === 1 ? label : `${label}s`}
          </p>
        </div>
      ))}
    </div>
  );
};

export default Timer;

Here, we are taking in the date as a prop and using it to calculate the difference between the current time and the drop start time that we obtained from the useClaimConditions hook. Then, we calculate the time left and store it in a timeLeft object that includes the following information: days, hours, minutes, and seconds. We use a setTimeout function inside a useEffect to update the object every second. Finally, we deconstruct timeLeft and store it in an items array which we map through and render.

I have also added these simple styles to it:

.timer {
  display: flex;
  gap: 15px;
  align-items: center;
  justify-content: center;
  align-self: center;
  margin-top: -40px;
}

.timerItem {
  flex-direction: column;
  display: flex;
  align-items: center;
}

.timerValue {
  font-size: 2rem;
  font-weight: 600;
}

.timerLabel {
  font-size: 1rem;
  opacity: 0.75;
  margin-top: -20px;
}

Feel free to keep it as it is or update it as you like!

If you now head over to your browser, you will see a countdown like this:

Drop countdown

Awesome! We have successfully created a mint page that allows users to view the time until the NFT drop and claim NFTs once the timer has ended.

Wrapping up

This guide taught us how to create a Next.js website for NFT Drop claiming with a countdown timer!

You learned a lot, now pat yourself on the back and share your amazing apps with us on the thirdweb discord!


Need help?

For support, join the official thirdweb Discord server or share your thoughts on our feedback board.