From fb153aa7dc2f1d2f88a7a5b6bc296583d4f54fab Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 5 Feb 2024 19:21:48 +0100 Subject: [PATCH 1/7] Revamp ExtendedProgressBar, add voting power, add block number link, fix quorum calc --- src/components/Proposals/ProposalSummary.tsx | 107 ++++++++++++++---- .../SnapshotProposalSummary.tsx | 32 +++++- .../hooks/useSnapshotUserVotingWeight.ts | 22 ++++ .../ui/links/EtherscanLinkBlock.tsx | 31 +++++ src/components/ui/modals/DelegateModal.tsx | 5 +- src/components/ui/utils/ProgressBar.tsx | 72 ++++++------ .../governance/useERC20LinearStrategy.ts | 18 ++- .../schemas/DAOCreate/useDAOCreateSchema.ts | 7 +- .../schemas/common/useValidationAddress.tsx | 16 --- src/i18n/locales/en/proposal.json | 8 +- src/providers/App/AppProvider.tsx | 5 +- src/providers/App/useReadOnlyValues.ts | 93 ++++++++------- src/types/fractal.ts | 1 + 13 files changed, 264 insertions(+), 153 deletions(-) create mode 100644 src/components/Proposals/SnapshotProposalDetails/hooks/useSnapshotUserVotingWeight.ts create mode 100644 src/components/ui/links/EtherscanLinkBlock.tsx diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 152184c4e0..6f19724103 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -1,7 +1,8 @@ -import { Text, Box, Divider, Flex } from '@chakra-ui/react'; +import { Text, Box, Divider, Flex, Tooltip } from '@chakra-ui/react'; +import { ArrowAngleUp } from '@decent-org/fractal-ui'; import { format } from 'date-fns'; import { BigNumber } from 'ethers'; -import { useMemo } from 'react'; +import { useMemo, useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../constants/common'; import useBlockTimestamp from '../../hooks/utils/useBlockTimestamp'; @@ -11,12 +12,14 @@ import { DEFAULT_DATE_TIME_FORMAT } from '../../utils/numberFormats'; import ContentBox from '../ui/containers/ContentBox'; import { DisplayAddress } from '../ui/links/DisplayAddress'; import DisplayTransaction from '../ui/links/DisplayTransaction'; +import EtherscanLinkBlock from '../ui/links/EtherscanLinkBlock'; import { InfoBoxLoader } from '../ui/loaders/InfoBoxLoader'; -import { ExtendedProgressBar } from '../ui/utils/ProgressBar'; +import { QuorumProgressBar } from '../ui/utils/ProgressBar'; import { InfoRow } from './MultisigProposalDetails/TxDetails'; export default function ProposalSummary({ proposal: { + eventDate, startBlock, votesSummary: { yes, no, abstain }, deadlineMs, @@ -26,12 +29,19 @@ export default function ProposalSummary({ }: { proposal: AzoriusProposal; }) { - const { governance } = useFractal(); + const { + governance, + governanceContracts: { tokenContract }, + readOnly: { + user: { votingWeight, address }, + }, + } = useFractal(); const azoriusGovernance = governance as AzoriusGovernance; const { votesToken, type, erc721Tokens, votingStrategy } = azoriusGovernance; const { t } = useTranslation(['proposal', 'common', 'navigation']); const startBlockTimeStamp = useBlockTimestamp(startBlock.toNumber()); + const [proposalsERC20VotingWeight, setProposalsERC20VotingWeight] = useState('0'); const totalVotesCasted = useMemo(() => yes.add(no).add(abstain), [yes, no, abstain]); const totalVotingWeight = useMemo( () => @@ -41,6 +51,24 @@ export default function ProposalSummary({ ), [erc721Tokens] ); + const votesTokenDecimalsDenominator = useMemo( + () => BigNumber.from(10).pow(votesToken?.decimals || 0), + [votesToken?.decimals] + ); + + useEffect(() => { + async function loadProposalVotingWeight() { + if (tokenContract && address) { + const pastVotingWeight = await tokenContract.asSigner.getPastVotes(address, startBlock); + setProposalsERC20VotingWeight( + pastVotingWeight.div(votesTokenDecimalsDenominator).toString() + ); + } + } + + loadProposalVotingWeight(); + }, [address, startBlock, tokenContract, votesTokenDecimalsDenominator]); + const getVotesPercentage = (voteTotal: BigNumber): number => { if (type === GovernanceType.AZORIUS_ERC20) { if (!votesToken?.totalSupply || votesToken.totalSupply.eq(0)) { @@ -70,22 +98,12 @@ export default function ProposalSummary({ } const yesVotesPercentage = getVotesPercentage(yes); - const noVotesPercentage = getVotesPercentage(no); const strategyQuorum = votesToken && isERC20 ? votingStrategy.quorumPercentage!.value.toNumber() : isERC721 ? votingStrategy.quorumThreshold!.value.toNumber() : 1; - const requiredVotesPercentageToPass = Math.max( - isERC721 - ? no - .mul(100) - .div(totalVotingWeight || 1) - .toNumber() + 1 - : noVotesPercentage + 1, - isERC721 ? (strategyQuorum * 100) / totalVotingWeight!.toNumber() : strategyQuorum - ); return ( @@ -113,6 +131,40 @@ export default function ProposalSummary({ + + + {t('snapshotTaken')} + + + {format(eventDate, DEFAULT_DATE_TIME_FORMAT)} + + + + + {t('votingPower')} + + + + {t('show')} + + + {transactionHash && ( - diff --git a/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalSummary.tsx b/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalSummary.tsx index b328fc6b16..6822244b89 100644 --- a/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalSummary.tsx +++ b/src/components/Proposals/SnapshotProposalDetails/SnapshotProposalSummary.tsx @@ -1,4 +1,4 @@ -import { Text, Box, Divider, Flex } from '@chakra-ui/react'; +import { Text, Box, Divider, Flex, Tooltip } from '@chakra-ui/react'; import { format } from 'date-fns'; import { useTranslation } from 'react-i18next'; import { BACKGROUND_SEMI_TRANSPARENT } from '../../../constants/common'; @@ -7,8 +7,9 @@ import { DEFAULT_DATE_TIME_FORMAT } from '../../../utils/numberFormats'; import ContentBox from '../../ui/containers/ContentBox'; import ExternalLink from '../../ui/links/ExternalLink'; import { InfoBoxLoader } from '../../ui/loaders/InfoBoxLoader'; -import { ExtendedProgressBar } from '../../ui/utils/ProgressBar'; +import { QuorumProgressBar } from '../../ui/utils/ProgressBar'; import { InfoRow } from '../MultisigProposalDetails/TxDetails'; +import useSnapshotUserVotingWeight from './hooks/useSnapshotUserVotingWeight'; import useTotalVotes from './hooks/useTotalVotes'; interface ISnapshotProposalSummary { @@ -18,6 +19,7 @@ interface ISnapshotProposalSummary { export default function SnapshotProposalSummary({ proposal }: ISnapshotProposalSummary) { const { t } = useTranslation(['proposal', 'common', 'navigation']); const { totalVotesCasted } = useTotalVotes({ proposal }); + const { votingWeight } = useSnapshotUserVotingWeight({ proposal }); if (!proposal) { return ( @@ -71,19 +73,39 @@ export default function SnapshotProposalSummary({ proposal }: ISnapshotProposalS property={t('proposalSummaryEndDate')} value={format(proposal.endTime * 1000, DEFAULT_DATE_TIME_FORMAT)} /> + + + {t('votingPower')} + + + + {t('show')} + + + {!!proposal.quorum && ( - 0 ? (totalVotesCasted / proposal.quorum || 1) * 100 : 100 } - requiredPercentage={100} + reachedQuorum={totalVotesCasted.toString()} + totalQuorum={proposal.quorum?.toString()} unit="" /> diff --git a/src/components/Proposals/SnapshotProposalDetails/hooks/useSnapshotUserVotingWeight.ts b/src/components/Proposals/SnapshotProposalDetails/hooks/useSnapshotUserVotingWeight.ts new file mode 100644 index 0000000000..6ba6ad839c --- /dev/null +++ b/src/components/Proposals/SnapshotProposalDetails/hooks/useSnapshotUserVotingWeight.ts @@ -0,0 +1,22 @@ +import { useState, useEffect } from 'react'; +import useSnapshotProposal from '../../../../hooks/DAO/loaders/snapshot/useSnapshotProposal'; +import { FractalProposal } from '../../../../types'; + +export default function useSnapshotUserVotingWeight({ + proposal, +}: { + proposal: FractalProposal | null | undefined; +}) { + const [votingWeight, setVotingWeight] = useState(0); + const { loadVotingWeight } = useSnapshotProposal(proposal); + + useEffect(() => { + async function getVotingWeight() { + const votingWeightData = await loadVotingWeight(); + setVotingWeight(votingWeightData.votingWeight); + } + getVotingWeight(); + }, [loadVotingWeight]); + + return { votingWeight }; +} diff --git a/src/components/ui/links/EtherscanLinkBlock.tsx b/src/components/ui/links/EtherscanLinkBlock.tsx new file mode 100644 index 0000000000..024d64eb10 --- /dev/null +++ b/src/components/ui/links/EtherscanLinkBlock.tsx @@ -0,0 +1,31 @@ +import { LinkProps } from '@chakra-ui/react'; +import { useNetworkConfig } from '../../../providers/NetworkConfig/NetworkConfigProvider'; +import EtherscanLinkBase from './EtherscanLinkBase'; + +interface Props extends LinkProps { + blockNumber?: string | null; + children: React.ReactNode; +} + +/** + * A transaction link to Etherscan. + * + * For most use cases, you probably want to use DisplayTransaction, + * to add proper styling. + */ +export default function EtherscanLinkBlock({ blockNumber, children, ...rest }: Props) { + const { etherscanBaseURL } = useNetworkConfig(); + + if (!blockNumber) { + return null; + } + + return ( + + {children} + + ); +} diff --git a/src/components/ui/modals/DelegateModal.tsx b/src/components/ui/modals/DelegateModal.tsx index c7b8c565a1..9cff91ec09 100644 --- a/src/components/ui/modals/DelegateModal.tsx +++ b/src/components/ui/modals/DelegateModal.tsx @@ -22,11 +22,11 @@ export function DelegateModal({ close }: { close: Function }) { governance, governanceContracts: { tokenContract, lockReleaseContract }, readOnly: { user }, + action: { loadReadOnlyValues }, } = useFractal(); const signer = useEthersSigner(); const azoriusGovernance = governance as AzoriusGovernance; - const decentGovernance = azoriusGovernance as DecentGovernance; const delegateeDisplayName = useDisplayName(azoriusGovernance?.votesToken?.delegatee); const lockedDelegateeDisplayName = useDisplayName(decentGovernance?.lockedVotesToken?.delegatee); @@ -56,7 +56,8 @@ export function DelegateModal({ close }: { close: Function }) { delegateVote({ delegatee: validAddress, votingTokenContract: lockReleaseContract.asSigner, - successCallback: () => { + successCallback: async () => { + await loadReadOnlyValues(); close(); }, }); diff --git a/src/components/ui/utils/ProgressBar.tsx b/src/components/ui/utils/ProgressBar.tsx index 5a069865d1..87f343502a 100644 --- a/src/components/ui/utils/ProgressBar.tsx +++ b/src/components/ui/utils/ProgressBar.tsx @@ -1,17 +1,18 @@ import { Box, Flex, Progress, Text } from '@chakra-ui/react'; -import { ProgressBarDelimiter } from '@decent-org/fractal-ui'; +import { useTranslation } from 'react-i18next'; +interface ProgressBarProps { + value: number; + unit?: string; + valueLabel?: string; + showValueWithinProgressBar?: boolean; +} export default function ProgressBar({ value, - requiredValue, unit = '%', + showValueWithinProgressBar = true, valueLabel, -}: { - value: number; - requiredValue?: number; - unit?: string; - valueLabel?: string; -}) { +}: ProgressBarProps) { return ( - {value > 0 && ( + {showValueWithinProgressBar && value > 0 && ( )} - {!!(requiredValue && requiredValue > 0) && ( - - - - )} ); } -interface ExtendedProgressBarProps { - label: string; +interface QuorumProgressBarProps { helperText?: string; percentage: number; - requiredPercentage: number; unit?: string; valueLabel?: string; + reachedQuorum?: string; + totalQuorum?: string; } -export function ExtendedProgressBar({ - label, - helperText, +export function QuorumProgressBar({ percentage, - requiredPercentage, unit, - valueLabel, -}: ExtendedProgressBarProps) { + helperText, + reachedQuorum, + totalQuorum, +}: QuorumProgressBarProps) { + const { t } = useTranslation('proposal'); return ( - - {label} - + {reachedQuorum && totalQuorum && ( + + {t('quorum', { ns: 'common' })} + + {reachedQuorum}/{totalQuorum} + + + )} {helperText && ( { if (!ozLinearVotingContract || !azoriusContract) { return {}; } - const [votingPeriodBlocks, quorumNumerator, quorumDenominator, timeLockPeriod] = - await Promise.all([ - ozLinearVotingContract.asSigner.votingPeriod(), - ozLinearVotingContract.asSigner.quorumNumerator(), - ozLinearVotingContract.asSigner.QUORUM_DENOMINATOR(), - azoriusContract.asSigner.timelockPeriod(), - ]); - - const quorumPercentage = quorumNumerator.mul(100).div(quorumDenominator); + const [votingPeriodBlocks, quorumNumerator, timeLockPeriod] = await Promise.all([ + ozLinearVotingContract.asSigner.votingPeriod(), + ozLinearVotingContract.asSigner.quorumNumerator(), + azoriusContract.asSigner.timelockPeriod(), + ]); const votingPeriodValue = await blocksToSeconds(votingPeriodBlocks, provider); const timeLockPeriodValue = await blocksToSeconds(timeLockPeriod, provider); @@ -46,8 +42,8 @@ export const useERC20LinearStrategy = () => { formatted: getTimeDuration(votingPeriodValue), }, quorumPercentage: { - value: quorumPercentage, - formatted: quorumPercentage.toString() + '%', + value: quorumNumerator, + formatted: quorumNumerator.toString() + '%', }, timeLockPeriod: { value: BigNumber.from(timeLockPeriodValue), diff --git a/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts b/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts index fd227d852b..f6c285114d 100644 --- a/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts +++ b/src/hooks/schemas/DAOCreate/useDAOCreateSchema.ts @@ -20,7 +20,6 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { addressValidationTest, uniqueAddressValidationTest, uniqueNFTAddressValidationTest, - ensNameValidationTest, } = useValidationAddress(); const { minValueValidation, @@ -40,10 +39,7 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { essentials: Yup.object().shape({ daoName: Yup.string().required(), governance: Yup.string().required(), - snapshotURL: Yup.string().when({ - is: (value: string) => !!value, - then: _schema => _schema.test(ensNameValidationTest), - }), + snapshotURL: Yup.string(), }), multisig: Yup.object().when('essentials', { is: ({ governance }: DAOEssentials) => governance === GovernanceType.MULTISIG, @@ -162,7 +158,6 @@ export const useDAOCreateSchema = ({ isSubDAO }: { isSubDAO?: boolean }) => { }), }), [ - ensNameValidationTest, isSubDAO, t, addressValidationTest, diff --git a/src/hooks/schemas/common/useValidationAddress.tsx b/src/hooks/schemas/common/useValidationAddress.tsx index a63d0312b2..66438fcaba 100644 --- a/src/hooks/schemas/common/useValidationAddress.tsx +++ b/src/hooks/schemas/common/useValidationAddress.tsx @@ -107,21 +107,6 @@ export const useValidationAddress = () => { }; }, [signerOrProvider, addressValidationMap, t]); - const ensNameValidationTest = useMemo(() => { - return { - name: 'ENS Name Validation', - message: t('errorInvalidENSName', { ns: 'common' }), - test: function (ensName: string | undefined) { - if (!ensName) return false; - const { validation } = validateENSName({ ensName }); - if (validation.isValidAddress) { - addressValidationMap.current.set(ensName, validation); - } - return validation.isValidAddress; - }, - }; - }, [addressValidationMap, t]); - const addressValidationTestSimple = useMemo(() => { return { name: 'Address Validation', @@ -222,7 +207,6 @@ export const useValidationAddress = () => { return { addressValidationTestSimple, addressValidationTest, - ensNameValidationTest, newSignerValidationTest, uniqueAddressValidationTest, uniqueNFTAddressValidationTest, diff --git a/src/i18n/locales/en/proposal.json b/src/i18n/locales/en/proposal.json index 7b5e7cb8de..45beb45c2f 100644 --- a/src/i18n/locales/en/proposal.json +++ b/src/i18n/locales/en/proposal.json @@ -48,9 +48,11 @@ "proposalSummaryTitle": "Proposal Details", "proposalSummaryStartDate": "Start Date", "proposalSummaryEndDate": "End Date", - "proposalSupportERC20SummaryHelper": "Minimum {{count}}% support required", - "proposalSupportERC721SummaryHelper_one": "Minimum {{count}} vote support required", - "proposalSupportERC721SummaryHelper_other": "Minimum {{count}} votes support required", + "snapshotTaken": "Snapshot taken", + "votingPower": "Voting power", + "show": "Show", + "proposalSupportERC20SummaryHelper": "Minimum {{quorum}}% required out of {{total}}", + "proposalSupportERC721SummaryHelper": "Minimum {{quorum}} token votes required out {{total}}", "proposedBy": "Proposed by", "proposalTitle_one": "Proposal to execute {{count}} transaction on {{target}}", "proposalTitle_other": "Proposal to execute {{count}} transactions on {{target}}", diff --git a/src/providers/App/AppProvider.tsx b/src/providers/App/AppProvider.tsx index ab1faf875c..23efa3d5ab 100644 --- a/src/providers/App/AppProvider.tsx +++ b/src/providers/App/AppProvider.tsx @@ -16,7 +16,7 @@ export function AppProvider({ children }: { children: ReactNode }) { // loads base Fractal contracts with provider into state const baseContracts = useSafeContracts(); // memoize fractal store - const { readOnlyValues } = useReadOnlyValues(state, account); + const { readOnlyValues, loadReadOnlyValues } = useReadOnlyValues(state, account); const fractalStore: FractalStore = useMemo(() => { return { node: state.node, @@ -28,13 +28,14 @@ export function AppProvider({ children }: { children: ReactNode }) { readOnly: readOnlyValues, action: { dispatch, + loadReadOnlyValues, resetDAO: async () => { await Promise.resolve(dispatch({ type: StoreAction.RESET })); }, }, baseContracts, }; - }, [state, baseContracts, readOnlyValues]); + }, [state, baseContracts, loadReadOnlyValues, readOnlyValues]); return {children}; } diff --git a/src/providers/App/useReadOnlyValues.ts b/src/providers/App/useReadOnlyValues.ts index 0591bcd40c..f965d07189 100644 --- a/src/providers/App/useReadOnlyValues.ts +++ b/src/providers/App/useReadOnlyValues.ts @@ -1,7 +1,6 @@ import { ERC721__factory } from '@fractal-framework/fractal-contracts'; import { BigNumber, utils } from 'ethers'; - -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import useSignerOrProvider from '../../hooks/utils/useSignerOrProvider'; import { ReadOnlyState, @@ -28,54 +27,52 @@ export const useReadOnlyValues = ({ node, governance }: Fractal, _account?: stri const [readOnlyValues, setReadOnlyValues] = useState(INITIAL_READ_ONLY_VALUES); const signerOrProvider = useSignerOrProvider(); - useEffect(() => { - const loadReadOnlyValues = async () => { - const getVotingWeight = async () => { - const azoriusGovernance = governance as AzoriusGovernance; - switch (governance.type) { - case GovernanceType.MULTISIG: - const isSigner = _account && node.safe?.owners.includes(_account); - return isSigner ? BigNumber.from(1) : BigNumber.from(0); - case GovernanceType.AZORIUS_ERC20: - const lockedTokenWeight = (governance as DecentGovernance).lockedVotesToken - ?.votingWeight; - const tokenWeight = azoriusGovernance.votesToken?.votingWeight || BigNumber.from(0); - return lockedTokenWeight || tokenWeight; - case GovernanceType.AZORIUS_ERC721: - if (!_account || !azoriusGovernance.erc721Tokens) { - return BigNumber.from(0); - } - const userVotingWeight = ( - await Promise.all( - azoriusGovernance.erc721Tokens.map(async ({ address, votingWeight }) => { - const tokenContract = ERC721__factory.connect(address, signerOrProvider); - const userBalance = await tokenContract.balanceOf(_account); - return userBalance.mul(votingWeight); - }) - ) - ).reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); - return userVotingWeight; - default: + const loadReadOnlyValues = useCallback(async () => { + const getVotingWeight = async () => { + const azoriusGovernance = governance as AzoriusGovernance; + switch (governance.type) { + case GovernanceType.MULTISIG: + const isSigner = _account && node.safe?.owners.includes(_account); + return isSigner ? BigNumber.from(1) : BigNumber.from(0); + case GovernanceType.AZORIUS_ERC20: + const lockedTokenWeight = (governance as DecentGovernance).lockedVotesToken?.votingWeight; + const tokenWeight = azoriusGovernance.votesToken?.votingWeight || BigNumber.from(0); + return lockedTokenWeight || tokenWeight; + case GovernanceType.AZORIUS_ERC721: + if (!_account || !azoriusGovernance.erc721Tokens) { return BigNumber.from(0); - } - }; - - setReadOnlyValues({ - user: { - address: _account ? utils.getAddress(_account) : undefined, - votingWeight: await getVotingWeight(), - }, - dao: !node.daoAddress - ? null // if there is no DAO connected, we return null for this - : { - isAzorius: - governance.type === GovernanceType.AZORIUS_ERC20 || - governance.type === GovernanceType.AZORIUS_ERC721, - }, - }); + } + const userVotingWeight = ( + await Promise.all( + azoriusGovernance.erc721Tokens.map(async ({ address, votingWeight }) => { + const tokenContract = ERC721__factory.connect(address, signerOrProvider); + const userBalance = await tokenContract.balanceOf(_account); + return userBalance.mul(votingWeight); + }) + ) + ).reduce((prev, curr) => prev.add(curr), BigNumber.from(0)); + return userVotingWeight; + default: + return BigNumber.from(0); + } }; - loadReadOnlyValues(); + setReadOnlyValues({ + user: { + address: _account ? utils.getAddress(_account) : undefined, + votingWeight: await getVotingWeight(), + }, + dao: !node.daoAddress + ? null // if there is no DAO connected, we return null for this + : { + isAzorius: + governance.type === GovernanceType.AZORIUS_ERC20 || + governance.type === GovernanceType.AZORIUS_ERC721, + }, + }); }, [node, governance, _account, signerOrProvider]); - return { readOnlyValues }; + useEffect(() => { + loadReadOnlyValues(); + }, [loadReadOnlyValues]); + return { readOnlyValues, loadReadOnlyValues }; }; diff --git a/src/types/fractal.ts b/src/types/fractal.ts index 0ef6efc47d..63a996aebd 100644 --- a/src/types/fractal.ts +++ b/src/types/fractal.ts @@ -197,6 +197,7 @@ export interface FractalStore extends Fractal { baseContracts: FractalContracts; action: { dispatch: Dispatch; + loadReadOnlyValues: () => Promise; resetDAO: () => Promise; }; } From 0569025ae1372982254394eba38d5f1813e4d361 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Mon, 5 Feb 2024 19:33:14 +0100 Subject: [PATCH 2/7] Add voting system indication for ERC-20 and ERC-721 ProposalSummary --- src/components/Proposals/ProposalSummary.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 6f19724103..b347a3e7ea 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -110,6 +110,10 @@ export default function ProposalSummary({ {t('proposalSummaryTitle')} + Date: Wed, 7 Feb 2024 14:20:15 +0100 Subject: [PATCH 3/7] Add tooltip for voting power explanation, convert voting power to button and reveal it on button click --- package-lock.json | 14 ++++---- package.json | 2 +- src/components/Proposals/ProposalSummary.tsx | 34 ++++++++++++++----- .../SnapshotProposalSummary.tsx | 31 ++++++++++++----- src/components/ui/utils/ProgressBar.tsx | 10 +++++- src/i18n/locales/en/proposal.json | 1 + 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 848384586b..78b3c296fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@apollo/client": "^3.7.10", "@chakra-ui/next-js": "^2.2.0", "@chakra-ui/react": "^2.8.2", - "@decent-org/fractal-ui": "^0.1.22", + "@decent-org/fractal-ui": "^0.1.23", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@ethersproject/abstract-signer": "^5.7.0", @@ -3380,9 +3380,9 @@ } }, "node_modules/@decent-org/fractal-ui": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@decent-org/fractal-ui/-/fractal-ui-0.1.22.tgz", - "integrity": "sha512-17exPAa4jXljrsXatDpB3fJC0l4XGo1nj3dxoS2H13ruJhPnOsQhH/ooDPXXUnmEC7YvFOp0gq4XNiLOMoOvDA==", + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@decent-org/fractal-ui/-/fractal-ui-0.1.23.tgz", + "integrity": "sha512-2ML5Yzj60k4faIdWQ0LRV9X4704JcOq3cRKUmqDNjFmTWRktSuQ9yeCTk6OOqBOktLVD1Ca8FcE4HUm/P/8sMg==", "peerDependencies": { "@chakra-ui/react": "^2.3.4", "react": "^16 || ^17 || ^18", @@ -28851,9 +28851,9 @@ } }, "@decent-org/fractal-ui": { - "version": "0.1.22", - "resolved": "https://registry.npmjs.org/@decent-org/fractal-ui/-/fractal-ui-0.1.22.tgz", - "integrity": "sha512-17exPAa4jXljrsXatDpB3fJC0l4XGo1nj3dxoS2H13ruJhPnOsQhH/ooDPXXUnmEC7YvFOp0gq4XNiLOMoOvDA==", + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/@decent-org/fractal-ui/-/fractal-ui-0.1.23.tgz", + "integrity": "sha512-2ML5Yzj60k4faIdWQ0LRV9X4704JcOq3cRKUmqDNjFmTWRktSuQ9yeCTk6OOqBOktLVD1Ca8FcE4HUm/P/8sMg==", "requires": {} }, "@emotion/babel-plugin": { diff --git a/package.json b/package.json index f7d532044c..3aab448e30 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "@apollo/client": "^3.7.10", "@chakra-ui/next-js": "^2.2.0", "@chakra-ui/react": "^2.8.2", - "@decent-org/fractal-ui": "^0.1.22", + "@decent-org/fractal-ui": "^0.1.23", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@ethersproject/abstract-signer": "^5.7.0", diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index b347a3e7ea..5751410c93 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -1,4 +1,4 @@ -import { Text, Box, Divider, Flex, Tooltip } from '@chakra-ui/react'; +import { Text, Box, Button, Divider, Flex, Tooltip } from '@chakra-ui/react'; import { ArrowAngleUp } from '@decent-org/fractal-ui'; import { format } from 'date-fns'; import { BigNumber } from 'ethers'; @@ -55,6 +55,9 @@ export default function ProposalSummary({ () => BigNumber.from(10).pow(votesToken?.decimals || 0), [votesToken?.decimals] ); + const [showVotingPower, setShowVotingPower] = useState(false); + + const toggleShowVotingPower = () => setShowVotingPower(prevState => !prevState); useEffect(() => { async function loadProposalVotingWeight() { @@ -105,6 +108,22 @@ export default function ProposalSummary({ ? votingStrategy.quorumThreshold!.value.toNumber() : 1; + const ShowVotingPowerButton = ( + + ); + return ( {t('proposalSummaryTitle')} @@ -160,14 +179,11 @@ export default function ProposalSummary({ > {t('votingPower')} - - - {t('show')} - - + {showVotingPower ? ( + {ShowVotingPowerButton} + ) : ( + ShowVotingPowerButton + )} {transactionHash && ( setShowVotingPower(prevState => !prevState); if (!proposal) { return ( @@ -42,6 +46,18 @@ export default function SnapshotProposalSummary({ proposal }: ISnapshotProposalS } }; + const ShowVotingPowerButton = ( + + ); + return ( {t('proposalSummaryTitle')} @@ -84,14 +100,11 @@ export default function SnapshotProposalSummary({ proposal }: ISnapshotProposalS > {t('votingPower')} - - - {t('show')} - - + {showVotingPower ? ( + {ShowVotingPowerButton} + ) : ( + ShowVotingPowerButton + )} {showValueWithinProgressBar && value > 0 && ( @@ -70,9 +71,16 @@ export function QuorumProgressBar({ marginTop={2} marginBottom={3} justifyContent="space-between" + alignItems="center" > {t('quorum', { ns: 'common' })} + {reachedQuorum >= totalQuorum && ( + + )} {reachedQuorum}/{totalQuorum} diff --git a/src/i18n/locales/en/proposal.json b/src/i18n/locales/en/proposal.json index 45beb45c2f..fd64dee58d 100644 --- a/src/i18n/locales/en/proposal.json +++ b/src/i18n/locales/en/proposal.json @@ -50,6 +50,7 @@ "proposalSummaryEndDate": "End Date", "snapshotTaken": "Snapshot taken", "votingPower": "Voting power", + "votingPowerTooltip": "This is your voting power at a time of taking snapshot, which happens in the moment of proposal creation", "show": "Show", "proposalSupportERC20SummaryHelper": "Minimum {{quorum}}% required out of {{total}}", "proposalSupportERC721SummaryHelper": "Minimum {{quorum}} token votes required out {{total}}", From 6bb091ac862db543dab229e9da93c728191fe249 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 7 Feb 2024 14:22:04 +0100 Subject: [PATCH 4/7] Remove explicitly setting progress bar height --- src/components/ui/utils/ProgressBar.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/ui/utils/ProgressBar.tsx b/src/components/ui/utils/ProgressBar.tsx index 66a56864d7..be4ceb2a06 100644 --- a/src/components/ui/utils/ProgressBar.tsx +++ b/src/components/ui/utils/ProgressBar.tsx @@ -21,7 +21,6 @@ export default function ProgressBar({ > {showValueWithinProgressBar && value > 0 && ( From 970c5f083c1546555b54cfc75f46d30278f2d454 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Wed, 7 Feb 2024 17:26:26 +0100 Subject: [PATCH 5/7] Fix showing required quorum --- src/components/Proposals/ProposalSummary.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 5751410c93..8e72e01156 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -230,8 +230,12 @@ export default function ProposalSummary({ } totalQuorum={ isERC721 - ? totalVotingWeight?.toString() - : votesToken?.totalSupply.div(votesTokenDecimalsDenominator).toString() + ? strategyQuorum.toString() + : votesToken?.totalSupply + .div(votesTokenDecimalsDenominator) + .div(100) + .mul(strategyQuorum) + .toString() } unit={isERC20 ? '%' : ''} /> From 20396afcbb940747db98e9b0da6a45da4da59829 Mon Sep 17 00:00:00 2001 From: Kirill Klimenko Date: Thu, 8 Feb 2024 20:16:16 +0100 Subject: [PATCH 6/7] Visual adjustments to the progress bar value positioning, fix quorum progress bar filling --- src/components/Proposals/ProposalSummary.tsx | 57 ++++++------------ .../Proposals/ProposalVotes/index.tsx | 25 ++++---- .../SnapshotProposalSummary.tsx | 8 +-- src/components/ui/utils/ProgressBar.tsx | 59 ++++++++++--------- 4 files changed, 67 insertions(+), 82 deletions(-) diff --git a/src/components/Proposals/ProposalSummary.tsx b/src/components/Proposals/ProposalSummary.tsx index 8e72e01156..3357332046 100644 --- a/src/components/Proposals/ProposalSummary.tsx +++ b/src/components/Proposals/ProposalSummary.tsx @@ -72,21 +72,6 @@ export default function ProposalSummary({ loadProposalVotingWeight(); }, [address, startBlock, tokenContract, votesTokenDecimalsDenominator]); - const getVotesPercentage = (voteTotal: BigNumber): number => { - if (type === GovernanceType.AZORIUS_ERC20) { - if (!votesToken?.totalSupply || votesToken.totalSupply.eq(0)) { - return 0; - } - return voteTotal.div(votesToken.totalSupply.div(100)).toNumber(); - } else if (type === GovernanceType.AZORIUS_ERC721) { - if (totalVotesCasted.eq(0) || !erc721Tokens || !totalVotingWeight) { - return 0; - } - return voteTotal.mul(100).div(totalVotingWeight).toNumber(); - } - return 0; - }; - const isERC20 = type === GovernanceType.AZORIUS_ERC20; const isERC721 = type === GovernanceType.AZORIUS_ERC721; if ( @@ -100,17 +85,32 @@ export default function ProposalSummary({ ); } - const yesVotesPercentage = getVotesPercentage(yes); const strategyQuorum = votesToken && isERC20 ? votingStrategy.quorumPercentage!.value.toNumber() : isERC721 ? votingStrategy.quorumThreshold!.value.toNumber() : 1; + const reachedQuorum = isERC721 + ? totalVotesCasted.sub(no).toString() + : votesToken + ? totalVotesCasted.sub(no).div(votesTokenDecimalsDenominator).toString() + : '0'; + const totalQuorum = isERC721 + ? strategyQuorum.toString() + : votesToken?.totalSupply + .div(votesTokenDecimalsDenominator) + .div(100) + .mul(strategyQuorum) + .toString(); const ShowVotingPowerButton = (