/ packages / DApp / src / componentsMobile / CardVoteMobile.tsx
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  `