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;
