How to Build a Transparent and Secure Lottery DApp with NextJs, Solidity, and CometChat

Dapp Lottery by Darlington Gospel

Introduction

Prerequisites

Installing Dependencies

{
  "name": "dapplottery",
  "description": "A Next.js starter that includes all you need to build amazing projects",
  "version": "1.0.0",
  "private": true,
  "author": "darlington gospel<darlingtongospel@gmail.com>",
  "license": "MIT",
  "keywords": [
    "nextjs",
    "starter",
    "typescript"
  ],
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "export": "next build && next export",
    "lint": "next lint",
    "format": "prettier --ignore-path .gitignore \"pages/**/*.+(ts|js|tsx)\" --write",
    "postinstall": "husky install"
  },
  "lint-staged": {
    "./src/**/*.{ts,js,jsx,tsx}": [
      "yarn lint --fix",
      "yarn format"
    ]
  },
  "dependencies": {
    "@cometchat-pro/chat": "3.0.11",
    "@reduxjs/toolkit": "1.9.3",
    "ethers": "^5.4.7",
    "next": "13.1.2",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-icons": "4.8.0",
    "react-identicons": "1.2.5",
    "react-redux": "8.0.5",
    "react-toastify": "9.1.2"
  },
  "devDependencies": {
    "@emotion/react": "11.10.5",
    "@emotion/styled": "11.10.5",
    "@ethersproject/abi": "^5.4.7",
    "@ethersproject/providers": "^5.4.7",
    "@faker-js/faker": "7.6.0",
    "@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
    "@nomicfoundation/hardhat-network-helpers": "^1.0.0",
    "@nomicfoundation/hardhat-toolbox": "^2.0.0",
    "@nomiclabs/hardhat-ethers": "^2.0.0",
    "@nomiclabs/hardhat-etherscan": "^3.0.0",
    "@nomiclabs/hardhat-waffle": "2.0.3",
    "@openzeppelin/contracts": "4.8.1",
    "@typechain/ethers-v5": "^10.1.0",
    "@typechain/hardhat": "^6.1.2",
    "@types/node": "18.11.18",
    "@types/react": "18.0.26",
    "@types/react-dom": "18.0.10",
    "@typescript-eslint/eslint-plugin": "5.48.1",
    "@typescript-eslint/parser": "5.48.1",
    "autoprefixer": "10.4.13",
    "chai": "^4.2.0",
    "dotenv": "16.0.3",
    "eslint": "8.32.0",
    "eslint-config-alloy": "4.9.0",
    "eslint-config-next": "13.1.2",
    "hardhat": "2.12.7",
    "hardhat-gas-reporter": "^1.0.8",
    "husky": "8.0.3",
    "lint-staged": "13.1.0",
    "postcss": "8.4.21",
    "prettier": "2.8.3",
    "solidity-coverage": "^0.8.0",
    "tailwindcss": "3.2.4",
    "typechain": "^8.1.0",
    "typescript": "4.9.4"
  }
}

Configuring CometChat SDK

Configuring the Hardhat script

require('@nomiclabs/hardhat-waffle')
require('dotenv').config()

module.exports = {
  defaultNetwork: 'localhost',
  networks: {
    hardhat: {},
    localhost: {
      url: 'http://127.0.0.1:8545',
    },
  },
  solidity: {
    version: '0.8.17',
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
    },
  },
  mocha: {
    timeout: 40000,
  },
}

Configuring the Deployment Script

const { ethers } = require('hardhat')
const fs = require('fs')

async function main() {
  const servicePercent = 7
  const Contract = await ethers.getContractFactory('DappLottery')
  const contract = await Contract.deploy(servicePercent)
  await contract.deployed()

  const address = JSON.stringify({ address: contract.address }, null, 4)
  fs.writeFile('./artifacts/contractAddress.json', address, 'utf8', (err) => {
    if (err) {
      console.error(err)
      return
    }
    console.log('Deployed contract address', contract.address)
  })
}

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

The Smart Contract File

//SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";

contract DappLottery is Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _totalLotteries;

    struct LotteryStruct {
        uint256 id;
        string title;
        string description;
        string image;
        uint256 prize;
        uint256 ticketPrice;
        uint256 participants;
        bool drawn;
        address owner;
        uint256 createdAt;
        uint256 expiresAt;
    }

    struct ParticipantStruct {
        address account;
        string lotteryNumber;
        bool paid;
    }

    struct LotteryResultStruct {
        uint256 id;
        bool completed;
        bool paidout;
        uint256 timestamp;
        uint256 sharePerWinner;
        ParticipantStruct[] winners;
    }

    uint256 public servicePercent;
    uint256 public serviceBalance;

    mapping(uint256 => LotteryStruct) lotteries;
    mapping(uint256 => ParticipantStruct[]) lotteryParticipants;
    mapping(uint256 => string[]) lotteryLuckyNumbers;
    mapping(uint256 => mapping(uint256 => bool)) luckyNumberUsed;
    mapping(uint256 => LotteryResultStruct) lotteryResult;

    constructor(uint256 _servicePercent) {
        servicePercent = _servicePercent;
    }

    function createLottery(
        string memory title,
        string memory description,
        string memory image,
        uint256 prize,
        uint256 ticketPrice,
        uint256 expiresAt
    ) public {
        require(bytes(title).length > 0, "title cannot be empty");
        require(bytes(description).length > 0, "description cannot be empty");
        require(bytes(image).length > 0, "image cannot be empty");
        require(prize > 0 ether, "prize cannot be zero");
        require(ticketPrice > 0 ether, "ticketPrice cannot be zero");
        require(
            expiresAt > block.timestamp,
            "expireAt cannot be less than the future"
        );

        _totalLotteries.increment();
        LotteryStruct memory lottery;

        lottery.id = _totalLotteries.current();
        lottery.title = title;
        lottery.description = description;
        lottery.image = image;
        lottery.prize = prize;
        lottery.ticketPrice = ticketPrice;
        lottery.owner = msg.sender;
        lottery.createdAt = block.timestamp;
        lottery.expiresAt = expiresAt;

        lotteries[lottery.id] = lottery;
    }

    function importLuckyNumbers(uint256 id, string[] memory luckyNumbers)
        public
    {
        require(lotteries[id].owner == msg.sender, "Unauthorized entity");
        require(lotteryLuckyNumbers[id].length < 1, "Already generated");
        require(lotteries[id].participants < 1, "Tickets have been purchased");
        require(luckyNumbers.length > 0, "Lucky numbers cannot be zero");
        lotteryLuckyNumbers[id] = luckyNumbers;
    }

    function buyTicket(uint256 id, uint256 luckyNumberId) public payable {
        require(
            !luckyNumberUsed[id][luckyNumberId],
            "Lucky number already used"
        );
        require(
            msg.value >= lotteries[id].ticketPrice,
            "insufficient ethers to buy ethers"
        );

        lotteries[id].participants++;
        lotteryParticipants[id].push(
            ParticipantStruct(
                msg.sender,
                lotteryLuckyNumbers[id][luckyNumberId],
                false
            )
        );
        luckyNumberUsed[id][luckyNumberId] = true;
        serviceBalance += msg.value;
    }

    function randomlySelectWinners(
        uint256 id,
        uint256 numOfWinners
    ) public {
        require(
            lotteries[id].owner == msg.sender ||
            lotteries[id].owner == owner(),
            "Unauthorized entity"
        );
        require(!lotteryResult[id].completed, "Lottery have already been completed");
        require(
            numOfWinners <= lotteryParticipants[id].length,
            "Number of winners exceeds number of participants"
        );

        // Initialize an array to store the selected winners
        ParticipantStruct[] memory winners = new ParticipantStruct[](numOfWinners);
        ParticipantStruct[] memory participants = lotteryParticipants[id];

        // Initialize the list of indices with the values 0, 1, ..., n-1
        uint256[] memory indices = new uint256[](participants.length);
        for (uint256 i = 0; i < participants.length; i++) {
            indices[i] = i;
        }

        // Shuffle the list of indices using Fisher-Yates algorithm
        for (uint256 i = participants.length - 1; i >= 1; i--) {
            uint256 j = uint256(
                keccak256(abi.encodePacked(block.timestamp, i))
            ) % (i + 1);
            uint256 temp = indices[j];
            indices[j] = indices[i];
            indices[i] = temp;
        }

        // Select the winners using the first numOfWinners indices
        for (uint256 i = 0; i < numOfWinners; i++) {
            winners[i] = participants[indices[i]];
            lotteryResult[id].winners.push(winners[i]);
        }

        lotteryResult[id].id = id;
        lotteryResult[id].completed = true;
        lotteryResult[id].timestamp = block.timestamp;

        payLotteryWinners(id);
    }

    function payLotteryWinners(uint256 id) internal {
        ParticipantStruct[] memory winners = lotteryResult[id].winners;
        uint256 totalShares = lotteries[id].ticketPrice * lotteryParticipants[id].length;
        uint256 platformShare = (totalShares * servicePercent) / 100 ;
        uint256 netShare = totalShares - platformShare;
        uint256 sharesPerWinner = netShare / winners.length;

        for (uint256 i = 0; i < winners.length; i++) 
        payTo(winners[i].account, sharesPerWinner);

        payTo(owner(), platformShare);
        serviceBalance -= totalShares;
        lotteryResult[id].paidout = true;
        lotteryResult[id].sharePerWinner = sharesPerWinner;
    }

    function getLotteries() public view returns (LotteryStruct[] memory Lotteries) {
        Lotteries = new LotteryStruct[](_totalLotteries.current());

        for (uint256 i = 1; i <= _totalLotteries.current(); i++) {
            Lotteries[i - 1] = lotteries[i];
        }
    }

    function getLottery(uint256 id) public view returns (LotteryStruct memory) {
        return lotteries[id];
    }
    
    function getLotteryParticipants(uint256 id) public view returns (ParticipantStruct[] memory) {
        return lotteryParticipants[id];
    }
    
    function getLotteryLuckyNumbers(uint256 id) public view returns (string[] memory) {
        return lotteryLuckyNumbers[id];
    }
    
    function getLotteryResult(uint256 id) public view returns (LotteryResultStruct memory) {
        return lotteryResult[id];
    }

    function payTo(address to, uint256 amount) internal {
        (bool success, ) = payable(to).call{value: amount}("");
        require(success);
    }
}

Developing the Frontend

Components

For each one of the components below, you will have to create their respective files in the components’ folder.

Header and Sub-header components

import networking from '../assets/networking.png'
import background from '../assets/background.jpg'
import Image from 'next/image'
import { useSelector } from 'react-redux'
import { connectWallet, truncate } from '@/services/blockchain'
import Link from 'next/link'

const Header = () => {
  const { wallet } = useSelector((state) => state.globalState)

  return (
    <div
      className="px-5 md:px-40"
      style={{ background: `url('${background.src}') fixed no-repeat top/cover` }}
    >
      <div className="flex items-center justify-between text-white py-5">
        <div>
          <h1 className="text-xl font-bold">DappLottery</h1>
        </div>

        <div className="hidden lg:flex items-center space-x-3 font-semibold">
          <p>Home</p>
          <p>How To Play</p>
          <p>All Lottery</p>
          <p>Contact</p>
        </div>

        {wallet ? (
          <button
            className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
          hover:bg-rose-600 cursor-pointer font-semibold text-sm"
          >
            {truncate(wallet, 4, 4, 11)}
          </button>
        ) : (
          <button
            onClick={connectWallet}
            className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
          hover:bg-rose-600 cursor-pointer font-semibold text-sm"
          >
            Connect Wallet
          </button>
        )}
      </div>

      <div className="flex items-center justify-between pb-5">
        <div>
          <div className="text-white py-5">
            <h2 className="text-4xl font-bold py-4 ">
              Take the chance to <br /> change your life
            </h2>

            <p className="text-xl">
              We bring a persolan and effective to every project we work on. <br />
              Which is why our client love why they keep coming back.
            </p>
          </div>
        </div>
        <div className="py-5 hidden sm:block">
          <Image src={networking} alt="network" className="rounded-lg w-80" />
        </div>
      </div>

      <div className="pb-10">
        <Link
          href={'/create'}
          className="bg-amber-500 hover:bg-rose-600 text-white rounded-md
        cursor-pointer font-semibold py-3 px-5"
        >
          Create Jackpot
        </Link>
      </div>
    </div>
  )
}

export default Header
view rawHeader.jsx hosted with ❤ by GitHub
import Link from 'next/link'
import background from '@/assets/background.jpg'
import { useSelector } from 'react-redux'
import { connectWallet, truncate } from '@/services/blockchain'

const SubHeader = () => {
  const { wallet } = useSelector((state) => state.globalState)
  return (
    <div
      style={{ background: `url('${background.src}') fixed no-repeat top/cover` }}
      className="flex items-center justify-between text-white px-10 py-5"
    >
      <div>
        <Link href="/" className="text-xl font-bold">
          DappLottery
        </Link>
      </div>

      <div className="hidden lg:flex items-center space-x-6 font-semibold">
        <p>Home</p>
        <p>How To Play</p>
        <p>All Lottery</p>
        <p>Contact</p>
      </div>

      {wallet ? (
        <button
          className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
          hover:bg-rose-600 cursor-pointer font-semibold text-sm"
        >
          {truncate(wallet, 4, 4, 11)}
        </button>
      ) : (
        <button
          onClick={connectWallet}
          className="flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
          hover:bg-rose-600 cursor-pointer font-semibold text-sm"
        >
          Connect Wallet
        </button>
      )}
    </div>
  )
}

export default SubHeader

Jackpots Component

import Link from 'next/link'
import Image from 'next/image'
import { truncate } from '@/services/blockchain'

const Jackpots = ({ jackpots }) => {
  return (
    <div className="bg-slate-100 pt-5">
      <div className=" flex flex-col items-center justify-center">
        <h1 className="text-2xl font-bold text-slate-800 py-5">Lottery Jackpots</h1>
        <p className="text-center text-sm text-slate-600">
          We bring a persolan and effective every project we work on. <br />
          which is why our client love why they keep coming back.
        </p>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6 md:gap-4 lg:gap-3 py-10 w-4/5 mx-auto">
        {jackpots?.map((jackpot, i) => (
          <Jackpot jackpot={jackpot} key={i} />
        ))}
      </div>
    </div>
  )
}

const Jackpot = ({ jackpot }) => {
  return (
    <div className="w-full shadow-xl shadow-black rounded-md overflow-hidden bg-gray-800 my-2 px-3 py-5">
      <div className="flex justify-start items-center space-x-2">
        <Image
          width={100}
          height={512}
          src={jackpot.image}
          alt="icon"
          className="rounded-lg w-20"
        />
        <div>
          <p className="text-green-300">Upto: {jackpot.prize} ETH</p>
          <p className="text-sm text-gray-500">Draws On: {jackpot.drawsAt}</p>
        </div>
      </div>
      <div className="py-5">
        <p className="font-semibold pb-2 text-green-300">{jackpot.title}</p>
        <p className="text-sm leading-5 text-gray-500">{truncate(jackpot.description, 90, 3, 0)}</p>
      </div>
      <Link
        href={'/jackpots/' + jackpot.id}
        className="bg-green-500 hover:bg-rose-600 py-2 px-5
              rounded-md text-white font-semibold"
      >
        PLAY NOW
      </Link>
    </div>
  )
}

export default Jackpots

Countdown Component

import { useState, useEffect } from 'react'

const Countdown = ({ timestamp }) => {
  const [timeLeft, setTimeLeft] = useState(timestamp - Date.now())

  useEffect(() => {
    const interval = setInterval(() => {
      setTimeLeft(timestamp - Date.now())
    }, 1000)

    return () => clearInterval(interval)
  }, [timestamp])

  const days = Math.floor(timeLeft / (1000 * 60 * 60 * 24))
  const hours = Math.floor((timeLeft % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
  const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60))
  const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000)

  return timestamp && Date.now() < timestamp ? (
    <div className="flex items-center justify-center space-x-3 flex-wrap">
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">{days}</p>
        <p className="text-xs font-semibold">DAYS</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">{hours}</p>
        <p className="text-xs font-semibold">HOURS</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">{minutes}</p>
        <p className="text-xs font-semibold">MINUTES</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">{seconds}</p>
        <p className="text-xs font-semibold">SECONDS</p>
      </div>
    </div>
  ) : (
    <div className="flex items-center justify-center space-x-3 flex-wrap">
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">00</p>
        <p className="text-xs font-semibold">DAYS</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">00</p>
        <p className="text-xs font-semibold">HOURS</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">00</p>
        <p className="text-xs font-semibold">MINUTES</p>
      </div>
      <div className="bg-white text-sm w-16 h-16 flex items-center flex-col justify-center rounded-md space-y-2 ">
        <p className="text-3xl text-gray-600 -light">00</p>
        <p className="text-xs font-semibold">SECONDS</p>
      </div>
    </div>
  )
}

export default Countdown

Draw Time Component


import Link from 'next/link'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { FaEthereum } from 'react-icons/fa'
import Countdown from '@/components/Countdown'
import { buyTicket } from '@/services/blockchain'
import { useDispatch, useSelector } from 'react-redux'
import { globalActions } from '@/store/global_reducer'
import { createNewGroup, joinGroup } from '@/services/chat'

const DrawTime = ({ jackpot, luckyNumbers, participants }) => {
  const { setGeneratorModal, setAuthModal, setChatModal, setGroup } = globalActions
  const { wallet, currentUser, group } = useSelector((state) => state.globalState)
  const dispatch = useDispatch()
  const router = useRouter()
  const { jackpotId } = router.query
  const { CometChat } = window

  const handlePurchase = async (luckyNumberId) => {
    if (!wallet) return toast.warning('Connect your wallet')
    await toast.promise(
      new Promise(async (resolve, reject) => {
        await buyTicket(jackpotId, luckyNumberId, jackpot?.ticketPrice)
          .then(async () => {
            resolve()
          })
          .catch(() => reject())
      }),
      {
        pending: 'Approve transaction...',
        success: 'Ticket purchased successfully ????',
        error: 'Encountered error ????',
      }
    )
  }

  const handleGroupCreation = async () => {
    if (!currentUser) return toast.warning('Please authenticate chat')
    await toast.promise(
      new Promise(async (resolve, reject) => {
        await createNewGroup(CometChat, `guid_${jackpot?.id}`, jackpot?.title)
          .then((group) => {
            dispatch(setGroup(JSON.parse(JSON.stringify(group))))
            resolve()
          })
          .catch(() => reject())
      }),
      {
        pending: 'Creating group...',
        success: 'Group created successfully ????',
        error: 'Encountered error ????',
      }
    )
  }

  const handleGroupJoin = async () => {
    if (!currentUser) return toast.warning('Please authenticate chat')
    await toast.promise(
      new Promise(async (resolve, reject) => {
        await joinGroup(CometChat, `guid_${jackpot?.id}`)
          .then((group) => {
            dispatch(setGroup(JSON.parse(JSON.stringify(group))))
            resolve()
            window.location.reload()
          })
          .catch(() => reject())
      }),
      {
        pending: 'Joining group...',
        success: 'Group joined successfully ????',
        error: 'Encountered error ????',
      }
    )
  }

  const onGenerate = () => {
    if (luckyNumbers.length > 0) return toast.warning('Already generated')
    dispatch(setGeneratorModal('scale-100'))
  }

  return (
    <div className="py-10 px-5 bg-slate-100">
      <div className="flex flex-col items-center justify-center text-center py-10">
        <h4 className="text-4xl text-slate-700 text-center font-bold pb-3">
          Buy Lottery Tickets Online
        </h4>
        <p className="text-lg text-gray-600 font-semibold capitalize">{jackpot?.title}</p>
        <p className="text-sm text-gray-500 w-full sm:w-2/3">{jackpot?.description}</p>
        <p className="text-sm font-medium text-black w-full sm:w-2/3">
          {jackpot?.participants} participants
        </p>
      </div>

      <div className="flex flex-col justify-center items-center space-y-4 mb-6">
        {jackpot?.expiresAt ? <Countdown timestamp={jackpot?.expiresAt} /> : null}

        <div className="flex justify-center items-center space-x-2">
          {wallet?.toLowerCase() == jackpot?.owner ? (
            <>
              <button
                disabled={jackpot?.expiresAt < Date.now()}
                onClick={onGenerate}
                className={`flex flex-nowrap border py-2 px-4 rounded-full bg-amber-500
                hover:bg-rose-600 font-semibold
                ${
                  jackpot?.expiresAt < Date.now()
                    ? 'opacity-50 cursor-not-allowed'
                    : 'hover:bg-rose-600'
                }
                `}
              >
                Generate Lucky Numbers
              </button>

              {!group ? (
                <button
                  onClick={handleGroupCreation}
                  className="flex flex-nowrap border py-2 px-4 rounded-full bg-gray-500
                hover:bg-rose-600 font-semibold text-white"
                >
                  Create Group
                </button>
              ) : null}
            </>
          ) : group && !group.hasJoined ? (
            <button
              onClick={handleGroupJoin}
              className="flex flex-nowrap border py-2 px-4 rounded-full bg-gray-500
                hover:bg-rose-600 font-semibold text-white"
            >
              Join Group
            </button>
          ) : null}

          <Link
            href={`/results/` + jackpot?.id}
            className="flex flex-nowrap border py-2 px-4 rounded-full bg-[#0c2856]
            hover:bg-[#1a396c] cursor-pointer font-semibold text-white"
          >
            Draw Result
          </Link>

          {!currentUser ? (
            <button
              onClick={() => dispatch(setAuthModal('scale-100'))}
              className="flex flex-nowrap border py-2 px-4 rounded-full bg-green-500
                hover:bg-amber-600 font-semibold"
            >
              Login Chat
            </button>
          ) : (
            <button
              onClick={() => dispatch(setChatModal('scale-100'))}
              className="flex flex-nowrap border py-2 px-4 rounded-full bg-green-500
            hover:bg-amber-600 font-semibold"
            >
              Enter Chat
            </button>
          )}
        </div>
      </div>

      <div className="bg-white text-sm overflow-x-auto flex flex-col w-full sm:w-3/4 mx-auto p-5 rounded-md">
        <div className="pb-4 text-center">
          <p className="semibold text-2xl">Select Your winning Lottery Numbers</p>
        </div>

        <table className="table-auto">
          <thead className="max-h-80 overflow-y-auto block">
            <tr className="flex justify-between text-left">
              <th className="px-4 py-2 ">#</th>
              <th className="px-4 py-2 ">Ticket Price</th>
              <th className="px-4 py-2 ">Draw Date</th>
              <th className="px-4 py-2 ">Ticket Number</th>
              <th className="px-4 py-2 ">Action</th>
            </tr>
          </thead>
          <tbody className="max-h-80 overflow-y-auto block">
            {luckyNumbers?.map((luckyNumber, i) => (
              <tr className="flex justify-between border-b text-left" key={i}>
                <td className="px-4 py-2 font-semibold">{i + 1}</td>
                <td className="px-4 py-2 font-semibold">
                  <div className="flex justify-center items-center space-x-1">
                    <FaEthereum />
                    <span>{jackpot?.ticketPrice}</span>
                  </div>
                </td>
                <td className="px-4 py-2 font-semibold">{jackpot?.drawsAt}</td>
                <td className="px-4 py-2 font-semibold">{luckyNumber}</td>
                <td className="px-4 py-2 font-semibold">
                  <button
                    disabled={participants.includes(luckyNumber)}
                    onClick={() => handlePurchase(i)}
                    className={`bg-black ${
                      participants.includes(luckyNumber)
                        ? 'opacity-50 cursor-not-allowed'
                        : 'hover:bg-rose-600'
                    } text-white text-sm py-2 px-4 rounded-full`}
                  >
                    BUY NOW
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  )
}

export default DrawTime

Generator Component

Auth Chat Component

Chat Component

import { useState } from 'react'
import { toast } from 'react-toastify'
import { useRouter } from 'next/router'
import { FaTimes } from 'react-icons/fa'
import { exportLuckyNumbers } from '@/services/blockchain'
import { useSelector, useDispatch } from 'react-redux'
import { globalActions } from '@/store/global_reducer'

const Generator = () => {
  const router = useRouter()
  const dispatch = useDispatch()
  const { jackpotId } = router.query
  const { setGeneratorModal } = globalActions
  const [luckyNumbers, setLuckyNumbers] = useState('')
  const { generatorModal } = useSelector((state) => state.globalState)

  const handleSubmit = async (e) => {
    e.preventDefault()

    await toast.promise(
      new Promise(async (resolve, reject) => {
        await exportLuckyNumbers(jackpotId, generateLuckyNumbers(luckyNumbers))
          .then(async () => {
            setLuckyNumbers('')
            dispatch(setGeneratorModal('scale-0'))
            resolve()
          })
          .catch(() => reject())
      }),
      {
        pending: 'Approve transaction...',
        success: 'Lucky numbers saved to chain ????',
        error: 'Encountered error ????',
      }
    )
  }

  const generateLuckyNumbers = (count) => {
    const result = []
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
    const charactersLength = characters.length
    for (let i = 0; i < count; i++) {
      let string = ''
      for (let j = 0; j < 6; j++) {
        string += characters.charAt(Math.floor(Math.random() * charactersLength))
      }
      result.push(string)
    }
    return result
  }

  return (
    <div
      className={`fixed top-0 left-0 w-screen h-screen flex
      items-center justify-center bg-black bg-opacity-50
      transform transition-transform duration-300 ${generatorModal}`}
    >
      <div
        className="bg-white shadow-xl shadow-[#0c2856] rounded-xl
        w-11/12 md:w-2/5 h-7/12 p-6"
      >
        <form onSubmit={handleSubmit} className="flex flex-col">
          <div className="flex justify-between items-center">
            <p className="font-semibold">Generate Numbers</p>
            <button
              onClick={() => dispatch(setGeneratorModal('scale-0'))}
              type="button"
              className="border-0 bg-transparent focus:outline-none"
            >
              <FaTimes />
            </button>
          </div>

          <div
            className="flex justify-between items-center
          bg-gray-300 rounded-xl p-2.5 my-5"
          >
            <input
              className="block w-full bg-transparent
              border-0 text-sm text-slate-500 focus:outline-none
              focus:ring-0"
              type="number"
              step={1}
              min={1}
              name="luckyNumbers"
              placeholder="Lucky Numbers e.g 19"
              onChange={(e) => setLuckyNumbers(e.target.value)}
              value={luckyNumbers}
            />
          </div>

          <button
            type="submit"
            className="flex flex-row justify-center items-center
              w-full text-white text-md py-2 px-5 rounded-full
              drop-shadow-xl bg-[#0c2856] hover:bg-[#1a396c]"
          >
            Generate and Save
          </button>
        </form>
      </div>
    </div>
  )
}

export default Generator

Winners Component

Result Component

CometChatNoSSR Component

The Pages Components

Please take not that these various pages must be created in the pages folder in the root directory of your project.

Home Page

Create Lottery Page

The Jackpots Page

The Results Page

The _app.tsx file

State Management Files

Services

Static Assets

Conclusion

About the Author

Leave a Comment

Your email address will not be published. Required fields are marked *