Skip to content

Commit

Permalink
fix: fw-476 snapshot voting
Browse files Browse the repository at this point in the history
  • Loading branch information
swkatmask committed Feb 29, 2024
1 parent d3c6730 commit f5c5c25
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 79 deletions.
1 change: 1 addition & 0 deletions packages/plugins/Snapshot/src/SiteAdaptor/SnapshotTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const useStyles = makeStyles()((theme) => {
overflowY: 'auto',
overflowX: 'hidden',
height: 400,
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
display: 'none',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ const useStyles = makeStyles()((theme) => ({
},
field: {
color: theme.palette.maskColor.second,
'&:first-of-type': {
marginTop: 0,
},
},
content: {
padding: 16,
'& > :first-child': {
marginTop: 0,
},
height: 492,
},
button: {
Expand Down
128 changes: 64 additions & 64 deletions packages/plugins/Snapshot/src/SiteAdaptor/VotingCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useContext, useState, useEffect, useMemo, unstable_useCacheRefresh } from 'react'
import { useContext, useState, useEffect, useMemo, unstable_useCacheRefresh, memo } from 'react'
import { Box, Button, Typography } from '@mui/material'
import { makeStyles } from '@masknet/theme'
import { checksumAddress } from '@masknet/web3-shared-evm'
Expand All @@ -25,6 +25,12 @@ const useStyles = makeStyles()((theme) => {
backgroundColor: theme.palette.maskColor.publicThirdMain,
color: theme.palette.maskColor.publicMain,
},
'&:first-of-type': {
marginTop: 0,
},
'&:last-of-type': {
marginTop: 0,
},
},
choiceButton: {
backgroundColor: theme.palette.maskColor.publicThirdMain,
Expand All @@ -37,26 +43,18 @@ const useStyles = makeStyles()((theme) => {
backgroundColor: `${theme.palette.maskColor.publicMain} !important`,
color: `${theme.palette.maskColor.white} !important`,
},
buttons: {
'& > :first-child': {
marginTop: 0,
},
'& > :last-child': {
marginBottom: 0,
},
},
}
})

export function VotingCard() {
export const VotingCard = memo(function VotingCard() {
const t = useSnapshotTrans()
const { classes, cx } = useStyles()
const identifier = useContext(SnapshotContext)
const proposal = useProposal(identifier.id)
const { account } = useChainContext<NetworkPluginID.PLUGIN_EVM>()
const { value: power } = usePower(identifier)
const proposal = useProposal(identifier.id)
const power = usePower(identifier)
const choices = proposal.choices
const [choices_, setChoices_] = useState<number[]>([])
const [selectedChoices, setSelectedChoices] = useState<number[]>([])
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)

Expand All @@ -80,7 +78,7 @@ export function VotingCard() {
space: identifier.space,
timestamp: Math.floor(Date.now() / 1000),
proposal: identifier.id,
choice: proposal.type === 'single-choice' ? choices_[0] : choices_,
choice: proposal.type === 'single-choice' ? selectedChoices[0] : selectedChoices,
metadata: JSON.stringify({}),
}
const domain = SNAPSHOT_VOTE_DOMAIN
Expand Down Expand Up @@ -109,7 +107,7 @@ export function VotingCard() {
const body = JSON.stringify({ data, sig, address: checksumAddress(account) })
return PluginSnapshotRPC.vote(body)
},
[choices_, identifier, account, proposal],
[selectedChoices, identifier, account, proposal.type],
() => {
setLoading(false)
setOpen(false)
Expand All @@ -127,68 +125,70 @@ export function VotingCard() {

const onClick = (n: number) => {
if (proposal.type === 'single-choice') {
setChoices_((d) => [n])
setSelectedChoices([n])
return
}
if (choices_.includes(n)) setChoices_((d) => d.filter((x) => x !== n))
else setChoices_((d) => [...d, n])
if (selectedChoices.includes(n)) setSelectedChoices((d) => d.filter((x) => x !== n))
else setSelectedChoices((d) => [...d, n])
}

const disabled = choices_.length === 0 || !account || !power
const disabled = selectedChoices.length === 0 || !account || !power
const choiceText = useMemo(() => {
let text = ''
for (const i of choices_) {
for (const i of selectedChoices) {
text += choices[i - 1]
if (i < choices_.length) text += ','
if (i < selectedChoices.length) text += ','
}
return text
}, [choices_])
return account && pluginID === NetworkPluginID.PLUGIN_EVM ?
<SnapshotCard title={t.plugin_snapshot_vote_title()}>
<Box className={classes.buttons}>
{choices.map((choiceText, i) => (
<Button
variant="roundedContained"
fullWidth
key={i}
onClick={() => onClick(i + 1)}
className={cx([
classes.button,
classes.choiceButton,
...(choices_.includes(i + 1) ? [classes.buttonActive] : []),
])}>
<Typography
fontWeight={700}
fontSize={16}
sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{choiceText}
</Typography>
</Button>
))}
}, [selectedChoices])

if (!account || pluginID !== NetworkPluginID.PLUGIN_EVM) return
return (
<SnapshotCard title={t.plugin_snapshot_vote_title()}>
<Box>
{choices.map((choiceText, i) => (
<Button
variant="roundedContained"
fullWidth
className={cx(classes.button, disabled ? '' : classes.buttonActive)}
disabled={disabled}
onClick={() => setOpen(true)}>
<Typography fontWeight={700} fontSize={16}>
{power && account ? t.plugin_snapshot_vote() : t.plugin_snapshot_no_power()}
key={i}
onClick={() => onClick(i + 1)}
className={cx([
classes.button,
classes.choiceButton,
...(selectedChoices.includes(i + 1) ? [classes.buttonActive] : []),
])}>
<Typography
fontWeight={700}
fontSize={16}
sx={{ whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{choiceText}
</Typography>
</Button>
</Box>
))}

<VoteConfirmDialog
open={open}
loading={loading}
onClose={() => setOpen(false)}
choiceText={choiceText}
snapshot={proposal.snapshot}
powerSymbol={proposal.space.symbol}
power={power}
onVoteConfirm={onVoteConfirm}
chainId={proposal.chainId}
/>
</SnapshotCard>
: null
}
<Button
variant="roundedContained"
fullWidth
className={cx(classes.button, disabled ? '' : classes.buttonActive)}
disabled={disabled}
onClick={() => setOpen(true)}>
<Typography fontWeight={700} fontSize={16}>
{power && account ? t.plugin_snapshot_vote() : t.plugin_snapshot_no_power()}
</Typography>
</Button>
</Box>

<VoteConfirmDialog
open={open}
loading={loading}
onClose={() => setOpen(false)}
choiceText={choiceText}
snapshot={proposal.snapshot}
powerSymbol={proposal.space.symbol}
power={power}
onVoteConfirm={onVoteConfirm}
chainId={proposal.chainId}
/>
</SnapshotCard>
)
})
55 changes: 47 additions & 8 deletions packages/plugins/Snapshot/src/SiteAdaptor/hooks/usePower.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,56 @@
import { useAsyncRetry } from 'react-use'
import { useChainContext } from '@masknet/web3-hooks-base'
import type { NetworkPluginID } from '@masknet/shared-base'
import type { ProposalIdentifier } from '../../types.js'
import { useProposal } from './useProposal.js'
import { find, sumBy } from 'lodash-es'
import { useChainContext } from '@masknet/web3-hooks-base'
import { fetchJSON } from '@masknet/web3-providers/helpers'
import { isSameAddress } from '@masknet/web3-shared-base'
import { useQuery } from '@tanstack/react-query'
import { find, omit, sumBy } from 'lodash-es'
import { useMemo } from 'react'
import type { Proposal, ProposalIdentifier, ScoreResponse } from '../../types.js'
import { getScores } from '../../utils.js'
import { useProposal } from './useProposal.js'

export function useMyScore(account: string, proposal: Proposal) {
const { network, snapshot, space, strategies } = proposal
const { data: score } = useQuery({
queryKey: ['snapshot', 'user-score', account, network, snapshot],
queryFn: async () => {
const res = await fetchJSON<ScoreResponse>('https://score.snapshot.org/api/scores', {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
params: {
addresses: [account],
space: space.id,
network,
snapshot,
strategies: strategies.map((x) => omit(x, ['network', '__typename'])),
},
}),
})
return res.result.scores
},
select(scores) {
return scores.reduce((total, record) => {
const entry = Object.entries(record).find(([key]) => {
return key.toLowerCase() === account.toLowerCase()
})
return entry ? entry[1] + total : total
}, 0)
},
})
return score
}

export function usePower(identifier: ProposalIdentifier) {
const proposal = useProposal(identifier.id)
const { account } = useChainContext<NetworkPluginID.PLUGIN_EVM>()
return useAsyncRetry(async () => {
if (!account) return 0
const score = useMyScore(account, proposal) || 0
const votedScore = useMemo(() => {
if (!account || !proposal) return 0
const scores = getScores(proposal)
return sumBy(scores, (score) => find(score, (_, key) => key.toLowerCase() === account.toLowerCase()) ?? 0)
return sumBy(scores, (score) => find(score, (_, key) => isSameAddress(key, account)) ?? 0)
}, [account, proposal])
return score + votedScore
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { PluginSnapshotRPC } from '../../messages.js'
import { useSuspenseQuery } from '@tanstack/react-query'
import { PluginSnapshotRPC } from '../../messages.js'
import type { Proposal } from '../../types.js'

export function useProposal(id: string) {
export function useProposal(id: string): Proposal {
return useSuspenseQuery({
queryKey: ['plugin', 'snapshot', 'fetchProposal', id],
queryFn: () => PluginSnapshotRPC.fetchProposal(id),
select(proposal) {
proposal.status =
const status =
!proposal.isStart ? 'Pending'
: proposal.isEnd ? 'Closed'
: 'Active'
return proposal
return { ...proposal, status } satisfies Proposal
},
}).data
}
5 changes: 5 additions & 0 deletions packages/plugins/Snapshot/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,8 @@ export enum ContentTabs {
Pending = 'Pending',
Closed = 'Closed',
}
export interface ScoreResponse {
result: {
scores: Array<Record<string, number>>
}
}

0 comments on commit f5c5c25

Please sign in to comment.