How to Build a Glorious Web3.0 DAO with React, Solidity, and CometChat
Tags: web3, dao, dapp, ethereum
Published: 2022-06-22 11:52:00

What you will be building, see [**demo**](https://dominiondao.web.app/) and [**git repo**](https://github.com/Daltonic/dominionDAO) here…

![Dominion DAO Demo](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1654937925596_dominion-dao.gif)

![The Chat Interface](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1654941823893_screenshoteasy+32.png)

## Introduction

I’m super excited to release this web3.0 build to you, I know you’ve been looking for a great example to get you started in developing decentralized applications.

If you are new here, I’m Darlington Gospel, a Dapp Mentor helping transition developers like you from Web 2.0 to Web 3.0. [**Book your private classes with me**](https://www.buymeacoffee.com/web3classes).

In this tutorial, you will learn step-by-step how to implement a decentralized autonomous organization (DAO) with anonymous chat features.

If you are pumped for this build, let’s jump into the tutorial…

[Check out my YouTube channel for FREE web3 tutorials now](https://www.youtube.com/channel/UCQteXYZ5mC-cuTxplzM4Hlw?sub_confirmation=1).

## Prerequisite

You will need the following tools installed to successfully crush this build:

- Node
- Ganache-Cli
- Truffle
- React
- Infuria
- Tailwind CSS
- CometChat SDK
- Metamask
- Yarn


## Installing Dependencies

**NodeJs Installation**
Make sure you have NodeJs installed on your machine already, and if you haven’t, install it from [**HERE**](http://). Next, run the code on the terminal to confirm it is installed.

![Node Installed](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1654939404310_Screenshot+259.png)


**Yarn, Ganache-cli and Truffle Installation**
Run the following codes on your terminal to install these essential packages globally.


    npm i -g yarn
    npm i -g truffle
    npm i -g ganache-cli

**Cloning Web3 Starter Project**
Using the commands below, clone the web 3.0 starter project below. This will ensure that we’re all on the same page and are using the same packages.


    git clone https://github.com/Daltonic/dominionDAO

Fantastic, let us replace the `package.json` file with the one below:


{% gist https://gist.github.com/Daltonic/444294fbbab45102695ea31912a35d47 %}


Great, replace your `package.json` file with the above code and then run `yarn install` on your terminal.

With that all installed, let’s start with writing the Dominion DAO smart contract.


## Configuring CometChat SDK

To configure the [**CometChat SDK**](https://hubs.la/Q01f8KXv0), follow the steps below, at the end, you need to store these keys as an environment variable.

**STEP 1:**
Head to [**CometChat**](https://hubs.la/Q01f8H7m0) [](https://app.cometchat.com/signup)Dashboard and create an account.


![Register a new CometChat account if you do not have one](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/61700dac76419b7bac2de69d_s_1988D50EEAEE632A7CEA2F32B0824BC13ED61EA41386BA94E99320DDC4403C74_1633934795404_image.png)


**STEP 2:**
Log in to the [**CometChat**](https://hubs.la/Q01f8H7m0) dashboard, only after registering.

![Log in to the CometChat Dashboard with your created account](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/618ea49640681708de79e9c8_s_C05E7B93FFC9B007F6C09747840C5EEC81AFEC86C6C89C570966797151AE7076_1635316482378_Screen%2BShot%2B2021-10-27%2Bat%2B13.34.18.png)


**STEP 3:**
From the dashboard, add a new app called **dominionDAO.**

![Create a new CometChat app - Step 1](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/61700dac7e33a881ced79837_s_1988D50EEAEE632A7CEA2F32B0824BC13ED61EA41386BA94E99320DDC4403C74_1633936628018_image.png)

![Create a new CometChat app - Step 2](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/618ea496d603ba7ef8cabeb3_s_C05E7B93FFC9B007F6C09747840C5EEC81AFEC86C6C89C570966797151AE7076_1635316586805_VLioyzP2.png)


**STEP 4:**
Select the app you just created from the list.

![Select your created app](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/618ea4969bad618548da9ccd_s_C05E7B93FFC9B007F6C09747840C5EEC81AFEC86C6C89C570966797151AE7076_1635316654035_bSCncNJN.png)


**STEP 5:**
From the Quick Start copy the `APP_ID`, `REGION`, and `AUTH_KEY`, to your `.env` file. See the image and code snippet.

![Copy the the APP_ID, REGION, and AUTH_KEY](https://assets-global.website-files.com/5f3c19f18169b62a0d0bf387/618ea4968574a3c1945af0bd_s_C05E7B93FFC9B007F6C09747840C5EEC81AFEC86C6C89C570966797151AE7076_1635316757752_QHRb0reB.png)


Replace the `REACT_COMET_CHAT` placeholders keys with their appropriate values.


    REACT_APP_COMET_CHAT_REGION=**
    REACT_APP_COMET_CHAT_APP_ID=**************
    REACT_APP_COMET_CHAT_AUTH_KEY=******************************


## Configuring Infuria App

**STEP 1:**
Head to [**Infuria**](https://infura.io/login), create an account.


![Login to your infuria account](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655136978524_screenshoteasy+58.png)


**STEP 2:**
From the dashboard create a new project.


![Create a new project step 1](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655137128053_screenshoteasy+59.png)



![Create a new project step 2](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655137248397_screenshoteasy+60.png)


**STEP 3:**
Copy the `Rinkeby` test network WebSocket endpoint URL to your `.env` file.

![Rinkeby Testnet Keys](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655137632390_screenshoteasy+61.png)


Next, add your Metamask secret phrase and your preferred account private key. If you have done those correctly, your environment variables should now look like this.


    ENDPOINT_URL=***************************
    DEPLOYER_KEY=**********************
    
    REACT_APP_COMET_CHAT_REGION=**
    REACT_APP_COMET_CHAT_APP_ID=**************
    REACT_APP_COMET_CHAT_AUTH_KEY=******************************

If you don’t know how to access your private key, see the section below.

## Accessing Your Metamask Private Key

**STEP 1:**
Click on your `Metamask` browser extension, and make sure `Rinkeby` is selected as the test network. Next, on the preferred account, click on the vertical dotted line and select account details. See the image below.

![Step One](https://i.ibb.co/NjkWgrf/kpK7sHUA.png)


**STEP 2:**
Enter your password on the field provided and click the confirm button, this will enable you to access your account private key.

![Step Two](https://i.ibb.co/Tqrx1RT/S5sWkwuA.png)


**STEP 3:**
Click on "export private key" to see your private key. Make sure you never expose your keys on a public page such as `Github`. That is why we are appending it as an environment variable.

![Step Three](https://i.ibb.co/0sW24xz/iHVXsxjw.png)


**STEP 4:**
Copy your private key to your `.env` file. See the image and code snippet below:

![Step Four](https://i.ibb.co/MCkMvyz/1KEkvmvw.png)

    ENDPOINT_URL=***************************
    SECRET_KEY=******************
    DEPLOYER_KEY=**********************
    
    REACT_APP_COMET_CHAT_REGION=**
    REACT_APP_COMET_CHAT_APP_ID=**************
    REACT_APP_COMET_CHAT_AUTH_KEY=******************************

As for your `SECRET_KEY`, you are required to paste your `Metamask` secret phrase in the space provided in the environment file.

## The Dominion DAO Smart Contract

Here is the full code for the smart contract, I will explain all the functions and variables one after the other.

{% gist https://gist.github.com/Daltonic/2982f769b933397f3f7a29620e525ca6 %}


In the project you just cloned, head to **src >> contract** directory and create a file named `DominionDAO.sol`, then paste the above codes inside of it.

**Explanation:**


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

Solidity requires a license identifier to compile your code, else it will produce a warning asking you to specify one. Also, Solidity requires that you specify the version of the compiler for your smart contract. That is what the word `pragma` represents.


    import "@openzeppelin/contracts/access/AccessControl.sol";
    import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

In the above code block, we are utilizing two `openzeppelin``'``s` smart contracts for specifying roles and guarding our smart contract against reentrancy attacks.


    bytes32 private immutable CONTRIBUTOR_ROLE = keccak256("CONTRIBUTOR");
    bytes32 private immutable STAKEHOLDER_ROLE = keccak256("STAKEHOLDER");
    uint32 immutable MIN_VOTE_DURATION = 1 weeks;
    uint256 totalProposals;
    uint256 public daoBalance;

We set up some state variables for stakeholder and contributor roles and specified the minimum vote duration to be one week. And we also initialized the total proposal counter and a variable to keep a record of our available balance.


    mapping(uint256 => ProposalStruct) private raisedProposals;
    mapping(address => uint256[]) private stakeholderVotes;
    mapping(uint256 => VotedStruct[]) private votedOn;
    mapping(address => uint256) private contributors;
    mapping(address => uint256) private stakeholders;

`raisedProposals` keep track of all proposals submitted to our smart contract. `stakeholderVotes` as its name imply keep track of votes made by stakeholders. `votedOn` keeps track of all the votes associated with a proposal. While contributors keep track of anyone who donated to our platform, stakeholders on the other hand keep track of people that have contributed up to `1 ether`.


    struct ProposalStruct {
        uint256 id;
        uint256 amount;
        uint256 duration;
        uint256 upvotes;
        uint256 downvotes;
        string title;
        string description;
        bool passed;
        bool paid;
        address payable beneficiary;
        address proposer;
        address executor;
    }
    
    struct VotedStruct {
        address voter;
        uint256 timestamp;
        bool choosen;
    }

`proposalStruct` describes the content of each proposal whereas `votedStruct` describes the content of each vote.


    event Action(
        address indexed initiator,
        bytes32 role,
        string message,
        address indexed beneficiary,
        uint256 amount
    );

This is a dynamic event called Action. This will help us enrich the information logged out per transaction.


    modifier stakeholderOnly(string memory message) {
        require(hasRole(STAKEHOLDER_ROLE, msg.sender), message);
        _;
    }
    
    modifier contributorOnly(string memory message) {
        require(hasRole(CONTRIBUTOR_ROLE, msg.sender), message);
        _;
    }

The above modifiers help us identify users by role and also prevent them from accessing some unauthorized resources.


    function createProposal(
        string calldata title,
        string calldata description,
        address beneficiary,
        uint256 amount
    )external
     stakeholderOnly("Proposal Creation Allowed for Stakeholders only")
    {
        uint256 proposalId = totalProposals++;
        ProposalStruct storage proposal = raisedProposals[proposalId];
    
        proposal.id = proposalId;
        proposal.proposer = payable(msg.sender);
        proposal.title = title;
        proposal.description = description;
        proposal.beneficiary = payable(beneficiary);
        proposal.amount = amount;
        proposal.duration = block.timestamp + MIN_VOTE_DURATION;
    
        emit Action(
            msg.sender,
            CONTRIBUTOR_ROLE,
            "PROPOSAL RAISED",
            beneficiary,
            amount
        );
    }

The above function takes a proposal's title, description, amount, and the beneficiary’s wallet address and creates a proposal. The function only permits stakeholders to create proposals. Stakeholders are users who have made at least a contribution of `1 ether`.


    function performVote(uint256 proposalId, bool choosen)
        external
        stakeholderOnly("Unauthorized: Stakeholders only")
    {
        ProposalStruct storage proposal = raisedProposals[proposalId];
    
        handleVoting(proposal);
    
        if (choosen) proposal.upvotes++;
        else proposal.downvotes++;
    
        stakeholderVotes[msg.sender].push(proposal.id);
    
        votedOn[proposal.id].push(
            VotedStruct(
                msg.sender,
                block.timestamp,
                choosen
            )
        );
    
        emit Action(
            msg.sender,
            STAKEHOLDER_ROLE,
            "PROPOSAL VOTE",
            proposal.beneficiary,
            proposal.amount
        );
    }

This function accepts two arguments, a proposal Id, and a preferred choice represented by a Boolean value. True means you accepted the vote and False represents a rejection.


    function handleVoting(ProposalStruct storage proposal) private {
        if (
            proposal.passed ||
            proposal.duration <= block.timestamp
        ) {
            proposal.passed = true;
            revert("Proposal duration expired");
        }
    
        uint256[] memory tempVotes = stakeholderVotes[msg.sender];
        for (uint256 votes = 0; votes < tempVotes.length; votes++) {
            if (proposal.id == tempVotes[votes])
                revert("Double voting not allowed");
        }
    }

This function performs the actual voting including checking if a user is a stakeholder and qualified to vote.


    function payBeneficiary(uint256 proposalId)
        external
        stakeholderOnly("Unauthorized: Stakeholders only")
        returns (bool)
    {
        ProposalStruct storage proposal = raisedProposals[proposalId];
        require(daoBalance >= proposal.amount, "Insufficient fund");
        require(block.timestamp > proposal.duration, "Proposal still ongoing");
    
        if (proposal.paid) revert("Payment sent before");
    
        if (proposal.upvotes <= proposal.downvotes)
            revert("Insufficient votes");
    
        payTo(proposal.beneficiary, proposal.amount);
    
        proposal.paid = true;
        proposal.executor = msg.sender;
        daoBalance -= proposal.amount;
    
        emit Action(
            msg.sender,
            STAKEHOLDER_ROLE,
            "PAYMENT TRANSFERED",
            proposal.beneficiary,
            proposal.amount
        );
    
        return true;
    }

This function is responsible for paying the beneficiary attached to a proposal based on certain criteria.


- One, the beneficiary must not already be paid.
- Two, the proposal duration must have expired.
- Three, the available balance must be able to pay the beneficiary.
- Four, there must be no tie in the number of votes.

```sol
   function contribute() payable external {
        if (!hasRole(STAKEHOLDER_ROLE, msg.sender)) {
            uint256 totalContribution =
                contributors[msg.sender] + msg.value;
    
            if (totalContribution >= 5 ether) {
                stakeholders[msg.sender] = totalContribution;
                contributors[msg.sender] += msg.value;
                _setupRole(STAKEHOLDER_ROLE, msg.sender);
                _setupRole(CONTRIBUTOR_ROLE, msg.sender);
            } else {
                contributors[msg.sender] += msg.value;
                _setupRole(CONTRIBUTOR_ROLE, msg.sender);
            }
        } else {
            contributors[msg.sender] += msg.value;
            stakeholders[msg.sender] += msg.value;
        }
        
        daoBalance += msg.value;
    
        emit Action(
            msg.sender,
            STAKEHOLDER_ROLE,
            "CONTRIBUTION RECEIVED",
            address(this),
            msg.value
        );
    }
```

This function is responsible for collecting contributions from donors and those interested in becoming stakeholders.


    function getProposals()
        external
        view
        returns (ProposalStruct[] memory props)
    {
        props = new ProposalStruct[](totalProposals);
    
        for (uint256 i = 0; i < totalProposals; i++) {
            props[i] = raisedProposals[i];
        }
    }

This function retrieves an array of proposals recorded on this smart contract.


    function getProposal(uint256 proposalId)
        external
        view
        returns (ProposalStruct memory)
    {
        return raisedProposals[proposalId];
    }

This function retrieves a particular proposal by Id.


    function getVotesOf(uint256 proposalId)
        external
        view
        returns (VotedStruct[] memory)
    {
        return votedOn[proposalId];
    }

This returns a list of votes associated with a particular proposal.


    function getStakeholderVotes()
        external
        view
        stakeholderOnly("Unauthorized: not a stakeholder")
        returns (uint256[] memory)
    {
        return stakeholderVotes[msg.sender];
    }

This returns the list of stakeholders on the smart contract and only a stakeholder can call this function.


    function getStakeholderBalance()
        external
        view
        stakeholderOnly("Unauthorized: not a stakeholder")
        returns (uint256)
    {
        return stakeholders[msg.sender];
    }

This returns the amount of money contributed by stakeholders.


    function isStakeholder() external view returns (bool) {
        return stakeholders[msg.sender] > 0;
    }

Returns True or False if a user is a stakeholder.


    function getContributorBalance()
        external
        view
        contributorOnly("Denied: User is not a contributor")
        returns (uint256)
    {
        return contributors[msg.sender];
    }

This returns the balance of a contributor and is only accessible to the contributor.


    function isContributor() external view returns (bool) {
        return contributors[msg.sender] > 0;
    }

This checks if a user is a contributor or not and it is represented with True or False.


    function getBalance() external view returns (uint256) {
        return contributors[msg.sender];
    }

Returns the balance of the calling user regardless of his role.


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

This function performs a payment with both a specified amount and account.


## Configuring the Deployment Script

One more thing to do with for the smart contract is to configure the deployment script. 

On the project head to the **migrations** folder >> **2_deploy_contracts.js** and update it with the code snippet below.


    const DominionDAO = artifacts.require('DominionDAO')
      module.exports = async function (deployer) {
      await deployer.deploy(DominionDAO)
    }

Fantastic, we just finished up with the smart contract for our application, it's time to start building the Dapp interface.


## Developing the Frontend

The front end comprises many components and parts. We will be creating all the components, views, and the rest of the peripherals.

**Header Component**

![Dark Mode](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655016805985_screenshoteasy+33.png)

![Light Mode](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655016960087_screenshoteasy+38.png)


This component captures information about the current user and carries a theme toggling button for light and dark modes. And if you wondered how I did that, it was through Tailwind CSS, see the code below.


{% gist https://gist.github.com/Daltonic/a188468e30c29664b55aff5f9714b41d %}


**Banner Component**

![Banner Component](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655017221351_screenshoteasy+39.png)


This component contains information about the DAO's current state, such as the total balance and the number of open proposals.

This component also includes the ability to use the contribute function to generate a new proposal. Look at the code below.


{% gist https://gist.github.com/Daltonic/983b9fb5528856186f01a7cdc323efbe %}


**Proposals Component**

![Proposals Components](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655651867842_screenshoteasy+9.png)


This component contains a list of proposals in our smart contract. Also, enables you to filter between closed and open proposals. At the expiry of a proposal, a payout button becomes available which gives a stakeholder the option of paying out the amount associated with the proposal. See the code below.


{% gist https://gist.github.com/Daltonic/39a89e3afeae185be115edea80c27791 %}


**The Proposal Details Component**

![Proposal Details](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655017823349_screenshoteasy+41.png)


This component displays information about the current proposal, including the cost. This component allows stakeholders to accept or reject a proposal.

The proposer can form a group, and other platform users can engage in web3.0-style anonymous chat.

This component also includes a bar chat that allows you to see the ratio of acceptees to rejectees. Look at the code below.


{% gist https://gist.github.com/Daltonic/26bc93a60cdb57a20b22277875208a64 %}


**Voters Component**

![Voters Component](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655018128848_screenshoteasy+42.png)


This component simply lists out the stakeholders that have voted on a proposal. The component also affords a user the chance to filter between rejectees and acceptees. See the code below.


{% gist https://gist.github.com/Daltonic/a018d4c2a945972de6f2c5f077a762c6 %}


**Messages Component**

![The Messages Component](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655018351350_screenshoteasy+43.png)


With the power of CometChat SDK combined with this component, users can engage in a one-to-many chat anonymously. Contributors and stakeholders can discuss a proposal further in their decision-making process here. All users maintain their anonymity and are represented by their Identicons.


{% gist https://gist.github.com/Daltonic/10cdb7d5beac93cadb0e4153a2652ba5 %}


**Create Proposal Component**

![Create Proposal Component](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655018699411_screenshoteasy+44.png)


This component simply lets you raise a proposal by supplying information on the fields seen in the image above. See the code below.


{% gist https://gist.github.com/Daltonic/1a4bb888b0082cbe37774a2c76bb9226 %}


**Authentication Component**

![Login Chat Component](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655018935343_screenshoteasy+45.png)


This component helps you participate in the chat features. You need to create an account or login in if you’ve already signed up. By logging in, you can be able to participate in a group chat and have some anonymous talk with other participants in a proposal in a web3.0 style. See the code below.


{% gist https://gist.github.com/Daltonic/f64a8e15e3a4b0ed2d8c900f5bc24c72 %}


Fantastic, let’s make sure the views are well represented…

**The Home View**

![The Home View](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655652102307_screenshoteasy+10.png)


This view includes the header, banner, and proposals components for providing an exceptional DAO user experience. We also used the power of Tailwind CSS to achieve this look. Look at the code below.


    import Banner from '../components/Banner'
    import ChatLogin from '../components/ChatLogin'
    import CreateProposal from '../components/CreateProposal'
    import Header from '../components/Header'
    import Proposals from '../components/Proposals'
    const Home = () => {
      return (
        <>
          <Header />
          <Banner />
          <Proposals />
          <CreateProposal />
          <ChatLogin />
        </>
      )
    }
    export default Home

**The Proposal View**

![The Proposal View](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655019515596_screenshoteasy+47.png)


This view couples together the header, proposal details, and voters component for rendering a smooth presentation of a singular component. See the code below.


    import Header from '../components/Header'
    import ProposalDetails from '../components/ProposalDetails'
    import Voters from '../components/Voters'
    const Proposal = () => {
      return (
        <>
          <Header />
          <ProposalDetails />
          <Voters />
        </>
      )
    }
    export default Proposal

**The Chat View**

![The Chat View](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655019707630_screenshoteasy+48.png)


Lastly, the chat view incorporates the header and messages component for rendering a quality chat interface. See the code below.


    import { useParams, useNavigate } from 'react-router-dom'
    import { useEffect, useState } from 'react'
    import { getGroup } from '../CometChat'
    import { toast } from 'react-toastify'
    import Header from '../components/Header'
    import Messages from '../components/Messages'
    const Chat = () => {
      const { gid } = useParams()
      const navigator = useNavigate()
      const [group, setGroup] = useState(null)
      useEffect(() => {
        getGroup(gid).then((group) => {
          if (!!!group.code) {
            setGroup(group)
          } else {
            toast.warning('Please join the group first!')
            navigator(`/proposal/${gid.substr(4)}`)
          }
        })
      }, [gid])
      return (
        <>
          <Header />
          <Messages gid={gid} />
        </>
      )
    }
    export default Chat

Amazing, don’t forget to update the `App.jsx` file too.

**The App Component**
Replace the App component with the code below.


    import { useEffect, useState } from 'react'
    import { Routes, Route } from 'react-router-dom'
    import { loadWeb3 } from './Dominion'
    import { ToastContainer } from 'react-toastify'
    import { isUserLoggedIn } from './CometChat'
    import Home from './views/Home'
    import Proposal from './views/Proposal'
    import Chat from './views/Chat'
    import 'react-toastify/dist/ReactToastify.min.css'
    
    const App = () => {
      const [loaded, setLoaded] = useState(false)
    
      useEffect(() => {
        loadWeb3().then((res) => {
          if (res) setLoaded(true)
        })
        isUserLoggedIn()
      }, [])
    
      return (
        <div className="min-h-screen bg-white text-gray-900 dark:bg-[#212936] dark:text-gray-300">
    
          {loaded ? (
            <Routes>
              <Route path="/" element={<Home />} />
              <Route path="proposal/:id" element={<Proposal />} />
              <Route path="chat/:gid" element={<Chat />} />
            </Routes>
          ) : null}
    
          <ToastContainer
            position="top-center"
            autoClose={5000}
            hideProgressBar={false}
            newestOnTop={false}
            closeOnClick
            rtl={false}
            pauseOnFocusLoss
            draggable
            pauseOnHover
          />
        </div>
      )
    }
    export default App

On the **src >> directory** paste the following codes in there respective files.

**Index.jsx File**

    import React from 'react'
    import ReactDOM from 'react-dom'
    import { BrowserRouter } from 'react-router-dom'
    import './index.css'
    import App from './App'
    import { initCometChat } from './CometChat'
    initCometChat().then(() => {
      ReactDOM.render(
        <BrowserRouter>
          <App />
        </BrowserRouter>,
        document.getElementById('root')
      )
    })

**Index.css File**

    @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap');
    * html {
      padding: 0;
      margin: 0;
      box-sizing: border-box;
    }
    body {
      margin: 0;
      font-family: 'Open Sans', sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    @tailwind base;
    @tailwind components;
    @tailwind utilities;

**CometChat.jsx**

{% gist https://gist.github.com/Daltonic/ed8af2ebd0689fb3b7240d460b48c26f %}



## Starting Up the Development Environment

**STEP 1:**
Spin up some test account with `ganache-cli` using the command below:


    ganache-cli -a

This will create some test accounts with 100 fake ethers loaded into each account, of course, these are for testing purposes only. See the image below:

![Generated Private Keys](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655139761096_Screenshot+262.png)


**STEP 2:**
Add a local test network with Metamask as seen in the image below.

![Localhost Network](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655140263313_screenshoteasy+62.png)


**STEP 3:**
Click on the account icon and select import account.

![Step one](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655140407367_screenshoteasy+63.png)


Copy about five of the private keys and add them one after the other to your local test network. See the image below.


![Importing Private Keys from ganache cli](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655140575911_screenshoteasy+64.png)


Observe the new account added to your local test network with 100 ETH preloaded. Make sure you add about five accounts so you can do a maximum test. See the image below.


![Free Test Ethers](https://paper-attachments.dropbox.com/s_9E8F3773E2C82153C7799BA2EBF2003A00F1060D3D03339D1C8009A6D5882B0F_1655140756309_screenshoteasy+65.png)

## Smart Contract Deployment

Now open a new terminal and run the command below.


    truffle migrate
    # or 
    truffle migrate --network rinkeby

The above command will deploy your smart contract to your local or the Infuria rinkeby test network.

Next, open up another terminal and spin up the react app with `yarn start`.

[Watch my FREE web3 tutorials on Youtube now](https://www.youtube.com/channel/UCQteXYZ5mC-cuTxplzM4Hlw?sub_confirmation=1).

## Conclusion

Hurray, we’ve just completed an amazing tutorial for developing a decentralized autonomous organization.

If you enjoyed this tutorial and would like to have me as your private mentor, kindly [**book your classes with me**](https://www.buymeacoffee.com/web3classes).

Till next time, all the best.


## About the Author

Gospel Darlington is a full-stack blockchain developer with `6+` years of experience in the software development industry.

By combining Software Development, writing, and teaching, he demonstrates how to build decentralized applications on EVM-compatible blockchain networks.

His stacks include `JavaScript`, `React`, `Vue`, `Angular`, `Node`, `React Native`, `NextJs`, `Solidity`, and more.

For more information about him, kindly visit and follow his page on [**Twitter**](https://twitter.com/idaltonic), [**Github**](https://github.com/Daltonic), [**LinkedIn**](https://www.linkedin.com/in/darlington-gospel-aa626b125), or on his [**website**](https://daltonic.github.io/).