Building a Decentralized Fundraiser on the Celo blockchain Part 2

Building a Decentralized Fundraiser on the Celo blockchain Part 2 https://celo.academy/uploads/default/optimized/2X/6/6b9562243b632d0dae418ae051e076b553472802_2_1024x576.png
none 0.0 0

Introduction

Welcome, dear reader, to Part 2 of our exciting journey into the world of fundraising decentralized applications (dApps). In this section, we will delve into the fascinating realm of conjugating our powerful smart contract with a cutting-edge frontend framework - none other than React!:wink:

Prepare yourself for an immersive and exhilarating experience as we combine the solidity of our smart contract with the dynamic capabilities of React. Get ready to unleash your technical prowess and embark on a thrilling adventure in building a seamless user interface for our fundraising dApp. With React by our side, we’ll create a visually stunning and user-friendly frontend that will leave users in awe.

Prerequisites

Building this dapp, you would need:

  • Solid understanding of Javascript
  • Basic grasp of solidity

Requirements

  • VSCode or any other editor
  • A terminal
  • Remix
  • React
  • Celo Extension Wallet

Getting Started :wink:

Clone the github repository here to follow along.
This is the structure you should see in your code editor.

├── node_modules
├── public
├── src
│   ├── components
│   ├── contracts
│   │    ├── ierc.abi.json
│   │    ├── donater.abi.json
│   │    ├── donater.sol
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── setupTests.js
├── .env
├── .gitignore
├── package-lock.json
├── package.json
└── README.md

Run npm install in your terminal to install all dependencies needed to make the dapp work properly.

Smart Contract Preview

Before we begin, let’s see the contract that we developed from the part 1 of the tutorial.

// SPDX-License-Identifier: MIT

pragma solidity >=0.7.0 <0.9.0;

interface IERC20Token {
    function transfer(address, uint256) external returns (bool);

    function approve(address, uint256) external returns (bool);

    function transferFrom(
        address,
        address,
        uint256
    ) external returns (bool);

    function totalSupply() external view returns (uint256);

    function balanceOf(address) external view returns (uint256);

    function allowance(address, address) external view returns (uint256);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

contract Donater{
    struct Donate{
        address payable owner;
        string title;
        string  description;
        string  image;
        uint goal;
        uint amountDonated;
        bool goalReached;
    }


    mapping(uint256=>Donate) internal donations;
    uint256 donateLength = 0;

    address internal cUsdTokenAddress =
        0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1;
    address internal adminAddress = 0xE2a0411465fd913502A8390Fe22DC7004AC47A04;

    function addDonation(
        string memory _title,
        string memory _description,
        string memory _image,
        uint _goal 
    )public payable{
        require(
             IERC20Token(cUsdTokenAddress).transferFrom(
                msg.sender,
                adminAddress,
                1e18
            ),
            "Transaction could not be performed"
        );
        donations[donateLength] = Donate(
            payable(msg.sender),
            _title,
            _description,
            _image,
            _goal,
            0,
            false
        );

        donateLength++;
    }

    function getDonation(uint _index)public view returns(
        address payable,
        string memory,
        string memory,
        string memory,
        uint,
        uint,
        bool
    ){
        Donate storage _donations = donations[_index];
        return(
            _donations.owner,
            _donations.title,
            _donations.description,
            _donations.image,
            _donations.goal,
            _donations.amountDonated,
            _donations.goalReached
        );
    }

    function donate(uint _index, uint amount)public payable {
        require(donations[_index].amountDonated < donations[_index].goal);
        require(
             IERC20Token(cUsdTokenAddress).transferFrom(
                msg.sender,
                donations[_index].owner,
                amount
            ),
            "Transaction could not be performed"
        );
        donations[_index].amountDonated+=amount;
        if(donations[_index].amountDonated >= donations[_index].goal){
            donations[_index].goalReached = true;
        }
    }

    function getDonationLength() public view returns (uint){
        return donateLength;
    }
}

App.js

In React, the App.js file serves as the main entry point and central component of the application. It acts as a container for other components and holds the overall structure and logic of the application. The App.js file allows us to define the layout of the application, handle routing, and manage the state of the application if needed. It acts as a bridge between different components and provides a cohesive structure for building your react dApp.

For the first section, we are importing various dependencies and components for our React application.

import { useState, useEffect } from "react";

import Footer from "./components/Footer";
import Header from "./components/Header";
import Main from "./components/Main";

import Web3 from "web3";
import { newKitFromWeb3 } from "@celo/contractkit";
import BigNumber from "bignumber.js";

import donater from "./contracts/donater.abi.json";
import ierc from "./contracts/ierc.abi.json";

We import useState and useEffect hooks from React, which allow us to manage state and perform side effects in functional components.

Next, we import Footer, Header, and Main components from their respective files. These components are responsible for rendering the footer, header, and main content of our application.

We import the Web3 library, which is a JavaScript library for interacting with the Celo blockchain. It provides functions to connect to a Celo node, send transactions, and interact with smart contracts.

Using the newKitFromWeb3 function from @celo/contractkit, we create a Celo-specific instance of the ContractKit library using a Web3 provider.

The BigNumber library is imported to handle large numerical values, such as token amounts or balances, in a precise manner.

We then import the ABI JSON files for the donater and ierc smart contracts. These files contain the necessary information to interact with the smart contracts, including function signatures and data types.

Moving forward, we define the App function, which represents the main component of our React application.

function App() {
  const [contract, setcontract] = useState(null);
  const [address, setAddress] = useState(null);
  const [kit, setKit] = useState(null);
  const [cUSDBalance, setcUSDBalance] = useState(0);
  const [donations, setDonations] = useState([]);

  const ERC20_DECIMALS = 18;

  const contractAddress = "0xf9b0A2ffeaCC51f94eE24d33304d26c8Fd3777cf";
  const cUSDContractAddress = "0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1";

Inside the App function, we use the useState hook to define several state variables:

  • contract represents the state variable for our smart contract instance.
  • address represents the state variable for the address associated with the user’s account.
  • kit represents the state variable for the ContractKit instance.
  • cUSDBalance represents the state variable for the balance of cUSD (Celo Dollar) tokens.
  • donations represents the state variable for storing the list of donations.

We have also declared a constant variable named ERC20_DECIMALS, which is set to 18. This variable is used to represent the number of decimal places for token amounts in the ERC20 standard.

We then define two constant variables: contractAddress and cUSDContractAddress. These variables hold the addresses of the smart contracts we will be interacting with.

By utilizing the useState hook and declaring these variables, we are setting up the initial state of our application and providing placeholders for the contract instance, user address, ContractKit instance, cUSD balance, and donations list. These state variables will be updated and used throughout the application as we interact with the smart contracts and retrieve data from the blockchain.

Up next, we utilize useEffect hooks, we can perform certain actions and fetch data from the blockchain at specific points in the component’s lifecycle. For example, when the component mounts, we establish a connection with the blockchain. When the kit and address values are available, we retrieve the cUSD balance. And when the contract value is set, we fetch the list of donations.

  useEffect(() => {
    celoConnect();
  }, []);

  useEffect(() => {
    if (kit && address) {
      getBalance();
    } else {
      console.log("no kit");
    }
  }, [kit, address]);

  useEffect(() => {
    if (contract) {
      getDonations();
    }
  }, [contract]);

The first useEffect hook is called when the component mounts, as indicated by the empty dependency array []. It invokes the celoConnect function, which is responsible for establishing a connection with the Celo blockchain.

The second useEffect hook is triggered whenever there are changes to the kit or address variables. It checks if both kit and address have values and then calls the getBalance function. This function retrieves the cUSD balance associated with the user’s address from the Celo blockchain. If either kit or address is null, it logs a message indicating that the ContractKit instance is not available.

The third useEffect hook is executed whenever there are changes to the contract variable. It checks if the contract has a valid value and then calls the getDonations function. This function retrieves the list of donations made using the smart contract.

Following this, we demonstrate the celoConnect function, which is an asynchronous function responsible for establishing a connection with the Celo blockchain.

  const celoConnect = async () => {
    if (window.celo) {
      try {
        await window.celo.enable();
        const web3 = new Web3(window.celo);
        let kit = newKitFromWeb3(web3);

        const accounts = await kit.web3.eth.getAccounts();
        const user_address = accounts[0];

        kit.defaultAccount = user_address;

        await setAddress(user_address);
        await setKit(kit);
        console.log(user_address);
      } catch (error) {
        console.log(error);
      }
    } else {
      console.log("Error");
    }
  };

Here’s a breakdown of what the function does:

  1. It checks if the window.celo object is available, which indicates that the Celo extension or wallet is installed in the user’s browser.

  2. If the Celo extension is available, it proceeds with the connection process. It first enables the Celo extension by calling window.celo.enable(), which prompts the user to authorize the connection.

  3. Once the extension is enabled, it creates a new instance of Web3 using new Web3(window.celo). This instance will be used for interacting with the Celo blockchain.

  4. It initializes a kit (ContractKit) object using the newKitFromWeb3 function, passing the web3 instance as a parameter.

  5. The function then retrieves the user’s accounts from the Celo blockchain using kit.web3.eth.getAccounts(). The first account in the returned array is considered the user’s address.

  6. It sets the defaultAccount of the kit object to the user’s address, allowing subsequent interactions with the Celo blockchain to use this address by default.

  7. The user’s address is set using the setAddress function, updating the address state variable in the React component.

  8. The kit object is set using the setKit function, updating the kit state variable in the React component.

  9. The user’s address is logged to the console for verification and debugging purposes.

In case the Celo extension or wallet is not available (window.celo is falsy), an error message is logged to the console.

Next, we create the getBalance function, which is an asynchronous function responsible for retrieving the user’s balance from the Celo blockchain.

  const getBalance = async () => {
    try {
      const balance = await kit.getTotalBalance(address);
      const USDBalance = balance.cUSD.shiftedBy(-ERC20_DECIMALS).toFixed(2);

      const contract = new kit.web3.eth.Contract(donater, contractAddress);
      setcontract(contract);
      setcUSDBalance(USDBalance);
    } catch (error) {
      console.log(error);
    }
  };

Here’s an explanation of what the function does:

  • It first attempts to retrieve the total balance of the user’s address using kit.getTotalBalance(address). This function returns a balance object containing the user’s balances across different Celo assets.

  • The balance object includes the balance of the cUSD asset, which represents the Celo equivalent of the U.S. Dollar. The USDBalance variable is calculated by shifting the decimal places of the cUSD balance by -ERC20_DECIMALS (18 in this case) and rounding it to 2 decimal places.

  • Next, it creates a new instance of the Contract object from the donater ABI (Application Binary Interface) and the contractAddress. This allows interaction with the deployed smart contract on the Celo blockchain.

  • The contract object is set using the setcontract function, updating the contract state variable in the React component.

  • The calculated USDBalance is set using the setcUSDBalance function, updating the cUSDBalance state variable in the React component.

  • If any error occurs during the process, it is caught in the catch block, and an error message is logged to the console.

Next Up, we define our next function, the getDonations function, which is an asynchronous function responsible for retrieving the list of donations from the smart contract deployed on the Celo blockchain.


  const getDonations = async () => {
    const donationLength = await contract.methods.getDonationLength().call();
    const _donations = [];

    for (let index = 0; index < donationLength; index++) {
      let _donate = new Promise(async (resolve, reject) => {
        let donate = await contract.methods.getDonation(index).call();
        resolve({
          index: index,
          owner: donate[0],
          title: donate[1],
          description: donate[2],
          image: donate[3],
          goal: donate[4],
          amountDonated: donate[5],
          isGoalReached: donate[6],
        });
      });
      _donations.push(_donate);
    }
    const donations = await Promise.all(_donations);
    setDonations(donations);
  };
  • It first calls the smart contract method getDonationLength() using contract.methods.getDonationLength().call(). This function retrieves the total number of donations stored in the smart contract.

  • The returned donationLength represents the number of donations present in the smart contract.

  • An empty array _donations is initialized to store the promises of individual donation objects.

  • A for loop is used to iterate over each donation index from 0 to donationLength - 1.

  • Inside the loop, a new promise _donate is created. This promise awaits the result of calling the smart contract method getDonation(index).call(), which retrieves the details of a specific donation at the given index.

  • The resolved value of the promise is an object containing the donation details, including the index, owner address, title, description, image, goal amount, donated amount, and a boolean indicating if the goal has been reached.

  • The resolved promise _donate is pushed into the _donations array.

  • After the loop, Promise.all(_donations) is called to wait for all the promises in the array to resolve. This returns an array of donation objects.

  • The setDonations function is used to update the donations state variable in the React component with the retrieved list of donations.

After this function, The donate function enables users to make a donation to a specific donation campaign.

  const donate = async (index, _amount) => {
    const cUSDContract = new kit.web3.eth.Contract(ierc, cUSDContractAddress);
    try {
      const amount = new BigNumber(_amount).shiftedBy(ERC20_DECIMALS).toString();
      await cUSDContract.methods
        .approve(contractAddress, amount)
        .send({ from: address });
      await contract.methods.donate(index, amount).send({ from: address });
      getBalance();
      getDonations();
    } catch (error) {
      console.log(error);
    }
  };
  • First, a new instance of the cUSD contract is created using the cUSDContractAddress. This contract represents the Celo Dollar (cUSD) token.

  • Inside a try-catch block, the function performs the following steps:

    • The amount parameter passed to the function is converted into the appropriate format. It is multiplied by 10^18 (the decimal places of cUSD) and converted to a string using shiftedBy() and toString() from the BigNumber library.

    • The approve() method of the cUSD contract is called to approve the smart contract at contractAddress to spend the specified amount of cUSD tokens on behalf of the user. This approval is necessary for the subsequent donation transaction.

    • The send() function is used to send the approval transaction, specifying the user’s address as the from parameter.

    • After the approval is successful, the donate() method of the smart contract at contractAddress is called to make the actual donation. The index parameter indicates the index of the donation campaign, and the amount parameter represents the donation amount in cUSD tokens.

    • Again, the send() function is used to send the donation transaction, specifying the user’s address as the from parameter.

    • After the donation is successful, the getBalance() and getDonations() functions are called to update the user’s balance and the list of donations.

  • If any error occurs during the donation process, it is caught in the catch block, and an error message is logged to the console.

Up Next, we have a new function the addDonations function enables users to create a new donation campaign.

  const addDonations = async (title, description, image, _goal) => {
    const cUSDContract = new kit.web3.eth.Contract(ierc, cUSDContractAddress);
    const amount = new BigNumber(1).shiftedBy(ERC20_DECIMALS).toString();
    const goal = new BigNumber(_goal).shiftedBy(ERC20_DECIMALS).toString();
    try {
      await cUSDContract.methods
        .approve(contractAddress, amount)
        .send({ from: address });
      await contract.methods
        .addDonation(title, description, image, goal)
        .send({ from: address });
    } catch (error) {
      console.log(error);
    }
    getDonations();
  };

The function begins by creating a new instance of the cUSD contract using the cUSDContractAddress, which represents the Celo Dollar (cUSD) token.

Next, the function sets the amount variable to 1 cUSD token and the goal variable to the specified fundraising goal amount. These amounts are adjusted to the appropriate decimal places using the shiftedBy() and toString() functions from the BigNumber library.

Inside a try-catch block, the function performs the following steps:

  1. Calls the approve() method of the cUSD contract to approve the smart contract at contractAddress to spend the specified amount of cUSD tokens on behalf of the user. This approval is required for the subsequent transaction to add a new donation campaign.

  2. Sends the approval transaction using the send() function, specifying the user’s address as the from parameter.

  3. Upon successful approval, calls the addDonation() method of the smart contract at contractAddress to create a new donation campaign. The title, description, image, and goal parameters are provided to set the details of the campaign.

  4. Sends the transaction to add the new donation campaign using the send() function, specifying the user’s address as the from parameter.

If any errors occur during the process, they are caught in the catch block, and an error message is logged to the console.

The getDonations() function is called to update the list of donations and reflect the newly added campaign.

Following this, we write the App component in React. It defines the structure and composition of the user interface (UI) for the fundraising dApp.

  return (
    <>
      <div>
        <Header balance={cUSDBalance} />
        <Main
          addDonations={addDonations}
          donations={donations}
          donate={donate}
        />
        <Footer />
      </div>
    </>
  );
  1. The <> and </> tags are React fragments, which allow grouping multiple elements without adding an additional parent element to the HTML structure.

  2. The <div> element wraps the entire UI content.

  3. The <Header> component is rendered and receives the cUSDBalance as a prop. This component represents the header section of the dApp UI, which typically displays information such as the user’s cUSD balance.

  4. The <Main> component is rendered and receives three props: addDonations, donations, and donate. This component represents the main content area of the dApp UI, where users can view existing donations, create new donation campaigns, and make donations to existing campaigns. These props are passed to enable communication and interaction between the Main component and other parts of the application.

  5. The <Footer> component is rendered and represents the footer section of the dApp UI.

Main.js

The Main component sets up several state variables to manage user input and data. It defines event handlers (submitHandler and donateHandler) that interact with the parent component by calling specific functions from the props (addDonations and donate). These functions facilitate the creation of new donation campaigns and the process of making donations.

import { useState } from "react";

const Main = (props) => {
  const [title, setTitle] = useState("");
  const [description, setDescription] = useState("");
  const [image, setImage] = useState("");
  const [goal, setGoal] = useState(0);
  const [amount, setAmount] = useState(0);

  const submitHandler = (event) => {
    event.preventDefault();
    props.addDonations(title, description, image, goal);
  };

  const donateHandler = (index) => {
    if (!amount) {
      alert("Amount is not valid");
      return;
    }
    props.donate(index, amount);
  };
  1. The import { useState } from "react"; statement imports the useState hook from the React library. This hook allows functional components to manage state.

  2. The Main component is defined as a function that takes props as its parameter. It represents the main content area of the fundraising dApp UI.

  3. Inside the Main component, several state variables are defined using the useState hook. These variables (title, description, image, goal, amount) are used to store and manage user input and data within the component.

  4. The submitHandler function is defined and triggered when a form submission event occurs. It prevents the default form submission behavior, and then calls the addDonations function from the props, passing the title, description, image, and goal values as arguments. This function is typically used to create new donation campaigns.

  5. The donateHandler function is defined and triggered when a donate button is clicked. It checks if the amount is valid (not empty or falsy), and then calls the donate function from the props, passing the index of the donation and the amount value as arguments. This function is used to make donations to existing campaigns.

You can follow the remaining part of the code to see how the frontend is built. I wont go into the details because this is a celo tutorial.

return (
    <>
      <main>
        <section className="py-5 text-center container">
          <div className="row py-lg-5">
            <div className="col-lg-6 col-md-8 mx-auto">
              <h1 className="fw-light">Welcome to Animal Donater</h1>
              <p className="lead text-muted">
                We are a community dedicated to helping animals and we raise
                money for certain wild animals who need our help. Join us today
                make the world a better place
              </p>
              <p>
                <a href="#" className="btn btn-primary my-2">
                  Donate Today
                </a>
              </p>
            </div>
          </div>
        </section>
        <div className="album py-5 bg-light">
          <div className="container">
            <div className="row row-cols-1 row-cols-sm-2 row-cols-md-3 g-3">
              {props.donations.map((donation) => (
                <div className="col">
                  <div className="card shadow-sm">
                    <img
                      src={donation.image}
                      className="bd-placeholder-img card-img-top"
                      width="100%"
                      height={225}
                      aria-label="Placeholder: Thumbnail"
                      preserveAspectRatio="xMidYMid slice"
                      focusable="false"
                    />
                    <div className="card-body">
                      <h3>{donation.title}</h3>
                      <p className="card-text">{donation.description}</p>

                      <div className="d-flex justify-content-between align-items-center">
                        <h6>Goal: {donation.goal / 10 ** 18}cUSD</h6>
                        <h6>
                          Amount Donated: {donation.amountDonated / 10 ** 18}
                          cUSD
                        </h6>
                      </div>
                      {!donation.isGoalReached && <div className="mb-3">
                        <label>Donate Amount</label>
                        <input
                          type="text"
                          className="form-control"
                          onChange={(e) => setAmount(e.target.value)}
                        />
                      </div>}
                      <div className="btn-group">
                        {!donation.isGoalReached ? (
                          <button
                            type="button"
                            className="btn btn-sm btn-outline-primary"
                            onClick={() => donateHandler(donation.index)}
                          >
                            Donate
                          </button>
                        ) : (
                          <button
                            type="button"
                            className="btn btn-sm btn-outline-primary"
                            disabled
                          >
                            Goal Reached
                          </button>
                        )}
                      </div>
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        </div>
      </main>
      <div
        className="modal modal-signin position-static d-block bg-secondary py-5"
        tabIndex={-1}
        role="dialog"
        id="modalSignin"
      >
        <div className="modal-dialog" role="document">
          <div className="modal-content rounded-5 shadow">
            <div className="modal-header p-5 pb-4 border-bottom-0">
              {/* <h5 class="modal-title">Modal title</h5> */}
              <h2 className="fw-bold mb-0">Add an animal</h2>
            </div>
            <div className="modal-body p-5 pt-0">
              <form onSubmit={submitHandler} className>
                <div className="form-floating mb-3">
                  <input
                    onChange={(e) => setTitle(e.target.value)}
                    type="text"
                    required
                    className="form-control rounded-4"
                    id="floatingInput"
                    placeholder=""
                  />
                  <label htmlFor="floatingInput">Name of animal</label>
                </div>
                <div className="form-floating mb-3">
                  <input
                    onChange={(e) => setDescription(e.target.value)}
                    type="text"
                    required
                    className="form-control rounded-4"
                    id="floatingInput"
                    placeholder=""
                  />
                  <label htmlFor="floatingInput">Description</label>
                </div>
                <div className="form-floating mb-3">
                  <input
                    type="text"
                    onChange={(e) => setImage(e.target.value)}
                    required
                    className="form-control rounded-4"
                    id="floatingInput"
                    placeholder=""
                  />
                  <label htmlFor="floatingInput">Image of animal</label>
                </div>
                <div className="form-floating mb-3">
                  <input
                    type="text"
                    onChange={(e) => setGoal(e.target.value)}
                    required
                    className="form-control rounded-4"
                    id="floatingInput"
                    placeholder=""
                  />
                  <label htmlFor="floatingInput">Goal to be reached</label>
                </div>
                <button
                  className="w-100 mb-2 btn btn-lg rounded-4 btn-primary"
                  type="submit"
                >
                  Add
                </button>
              </form>
            </div>
          </div>
        </div>
      </div>
    </>
  );
};

export default Main;

Header.js

const Header = (props) => {
  return (
    <>
      <div className="collapse bg-dark" id="navbarHeader">
        <div className="container">
          <div className="row">
            <div className="col-sm-8 col-md-7 py-4">
              <h4 className="text-white">Balance: {props.balance}cUSD</h4>
              <p className="text-muted">
                We are a community dedicated to helping animals and we raise
                money for certain wild animals who need our help. Join us today
                make the world a better place
              </p>
            </div>
            <div className="col-sm-4 offset-md-1 py-4">
              <h4 className="text-white">Contact</h4>
              <ul className="list-unstyled">
                <li>
                  <a href="#" className="text-white">
                    Follow on Twitter
                  </a>
                </li>
                <li>
                  <a href="#" className="text-white">
                    Like on Facebook
                  </a>
                </li>
                <li>
                  <a href="#" className="text-white">
                    Email me
                  </a>
                </li>
              </ul>
            </div>
          </div>
        </div>
      </div>
      <div className="navbar navbar-dark bg-dark shadow-sm">
        <div className="container">
          <a href="#" className="navbar-brand d-flex align-items-center">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width={20}
              height={20}
              fill="none"
              stroke="currentColor"
              strokeLinecap="round"
              strokeLinejoin="round"
              strokeWidth={2}
              aria-hidden="true"
              class="bi bi-cash"
              viewBox="0 0 24 24"
            >
              <path d="M8 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" />
              <path d="M0 4a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1V4zm3 0a2 2 0 0 1-2 2v4a2 2 0 0 1 2 2h10a2 2 0 0 1 2-2V6a2 2 0 0 1-2-2H3z" />
            </svg>
            {/* <svg
              xmlns="http://www.w3.org/2000/svg"
              
              
              className="me-2"
            >
              <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
              <circle cx={12} cy={13} r={4} />
            </svg> */}
            <strong>Donater</strong>
          </a>
          <button
            className="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarHeader"
            aria-controls="navbarHeader"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span className="navbar-toggler-icon" />
          </button>
        </div>
      </div>
    </>
  );
};

export default Header;

Footer.js

const Footer = props=>{
    return (
        <>
             <footer className="text-muted py-5">
          <div className="container">
            <p className="float-end mb-1">
              <a href="#">Back to top</a>
            </p>
            <p className="mb-1">
              Donater is © A licensed mock application, use at your own risk
            </p>

          </div>
        </footer>
        </>
    )
}

export default Footer;

Conclusion

Throughout the tutorial, we learned about important concepts such as smart contracts, web3 integration, state management with React’s useState hook, and interacting with the Celo blockchain using the ContractKit library. We also gained insights into the functionalities of different functions and their roles in the dApp.
By combining our frontend knowledge with blockchain technology, we created a user-friendly interface that allows users to create donation campaigns, donate to existing campaigns, and view campaign details. We demonstrated how to fetch data from the smart contract and update the UI dynamically.

Next Steps

Remember to continue experimenting, exploring, and improving your skills as you delve deeper into the world of blockchain development. Happy coding!:rocket::blush:

About the Author

Daniel Ogbuti is a web3 developer with a passion for teaching as well as learning. I would love to connect on Twitter @daniel_ogbuti and linkedin @ Daniel Ogbuti

See you soon!

6 Likes

Fantastic news! Your proposal has landed in this week’s top voted list. As you begin your project journey, remember to align with our community and technical guidelines, ensuring a high quality platform for our developers. Congratulations! :mortar_board: :seedling:

6 Likes

Hi @danielogbuti i will be reviewing this

2 Likes

Looks good

3 Likes

Your tutorial was interesting @danielogbuti from the explanation to code presentation. Looking forward to more of your tutorial

3 Likes

Where can I get the link to the first part

I think it is better off for me to start from the beginning

This is a cool front-end piece, I am going to use this information for integration :raised_hands:

1 Like