# Day9. 使用 javascript 建構 DAO

By [Samumu.eth](https://paragraph.com/@samumu) · 2022-02-26

---

OK，現在我們要將在 [Day8. 使用 javascript 建構 DAO](https://mirror.xyz/samumu.eth/NNt-l1DP5Uswn_W500ba31TDgRCAM5rdciMa_3CR-fg) 拿到的提案資料顯示於網頁，並讓使用者可以進行投票，由於我們修改了 `App.jsx` 很多次，為了不讓各位搞混，這邊我直接將整段代碼附上，由於新增的代碼都是 React js 的部分，這邊就不多做說明，對於前端如何優化、如何互動有興趣的同學可以到下一系列教程進行學習，大致上這系列”**使用 javascript 建構 DAO**”教程已經完成，不過我們還剩下最後一步，請在完成下方代碼編寫後，堅持到最後一個部分

    import { useEffect, useMemo, useState } from "react";
    import { ethers } from "ethers";
    import dotenv from "dotenv";
    dotenv.config();
    
    import { useWeb3 } from "@3rdweb/hooks";
    import { ThirdwebSDK } from "@3rdweb/sdk";
    
    const sdk = new ThirdwebSDK("rinkeby");
    
    const bundleDropModule = sdk.getBundleDropModule(
      process.env.BUNDLEDROP_MODULE_ADDRESS,
    );
    
    const tokenModule = sdk.getTokenModule(
      process.env.TOKEN_MODULE_ADDRESS,
    );
    
    const voteModule = sdk.getVoteModule(
      process.env.VOTE_MODULE_ADDRESS,
    );
    
    const App = () => {
      const { connectWallet, address, error, provider } = useWeb3();
    
      const signer = provider ? provider.getSigner() : undefined;
    
      const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
      const [isClaiming, setIsClaiming] = useState(false);
    
      const [memberTokenAmounts, setMemberTokenAmounts] = useState({});
      const [memberAddresses, setMemberAddresses] = useState([]);
    
      const shortenAddress = (str) => {
        return str.substring(0, 6) + "..." + str.substring(str.length - 4);
      };
    
      useEffect(() => {
        if (!hasClaimedNFT) {
          return;
        }
    
        bundleDropModule
          .getAllClaimerAddresses("0")
          .then((addresess) => {
            setMemberAddresses(addresess);
          })
          .catch((err) => {
            console.error("failed to get member list", err);
          });
      }, [hasClaimedNFT]);
    
      useEffect(() => {
        if (!hasClaimedNFT) {
          return;
        }
    
        tokenModule
          .getAllHolderBalances()
          .then((amounts) => {
            setMemberTokenAmounts(amounts);
          })
          .catch((err) => {
            console.error("failed to get token amounts", err);
          });
      }, [hasClaimedNFT]);
    
      const memberList = useMemo(() => {
        return memberAddresses.map((address) => {
          return {
            address,
            tokenAmount: ethers.utils.formatUnits(
              memberTokenAmounts[address] || 0,
              18,
            ),
          };
        });
      }, [memberAddresses, memberTokenAmounts]);
    
      useEffect(() => {
        sdk.setProviderOrSigner(signer);
      }, [signer]);
    
      useEffect(() => {
        if (!address) {
          return;
        }
        return bundleDropModule
          .balanceOf(address, "0")
          .then((balance) => {
            if (balance.gt(0)) {
              setHasClaimedNFT(true);
            } else {
              setHasClaimedNFT(false);
            }
          })
          .catch((error) => {
            setHasClaimedNFT(false);
            console.error("failed to nft balance", error);
          });
      }, [address]);
    
      const [proposals, setProposals] = useState([]);
      const [isVoting, setIsVoting] = useState(false);
      const [hasVoted, setHasVoted] = useState(false);
    
      useEffect(() => {
        if (!hasClaimedNFT) {
          return;
        }
        voteModule
          .getAll()
          .then((proposals) => {
            setProposals(proposals);
          })
          .catch((err) => {
            console.error("failed to get proposals", err);
          });
      }, [hasClaimedNFT]);
    
      useEffect(() => {
        if (!hasClaimedNFT) {
          return;
        }
    
        if (!proposals.length) {
          return;
        }
    
        voteModule
          .hasVoted(proposals[0].proposalId, address)
          .then((hasVoted) => {
            setHasVoted(hasVoted);
          })
          .catch((err) => {
            console.error("failed to check if wallet has voted", err);
          });
      }, [hasClaimedNFT, proposals, address]);
    
      if (error && error.name === "UnsupportedChainIdError") {
        return (
          <div className="unsupported-network">
            <h2>Please connect to Rinkeby</h2>
            <p>
              This dapp only works on the Rinkeby network, please switch networks
              in your connected wallet.
            </p>
          </div>
        );
      }
    
      if (!address) {
        return (
          <div className="landing">
            <h1>Welcome to DevDAO</h1>
            <button onClick={() => connectWallet("injected")} className="btn-hero">
              Connect your wallet
            </button>
          </div>
        );
      }
    
      if (hasClaimedNFT) {
        return (
          <div className="member-page">
            <h1>💻 DevDAO Member Page</h1>
            <p>Congratulations on being a member</p>
            <div>
              <div>
                <h2>Member List</h2>
                <table className="card">
                  <thead>
                    <tr>
                      <th>Address</th>
                      <th>Token Amount</th>
                    </tr>
                  </thead>
                  <tbody>
                    {memberList.map((member) => {
                      return (
                        <tr key={member.address}>
                          <td>{shortenAddress(member.address)}</td>
                          <td>{member.tokenAmount}</td>
                        </tr>
                      );
                    })}
                  </tbody>
                </table>
              </div>
              <div>
                <h2>Active Proposals</h2>
                <form
                  onSubmit={async (e) => {
                    e.preventDefault();
                    e.stopPropagation();
    
                    setIsVoting(true);
    
                    const votes = proposals.map((proposal) => {
                      let voteResult = {
                        proposalId: proposal.proposalId,
                        vote: 2,
                      };
                      proposal.votes.forEach((vote) => {
                        const elem = document.getElementById(
                          proposal.proposalId + "-" + vote.type
                        );
    
                        if (elem.checked) {
                          voteResult.vote = vote.type;
                          return;
                        }
                      });
                      return voteResult;
                    });
    
                    try {
                      //we'll check if the wallet still needs to delegate their tokens before they can vote
                      const delegation = await tokenModule.getDelegationOf(address);
                      if (delegation === ethers.constants.AddressZero) {
                        await tokenModule.delegateTo(address);
                      }
                      try {
                        await Promise.all(
                          votes.map(async (vote) => {
                            const proposal = await voteModule.get(vote.proposalId);
                            // then we check if the proposal is open for voting (state === 1 means it is open)
                            if (proposal.state === 1) {
                              return voteModule.vote(vote.proposalId, vote.vote);
                            }
                            return;
                          })
                        );
                        try {
                          await Promise.all(
                            votes.map(async (vote) => {
                              const proposal = await voteModule.get(
                                vote.proposalId
                              );
    
                              //if the state is in state 4 (meaning that it is ready to be executed), we'll execute the proposal
                              if (proposal.state === 4) {
                                return voteModule.execute(vote.proposalId);
                              }
                            })
                          );
                          setHasVoted(true);
                        } catch (err) {
                          console.error("failed to execute votes", err);
                        }
                      } catch (err) {
                        console.error("failed to vote", err);
                      }
                    } catch (err) {
                      console.error("failed to delegate tokens");
                    } finally {
                      setIsVoting(false);
                    }
                  }}
                >
                  {proposals.map((proposal, index) => (
                    <div key={proposal.proposalId} className="card">
                      <h5>{proposal.description}</h5>
                      <div>
                        {proposal.votes.map((vote) => (
                          <div key={vote.type}>
                            <input
                              type="radio"
                              id={proposal.proposalId + "-" + vote.type}
                              name={proposal.proposalId}
                              value={vote.type}
                              defaultChecked={vote.type === 2}
                            />
                            <label htmlFor={proposal.proposalId + "-" + vote.type}>
                              {vote.label}
                            </label>
                          </div>
                        ))}
                      </div>
                    </div>
                  ))}
                  <button disabled={isVoting || hasVoted} type="submit">
                    {isVoting
                      ? "Voting..."
                      : hasVoted
                        ? "You Already Voted"
                        : "Submit Votes"}
                  </button>
                  <small>
                    This will trigger multiple transactions that you will need to
                    sign.
                  </small>
                </form>
              </div>
            </div>
          </div>
        );
      };
    
      return (
        <div className="mint-nft">
          <h1>Mint your free DevDAO Membership NFT</h1>
          <button
            disabled={isClaiming}
            onClick={() => {
              setIsClaiming(true);
              bundleDropModule
                .claim("0", 1)
                .catch((err) => {
                  console.error("failed to claim", err);
                  setIsClaiming(false);
                })
                .finally(() => {
                  setIsClaiming(false);
                  setHasClaimedNFT(true);
                  console.log(
                    `Successfully Minted! Check it our on OpenSea: https://testnets.opensea.io/assets/${bundleDropModule.address}/0`
                  );
                });
            }}
          >
            {isClaiming ? "Minting..." : "Mint your nft (FREE)"}
          </button>
        </div>
      );
    };
    
    export default App;

---

*Originally published on [Samumu.eth](https://paragraph.com/@samumu/day9-javascript-dao)*
