E1: NextJs Front End

I decided to do a front end and deploy the wallet app to ipfs, to refresh my mind on NextJs, and using wallet providers, which I also briefly explored, going through Patrick Collins' youtube course.

Front-end project aims:

  1. Have a front end that properly connects and works with the backend.

  2. Have a working wallet button, so I could interact with the contract using Metamask

  3. Have links to all my other information.

  4. All built with a production ready framework.

I will outline the code below, but you can find the full repo here - https://github.com/SimSimButDifferent/L3-EthWalletFrontEnd

Or check the deployment here - https://ethwalletapp.on.fleek.co/

Code outline

App folder

/app/layout

import { ReactNode } from "react"
import { Web3ModalProvider } from "@/context/Web3Modal"

import Header from "@/components/Header"
import EthWallet from "@/components/EthWallet"
import { Poppins } from "next/font/google"
import "./globals.css"

const inter = Poppins({
    weight: ["200", "300", "400", "500", "600"],
    subsets: ["latin"],
})

export const metadata = {
    title: "EthWalletApp",
    description: "Simple EthWalletApp by simsimbutdifferent",
}

export default function RootLayout({ children }: { children: ReactNode }) {
    return (
        <html lang="en">
            <body className="flex flex-col justify-between bg-[url(../public/Space-Background.jpg)] bg-cover bg-fixed">
                <Web3ModalProvider>
                    <Header />
                    <EthWallet />
                    {children}
                </Web3ModalProvider>
            </body>
        </html>
    )
}

/app/page.ts

import Head from "next/head"

export default function Home() {
    return (
        <div>
            <Head>
                <title>Eth Wallet App</title>
                <meta name="description" content="Eth Wallet App"></meta>
            </Head>
            <main className="flex min-h-screen flex-col items-center justify-between p-24 pt-4 ">
                <div className="mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-3 lg:text-left">
                    <a
                        href="https://github.com/SimSimButDifferent/L3-EthWalletFrontEnd"
                        className="group rounded-lg border border-transparent px-10 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        <h2 className={`mb-3 text-2xl font-semibold`}>
                            Front End Repo{" "}
                            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
                                -&gt;
                            </span>
                        </h2>
                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
                            Check out the repo if you want to mess around with
                            the code yourself.
                        </p>
                    </a>

                    <a
                        href="https://github.com/SimSimButDifferent/L3-EthWallet"
                        className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        <h2 className={`mb-3 text-2xl font-semibold`}>
                            Backend Repo{" "}
                            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
                                -&gt;
                            </span>
                        </h2>
                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
                            Take a look at the hardhat deployment of the smart
                            contract.
                        </p>
                    </a>

                    <a
                        href="https://simsimbutdifferent.gitbook.io/prompt_web3/"
                        className="group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30"
                        target="_blank"
                        rel="noopener noreferrer"
                    >
                        <h2 className={`mb-3 text-2xl font-semibold`}>
                            My Journey{" "}
                            <span className="inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none">
                                -&gt;
                            </span>
                        </h2>
                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
                            Have a look into my self-learning journey into web3
                            using chatGpt.
                        </p>
                        <p className={`m-0 max-w-[30ch] text-sm opacity-50`}>
                            A template to learn anything software related.
                        </p>
                    </a>
                </div>
            </main>
        </div>
    )
}

/app/styles.css

@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
    --foreground-rgb: 0, 0, 0;
    --background-start-rgb: 214, 219, 220;
    --background-end-rgb: 255, 255, 255;
}

@media (prefers-color-scheme: dark) {
    :root {
        --foreground-rgb: 255, 255, 255;
        --background-start-rgb: 0, 0, 0;
        --background-end-rgb: 0, 0, 0;
    }
}

body {
    color: rgb(var(--foreground-rgb));
    background: linear-gradient(
            to bottom,
            transparent,
            rgb(var(--background-end-rgb))
        )
        rgb(var(--background-start-rgb));
}

button,
label,
input {
    align-self: center;
    margin-bottom: 5px;
    font-size: 24px;
    border-radius: 8px;
    margin-top: 10px;
    text-align: center;
    font-weight: 300;
}

Components folder

/components/EthWallet.tsx

"use client"

import { ethers, Contract, BrowserProvider } from "ethers"
import { useWeb3ModalAccount } from "@web3modal/ethers/react"
import { contractAddresses, abi } from "../context"

import React, { useState } from "react"
import * as dotenv from "dotenv"

dotenv.config()

const EthWallet: React.FC = () => {
    const { address, chainId, isConnected } = useWeb3ModalAccount()

    const contractAddress =
        chainId in contractAddresses ? contractAddresses[chainId][0] : null

    const [depositAmount, setDepositAmount] = useState("")
    const [withdrawAmount, setWithdrawAmount] = useState("")
    const [successMessage, setSuccessMessage] = useState("")

    const [isLoading, setIsLoading] = useState(false)

    async function deposit(value: any) {
        if (!isConnected) throw Error("User Disconnected")
        const provider = new BrowserProvider(window.ethereum)

        const getSigner = provider.getSigner()
        const signer = await getSigner
        const ethWallet = new Contract(contractAddress, abi, signer)
        const tx = await ethWallet.deposit({ value: ethers.parseEther(value) })
        const receipt = await tx.wait()

        console.log(`${value.toString()} ETH Deposited!`)
        return receipt
    }

    async function withdraw(value: any) {
        if (!isConnected) throw Error("User Disconnected")
        const provider = new BrowserProvider(window.ethereum)

        const getSigner = provider.getSigner()
        const signer = await getSigner
        const ethWallet = new Contract(contractAddress, abi, signer)
        const tx = await ethWallet.withdraw(ethers.parseEther(value))
        const receipt = await tx.wait()

        console.log(`${value.toString()} ETH Withdrawn!`)
        return receipt
    }

    async function getUserBalance() {
        const provider = new BrowserProvider(window.ethereum)

        const getSigner = provider.getSigner()
        const signer = await getSigner
        const ethWallet = new Contract(contractAddress, abi, signer)
        console.log(signer)

        const balance = await ethWallet.getUserBalance()

        return balance.toString()
    }

    const handleDepositSubmit = async (
        event: React.FormEvent<HTMLFormElement>,
    ) => {
        event.preventDefault()
        setIsLoading(true)
        try {
            await deposit(depositAmount)

            setSuccessMessage(`Successfully deposited ${depositAmount} ETH!`)
            setDepositAmount("") // Optional: Reset input field after successful deposit
            setTimeout(() => setSuccessMessage(""), 10000)
        } catch (error) {
            console.error("Error during deposit: ", error)
            setSuccessMessage("User Disconnected!")
        }
        setIsLoading(false)
    }

    const handleWithdrawSubmit = async (event) => {
        event.preventDefault()
        setIsLoading(true)
        try {
            await withdraw(withdrawAmount)

            setSuccessMessage(`Successfully withdrawn ${withdrawAmount} ETH!`)
            setWithdrawAmount("") // Optional: Reset input field after successful deposit
            setTimeout(() => setSuccessMessage(""), 10000)
        } catch (error) {
            console.error("Error during withdraw: ", error)
            setSuccessMessage("User Disconnected!")
        }
        setIsLoading(false)
    }

    const handleGetUserBalance = async (event) => {
        event.preventDefault()
        setIsLoading(true)
        try {
            const balance = await getUserBalance()

            setSuccessMessage(
                `User Balance: ${ethers.formatEther(balance)} ETH!`,
            )

            setTimeout(() => setSuccessMessage(""), 10000)
        } catch (error) {
            console.error("User does not exist!", error)
            setSuccessMessage("User does not exist!")
        }
        setIsLoading(false)
    }

    return (
        <div className="">
            <form onSubmit={handleDepositSubmit}>
                <div className="p-4">
                    <h1 className="text-6xl text-center rounded-lg border border-transparent px-5 py-4 transition-colors border-neutral-300 bg-gray-100 dark:border-neutral-700 dark:bg-neutral-800/30">
                        Eth Wallet App
                    </h1>
                </div>
                <div className="flex flex-col p-4">
                    <input
                        type="text"
                        value={depositAmount}
                        onChange={(e) => setDepositAmount(e.target.value)}
                        className="text-black text-xl text-align-center py-2"
                        placeholder="Deposit amount..."
                    />
                    <button
                        type="submit"
                        disabled={isLoading}
                        className={`px-4 pb-1 ${
                            isLoading
                                ? "bg-violet-600"
                                : "bg-sky-500 hover:bg-sky-400"
                        } text-white rounded-lg`}
                    >
                        Deposit
                    </button>
                </div>
            </form>
            <form onSubmit={handleWithdrawSubmit}>
                <div className="flex flex-col p-4">
                    <input
                        type="text"
                        value={withdrawAmount}
                        onChange={(e) => setWithdrawAmount(e.target.value)}
                        className="text-black text-xl text-align-center py-2"
                        placeholder="Withdraw amount..."
                    />
                    <button
                        type="submit"
                        disabled={isLoading}
                        className={`px-4 pb-1 ${
                            isLoading
                                ? "bg-violet-600"
                                : "bg-sky-500 hover:bg-sky-400"
                        } text-white rounded-lg`}
                    >
                        Withdraw
                    </button>
                </div>
            </form>
            <div className="flex flex-col justify-center pt-8">
                <button
                    onClick={handleGetUserBalance}
                    className="flex justify-center px-4 py-2 text-2xl bg-sky-500 hover:bg-sky-400 text-white rounded-lg"
                >
                    Get user balance
                </button>
            </div>
            {successMessage && (
                <div className="flex items-center justify-center pt-10">
                    <div className="flex justify-center px-4 py-2 text-2xl bg-purple-900 rounded-lg max-w-fit font-light animate-fadeOut">
                        {successMessage}
                    </div>
                </div>
            )}
        </div>
    )
}

export default EthWallet

/components/Header.tsx

"use client"

import React from "react"

const Header: React.FC = () => {
    return (
        <nav>
            <div className="flex flex-cols-2 justify-end">
                <div className="p-2">
                    <w3m-button size="md" />
                </div>
                <div className="p-2">
                    <w3m-network-button />
                </div>
            </div>
        </nav>
    )
}
export default Header

Context folder

/context/Web3Modal.tsx

"use client"

import { createWeb3Modal, defaultConfig } from "@web3modal/ethers/react"

// 1. Get projectId at https://cloud.walletconnect.com
const projectId = "cf80e8dfdf111fd756361f3117399405"

// 2. Set chains
const mainnet = {
    chainId: 1,
    name: "Ethereum",
    currency: "ETH",
    explorerUrl: "https://etherscan.io",
    rpcUrl: "https://cloudflare-eth.com",
}

const sepolia = {
    chainId: 11155111,
    name: "Sepolia",
    currency: "ETH",
    explorerUrl: "https://sepolia.etherscan.io",
    rpcUrl: "https://rpc.sepolia.org",
}

const localhost = {
    chainId: 31337,
    name: "Localhost 8545",
    currency: "ETH",
    explorerUrl: "",
    rpcUrl: "http://localhost:8545",
}

// 3. Create modal
const metadata = {
    name: "Eth Wallet",
    description: "Simple Eth Wallet app created by Simeon Campbell",
    url: "ethwalletapp.on.fleek.co",
    icons: ["https://avatars.mywebsite.com/"],
}

createWeb3Modal({
    ethersConfig: defaultConfig({ metadata }),
    chains: [mainnet, sepolia, localhost],
    projectId,
})

import { ReactNode } from "react"

export function Web3ModalProvider({ children }: { children: ReactNode }) {
    return children
}

/context/abi.json

[
    {
        "inputs": [],
        "name": "EthWallet_InsufficientContractBalance",
        "type": "error"
    },
    {
        "inputs": [],
        "name": "EthWallet__DepositMustBeAboveZero",
        "type": "error"
    },
    {
        "inputs": [],
        "name": "EthWallet__WalletIsEmpty",
        "type": "error"
    },
    {
        "inputs": [],
        "name": "EthWallet__WithdrawalExceedsUserBalance",
        "type": "error"
    },
    {
        "inputs": [],
        "name": "EthWallet__WithdrawalMustBeAboveZero",
        "type": "error"
    },
    {
        "inputs": [],
        "name": "dailyWithdrawalLimit",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "deposit",
        "outputs": [],
        "stateMutability": "payable",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "getUserBalance",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "_withdrawalAmount",
                "type": "uint256"
            }
        ],
        "name": "withdraw",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
]

/context/contractAddresses.json

{
    "11155111": ["0x0597071313ae58624FFbbDAB8643aD96E27eD3bc"],
    "31337": ["0x5FbDB2315678afecb367f032d93F642f64180aa3"]
}

/context/index.js

const contractAddresses = require("./contractAddresses.json")
const abi = require("./abi.json")

module.exports = {
    contractAddresses,
    abi,
}

Last updated