CardVoteMobile.tsx
1 import { useContractFunction } from '@usedapp/core' 2 import React, { useEffect, useState } from 'react' 3 import styled from 'styled-components' 4 import { VotesBtns, VoteBtn } from '../components/Button' 5 import { CardVoteBlock, CardHeading } from '../components/Card' 6 import { 7 VoteHistoryTableCell, 8 VoteHistoryTableColumnCell, 9 VoteHistoryTableColumnCellDate, 10 } from '../components/card/CardCommunity' 11 import { VoteHistoryTable } from '../components/card/CardCommunity' 12 import { CardHeadingEndedVote } from '../components/card/CardVote/CardVote' 13 import { LinkInternal } from '../components/Link' 14 import { VoteChart } from '../components/votes/VoteChart' 15 import { VotePropose } from '../components/votes/VotePropose' 16 import { voteTypes } from '../constants/voteTypes' 17 import voting, { getVotingWinner } from '../helpers/voting' 18 import { useContracts } from '../hooks/useContracts' 19 import { DetailedVotingRoom } from '../models/smartContract' 20 import arrowDown from '../assets/images/arrowDown.svg' 21 import { useSendWakuVote } from '../hooks/useSendWakuVote' 22 import { WrapperBottom, WrapperTop } from '../constants/styles' 23 import { useUnverifiedVotes } from '../hooks/useUnverifiedVotes' 24 import { useVotingBatches } from '../hooks/useVotingBatches' 25 import { useAccount } from '../hooks/useAccount' 26 import { useWaku } from '../providers/waku/provider' 27 28 interface CardVoteMobileProps { 29 room: DetailedVotingRoom 30 } 31 32 export const CardVoteMobile = ({ room }: CardVoteMobileProps) => { 33 const { isActive, account } = useAccount() 34 const selectedVoted = voteTypes['Add'].for 35 const [sentVotesFor, setSentVotesFor] = useState(0) 36 const [sentVotesAgainst, setSentVotesAgainst] = useState(0) 37 const [verificationPeriod, setVerificationPeriod] = useState(false) 38 const [finalizationPeriod, setFinalizationPeriod] = useState(false) 39 const [voted, setVoted] = useState<null | boolean>(null) 40 41 useEffect(() => { 42 setVoted(null) 43 }, [account]) 44 45 const { votingContract } = useContracts() 46 const vote = voting.fromRoom(room) 47 const voteConstants = voteTypes[vote.type] 48 const castVotes = useContractFunction(votingContract, 'castVotes') 49 const { finalizeVotingLimit, batchedVotes } = useVotingBatches({ room }) 50 51 const finalizeVoting = useContractFunction(votingContract, 'finalizeVotingRoom') 52 53 useEffect(() => { 54 const checkPeriod = () => { 55 const now = Date.now() / 1000 56 const verificationStarted = room.verificationStartAt.toNumber() - now < 0 57 const verificationEnded = room.endAt.toNumber() - now < 0 58 const verificationPeriod = verificationStarted && !verificationEnded 59 const finalizationPeriod = verificationStarted && verificationEnded 60 setVerificationPeriod(verificationPeriod) 61 setFinalizationPeriod(finalizationPeriod) 62 } 63 64 checkPeriod() 65 66 const timer = setInterval(checkPeriod, 1000) 67 return () => clearInterval(timer) 68 }, []) 69 70 useEffect(() => { 71 if (finalizeVoting.state.status === 'Success' || castVotes.state.status === 'Success') { 72 history.go(0) 73 } 74 }, [finalizeVoting.state.status, castVotes.state.status]) 75 76 const winner = verificationPeriod ? 0 : getVotingWinner(vote) 77 78 const { 79 votesFor: votesForUnverified, 80 votesAgainst: votesAgainstUnverified, 81 voters, 82 } = useUnverifiedVotes(vote.ID, room.verificationStartAt, room.startAt) 83 84 const [proposingAmount, setProposingAmount] = useState(0) 85 86 const [showHistory, setShowHistory] = useState(false) 87 const isDisabled = room.details.votingHistory.length === 0 88 const { isConnected } = useWaku() 89 const sendWakuVote = useSendWakuVote() 90 91 const includeUnverifiedVotes = !winner || verificationPeriod 92 93 const votesFor = !includeUnverifiedVotes 94 ? vote.voteFor.toNumber() 95 : vote.voteFor.toNumber() + votesForUnverified + sentVotesFor 96 const votesAgainst = !includeUnverifiedVotes 97 ? vote.voteAgainst.toNumber() 98 : vote.voteAgainst.toNumber() + votesAgainstUnverified + sentVotesAgainst 99 100 const canVote = voted ? false : Boolean(account && !voters.includes(account)) 101 102 if (!vote) { 103 return <CardVoteBlock /> 104 } 105 return ( 106 <CardVoteBlock> 107 {verificationPeriod && ( 108 <CardHeadingEndedVote>Verification period in progress, please verify your vote.</CardHeadingEndedVote> 109 )} 110 {winner ? ( 111 <CardHeadingEndedVote> 112 SNT holders have decided <b>{winner == 1 ? voteConstants.against.verb : voteConstants.for.verb}</b> this 113 community to the directory! 114 </CardHeadingEndedVote> 115 ) : ( 116 !verificationPeriod && <CardHeadingMobile>{voteConstants.question}</CardHeadingMobile> 117 )} 118 <div> 119 <WrapperBottom> 120 <VoteChart 121 vote={vote} 122 voteWinner={winner} 123 isAnimation={true} 124 votesFor={votesFor} 125 votesAgainst={votesAgainst} 126 /> 127 </WrapperBottom> 128 {!winner && ( 129 <WrapperTop> 130 <VotePropose 131 vote={vote} 132 selectedVote={selectedVoted} 133 proposingAmount={proposingAmount} 134 setProposingAmount={setProposingAmount} 135 /> 136 </WrapperTop> 137 )} 138 {verificationPeriod && ( 139 <VoteBtnFinal 140 onClick={async () => { 141 await castVotes.send(batchedVotes) 142 143 setSentVotesFor(0) 144 setSentVotesAgainst(0) 145 }} 146 disabled={!isActive} 147 > 148 Verify votes 149 </VoteBtnFinal> 150 )} 151 {finalizationPeriod && ( 152 <VoteBtnFinal 153 onClick={() => finalizeVoting.send(room.roomNumber, finalizeVotingLimit < 1 ? 1 : finalizeVotingLimit)} 154 disabled={!isActive} 155 > 156 Finalize the vote <span>✍️</span> 157 </VoteBtnFinal> 158 )} 159 160 {!verificationPeriod && !finalizationPeriod && ( 161 <VotesBtns> 162 <VoteBtn 163 disabled={!isConnected || !canVote} 164 onClick={async () => { 165 await sendWakuVote(proposingAmount, room.roomNumber, 0) 166 setVoted(true) 167 setSentVotesAgainst(sentVotesAgainst + proposingAmount) 168 }} 169 > 170 {voteConstants.against.text} <span>{voteConstants.against.icon}</span> 171 </VoteBtn> 172 <VoteBtn 173 disabled={!isConnected || !canVote} 174 onClick={async () => { 175 await sendWakuVote(proposingAmount, room.roomNumber, 1) 176 setVoted(true) 177 setSentVotesFor(sentVotesFor + proposingAmount) 178 }} 179 > 180 {voteConstants.for.text} <span>{voteConstants.for.icon}</span> 181 </VoteBtn> 182 </VotesBtns> 183 )} 184 </div> 185 {!isDisabled && ( 186 <HistoryLink 187 className={showHistory ? 'opened' : ''} 188 onClick={() => setShowHistory(!showHistory)} 189 disabled={isDisabled} 190 > 191 Voting history 192 </HistoryLink> 193 )} 194 195 {showHistory && ( 196 <VoteHistoryTable> 197 <tbody> 198 <tr> 199 <VoteHistoryTableColumnCellDate>Date</VoteHistoryTableColumnCellDate> 200 <VoteHistoryTableColumnCell>Type</VoteHistoryTableColumnCell> 201 <VoteHistoryTableColumnCell>Result</VoteHistoryTableColumnCell> 202 </tr> 203 {room.details.votingHistory.map((vote) => { 204 return ( 205 <tr key={vote.ID}> 206 <VoteHistoryTableCell>{vote.date.toLocaleDateString()}</VoteHistoryTableCell> 207 <VoteHistoryTableCell>{vote.type}</VoteHistoryTableCell> 208 <VoteHistoryTableCell>{vote.result}</VoteHistoryTableCell> 209 </tr> 210 ) 211 })} 212 </tbody> 213 </VoteHistoryTable> 214 )} 215 </CardVoteBlock> 216 ) 217 } 218 219 const CardHeadingMobile = styled(CardHeading)` 220 margin-bottom: 24px; 221 ` 222 const VoteBtnFinal = styled(VoteBtn)` 223 width: 100%; 224 ` 225 export const HistoryLink = styled(LinkInternal)` 226 width: 120px; 227 position: relative; 228 margin: 24px 0; 229 text-align: start; 230 padding: 0; 231 232 &::after { 233 content: ''; 234 width: 24px; 235 height: 24px; 236 position: absolute; 237 top: 50%; 238 right: 0; 239 transform: translateY(-50%); 240 background-image: url(${arrowDown}); 241 background-size: contain; 242 background-repeat: no-repeat; 243 } 244 245 &.opened { 246 &::after { 247 content: ''; 248 width: 24px; 249 height: 24px; 250 position: absolute; 251 top: 50%; 252 right: 0; 253 transform: translateY(-50%) rotate(180deg); 254 background-image: url(${arrowDown}); 255 background-size: contain; 256 background-repeat: no-repeat; 257 } 258 } 259 `