# Day9. 使用 javascript 建構 DAO **Published by:** [Samumu.eth](https://paragraph.com/@samumu/) **Published on:** 2022-02-26 **URL:** https://paragraph.com/@samumu/day9-javascript-dao ## Content OK,現在我們要將在 Day8. 使用 javascript 建構 DAO 拿到的提案資料顯示於網頁,並讓使用者可以進行投票,由於我們修改了 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; ## Publication Information - [Samumu.eth](https://paragraph.com/@samumu/): Publication homepage - [All Posts](https://paragraph.com/@samumu/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@samumu): Subscribe to updates - [Twitter](https://twitter.com/SamumuClan): Follow on Twitter