From b683e55f78ce8fbdef9245867435de28a619e41d Mon Sep 17 00:00:00 2001 From: MrX-SNX Date: Fri, 6 Sep 2024 15:46:34 +0100 Subject: [PATCH] Fix voting power row (#443) * fix: parsing number * fix: vote row component * fix: row * ref: remove error in toast * fix: overflow * fix: crashing site * fix: sorting * fix: my vote row * fix * ci: fix * remove consolelog --- .../cypress/e2e/Councils - Voting.e2e.js | 5 +- .../CouncilNominees/CouncilNominees.tsx | 3 +- .../components/CouncilTabs/CouncilTabs.tsx | 39 ++++-- .../ui/src/components/MyVoteRow/MyVoteRow.tsx | 31 +++-- .../MyVotesSummary/MyVotesSummary.tsx | 10 +- .../UserProfileCard/UserProfileDetails.tsx | 31 ++++- .../UserTableView/UserTableView.tsx | 30 ++--- governance/ui/src/context/VoteContext.tsx | 122 +++++++++++++----- governance/ui/src/mutations/useCastVotes.ts | 16 ++- governance/ui/src/pages/MyVotes.tsx | 46 ++++++- governance/ui/src/queries/useGetEpochIndex.ts | 5 +- governance/ui/src/utils/localstorage.ts | 81 +++++++----- governance/ui/src/utils/sort-users.ts | 22 ++-- 13 files changed, 302 insertions(+), 139 deletions(-) diff --git a/governance/cypress/cypress/e2e/Councils - Voting.e2e.js b/governance/cypress/cypress/e2e/Councils - Voting.e2e.js index ceb9f874f..503d86acc 100644 --- a/governance/cypress/cypress/e2e/Councils - Voting.e2e.js +++ b/governance/cypress/cypress/e2e/Councils - Voting.e2e.js @@ -34,10 +34,7 @@ it('Councils - Administration', () => { cy.get( '[data-cy="user-blockies-council-tabs-0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]' ).should('exist'); - cy.reload(); - cy.get( - '[data-cy="user-blockies-council-tabs-0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]' - ).should('exist'); + cy.get('[data-cy="selected-badge-my-row"]').should('exist'); cy.wait(3000); cy.get('[data-cy="my-votes-voting-power"]').contains('30.00'); diff --git a/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx b/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx index 4a6715258..7dfa69a29 100644 --- a/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx +++ b/governance/ui/src/components/CouncilNominees/CouncilNominees.tsx @@ -49,7 +49,8 @@ export default function CouncilNominees({ activeCouncil }: { activeCouncil: Coun const { state } = useVoteContext(); const currentSelectedUser = getVoteSelectionState( state, - epochId, + activeWallet?.address, + epochId?.toString(), network?.id.toString(), activeCouncil ); diff --git a/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx b/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx index 82ebdf3d9..4a7f6c95f 100644 --- a/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx +++ b/governance/ui/src/components/CouncilTabs/CouncilTabs.tsx @@ -11,6 +11,7 @@ import { useGetUserBallot, useNetwork, useGetEpochIndex, + useWallet, } from '../../queries'; import { ProfilePicture } from '../UserProfileCard/ProfilePicture'; import { ArrowBackIcon, ArrowForwardIcon } from '@chakra-ui/icons'; @@ -21,6 +22,7 @@ import { getVoteSelectionState } from '../../utils/localstorage'; export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilSlugs }) { const { data: councilPeriod } = useGetCurrentPeriod(activeCouncil); const location = useLocation(); + const { activeWallet } = useWallet(); const isInMyVotesPage = location.pathname.includes('my-votes'); const isInMyProfilePage = location.pathname.includes('profile'); const { data: schedule, isLoading } = useGetEpochSchedule(activeCouncil); @@ -28,7 +30,12 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS const { data: epochId } = useGetEpochIndex(activeCouncil); const { state } = useVoteContext(); // @dev dont put activeCounil in here cause its always spartan for the timer - const networkForState = getVoteSelectionState(state, epochId, network?.id.toString()); + const networkForState = getVoteSelectionState( + state, + activeWallet?.address, + epochId?.toString(), + network?.id.toString() + ); const votedNomineesData = [ useGetUserBallot('spartan'), @@ -88,7 +95,6 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS Back to Councils ) : ( - // If on my votes page, spartan council is active by default for navigation <> {councilPeriod === '2' && utils.isAddress(newVoteCast || '') ? ( - + <> + + {userInformation[index]?.userInformation?.address + ? newVoteCast?.toLowerCase() !== + userInformation[index]?.userInformation?.address?.toLowerCase() && ( + <> + + + + ) + : null} + ) : ( councilPeriod === '2' && ( <> @@ -178,6 +200,7 @@ export default function CouncilTabs({ activeCouncil }: { activeCouncil: CouncilS address={userInformation[index].userInformation?.address} size={9} newVoteCast={newVoteCast} + isCouncilTabs /> )} diff --git a/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx b/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx index 94e998e20..896aec577 100644 --- a/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx +++ b/governance/ui/src/components/MyVoteRow/MyVoteRow.tsx @@ -4,7 +4,7 @@ import { AddIcon, ArrowForwardIcon, CloseIcon } from '@chakra-ui/icons'; import { useNavigate } from 'react-router-dom'; import CouncilUser from '../CouncilUser/CouncilUser'; import { useVoteContext } from '../../context/VoteContext'; -import { useGetEpochIndex, useGetUserBallot, useNetwork } from '../../queries'; +import { useGetEpochIndex, useGetUserBallot, useNetwork, useWallet } from '../../queries'; import { getVoteSelectionState } from '../../utils/localstorage'; import { Badge } from '../Badge'; @@ -18,6 +18,7 @@ export default function MyVoteRow({ isLast: boolean; }) { const navigate = useNavigate(); + const { activeWallet } = useWallet(); const { data: ballot } = useGetUserBallot(councilSlug); const { data: epochId } = useGetEpochIndex(councilSlug); const { dispatch, state } = useVoteContext(); @@ -25,13 +26,13 @@ export default function MyVoteRow({ const networkForState = getVoteSelectionState( state, - epochId, + activeWallet?.address, + epochId?.toString(), network?.id.toString(), councilSlug ); const voteAddressState = typeof networkForState === 'string' ? networkForState : ''; - return ( - {ballot?.votedCandidates[0] && networkForState === 'remove' && ( - <> - - - - )} + {ballot?.votedCandidates[0] && + voteAddressState && + ballot.votedCandidates[0].toLowerCase() !== voteAddressState.toLowerCase() && ( + <> + + + + )} {ballot?.votedCandidates.includes(voteAddressState) ? ( Your Vote @@ -95,7 +98,8 @@ export default function MyVoteRow({ payload: { action: 'remove', network: network.id.toString(), - epochId, + epochId: epochId?.toString(), + wallet: activeWallet?.address, }, }); } else { @@ -104,7 +108,8 @@ export default function MyVoteRow({ payload: { action: networkForState === 'remove' ? 'remove' : undefined, network: network.id.toString(), - epochId, + epochId: epochId?.toString(), + wallet: activeWallet?.address, }, }); } diff --git a/governance/ui/src/components/MyVotesSummary/MyVotesSummary.tsx b/governance/ui/src/components/MyVotesSummary/MyVotesSummary.tsx index aed2eed0a..aab329e3c 100644 --- a/governance/ui/src/components/MyVotesSummary/MyVotesSummary.tsx +++ b/governance/ui/src/components/MyVotesSummary/MyVotesSummary.tsx @@ -5,7 +5,7 @@ import MyVotesBox from '../MyVotesBox/MyVotesBox'; import { useNavigate } from 'react-router-dom'; import councils from '../../utils/councils'; import { useVoteContext, VoteStateForNetwork } from '../../context/VoteContext'; -import { useGetEpochIndex, useNetwork } from '../../queries'; +import { useGetEpochIndex, useNetwork, useWallet } from '../../queries'; import { voteCardState } from '../../state/vote-card'; import { useRecoilState } from 'recoil'; import { getVoteSelectionState } from '../../utils/localstorage'; @@ -36,7 +36,13 @@ export const MyVotesSummary = ({ const { network } = useNetwork(); const { data: epochId } = useGetEpochIndex('spartan'); const { state } = useVoteContext(); - const networkForState = getVoteSelectionState(state, epochId, network?.id.toString()); + const { activeWallet } = useWallet(); + const networkForState = getVoteSelectionState( + state, + activeWallet?.address, + epochId?.toString(), + network?.id.toString() + ); const stateFromCouncils = ( typeof networkForState !== 'string' ? networkForState : { spartan: networkForState } ) as VoteStateForNetwork; diff --git a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx index a2e56636b..06d6786f1 100644 --- a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx +++ b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx @@ -8,7 +8,7 @@ import { useNavigate } from 'react-router-dom'; import { useVoteContext } from '../../context/VoteContext'; import { ProfilePicture } from './ProfilePicture'; import { EditIcon, ShareIcon } from '../Icons'; -import { useGetEpochIndex, useGetUserBallot, useNetwork } from '../../queries'; +import { useGetEpochIndex, useGetUserBallot, useNetwork, useWallet } from '../../queries'; import { useEffect, useRef, useState } from 'react'; import { useRecoilState } from 'recoil'; import { voteCardState } from '../../state/vote-card'; @@ -32,6 +32,7 @@ export const UserProfileDetails = ({ councilPeriod, }: UserProfileDetailsProps) => { const [_, setVoteCard] = useRecoilState(voteCardState); + const { activeWallet } = useWallet(); const [tooltipLabel, setTooltipLabel] = useState('Copy Profile Link'); const [walletToolTipLabel, setWalletTooltipLabel] = useState('Copy'); const [isTooltipOpen, setIsTooltipOpen] = useState(false); @@ -43,11 +44,16 @@ export const UserProfileDetails = ({ const { data: ballot } = useGetUserBallot(activeCouncil); const elementRef = useRef(null); const [isOverflowing, setIsOverflowing] = useState(false); - const networkForState = getVoteSelectionState(state, epochId, network?.id.toString()); + const networkForState = getVoteSelectionState( + state, + activeWallet?.address, + epochId?.toString(), + network?.id.toString(), + activeCouncil + ); const voteAddressState = typeof networkForState === 'string' ? networkForState : ''; - const isSelected = voteAddressState - ? voteAddressState?.toLowerCase() === userData?.address?.toLowerCase() + ? voteAddressState?.toLowerCase().trim() === userData?.address?.toLowerCase().trim() : false; const isAlreadyVoted = @@ -293,12 +299,22 @@ export const UserProfileDetails = ({ if (isAlreadyVoted) { dispatch({ type: activeCouncil.toUpperCase(), - payload: { action: 'remove', network: parsedNetwork, epochId }, + payload: { + action: 'remove', + network: parsedNetwork, + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, }); } else if (isSelected) { dispatch({ type: activeCouncil.toUpperCase(), - payload: { action: undefined, network: parsedNetwork, epochId }, + payload: { + action: undefined, + network: parsedNetwork, + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, }); } else { dispatch({ @@ -306,7 +322,8 @@ export const UserProfileDetails = ({ payload: { action: userData?.address.toLowerCase(), network: parsedNetwork, - epochId, + epochId: epochId?.toString(), + wallet: activeWallet?.address, }, }); } diff --git a/governance/ui/src/components/UserTableView/UserTableView.tsx b/governance/ui/src/components/UserTableView/UserTableView.tsx index 71b52d192..03fed4635 100644 --- a/governance/ui/src/components/UserTableView/UserTableView.tsx +++ b/governance/ui/src/components/UserTableView/UserTableView.tsx @@ -6,10 +6,9 @@ import { useGetCurrentPeriod } from '../../queries/useGetCurrentPeriod'; import { CouncilSlugs } from '../../utils/councils'; import { ProfilePicture } from '../UserProfileCard/ProfilePicture'; import { prettyString } from '@snx-v3/format'; -import { useGetEpochIndex, useGetUserBallot, useNetwork } from '../../queries'; -import { getVoteSelectionState } from '../../utils/localstorage'; -import { useVoteContext } from '../../context/VoteContext'; -import { BigNumber } from 'ethers'; +import { useGetUserBallot } from '../../queries'; +import { BigNumber, utils } from 'ethers'; +import { formatNumber } from '@snx-v3/formatters'; import { renderCorrectBorder } from '../../utils/table-border'; export default function UserTableView({ @@ -31,20 +30,11 @@ export default function UserTableView({ const { data: ballot } = useGetUserBallot(activeCouncil); const { data: councilPeriod } = useGetCurrentPeriod(activeCouncil); const isSelected = searchParams.get('view') === user.address; - const { network } = useNetwork(); - const { data: epochId } = useGetEpochIndex(activeCouncil); - const { state } = useVoteContext(); const councilIsInAdminOrVoting = councilPeriod === '2' || councilPeriod === '0'; - const networkForState = getVoteSelectionState( - state, - epochId, - network?.id.toString(), - activeCouncil - ); - const voteAddressState = typeof networkForState === 'string' ? networkForState : ''; - const totalVotingPowerPercentage = totalVotingPower - ? user.voteResult?.votePower.mul(100).div(totalVotingPower).toNumber().toFixed(2) - : 'N/A'; + const totalVotingPowerPercentage = + totalVotingPower && user.voteResult + ? formatNumber(user.voteResult?.votePower.mul(100).div(totalVotingPower).toString()) + : 'N/A'; return ( {totalVotingPowerPercentage ? totalVotingPowerPercentage + '%' : 'N/A'} - {totalVotingPowerPercentage ? user.voteResult?.votePower.toString() : '—'} + {totalVotingPowerPercentage && user.voteResult + ? formatNumber(utils.formatEther(user.voteResult.votePower || '0')) + : '—'} )} @@ -144,7 +136,7 @@ export default function UserTableView({ fontSize="sm" fontWeight={700} > - {ballot?.votedCandidates.includes(voteAddressState) ? ( + {ballot?.votedCandidates.includes(user.address) ? ( Your Vote diff --git a/governance/ui/src/context/VoteContext.tsx b/governance/ui/src/context/VoteContext.tsx index 8e0827088..fec24a5fe 100644 --- a/governance/ui/src/context/VoteContext.tsx +++ b/governance/ui/src/context/VoteContext.tsx @@ -6,7 +6,7 @@ import React, { useReducer, useState, } from 'react'; -import { useGetEpochIndex, useGetUserBallot, useNetwork } from '../queries'; +import { useGetEpochIndex, useGetUserBallot, useNetwork, useWallet } from '../queries'; import { localStorageKey, removeCandidate, setCandidate } from '../utils/localstorage'; export interface VoteStateForNetwork { @@ -17,26 +17,35 @@ export interface VoteStateForNetwork { export interface VoteState { [key: string]: { - [key: string]: VoteStateForNetwork; + [key: string]: { + [key: string]: VoteStateForNetwork; + }; }; } type Action = { type: string; - payload: { action: string | undefined; network: string | undefined; epochId: string | undefined }; + payload: { + action: string | undefined; + network: string | undefined; + epochId: string | undefined; + wallet: string | undefined; + }; }; -const initialState = (chainId?: string, epochId?: string) => { - if (!epochId || !chainId) return {}; +const initialState = (chainId?: string, epochId?: string, wallet?: string) => { + if (!epochId || !chainId || !wallet) return {}; const parsedState = JSON.parse(localStorage.getItem(localStorageKey) || '{}'); return { - [epochId]: { - [chainId]: { - spartan: parsedState[epochId]?.[chainId]?.spartan || undefined, - ambassador: parsedState[epochId]?.[chainId]?.ambassador || undefined, - treasury: parsedState[epochId]?.[chainId]?.treasury || undefined, + [wallet]: { + [epochId]: { + [chainId]: { + spartan: parsedState[epochId]?.[chainId]?.spartan || undefined, + ambassador: parsedState[epochId]?.[chainId]?.ambassador || undefined, + treasury: parsedState[epochId]?.[chainId]?.treasury || undefined, + }, }, }, } as VoteState; @@ -56,20 +65,31 @@ const voteReducer = (state: VoteState, action: Action): VoteState => { if (action.payload.action && action.payload.action !== 'remove') { setCandidate( action.payload.action, + action.payload.wallet, 'spartan', action.payload.network, action.payload.epochId ); } else { - removeCandidate('spartan', action.payload.network, action.payload.epochId); + removeCandidate( + 'spartan', + action.payload.wallet, + action.payload.network, + action.payload.epochId + ); } return { ...state, - [action.payload.epochId!]: { - ...(state[action.payload.epochId!] || {}), - [action.payload.network!]: { - ...(state[action.payload.epochId!]?.[action.payload.network!] || {}), - spartan: action.payload.action, + [action.payload.wallet!]: { + ...(state[action.payload.wallet!] || {}), + [action.payload.epochId!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!] || {}), + [action.payload.network!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!]?.[ + action.payload.network! + ] || {}), + spartan: action.payload.action, + }, }, }, }; @@ -78,20 +98,31 @@ const voteReducer = (state: VoteState, action: Action): VoteState => { if (action.payload.action && action.payload.action !== 'remove') { setCandidate( action.payload.action, + action.payload.wallet, 'ambassador', action.payload.network, action.payload.epochId ); } else { - removeCandidate('ambassador', action.payload.network, action.payload.epochId); + removeCandidate( + 'ambassador', + action.payload.wallet, + action.payload.network, + action.payload.epochId + ); } return { ...state, - [action.payload.epochId!]: { - ...(state[action.payload.epochId!] || {}), - [action.payload.network!]: { - ...(state[action.payload.epochId!]?.[action.payload.network!] || {}), - ambassador: action.payload.action, + [action.payload.wallet!]: { + ...(state[action.payload.wallet!] || {}), + [action.payload.epochId!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!] || {}), + [action.payload.network!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!]?.[ + action.payload.network! + ] || {}), + ambassador: action.payload.action, + }, }, }, }; @@ -100,20 +131,31 @@ const voteReducer = (state: VoteState, action: Action): VoteState => { if (action.payload.action && action.payload.action !== 'remove') { setCandidate( action.payload.action, + action.payload.wallet, 'treasury', action.payload.network, action.payload.epochId ); } else { - removeCandidate('treasury', action.payload.network, action.payload.epochId); + removeCandidate( + 'treasury', + action.payload.wallet, + action.payload.network, + action.payload.epochId + ); } return { ...state, - [action.payload.epochId!]: { - ...(state[action.payload.epochId!] || {}), - [action.payload.network!]: { - ...(state[action.payload.epochId!]?.[action.payload.network!] || {}), - treasury: action.payload.action, + [action.payload.wallet!]: { + ...(state[action.payload.wallet!] || {}), + [action.payload.epochId!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!] || {}), + [action.payload.network!]: { + ...(state[action.payload.wallet!]?.[action.payload.epochId!]?.[ + action.payload.network! + ] || {}), + treasury: action.payload.action, + }, }, }, }; @@ -125,6 +167,7 @@ const voteReducer = (state: VoteState, action: Action): VoteState => { const VoteProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const { network } = useNetwork(); + const { activeWallet } = useWallet(); const { data: epochId } = useGetEpochIndex('spartan'); const [init, setInit] = useState(false); const [state, dispatch] = useReducer( @@ -147,30 +190,40 @@ const VoteProvider: React.FC<{ children: ReactNode }> = ({ children }) => { isTreasuryBallotFetched && spartanBallot && ambassadorBallot && - treasuryBallot + treasuryBallot && + activeWallet?.address ) { [spartanBallot, ambassadorBallot, treasuryBallot].forEach((ballot) => { dispatch({ payload: { action: ballot?.votedCandidates[0], network: network.id.toString(), - epochId, + epochId: epochId.toString(), + wallet: activeWallet.address, }, type: ballot.council.toUpperCase(), }); }); - const initState = initialState(network.id.toString(), epochId); - const voteStateForNetwork = initState[epochId][network.id.toString()]; + const initState = initialState( + network.id.toString(), + epochId.toString(), + activeWallet.address + ); + const voteStateForNetwork = + initState[activeWallet.address][epochId.toString()][network.id.toString()]; - Object.keys(initState[epochId][network.id.toString()]).forEach((key) => { + Object.keys( + initState[activeWallet.address][epochId.toString()][network.id.toString()] + ).forEach((key) => { const candidate = voteStateForNetwork[key as keyof VoteStateForNetwork]; if (candidate) { dispatch({ payload: { action: candidate, network: network.id.toString(), - epochId, + epochId: epochId.toString(), + wallet: activeWallet.address, }, type: key.toUpperCase(), }); @@ -188,6 +241,7 @@ const VoteProvider: React.FC<{ children: ReactNode }> = ({ children }) => { spartanBallot, ambassadorBallot, treasuryBallot, + activeWallet?.address, ]); return {children}; }; diff --git a/governance/ui/src/mutations/useCastVotes.ts b/governance/ui/src/mutations/useCastVotes.ts index f721eb49e..5b45069bc 100644 --- a/governance/ui/src/mutations/useCastVotes.ts +++ b/governance/ui/src/mutations/useCastVotes.ts @@ -133,7 +133,7 @@ export function useCastVotes( console.error(error); toast({ title: 'Could not cast votes.', - description: error && 'message' in error ? error.message : '', + description: 'Check the browser console for more information', status: 'error', isClosable: true, }); @@ -157,11 +157,21 @@ export function useCastVotes( shouldWithdrawVote ? dispatch({ type: council.toUpperCase(), - payload: { action: undefined, network: network!.id.toString(), epochId }, + payload: { + action: undefined, + network: network!.id.toString(), + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, }) : dispatch({ type: council.toUpperCase(), - payload: { action: candidates[council], network: network!.id.toString(), epochId }, + payload: { + action: candidates[council], + network: network!.id.toString(), + epochId: epochId?.toString(), + wallet: activeWallet?.address, + }, }); }); await Promise.all( diff --git a/governance/ui/src/pages/MyVotes.tsx b/governance/ui/src/pages/MyVotes.tsx index 158768304..a7dd94e8a 100644 --- a/governance/ui/src/pages/MyVotes.tsx +++ b/governance/ui/src/pages/MyVotes.tsx @@ -17,7 +17,13 @@ import { useGetCurrentPeriod } from '../queries/useGetCurrentPeriod'; import { useGetEpochSchedule } from '../queries/useGetEpochSchedule'; import { Timer } from '../components/Timer'; import CouncilTabs from '../components/CouncilTabs/CouncilTabs'; -import { useGetEpochIndex, useGetUserVotingPower, useNetwork, useWallet } from '../queries/'; +import { + useGetEpochIndex, + useGetUserBallot, + useGetUserVotingPower, + useNetwork, + useWallet, +} from '../queries/'; import { useCastVotes } from '../mutations'; import { formatNumber } from '@snx-v3/formatters'; import MyVoteRow from '../components/MyVoteRow/MyVoteRow'; @@ -35,19 +41,45 @@ export default function MyVotes() { const { data: schedule } = useGetEpochSchedule('spartan'); const { data: epochId } = useGetEpochIndex('spartan'); const { network } = useNetwork(); - const { connect } = useWallet(); + const { activeWallet, connect } = useWallet(); const { data: votingPowerSpartan } = useGetUserVotingPower('spartan'); const { data: votingPowerAmbassador } = useGetUserVotingPower('ambassador'); const { data: votingPowerTreassury } = useGetUserVotingPower('treasury'); + const { data: spartanBallot } = useGetUserBallot('spartan'); + const { data: ambassadorBallot } = useGetUserBallot('ambassador'); + const { data: treasuryBallot } = useGetUserBallot('treasury'); + const ballots = { + spartan: spartanBallot, + ambassador: ambassadorBallot, + treasury: treasuryBallot, + }; const { state } = useVoteContext(); - const networkForState = getVoteSelectionState(state, epochId, network?.id.toString()); + const networkForState = getVoteSelectionState( + state, + activeWallet?.address, + epochId?.toString(), + network?.id.toString() + ); const stateFromCouncils = ( typeof networkForState !== 'string' ? networkForState : {} ) as VoteStateForNetwork; const councilToCastVote = Object.entries(stateFromCouncils) - .filter(([_, candidate]) => !!candidate) + .filter(([council, candidate]) => { + if ((ballots as any)[council].votedCandidates.length) { + return !(ballots as any)[council].votedCandidates.includes(candidate); + } + return !!(stateFromCouncils as any)[council]; + }) .map(([council]) => council) as CouncilSlugs[]; - const { mutateAsync, isPending } = useCastVotes(councilToCastVote, stateFromCouncils); + + const councilsToAddress = councilToCastVote.reduce( + (cur, prev) => { + cur[prev] = stateFromCouncils[prev] ?? ''; + return cur; + }, + {} as Record + ); + const { mutateAsync, isPending } = useCastVotes(councilToCastVote, councilsToAddress); const navigate = useNavigate(); const formattedVotePower = formatNumber( votingPowerSpartan?.power && votingPowerAmbassador?.power && votingPowerTreassury?.power @@ -262,8 +294,8 @@ export default function MyVotes() { {council.title} - {stateFromCouncils[council.slug] ? ( - + {councilsToAddress[council.slug] ? ( + ) : ( )} diff --git a/governance/ui/src/queries/useGetEpochIndex.ts b/governance/ui/src/queries/useGetEpochIndex.ts index e2158b874..52a69a6c2 100644 --- a/governance/ui/src/queries/useGetEpochIndex.ts +++ b/governance/ui/src/queries/useGetEpochIndex.ts @@ -3,6 +3,7 @@ import { CouncilSlugs } from '../utils/councils'; import { getCouncilContract } from '../utils/contracts'; import { motherShipProvider } from '../utils/providers'; import { useNetwork } from './useWallet'; +import { BigNumber } from 'ethers'; export function useGetEpochIndex(council: CouncilSlugs) { const { network } = useNetwork(); @@ -10,9 +11,9 @@ export function useGetEpochIndex(council: CouncilSlugs) { return useQuery({ queryKey: ['epochId', council, network?.id], queryFn: async () => { - return await getCouncilContract(council) + return (await getCouncilContract(council) .connect(motherShipProvider(network?.id || 2192)) - .getEpochIndex(); + .getEpochIndex()) as BigNumber; }, staleTime: 900000, }); diff --git a/governance/ui/src/utils/localstorage.ts b/governance/ui/src/utils/localstorage.ts index 039fb5158..fbee679bd 100644 --- a/governance/ui/src/utils/localstorage.ts +++ b/governance/ui/src/utils/localstorage.ts @@ -5,23 +5,28 @@ export const localStorageKey = 'voteSelection'; export const setCandidate = ( candidate?: string, + wallet?: string, council?: CouncilSlugs, network?: string, epochId?: string ) => { try { - if (!candidate || !council || !network || !epochId) return; + if (!candidate || !council || !network || !epochId || !wallet) return; const parsedSelection = JSON.parse(localStorage.getItem(localStorageKey) || '{}'); - if (!parsedSelection[epochId]) { - parsedSelection[epochId] = {}; + if (!parsedSelection[wallet]) { + parsedSelection[wallet] = {}; } - if (!parsedSelection[epochId][network]) { - parsedSelection[epochId][network] = {}; + + if (!parsedSelection[wallet][epochId]) { + parsedSelection[wallet][epochId] = {}; + } + if (!parsedSelection[wallet][epochId][network]) { + parsedSelection[wallet][epochId][network] = {}; } - parsedSelection[epochId][network][council] = candidate; + parsedSelection[wallet][epochId][network][council] = candidate; localStorage.setItem(localStorageKey, JSON.stringify(parsedSelection)); } catch (error) { @@ -29,24 +34,33 @@ export const setCandidate = ( } }; -export const removeCandidate = (council?: CouncilSlugs, network?: string, epochId?: string) => { - if (!council || !network || !epochId) return; +export const removeCandidate = ( + council?: CouncilSlugs, + wallet?: string, + network?: string, + epochId?: string +) => { + if (!council || !network || !epochId || !wallet) return; try { const parsedSelection = JSON.parse(localStorage.getItem(localStorageKey) || '{}'); if ( - parsedSelection[epochId] && - parsedSelection[epochId][network] && - parsedSelection[epochId][network][council] + parsedSelection[wallet] && + parsedSelection[wallet][epochId] && + parsedSelection[wallet][epochId][network] && + parsedSelection[wallet][epochId][network][council] ) { - delete parsedSelection[epochId][network][council]; + delete parsedSelection[wallet][epochId][network][council]; - if (Object.keys(parsedSelection[epochId][network]).length === 0) { - delete parsedSelection[epochId][network]; + if (Object.keys(parsedSelection[wallet][epochId][network]).length === 0) { + delete parsedSelection[wallet][epochId][network]; + } + if (Object.keys(parsedSelection[wallet][epochId]).length === 0) { + delete parsedSelection[wallet][epochId]; } - if (Object.keys(parsedSelection[epochId]).length === 0) { - delete parsedSelection[epochId]; + if (Object.keys(parsedSelection[wallet]).length === 0) { + delete parsedSelection[wallet]; } localStorage.setItem(localStorageKey, JSON.stringify(parsedSelection)); @@ -55,6 +69,7 @@ export const removeCandidate = (council?: CouncilSlugs, network?: string, epochI console.error( 'Tried to remove address that wasn’t present in local storage: ', council, + wallet, network, epochId, err @@ -64,30 +79,38 @@ export const removeCandidate = (council?: CouncilSlugs, network?: string, epochI export const getVoteSelectionState = ( state: VoteState, + wallet?: string, epochId?: string, networkId?: string, councilSlug?: CouncilSlugs ) => { - if (!epochId || !networkId) return ''; - + if (!epochId || !networkId || !wallet) return ''; if (councilSlug) { - return epochId - ? state[epochId] - ? networkId - ? state[epochId][networkId] - ? state[epochId][networkId][councilSlug] - ? state[epochId][networkId][councilSlug] + return wallet + ? state[wallet] + ? epochId + ? state[wallet][epochId] + ? networkId + ? state[wallet][epochId][networkId] + ? state[wallet][epochId][networkId][councilSlug] + ? state[wallet][epochId][networkId][councilSlug] + : '' + : '' : '' : '' : '' : '' : ''; } - return epochId - ? state[epochId] - ? networkId - ? state[epochId][networkId] - ? state[epochId][networkId] + return wallet + ? state[wallet] + ? epochId + ? state[wallet][epochId] + ? networkId + ? state[wallet][epochId][networkId] + ? state[wallet][epochId][networkId] + : '' + : '' : '' : '' : '' diff --git a/governance/ui/src/utils/sort-users.ts b/governance/ui/src/utils/sort-users.ts index 6ea98e1c4..abd24797a 100644 --- a/governance/ui/src/utils/sort-users.ts +++ b/governance/ui/src/utils/sort-users.ts @@ -21,9 +21,9 @@ export const sortUsers = ( if (councilNomineesDetails?.length && votes) { const candidatesByVotePowerRanking = votes ? Object.keys(votes[activeCouncil]).sort((a, b) => { - return votes[activeCouncil][b].votePower - .sub(votes[activeCouncil][a].votePower) - .toNumber(); + return votes[activeCouncil][b].votePower.sub(votes[activeCouncil][a].votePower).gt(0) + ? 1 + : -1; }) : []; return councilNomineesDetails @@ -72,15 +72,17 @@ export const sortUsers = ( if (sortConfig[1] === 'votingPower') { if (sortConfig[0]) { if (!b.voteResult?.votePower) return -1; - return b.voteResult?.votePower - .sub(a.voteResult?.votePower || BigNumber.from(0)) - .toNumber(); + return b.voteResult?.votePower.sub(a.voteResult?.votePower || BigNumber.from(0)).gt(0) + ? 1 + : -1; } else { if (!b.voteResult?.votePower) return 1; return b.voteResult?.votePower .sub(a.voteResult?.votePower || BigNumber.from(0)) .mul(-1) - .toNumber(); + .gt(0) + ? 1 + : -1; } } if (sortConfig[1] === 'votes') { @@ -92,9 +94,9 @@ export const sortUsers = ( } if (sortConfig[1] === 'ranking') { if (!b.voteResult?.votePower) return -1; - return b.voteResult?.votePower - .sub(a.voteResult?.votePower || BigNumber.from(0)) - .toNumber(); + return b.voteResult?.votePower.sub(a.voteResult?.votePower || BigNumber.from(0)).gt(0) + ? 1 + : -1; } return 0; });