[FE] Batching functionality (#88)
This commit is contained in:
parent
afdfe715f5
commit
2ea23bd37d
|
@ -1,7 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { GlobalStyle } from './providers/GlobalStyle'
|
||||
import { NotificationsList } from './components/NotificationsList'
|
||||
import { MobileRouter } from './pagesMobile/MobileRouter'
|
||||
import { DesktopRouter } from './pages/DesktopRouter'
|
||||
|
||||
|
@ -25,7 +24,6 @@ export function App() {
|
|||
<Page>
|
||||
<GlobalStyle />
|
||||
{mobileVersion ? <MobileRouter /> : <DesktopRouter />}
|
||||
{/* <NotificationsList /> */}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { ProposeButton } from './Button'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
|
||||
|
|
|
@ -37,6 +37,28 @@ export function NotificationItem({ publicKey, text, transaction }: NotificationI
|
|||
</NotificationBlock>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
interface NotificationItemPlainProps {
|
||||
text: string
|
||||
}
|
||||
|
||||
export function NotificationItemPlain({ text }: NotificationItemPlainProps) {
|
||||
const [show, setShow] = useState(true)
|
||||
|
||||
if (show) {
|
||||
return (
|
||||
<NotificationBlock>
|
||||
<NotificationContent>
|
||||
<NotificationText>{text}</NotificationText>
|
||||
</NotificationContent>
|
||||
<NotificationCloseButton onClick={() => setShow(false)} />
|
||||
</NotificationBlock>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
@ -3,44 +3,83 @@ import React from 'react'
|
|||
import styled from 'styled-components'
|
||||
import { AnimationNotification, AnimationNotificationMobile } from '../constants/animation'
|
||||
import { useContracts } from '../hooks/useContracts'
|
||||
import { NotificationItem } from './NotificationItem'
|
||||
import { NotificationItem, NotificationItemPlain } from './NotificationItem'
|
||||
|
||||
export function NotificationsList() {
|
||||
interface Props {
|
||||
type: 'votes' | 'featured'
|
||||
}
|
||||
|
||||
export function NotificationsList({ type }: Props) {
|
||||
const { notifications } = useNotifications()
|
||||
const { votingContract } = useContracts()
|
||||
const { votingContract, featuredVotingContract } = useContracts()
|
||||
|
||||
const getParsedLog = (log: any, type: 'votes' | 'featured') => {
|
||||
switch (type) {
|
||||
case 'votes': {
|
||||
return votingContract.interface.parseLog(log)
|
||||
}
|
||||
case 'featured': {
|
||||
return featuredVotingContract.interface.parseLog(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const parseVoting = (parsedLog: any) => {
|
||||
let text = ''
|
||||
if (parsedLog.name === 'VotingRoomStarted') {
|
||||
text = ' voting room started.'
|
||||
}
|
||||
if (parsedLog.name === 'VotingRoomFinalized') {
|
||||
if (parsedLog.args.passed == true) {
|
||||
if (parsedLog.args.voteType === 1) {
|
||||
text = ' is now in the communities directory!'
|
||||
}
|
||||
|
||||
if (parsedLog.args.voteType === 0) {
|
||||
text = ' is now removed from communities directory!'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
const parseFeatured = (parsedLog: any) => {
|
||||
let text = ''
|
||||
if (parsedLog.name === 'VotingStarted') {
|
||||
text = 'Featured voting started.'
|
||||
}
|
||||
if (parsedLog.name === 'VotingFinalized') {
|
||||
text = 'Featured voting was finalized.'
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
return (
|
||||
<NotificationsWrapper>
|
||||
{notifications.map((notification) => {
|
||||
if ('receipt' in notification) {
|
||||
return notification.receipt.logs.map((log) => {
|
||||
// this needs to be updated so it takes into account also interface of featuredVotingContract
|
||||
const parsedLog = votingContract.interface.parseLog(log)
|
||||
const parsedLog = getParsedLog(log, type)
|
||||
|
||||
let text
|
||||
if (parsedLog.name === 'VotingRoomStarted') {
|
||||
text = ' voting room started.'
|
||||
let res = ''
|
||||
if (type === 'votes') {
|
||||
res = parseVoting(parsedLog)
|
||||
} else if (type === 'featured') {
|
||||
res = parseFeatured(parsedLog)
|
||||
}
|
||||
if (parsedLog.name === 'VotingRoomFinalized') {
|
||||
if (parsedLog.args.passed == true) {
|
||||
if (parsedLog.args.voteType === 1) {
|
||||
text = ' is now in the communities directory!'
|
||||
}
|
||||
|
||||
if (parsedLog.args.voteType === 0) {
|
||||
text = ' is now removed from communities directory!'
|
||||
}
|
||||
}
|
||||
}
|
||||
if (text) {
|
||||
if (res && type === 'votes') {
|
||||
return (
|
||||
<NotificationItem
|
||||
key={log.transactionHash}
|
||||
publicKey={parsedLog.args.publicKey}
|
||||
text={text}
|
||||
text={res}
|
||||
transaction={notification.transaction}
|
||||
/>
|
||||
)
|
||||
} else if (res && type === 'featured') {
|
||||
return <NotificationItemPlain key={log.transactionHash} text={res} />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -27,13 +27,6 @@ export function Rules() {
|
|||
directory. Otherwise, a new vote for removal can be submitted after about 30 days.
|
||||
</RuleText>
|
||||
</Rule>
|
||||
{/* <Rule>
|
||||
<RuleIcon>✌️️</RuleIcon>
|
||||
<RuleText>
|
||||
The minimum amount of SNT needed to start a new vote doubles with every failed vote attempt (for both addition
|
||||
and removal votes).
|
||||
</RuleText>
|
||||
</Rule> */}
|
||||
<Rule>
|
||||
<RuleIcon>⏳</RuleIcon>
|
||||
<RuleText>
|
||||
|
@ -48,14 +41,6 @@ export function Rules() {
|
|||
a finalization transaction.
|
||||
</RuleText>
|
||||
</Rule>
|
||||
{/* <Rule>
|
||||
<RuleIcon>⚠️</RuleIcon>
|
||||
<RuleText>
|
||||
If a single vote of more than 2,000,000 SNT votes for is made in favor of the removal of a community, the
|
||||
remaining vote duration shortens to 24 hours. This shortening of the vote duration can be reversed if a single
|
||||
vote of more than 2,000,000 SNT is made against the removal.
|
||||
</RuleText>
|
||||
</Rule> */}
|
||||
<Rule>
|
||||
<RuleIcon>⭐</RuleIcon>
|
||||
<RuleText>
|
||||
|
|
|
@ -2,18 +2,18 @@ import React from 'react'
|
|||
import styled from 'styled-components'
|
||||
import backgroundImage from '../assets/images/curve-shape.svg'
|
||||
import { Colors } from '../constants/styles'
|
||||
import { getFeaturedVotingState } from '../helpers/featuredVoting'
|
||||
import { useFeaturedVotingState } from '../hooks/useFeaturedVotingState'
|
||||
import { useFeaturedVotes } from '../hooks/useFeaturedVotes'
|
||||
import { formatTimeLeft } from '../helpers/fomatTimeLeft'
|
||||
|
||||
export const WeeklyFeature = () => {
|
||||
const { activeVoting } = useFeaturedVotes()
|
||||
if (!activeVoting) {
|
||||
const featuredVotingState = useFeaturedVotingState(activeVoting)
|
||||
|
||||
if (!activeVoting || !featuredVotingState) {
|
||||
return null
|
||||
}
|
||||
|
||||
const featuredVotingState = getFeaturedVotingState(activeVoting)
|
||||
|
||||
if (featuredVotingState === 'ended') {
|
||||
return (
|
||||
<div>
|
||||
|
|
|
@ -138,7 +138,7 @@ export const CardCommunityBlock = styled.div`
|
|||
|
||||
&.notModal {
|
||||
@media (max-width: 768px) {
|
||||
align-items: flex-end;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -10,7 +10,7 @@ import { VoteConfirmModal } from './VoteConfirmModal'
|
|||
import { useContractCall, useEthers } from '@usedapp/core'
|
||||
import { VoteBtn } from '../Button'
|
||||
import { useFeaturedVotes } from '../../hooks/useFeaturedVotes'
|
||||
import { getFeaturedVotingState } from '../../helpers/featuredVoting'
|
||||
import { useFeaturedVotingState } from '../../hooks/useFeaturedVotingState'
|
||||
import { useContracts } from '../../hooks/useContracts'
|
||||
import { BigNumber } from 'ethers'
|
||||
|
||||
|
@ -38,7 +38,7 @@ export const CardFeature = ({ community, featured }: CardFeatureProps) => {
|
|||
const [heading, setHeading] = useState('Weekly Feature vote')
|
||||
const [icon, setIcon] = useState('⭐')
|
||||
const { activeVoting, alreadyVoted } = useFeaturedVotes()
|
||||
const featuredVotingState = getFeaturedVotingState(activeVoting)
|
||||
const featuredVotingState = useFeaturedVotingState(activeVoting)
|
||||
|
||||
const [savedVotes] =
|
||||
useContractCall({
|
||||
|
|
|
@ -11,13 +11,12 @@ import { getVotingWinner } from '../../../helpers/voting'
|
|||
import { VoteAnimatedModal } from './../VoteAnimatedModal'
|
||||
import voting from '../../../helpers/voting'
|
||||
import { DetailedVotingRoom } from '../../../models/smartContract'
|
||||
import { useRoomAggregateVotes } from '../../../hooks/useRoomAggregateVotes'
|
||||
import styled from 'styled-components'
|
||||
import { Modal } from './../../Modal'
|
||||
import { VoteBtn, VotesBtns } from '../../Button'
|
||||
import { CardHeading, CardVoteBlock } from '../../Card'
|
||||
import { useVotesAggregate } from '../../../hooks/useVotesAggregate'
|
||||
import { useUnverifiedVotes } from '../../../hooks/useUnverifiedVotes'
|
||||
import { useVotingBatches } from '../../../hooks/useVotingBatches'
|
||||
|
||||
interface CardVoteProps {
|
||||
room: DetailedVotingRoom
|
||||
|
@ -33,6 +32,11 @@ export const CardVote = ({ room, hideModalFunction }: CardVoteProps) => {
|
|||
const [sentVotesFor, setSentVotesFor] = useState(0)
|
||||
const [sentVotesAgainst, setSentVotesAgainst] = useState(0)
|
||||
const [voted, setVoted] = useState<null | boolean>(null)
|
||||
const [verificationPeriod, setVerificationPeriod] = useState(false)
|
||||
const [finalizationPeriod, setFinalizationPeriod] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { finalizeVotingLimit, batchCount, batchDoneCount, beingEvaluated, beingFinalized, batchedVotes } =
|
||||
useVotingBatches({ room })
|
||||
|
||||
useEffect(() => {
|
||||
setVoted(null)
|
||||
|
@ -41,11 +45,7 @@ export const CardVote = ({ room, hideModalFunction }: CardVoteProps) => {
|
|||
const { votingContract } = useContracts()
|
||||
const vote = voting.fromRoom(room)
|
||||
const voteConstants = voteTypes[vote.type]
|
||||
const { votes } = useVotesAggregate(vote.ID, room.verificationStartAt, room.startAt)
|
||||
const castVotes = useContractFunction(votingContract, 'castVotes')
|
||||
|
||||
room = useRoomAggregateVotes(room, showConfirmModal)
|
||||
|
||||
const finalizeVoting = useContractFunction(votingContract, 'finalizeVotingRoom')
|
||||
|
||||
const setNext = (val: boolean) => {
|
||||
|
@ -60,10 +60,28 @@ export const CardVote = ({ room, hideModalFunction }: CardVoteProps) => {
|
|||
setShowConfirmModal(val)
|
||||
}
|
||||
|
||||
const now = Date.now() / 1000
|
||||
const verificationStarted = room.verificationStartAt.toNumber() - now < 0
|
||||
const verificationEnded = room.endAt.toNumber() - now < 0
|
||||
const verificationPeriod = verificationStarted && !verificationEnded
|
||||
useEffect(() => {
|
||||
const checkPeriod = () => {
|
||||
const now = Date.now() / 1000
|
||||
const verificationStarted = room.verificationStartAt.toNumber() - now < 0
|
||||
const verificationEnded = room.endAt.toNumber() - now < 0
|
||||
const verificationPeriod = verificationStarted && !verificationEnded
|
||||
const finalizationPeriod = verificationStarted && verificationEnded
|
||||
setVerificationPeriod(verificationPeriod)
|
||||
setFinalizationPeriod(finalizationPeriod)
|
||||
}
|
||||
|
||||
checkPeriod()
|
||||
|
||||
const timer = setInterval(checkPeriod, 1000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizeVoting.state.status === 'Success' || castVotes.state.status === 'Success') {
|
||||
history.go(0)
|
||||
}
|
||||
}, [finalizeVoting.state.status, castVotes.state.status])
|
||||
|
||||
const winner = verificationPeriod ? 0 : getVotingWinner(vote)
|
||||
|
||||
|
@ -132,10 +150,17 @@ export const CardVote = ({ room, hideModalFunction }: CardVoteProps) => {
|
|||
)}
|
||||
|
||||
{verificationPeriod && (
|
||||
<CardHeadingEndedVote>Verification period in progress, please verify your vote.</CardHeadingEndedVote>
|
||||
<CardHeadingEndedVote>
|
||||
Verification period in progress, please verify your vote.{' '}
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
<br />({beingEvaluated ? batchDoneCount : 0}/{batchCount} verified)
|
||||
</>
|
||||
)}
|
||||
</CardHeadingEndedVote>
|
||||
)}
|
||||
|
||||
{winner ? (
|
||||
{finalizationPeriod ? (
|
||||
<CardHeadingEndedVote>
|
||||
SNT holders have decided <b>{winner == 1 ? voteConstants.against.verb : voteConstants.for.verb}</b> this
|
||||
community to the directory!
|
||||
|
@ -157,24 +182,36 @@ export const CardVote = ({ room, hideModalFunction }: CardVoteProps) => {
|
|||
{verificationPeriod && (
|
||||
<VoteBtnFinal
|
||||
onClick={async () => {
|
||||
await castVotes.send(votes)
|
||||
setLoading(true)
|
||||
await castVotes.send(batchedVotes)
|
||||
|
||||
setSentVotesFor(0)
|
||||
setSentVotesAgainst(0)
|
||||
setLoading(false)
|
||||
}}
|
||||
disabled={!account}
|
||||
disabled={!account || loading}
|
||||
>
|
||||
Verify votes
|
||||
{loading ? 'Waiting...' : 'Verify votes'}
|
||||
</VoteBtnFinal>
|
||||
)}
|
||||
{Boolean(winner) && (
|
||||
// note: @jkbktl PR
|
||||
<VoteBtnFinal onClick={() => finalizeVoting.send(room.roomNumber, 1)} disabled={!account}>
|
||||
Finalize the vote <span>✍️</span>
|
||||
{finalizationPeriod && (
|
||||
<VoteBtnFinal
|
||||
onClick={() => finalizeVoting.send(room.roomNumber, finalizeVotingLimit < 1 ? 1 : finalizeVotingLimit)}
|
||||
disabled={!account}
|
||||
>
|
||||
<>
|
||||
Finalize the vote <span>✍️</span>
|
||||
<br />
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
({beingFinalized ? batchDoneCount : 0}/{batchCount} finalized)
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</VoteBtnFinal>
|
||||
)}
|
||||
|
||||
{!verificationPeriod && !winner && (
|
||||
{!verificationPeriod && !finalizationPeriod && (
|
||||
<VotesBtns>
|
||||
<VoteBtn
|
||||
disabled={!canVote}
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import React from 'react'
|
||||
import { votingFromRoom } from '../../helpers/voting'
|
||||
import { CommunityDetail } from '../../models/community'
|
||||
import { VotingRoom } from '../../models/smartContract'
|
||||
import { CardVoteBlock } from '../Card'
|
||||
import { CardVote } from './CardVote/CardVote'
|
||||
import { Modal } from '../Modal'
|
||||
|
||||
export interface OngoingVoteProps {
|
||||
community: CommunityDetail
|
||||
setShowOngoingVote: (show: boolean) => void
|
||||
room: VotingRoom
|
||||
}
|
||||
|
||||
export function OngoingVote({ community, setShowOngoingVote, room }: OngoingVoteProps) {
|
||||
const vote = votingFromRoom(room)
|
||||
const detailedVoting = { ...room, details: community }
|
||||
if (!vote) {
|
||||
return <CardVoteBlock />
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal heading={`${vote?.type} ${community.name}?`} setShowModal={setShowOngoingVote}>
|
||||
<CardVote hideModalFunction={setShowOngoingVote} room={detailedVoting} />
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -32,7 +32,7 @@ export function RemoveAmountPicker({ community, setShowConfirmModal }: RemoveAmo
|
|||
if (community.votingHistory && community.votingHistory.length > 0) {
|
||||
const lastVote = community.votingHistory[community.votingHistory.length - 1]
|
||||
const lastVoteDate = lastVote.date
|
||||
if (timespan(lastVoteDate) < 30 && lastVote.type === 'Remove') {
|
||||
if (timespan(lastVoteDate) < 3 && lastVote.type === 'Remove') {
|
||||
return (
|
||||
<WarningWrapRemoval>
|
||||
<Warning
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
import { useContractFunction } from '@usedapp/core'
|
||||
import React from 'react'
|
||||
import { useContracts } from '../../hooks/useContracts'
|
||||
import { useVotesAggregate } from '../../hooks/useVotesAggregate'
|
||||
import { CurrentVoting } from '../../models/community'
|
||||
import { VoteSendingBtn } from '../Button'
|
||||
import { BigNumber } from 'ethers'
|
||||
import { addCommas } from '../../helpers/addCommas'
|
||||
import { VotingRoom } from '../../models/smartContract'
|
||||
|
||||
interface VoteSubmitButtonProps {
|
||||
vote: CurrentVoting
|
||||
room: VotingRoom
|
||||
}
|
||||
|
||||
export function VoteSubmitButton({ vote, room }: VoteSubmitButtonProps) {
|
||||
const { votes } = useVotesAggregate(vote.ID, room.verificationStartAt, room.startAt)
|
||||
const { votingContract } = useContracts()
|
||||
const { send } = useContractFunction(votingContract, 'castVotes')
|
||||
const voteAmount = votes.reduce((prev, curr) => prev.add(curr[2]), BigNumber.from(0))
|
||||
if (votes.length > 0) {
|
||||
return (
|
||||
<VoteSendingBtn onClick={() => send(votes)}> {addCommas(voteAmount.toString())} votes need saving</VoteSendingBtn>
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
|
@ -1,26 +1,46 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { InfoWrap, PageInfo } from '../PageInfo'
|
||||
import { useContractFunction, useEthers } from '@usedapp/core'
|
||||
import { ConnectButton } from '../ConnectButton'
|
||||
import { ProposeButton } from '../Button'
|
||||
import { useFeaturedVotes } from '../../hooks/useFeaturedVotes'
|
||||
import { getFeaturedVotingState } from '../../helpers/featuredVoting'
|
||||
import { useFeaturedVotingState } from '../../hooks/useFeaturedVotingState'
|
||||
import { useContracts } from '../../hooks/useContracts'
|
||||
import { useWaku } from '../../providers/waku/provider'
|
||||
import { mapFeaturesVotes, receiveWakuFeature } from '../../helpers/receiveWakuFeature'
|
||||
import { config } from '../../config'
|
||||
import { useTypedFeatureVote } from '../../hooks/useTypedFeatureVote'
|
||||
import { useFeaturedBatches } from '../../hooks/useFeaturedBatches'
|
||||
|
||||
export function DirectoryInfo() {
|
||||
const { account } = useEthers()
|
||||
const { featuredVotingContract } = useContracts()
|
||||
const { getTypedFeatureVote } = useTypedFeatureVote()
|
||||
const { activeVoting } = useFeaturedVotes()
|
||||
const { waku } = useWaku()
|
||||
|
||||
const featuredVotingState = getFeaturedVotingState(activeVoting)
|
||||
const { activeVoting } = useFeaturedVotes()
|
||||
const featuredVotingState = useFeaturedVotingState(activeVoting)
|
||||
const castVotes = useContractFunction(featuredVotingContract, 'castVotes')
|
||||
const finalizeVoting = useContractFunction(featuredVotingContract, 'finalizeVoting')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { finalizeVotingLimit, batchCount, batchDoneCount, beingEvaluated, beingFinalized } = useFeaturedBatches()
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizeVoting.state.status === 'Success' || castVotes.state.status === 'Success') {
|
||||
history.go(0)
|
||||
}
|
||||
}, [finalizeVoting.state.status, castVotes.state.status])
|
||||
|
||||
if (!activeVoting) {
|
||||
return (
|
||||
<InfoWrap>
|
||||
<PageInfo
|
||||
heading="Current directory"
|
||||
text="Vote on your favourite communities being included in
|
||||
Weekly Featured Communities"
|
||||
/>
|
||||
</InfoWrap>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<InfoWrap>
|
||||
|
@ -29,27 +49,50 @@ export function DirectoryInfo() {
|
|||
text="Vote on your favourite communities being included in
|
||||
Weekly Featured Communities"
|
||||
/>
|
||||
|
||||
{!account && <ConnectButton />}
|
||||
{account && featuredVotingState === 'verification' && (
|
||||
<ProposeButton
|
||||
onClick={async () => {
|
||||
setLoading(true)
|
||||
const { votesToSend } = await receiveWakuFeature(waku, config.wakuConfig.wakuFeatureTopic, activeVoting!)
|
||||
const votes = mapFeaturesVotes(votesToSend, getTypedFeatureVote)
|
||||
|
||||
await castVotes.send(votes)
|
||||
const batchedVotes = votes.slice(
|
||||
batchDoneCount * config.votesLimit,
|
||||
batchDoneCount * config.votesLimit + finalizeVotingLimit
|
||||
)
|
||||
|
||||
await castVotes.send(batchedVotes)
|
||||
setLoading(false)
|
||||
}}
|
||||
>
|
||||
Verify Weekly featured
|
||||
{loading ? (
|
||||
'Waiting...'
|
||||
) : (
|
||||
<>
|
||||
Verify Weekly featured{' '}
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
({beingEvaluated ? batchDoneCount : 0}/{batchCount} verified)
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ProposeButton>
|
||||
)}
|
||||
{account && featuredVotingState === 'ended' && (
|
||||
<ProposeButton
|
||||
onClick={async () => {
|
||||
// note: @jkbktl PR
|
||||
await finalizeVoting.send(activeVoting?.evaluatingPos)
|
||||
onClick={() => {
|
||||
finalizeVoting.send(finalizeVotingLimit < 1 ? 1 : finalizeVotingLimit)
|
||||
}}
|
||||
>
|
||||
Finalize Weekly featured
|
||||
Finalize Weekly featured{' '}
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
({beingFinalized ? batchDoneCount : 0}/{batchCount} finalized)
|
||||
</>
|
||||
)}
|
||||
</ProposeButton>
|
||||
)}
|
||||
</InfoWrap>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { SearchEmpty } from '../SearchEmpty'
|
||||
import { Colors } from '../../constants/styles'
|
||||
import { useFeaturedCommunities } from '../../hooks/useFeaturedCommunities'
|
||||
import { DirectoryCard } from '../directory/DirectoryCard'
|
||||
import { DirectoryCardSkeleton } from '../directory/DirectoryCardSkeleton'
|
||||
|
@ -14,7 +14,11 @@ export function FeaturedCards() {
|
|||
}
|
||||
|
||||
if (publicKeys.length === 0) {
|
||||
return <SearchEmpty />
|
||||
return (
|
||||
<EmptyWrap>
|
||||
<span>No communities were featured last week.</span>
|
||||
</EmptyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
if (communities.length === 0) {
|
||||
|
@ -37,3 +41,24 @@ const Voting = styled.div`
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
export const EmptyWrap = styled.div`
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 90px;
|
||||
background: ${Colors.White};
|
||||
font-size: 22px;
|
||||
line-height: 38px;
|
||||
z-index: 99;
|
||||
|
||||
& > p {
|
||||
font-weight: bold;
|
||||
font-size: 64px;
|
||||
line-height: 64%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -7,6 +7,7 @@ import { VoteType, voteTypes } from './../../constants/voteTypes'
|
|||
import { CurrentVoting } from '../../models/community'
|
||||
import { VoteGraphBar } from './VoteGraphBar'
|
||||
import { formatTimeLeft, formatTimeLeftVerification } from '../../helpers/fomatTimeLeft'
|
||||
import { useTimeLeft } from '../../hooks/useTimeLeft'
|
||||
export interface VoteChartProps {
|
||||
vote: CurrentVoting
|
||||
votesFor: number
|
||||
|
@ -43,6 +44,9 @@ export function VoteChart({
|
|||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
|
||||
const timeLeft = useTimeLeft(vote.votingEndAt)
|
||||
const timeLeftVerification = useTimeLeft(vote.verificationEndAt)
|
||||
|
||||
const voteConstants = voteTypes[vote.type]
|
||||
|
||||
const voteSum = votesFor + votesAgainst
|
||||
|
@ -74,9 +78,8 @@ export function VoteChart({
|
|||
<span style={{ fontWeight: 'normal' }}>SNT</span>
|
||||
</span>
|
||||
</VoteBox>
|
||||
{/* todo: wrapper component with timer and setInterval */}
|
||||
<TimeLeft className={selectedVote ? '' : 'notModal'}>
|
||||
{vote.timeLeft > 0 ? formatTimeLeft(vote.timeLeft) : formatTimeLeftVerification(vote.timeLeftVerification)}
|
||||
{timeLeft > 0 ? formatTimeLeft(timeLeft) : formatTimeLeftVerification(timeLeftVerification)}
|
||||
</TimeLeft>
|
||||
<VoteBox
|
||||
style={{
|
||||
|
@ -107,7 +110,7 @@ export function VoteChart({
|
|||
voteWinner={voteWinner}
|
||||
isAnimation={isAnimation}
|
||||
/>
|
||||
<TimeLeftMobile className={selectedVote ? '' : 'notModal'}>{formatTimeLeft(vote.timeLeft)}</TimeLeftMobile>
|
||||
<TimeLeftMobile className={selectedVote ? '' : 'notModal'}>{formatTimeLeft(timeLeft)}</TimeLeftMobile>
|
||||
</VoteGraphBarWrap>
|
||||
</Votes>
|
||||
)
|
||||
|
|
|
@ -20,9 +20,8 @@ import { DetailedVotingRoom } from '../models/smartContract'
|
|||
import arrowDown from '../assets/images/arrowDown.svg'
|
||||
import { useSendWakuVote } from '../hooks/useSendWakuVote'
|
||||
import { WrapperBottom, WrapperTop } from '../constants/styles'
|
||||
import { useRoomAggregateVotes } from '../hooks/useRoomAggregateVotes'
|
||||
import { useVotesAggregate } from '../hooks/useVotesAggregate'
|
||||
import { useUnverifiedVotes } from '../hooks/useUnverifiedVotes'
|
||||
import { useVotingBatches } from '../hooks/useVotingBatches'
|
||||
|
||||
interface CardVoteMobileProps {
|
||||
room: DetailedVotingRoom
|
||||
|
@ -33,6 +32,8 @@ export const CardVoteMobile = ({ room }: CardVoteMobileProps) => {
|
|||
const selectedVoted = voteTypes['Add'].for
|
||||
const [sentVotesFor, setSentVotesFor] = useState(0)
|
||||
const [sentVotesAgainst, setSentVotesAgainst] = useState(0)
|
||||
const [verificationPeriod, setVerificationPeriod] = useState(false)
|
||||
const [finalizationPeriod, setFinalizationPeriod] = useState(false)
|
||||
const [voted, setVoted] = useState<null | boolean>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -42,16 +43,33 @@ export const CardVoteMobile = ({ room }: CardVoteMobileProps) => {
|
|||
const { votingContract } = useContracts()
|
||||
const vote = voting.fromRoom(room)
|
||||
const voteConstants = voteTypes[vote.type]
|
||||
const { votes } = useVotesAggregate(vote.ID, room.verificationStartAt, room.startAt)
|
||||
const castVotes = useContractFunction(votingContract, 'castVotes')
|
||||
const { finalizeVotingLimit, batchedVotes } = useVotingBatches({ room })
|
||||
|
||||
const finalizeVoting = useContractFunction(votingContract, 'finalizeVotingRoom')
|
||||
room = useRoomAggregateVotes(room, false)
|
||||
|
||||
const now = Date.now() / 1000
|
||||
const verificationStarted = room.verificationStartAt.toNumber() - now < 0
|
||||
const verificationEnded = room.endAt.toNumber() - now < 0
|
||||
const verificationPeriod = verificationStarted && !verificationEnded
|
||||
useEffect(() => {
|
||||
const checkPeriod = () => {
|
||||
const now = Date.now() / 1000
|
||||
const verificationStarted = room.verificationStartAt.toNumber() - now < 0
|
||||
const verificationEnded = room.endAt.toNumber() - now < 0
|
||||
const verificationPeriod = verificationStarted && !verificationEnded
|
||||
const finalizationPeriod = verificationStarted && verificationEnded
|
||||
setVerificationPeriod(verificationPeriod)
|
||||
setFinalizationPeriod(finalizationPeriod)
|
||||
}
|
||||
|
||||
checkPeriod()
|
||||
|
||||
const timer = setInterval(checkPeriod, 1000)
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizeVoting.state.status === 'Success' || castVotes.state.status === 'Success') {
|
||||
history.go(0)
|
||||
}
|
||||
}, [finalizeVoting.state.status, castVotes.state.status])
|
||||
|
||||
const winner = verificationPeriod ? 0 : getVotingWinner(vote)
|
||||
|
||||
|
@ -117,7 +135,7 @@ export const CardVoteMobile = ({ room }: CardVoteMobileProps) => {
|
|||
{verificationPeriod && (
|
||||
<VoteBtnFinal
|
||||
onClick={async () => {
|
||||
await castVotes.send(votes)
|
||||
await castVotes.send(batchedVotes)
|
||||
|
||||
setSentVotesFor(0)
|
||||
setSentVotesAgainst(0)
|
||||
|
@ -127,13 +145,16 @@ export const CardVoteMobile = ({ room }: CardVoteMobileProps) => {
|
|||
Verify votes
|
||||
</VoteBtnFinal>
|
||||
)}
|
||||
{Boolean(winner) && (
|
||||
<VoteBtnFinal onClick={() => finalizeVoting.send(room.roomNumber)} disabled={!account}>
|
||||
{finalizationPeriod && (
|
||||
<VoteBtnFinal
|
||||
onClick={() => finalizeVoting.send(room.roomNumber, finalizeVotingLimit < 1 ? 1 : finalizeVotingLimit)}
|
||||
disabled={!account}
|
||||
>
|
||||
Finalize the vote <span>✍️</span>
|
||||
</VoteBtnFinal>
|
||||
)}
|
||||
|
||||
{!verificationPeriod && !winner && (
|
||||
{!verificationPeriod && !finalizationPeriod && (
|
||||
<VotesBtns>
|
||||
<VoteBtn
|
||||
disabled={!canVote}
|
||||
|
|
|
@ -15,16 +15,32 @@ import { CommunitySkeleton } from '../components/skeleton/CommunitySkeleton'
|
|||
import { HeaderVotingMobile } from './VotingMobile'
|
||||
import { ConnectMobile } from './ConnectMobile'
|
||||
import { HistoryLink } from './CardVoteMobile'
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import { useContractCall, useContractFunction, useEthers } from '@usedapp/core'
|
||||
import { useGetCurrentVoting } from '../hooks/useGetCurrentVoting'
|
||||
import { MobileHeading, MobileBlock, MobileTop, MobileWrap, ColumnFlexDiv } from '../constants/styles'
|
||||
import { useFeaturedVotes } from '../hooks/useFeaturedVotes'
|
||||
import { useContracts } from '../hooks/useContracts'
|
||||
import { useSendWakuFeature } from '../hooks/useSendWakuFeature'
|
||||
import { useFeaturedVotingState } from '../hooks/useFeaturedVotingState'
|
||||
|
||||
export function FeatureMobile() {
|
||||
const { publicKey } = useParams<{ publicKey: string }>()
|
||||
const [community] = useCommunities([publicKey])
|
||||
const [proposingAmount, setProposingAmount] = useState(0)
|
||||
const { account } = useEthers()
|
||||
const disabled = proposingAmount === 0 || !account
|
||||
const sendWaku = useSendWakuFeature()
|
||||
const { activeVoting } = useFeaturedVotes()
|
||||
const { featuredVotingContract } = useContracts()
|
||||
const { send } = useContractFunction(featuredVotingContract, 'initializeVoting')
|
||||
const featuredVotingState = useFeaturedVotingState(activeVoting)
|
||||
const [isInCooldownPeriod] =
|
||||
useContractCall({
|
||||
abi: featuredVotingContract.interface,
|
||||
address: featuredVotingContract.address,
|
||||
method: 'isInCooldownPeriod',
|
||||
args: [community.publicKey],
|
||||
}) ?? []
|
||||
const inFeatured = isInCooldownPeriod
|
||||
|
||||
const [showHistory, setShowHistory] = useState(false)
|
||||
const isDisabled = community ? community.votingHistory.length === 0 : false
|
||||
|
@ -47,9 +63,19 @@ export function FeatureMobile() {
|
|||
<MobileBlock>
|
||||
<FeatureHeading>{`Feature ${community.name}?`}</FeatureHeading>
|
||||
<VotePropose setProposingAmount={setProposingAmount} proposingAmount={proposingAmount} />
|
||||
<FeatureBtn disabled>Coming soon!</FeatureBtn>
|
||||
|
||||
<FeatureBtn disabled={disabled}>
|
||||
<FeatureBtn
|
||||
disabled={
|
||||
!account || inFeatured || featuredVotingState === 'verification' || featuredVotingState === 'ended'
|
||||
}
|
||||
onClick={async () => {
|
||||
if (!activeVoting) {
|
||||
await send(community.publicKey, proposingAmount)
|
||||
} else {
|
||||
await sendWaku(proposingAmount, community.publicKey)
|
||||
}
|
||||
history.go(-1)
|
||||
}}
|
||||
>
|
||||
Feature this community! <span style={{ fontSize: '20px' }}>⭐️</span>
|
||||
</FeatureBtn>
|
||||
{currentVoting && (
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface Config {
|
|||
wakuFeatureTopic: string
|
||||
}
|
||||
daapConfig: DAppConfig
|
||||
votesLimit: number
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,6 +42,7 @@ const configs: Record<typeof process.env.ENV, Config> = {
|
|||
expirationPeriod: 50000,
|
||||
},
|
||||
},
|
||||
votesLimit: 2,
|
||||
},
|
||||
/**
|
||||
* Preview/Stage.
|
||||
|
@ -64,6 +66,7 @@ const configs: Record<typeof process.env.ENV, Config> = {
|
|||
expirationPeriod: 50000,
|
||||
},
|
||||
},
|
||||
votesLimit: 2,
|
||||
},
|
||||
/**
|
||||
* Production.
|
||||
|
@ -82,6 +85,7 @@ const configs: Record<typeof process.env.ENV, Config> = {
|
|||
expirationPeriod: 50000,
|
||||
},
|
||||
},
|
||||
votesLimit: 400,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ export const communities: Array<CommunityDetail> = [
|
|||
type: 'Remove',
|
||||
voteFor: BigNumber.from(16740235),
|
||||
voteAgainst: BigNumber.from(6740235),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
},
|
||||
featureVotes: BigNumber.from(62142321),
|
||||
directoryInfo: {
|
||||
|
@ -145,6 +147,8 @@ export const communities: Array<CommunityDetail> = [
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(16740235),
|
||||
voteAgainst: BigNumber.from(126740235),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -165,6 +169,8 @@ export const communities: Array<CommunityDetail> = [
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(16740235),
|
||||
voteAgainst: BigNumber.from(126740235),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { FeaturedVoting } from '../models/smartContract'
|
||||
|
||||
type Phase = 'not started' | 'voting' | 'verification' | 'ended' | null
|
||||
|
||||
export function getFeaturedVotingState(featuredVoting: FeaturedVoting | null): Phase {
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000)
|
||||
|
||||
if (!featuredVoting) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (featuredVoting.startAt.toNumber() > currentTimestamp) {
|
||||
return 'not started'
|
||||
}
|
||||
|
||||
if (featuredVoting.verificationStartAt.toNumber() > currentTimestamp) {
|
||||
return 'voting'
|
||||
}
|
||||
|
||||
if (
|
||||
featuredVoting.verificationStartAt.toNumber() < currentTimestamp &&
|
||||
featuredVoting.endAt.toNumber() > currentTimestamp
|
||||
) {
|
||||
return 'verification'
|
||||
}
|
||||
|
||||
if (featuredVoting.endAt.toNumber() < currentTimestamp) {
|
||||
return 'ended'
|
||||
}
|
||||
|
||||
return 'ended'
|
||||
}
|
||||
|
||||
export default { getFeaturedVotingState }
|
|
@ -14,6 +14,8 @@ export function votingFromRoom(votingRoom: VotingRoom) {
|
|||
const currentVoting: CurrentVoting = {
|
||||
timeLeft: votingRoom.verificationStartAt.toNumber() - currentTimestamp,
|
||||
timeLeftVerification: votingRoom.endAt.toNumber() - currentTimestamp,
|
||||
votingEndAt: votingRoom.verificationStartAt.toNumber(),
|
||||
verificationEndAt: votingRoom.endAt.toNumber(),
|
||||
type: votingRoom.voteType === 1 ? 'Add' : 'Remove',
|
||||
voteFor: votingRoom.totalVotesFor,
|
||||
voteAgainst: votingRoom.totalVotesAgainst,
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import { config } from '../config'
|
||||
import { useFeaturedVotes } from './useFeaturedVotes'
|
||||
|
||||
export const useFeaturedBatches = () => {
|
||||
const { activeVoting, votes } = useFeaturedVotes()
|
||||
|
||||
if (!activeVoting) {
|
||||
return {
|
||||
finalizeVotingLimit: 1,
|
||||
batchCount: 1,
|
||||
batchDoneCount: 0,
|
||||
beingEvaluated: false,
|
||||
beingFinalized: false,
|
||||
}
|
||||
}
|
||||
|
||||
const evaluated = activeVoting.evaluated
|
||||
const finalized = activeVoting.finalized
|
||||
const allVotes = votes ?? {}
|
||||
const votesCount: number = Object.values(allVotes).reduce(
|
||||
(acc: number, curr: any) => acc + Object.keys(curr?.votes).length,
|
||||
0
|
||||
)
|
||||
|
||||
const beingFinalized = !evaluated && finalized
|
||||
const beingEvaluated = evaluated && !finalized
|
||||
const currentPosition = activeVoting.evaluatingPos
|
||||
const firstFinalization = beingEvaluated && currentPosition === votesCount + 1
|
||||
|
||||
const votesLeftCount = votesCount - currentPosition + 1
|
||||
const finalizeVotingLimit = firstFinalization
|
||||
? Math.min(votesCount, config.votesLimit)
|
||||
: Math.min(votesLeftCount, config.votesLimit)
|
||||
const batchCount = Math.ceil((beingFinalized ? votesCount + 1 : votesCount) / config.votesLimit)
|
||||
const batchLeftCount = Math.ceil(votesLeftCount / config.votesLimit)
|
||||
|
||||
const batchDoneCount = batchCount - batchLeftCount
|
||||
|
||||
return {
|
||||
finalizeVotingLimit,
|
||||
batchCount,
|
||||
batchDoneCount,
|
||||
beingFinalized,
|
||||
beingEvaluated,
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ export function useFeaturedVotes() {
|
|||
if (featuredVotings) {
|
||||
const lastVoting: FeaturedVoting = featuredVotings[featuredVotings.length - 1]
|
||||
|
||||
if (lastVoting && !lastVoting.finalized) {
|
||||
if (lastVoting && (!lastVoting.evaluated || !lastVoting.finalized)) {
|
||||
setActiveVoting(lastVoting)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { FeaturedVoting } from '../models/smartContract'
|
||||
|
||||
type Phase = 'not started' | 'voting' | 'verification' | 'ended' | null
|
||||
|
||||
export const useFeaturedVotingState = (featuredVoting: FeaturedVoting | null): Phase => {
|
||||
const [votingState, setVotingState] = useState<Phase>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const getState = () => {
|
||||
const currentTimestamp = Math.floor(Date.now() / 1000)
|
||||
|
||||
const startAt = featuredVoting?.startAt.toNumber() ?? 0
|
||||
const verificationStartAt = featuredVoting?.verificationStartAt.toNumber() ?? 0
|
||||
const endAt = featuredVoting?.endAt.toNumber() ?? 0
|
||||
|
||||
if (!featuredVoting || featuredVoting === null) {
|
||||
setVotingState(null)
|
||||
} else if (endAt < currentTimestamp) {
|
||||
setVotingState('ended')
|
||||
} else if (verificationStartAt < currentTimestamp && endAt > currentTimestamp) {
|
||||
setVotingState('verification')
|
||||
} else if (verificationStartAt > currentTimestamp) {
|
||||
setVotingState('voting')
|
||||
} else if (startAt > currentTimestamp) {
|
||||
setVotingState('not started')
|
||||
}
|
||||
}
|
||||
|
||||
const timer = setInterval(() => {
|
||||
getState()
|
||||
}, 1000)
|
||||
|
||||
getState()
|
||||
|
||||
return () => clearInterval(timer)
|
||||
})
|
||||
|
||||
return votingState
|
||||
}
|
|
@ -3,13 +3,13 @@ import { DetailedVotingRoom } from '../models/smartContract'
|
|||
import { useVotesAggregate } from './useVotesAggregate'
|
||||
|
||||
export function useRoomAggregateVotes(room: DetailedVotingRoom, showConfirmModal: boolean) {
|
||||
const { votes } = useVotesAggregate(room.roomNumber, room.verificationStartAt, room.startAt)
|
||||
const { votesToSend } = useVotesAggregate(room.roomNumber, room.verificationStartAt, room.startAt)
|
||||
|
||||
const [returnRoom, setReturnRoom] = useState(room)
|
||||
|
||||
useEffect(() => {
|
||||
if (room.endAt.toNumber() > Date.now() / 1000 && showConfirmModal === false) {
|
||||
const reducedVotes = votes.reduce(
|
||||
const reducedVotes = votesToSend.reduce(
|
||||
(accumulator, vote) => {
|
||||
if (vote[1].mod(2).toNumber()) {
|
||||
return { for: accumulator.for.add(vote[2]), against: accumulator.against }
|
||||
|
@ -20,7 +20,7 @@ export function useRoomAggregateVotes(room: DetailedVotingRoom, showConfirmModal
|
|||
)
|
||||
setReturnRoom({ ...room, totalVotesAgainst: reducedVotes.against, totalVotesFor: reducedVotes.for })
|
||||
}
|
||||
}, [JSON.stringify(votes), JSON.stringify(room), showConfirmModal])
|
||||
}, [JSON.stringify(votesToSend), JSON.stringify(room), showConfirmModal])
|
||||
|
||||
return returnRoom
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
|
||||
export function useTimeLeft(timeEndAt: number): number {
|
||||
const [timeLeft, setTimeLeft] = useState(timeEndAt - Math.floor(Date.now() / 1000))
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setTimeLeft(timeEndAt - Math.floor(Date.now() / 1000)), 1000)
|
||||
|
||||
return () => clearInterval(timer)
|
||||
}, [])
|
||||
|
||||
return timeLeft
|
||||
}
|
|
@ -20,19 +20,22 @@ export function useVotesAggregate(room: number | undefined, verificationStartAt:
|
|||
}) ?? []
|
||||
const { waku } = useWaku()
|
||||
const [votesToSend, setVotesToSend] = useState<any[]>([])
|
||||
const [allVotes, setAllVotes] = useState<any[]>([])
|
||||
const { getTypedVote } = useTypedVote()
|
||||
|
||||
useEffect(() => {
|
||||
const accumulateVotes = async () => {
|
||||
if (waku && alreadyVotedList && room) {
|
||||
const messages = await wakuMessage.receive(waku, config.wakuConfig.wakuTopic, room)
|
||||
const validMessages = messages?.filter((message) => validateVote(message, verificationStartAt, startAt))
|
||||
const validMessages = messages?.filter((message) => validateVote(message, verificationStartAt, startAt)) ?? []
|
||||
const verifiedMessages = wakuMessage.filterVerified(validMessages, alreadyVotedList, getTypedVote)
|
||||
|
||||
setAllVotes(validMessages)
|
||||
setVotesToSend(verifiedMessages)
|
||||
}
|
||||
}
|
||||
accumulateVotes()
|
||||
}, [waku, room, alreadyVotedList])
|
||||
|
||||
return { votes: votesToSend }
|
||||
return { votesToSend, allVotes }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { config } from '../config'
|
||||
import voting from '../helpers/voting'
|
||||
import { DetailedVotingRoom } from '../models/smartContract'
|
||||
import { useRoomAggregateVotes } from './useRoomAggregateVotes'
|
||||
import { useVotesAggregate } from './useVotesAggregate'
|
||||
|
||||
interface Props {
|
||||
room: DetailedVotingRoom
|
||||
}
|
||||
|
||||
export const useVotingBatches = ({ room }: Props) => {
|
||||
const vote = voting.fromRoom(room)
|
||||
const { votesToSend, allVotes } = useVotesAggregate(vote.ID, room.verificationStartAt, room.startAt)
|
||||
const { evaluated, finalized, evaluatingPos } = useRoomAggregateVotes(room, false)
|
||||
|
||||
const beingFinalized = !evaluated && finalized
|
||||
const beingEvaluated = evaluated && !finalized
|
||||
const firstFinalization = beingEvaluated && evaluatingPos === allVotes.length + 1
|
||||
|
||||
const votesLeftCount = allVotes.length - evaluatingPos + 1
|
||||
const finalizeVotingLimit = firstFinalization
|
||||
? Math.min(allVotes.length, config.votesLimit)
|
||||
: Math.min(votesLeftCount, config.votesLimit)
|
||||
|
||||
const batchCount = Math.ceil((beingFinalized ? allVotes.length + 1 : allVotes.length) / config.votesLimit)
|
||||
const batchLeftCount = Math.ceil(votesLeftCount / config.votesLimit)
|
||||
const batchDoneCount = batchCount - batchLeftCount
|
||||
const batchedVotes = votesToSend.slice(0, finalizeVotingLimit)
|
||||
|
||||
return {
|
||||
finalizeVotingLimit,
|
||||
batchCount,
|
||||
batchDoneCount,
|
||||
beingFinalized,
|
||||
beingEvaluated,
|
||||
batchedVotes,
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ export type CurrentVoting = {
|
|||
voteFor: BigNumber // number of snt for a vote
|
||||
voteAgainst: BigNumber // number of snt against a vote
|
||||
ID?: number // id of voting room
|
||||
verificationEndAt: number
|
||||
votingEndAt: number
|
||||
}
|
||||
|
||||
export type CommunityDetail = {
|
||||
|
|
|
@ -27,4 +27,6 @@ export type FeaturedVoting = {
|
|||
verificationStartAt: BigNumber
|
||||
finalized: boolean
|
||||
evaluatingPos: number
|
||||
evaluated: boolean
|
||||
endBlock: BigNumber
|
||||
}
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import React from 'react'
|
||||
import { VotingCards } from '../components/votes/VotingCards'
|
||||
import { VotesInfo } from '../components/votes/VotesInfo'
|
||||
import { NotificationsList } from '../components/NotificationsList'
|
||||
|
||||
export function Votes() {
|
||||
return (
|
||||
<div>
|
||||
<VotesInfo />
|
||||
<VotingCards />
|
||||
<NotificationsList type="votes" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { DirectoryCard } from '../components/directory/DirectoryCard'
|
||||
import { TopBarMobile } from '../componentsMobile/TopBarMobile'
|
||||
import { useDirectoryCommunities } from '../hooks/useDirectoryCommunities'
|
||||
|
@ -11,13 +11,42 @@ import { WeeklyFeature } from '../components/WeeklyFeature'
|
|||
import { FilterList } from '../components/Filter'
|
||||
import { useHistory } from 'react-router'
|
||||
import { DirectorySkeletonMobile } from '../componentsMobile/DirectorySkeletonMobile'
|
||||
import { useContractFunction, useEthers } from '@usedapp/core'
|
||||
import { useContracts } from '../hooks/useContracts'
|
||||
import { useFeaturedVotes } from '../hooks/useFeaturedVotes'
|
||||
import { useFeaturedVotingState } from '../hooks/useFeaturedVotingState'
|
||||
import { config } from '../config'
|
||||
import { ConnectButton } from '../components/ConnectButton'
|
||||
import { ProposeButton } from './VotesMobile'
|
||||
import { useFeaturedBatches } from '../hooks/useFeaturedBatches'
|
||||
import { mapFeaturesVotes, receiveWakuFeature } from '../helpers/receiveWakuFeature'
|
||||
import { useTypedFeatureVote } from '../hooks/useTypedFeatureVote'
|
||||
import { useWaku } from '../providers/waku/provider'
|
||||
|
||||
export function DirectoryMobile() {
|
||||
const { account } = useEthers()
|
||||
const { featuredVotingContract } = useContracts()
|
||||
const { getTypedFeatureVote } = useTypedFeatureVote()
|
||||
const { waku } = useWaku()
|
||||
|
||||
const { activeVoting } = useFeaturedVotes()
|
||||
const featuredVotingState = useFeaturedVotingState(activeVoting)
|
||||
const castVotes = useContractFunction(featuredVotingContract, 'castVotes')
|
||||
const finalizeVoting = useContractFunction(featuredVotingContract, 'finalizeVoting')
|
||||
|
||||
const [filterKeyword, setFilterKeyword] = useState('')
|
||||
const [sortedBy, setSortedBy] = useState(DirectorySortingEnum.IncludedRecently)
|
||||
const [communities, publicKeys] = useDirectoryCommunities(filterKeyword, sortedBy)
|
||||
const history = useHistory()
|
||||
|
||||
const { finalizeVotingLimit, batchCount, batchDoneCount, beingEvaluated, beingFinalized } = useFeaturedBatches()
|
||||
|
||||
useEffect(() => {
|
||||
if (finalizeVoting.state.status === 'Success' || castVotes.state.status === 'Success') {
|
||||
history.go(0)
|
||||
}
|
||||
}, [finalizeVoting.state.status, castVotes.state.status])
|
||||
|
||||
const renderCommunities = () => {
|
||||
if (!publicKeys) {
|
||||
return null
|
||||
|
@ -61,6 +90,49 @@ export function DirectoryMobile() {
|
|||
<Voting>
|
||||
<WeeklyFeature />
|
||||
{renderCommunities()}
|
||||
<>
|
||||
{!account && <ConnectButton />}
|
||||
{account && featuredVotingState === 'verification' && (
|
||||
<ProposeButton
|
||||
onClick={async () => {
|
||||
const { votesToSend } = await receiveWakuFeature(
|
||||
waku,
|
||||
config.wakuConfig.wakuFeatureTopic,
|
||||
activeVoting!
|
||||
)
|
||||
const votes = mapFeaturesVotes(votesToSend, getTypedFeatureVote)
|
||||
|
||||
const batchedVotes = votes.slice(
|
||||
batchDoneCount * config.votesLimit,
|
||||
batchDoneCount * config.votesLimit + finalizeVotingLimit
|
||||
)
|
||||
|
||||
await castVotes.send(batchedVotes)
|
||||
}}
|
||||
>
|
||||
Verify Weekly featured{' '}
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
({beingEvaluated ? batchDoneCount : 0}/{batchCount})
|
||||
</>
|
||||
)}
|
||||
</ProposeButton>
|
||||
)}
|
||||
{account && featuredVotingState === 'ended' && (
|
||||
<ProposeButton
|
||||
onClick={() => {
|
||||
finalizeVoting.send(finalizeVotingLimit < 1 ? 1 : finalizeVotingLimit)
|
||||
}}
|
||||
>
|
||||
Finalize Weekly featured{' '}
|
||||
{batchCount > 1 && (
|
||||
<>
|
||||
({beingFinalized ? batchDoneCount : 0}/{batchCount})
|
||||
</>
|
||||
)}
|
||||
</ProposeButton>
|
||||
)}
|
||||
</>
|
||||
</Voting>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react'
|
|||
import { DirectoryCard } from '../components/directory/DirectoryCard'
|
||||
import { TopBarMobile } from '../componentsMobile/TopBarMobile'
|
||||
import styled from 'styled-components'
|
||||
import { SearchEmpty } from '../components/SearchEmpty'
|
||||
import { useHistory } from 'react-router'
|
||||
import { DirectorySkeletonMobile } from '../componentsMobile/DirectorySkeletonMobile'
|
||||
import { useFeaturedCommunities } from '../hooks/useFeaturedCommunities'
|
||||
import { Colors } from '../constants/styles'
|
||||
|
||||
export function FeaturedMobile() {
|
||||
const [communities, publicKeys] = useFeaturedCommunities()
|
||||
|
@ -17,7 +17,11 @@ export function FeaturedMobile() {
|
|||
}
|
||||
|
||||
if (publicKeys.length === 0) {
|
||||
return <SearchEmpty />
|
||||
return (
|
||||
<EmptyWrap>
|
||||
<span>No communities were featured last week.</span>
|
||||
</EmptyWrap>
|
||||
)
|
||||
}
|
||||
|
||||
if (communities.length === 0) {
|
||||
|
@ -41,6 +45,43 @@ export function FeaturedMobile() {
|
|||
)
|
||||
}
|
||||
|
||||
const EmptyWrap = styled.div`
|
||||
position: absolute;
|
||||
top: 96px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 0 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: calc(100vh - 96px);
|
||||
background: ${Colors.White};
|
||||
z-index: 99;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
height: 250px;
|
||||
top: 50vh;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
& > p {
|
||||
font-weight: bold;
|
||||
font-size: 64px;
|
||||
line-height: 64%;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
@media (max-width: 375px) {
|
||||
font-size: 34px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Voting = styled.div`
|
||||
padding: 256px 16px 16px;
|
||||
|
||||
|
|
|
@ -7,9 +7,10 @@ describe('voting', () => {
|
|||
describe('fromRoom', () => {
|
||||
it('success', () => {
|
||||
const votingRoom: VotingRoom = {
|
||||
endAt: BigNumber.from(10000200),
|
||||
startBlock: BigNumber.from(10000000),
|
||||
startAt: BigNumber.from(10000050),
|
||||
startBlock: BigNumber.from(10000000),
|
||||
endAt: BigNumber.from(10000200),
|
||||
endBlock: BigNumber.from(10000300),
|
||||
verificationStartAt: BigNumber.from(10000100),
|
||||
voteType: 0,
|
||||
finalized: false,
|
||||
|
@ -17,9 +18,8 @@ describe('voting', () => {
|
|||
totalVotesFor: BigNumber.from(100),
|
||||
totalVotesAgainst: BigNumber.from(100),
|
||||
roomNumber: 1,
|
||||
endBlock: BigNumber.from(0),
|
||||
evaluatingPos: 0,
|
||||
evaluated: false,
|
||||
evaluatingPos: 1,
|
||||
}
|
||||
const room = voting.fromRoom(votingRoom)
|
||||
|
||||
|
@ -29,9 +29,10 @@ describe('voting', () => {
|
|||
})
|
||||
it('different type', () => {
|
||||
const votingRoom: VotingRoom = {
|
||||
endAt: BigNumber.from(10000200),
|
||||
startBlock: BigNumber.from(10000000),
|
||||
startAt: BigNumber.from(10000050),
|
||||
startBlock: BigNumber.from(10000000),
|
||||
endAt: BigNumber.from(10000200),
|
||||
endBlock: BigNumber.from(10000300),
|
||||
verificationStartAt: BigNumber.from(10000100),
|
||||
voteType: 1,
|
||||
finalized: false,
|
||||
|
@ -39,9 +40,8 @@ describe('voting', () => {
|
|||
totalVotesFor: BigNumber.from(1000),
|
||||
totalVotesAgainst: BigNumber.from(100),
|
||||
roomNumber: 1,
|
||||
endBlock: BigNumber.from(0),
|
||||
evaluatingPos: 0,
|
||||
evaluated: false,
|
||||
evaluatingPos: 1,
|
||||
}
|
||||
const room = voting.fromRoom(votingRoom)
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ describe('voting', () => {
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(1000),
|
||||
voteAgainst: BigNumber.from(100),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
}
|
||||
expect(voting.getWinner(vote)).to.eq(2)
|
||||
})
|
||||
|
@ -22,6 +24,8 @@ describe('voting', () => {
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(100),
|
||||
voteAgainst: BigNumber.from(1000),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
}
|
||||
expect(voting.getWinner(vote)).to.eq(1)
|
||||
})
|
||||
|
@ -32,6 +36,8 @@ describe('voting', () => {
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(100),
|
||||
voteAgainst: BigNumber.from(1000),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
}
|
||||
expect(voting.getWinner(vote)).to.eq(undefined)
|
||||
})
|
||||
|
@ -42,6 +48,8 @@ describe('voting', () => {
|
|||
type: 'Add',
|
||||
voteFor: BigNumber.from(100),
|
||||
voteAgainst: BigNumber.from(100),
|
||||
votingEndAt: 10000,
|
||||
verificationEndAt: 10000,
|
||||
}
|
||||
expect(voting.getWinner(vote)).to.eq(1)
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue