From d2c3ff868152594c23c3db29a10944572859f070 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 19 Mar 2024 01:20:13 -0400 Subject: [PATCH 01/27] update Badge component with updated designs - ProposalCountdown updated for compadability --- src/components/ui/badges/Badge.tsx | 125 +++++++++++------- .../ui/proposal/ProposalCountdown.tsx | 9 +- 2 files changed, 86 insertions(+), 48 deletions(-) diff --git a/src/components/ui/badges/Badge.tsx b/src/components/ui/badges/Badge.tsx index 3c0de816ec..4fcd0981b5 100644 --- a/src/components/ui/badges/Badge.tsx +++ b/src/components/ui/badges/Badge.tsx @@ -1,91 +1,106 @@ -import { Flex, Text, Tooltip } from '@chakra-ui/react'; -import { ActiveTwo, Check, ClockTwo, CloseX, DoubleCheck } from '@decent-org/fractal-ui'; +import { Box, Flex, Text, Tooltip } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { TOOLTIP_MAXW } from '../../../constants/common'; -import { FractalProposalState, DAOState } from '../../../types'; -import { Green, Red } from '../colors'; +import { FractalProposalState, DAOState, FractalProposal } from '../../../types'; +import { ProposalCountdown } from '../proposal/ProposalCountdown'; -type BadgeType = { [key: string]: { Icon?: any; tooltipKey?: string; bg: string; color: string } }; +type BadgeType = { + [key: string]: { + tooltipKey?: string; + bg: string; + _hover: { bg: string }; + textColor: string; + }; +}; + +const greenPlus2 = '#56A355'; +const greenMinus4 = '#0A320A'; +const greenHover = '#0E440E'; + +const redPlus4 = '#FFC7C7'; +const redMinus2 = '#640E0D'; +const redHover = '#76110F'; const BADGE_MAPPING: BadgeType = { [FractalProposalState.ACTIVE]: { - Icon: ActiveTwo, tooltipKey: 'stateActiveTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [FractalProposalState.TIMELOCKED]: { - Icon: ClockTwo, tooltipKey: 'stateTimelockedTip', bg: 'sand.700', - color: 'grayscale.black', + textColor: 'grayscale.black', + _hover: { bg: 'grayscale.black' }, }, [FractalProposalState.EXECUTED]: { - Icon: DoubleCheck, tooltipKey: 'stateExecutedTip', - bg: Green._600, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [FractalProposalState.EXECUTABLE]: { - Icon: Check, tooltipKey: 'stateExecutableTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [FractalProposalState.FAILED]: { - Icon: CloseX, tooltipKey: 'stateFailedTip', - bg: Red._600, - color: 'grayscale.black', + bg: redMinus2, + textColor: redPlus4, + _hover: { bg: redHover }, }, [FractalProposalState.TIMELOCKABLE]: { - Icon: ClockTwo, tooltipKey: 'stateTimelockableTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [FractalProposalState.MODULE]: { tooltipKey: 'stateModuleTip', bg: 'sand.700', - color: 'grayscale.black', + textColor: 'grayscale.black', + _hover: { bg: '' }, }, [FractalProposalState.EXPIRED]: { - Icon: ClockTwo, tooltipKey: 'stateExpiredTip', - bg: Red._600, - color: 'grayscale.black', + bg: redMinus2, + textColor: redPlus4, + _hover: { bg: redHover }, }, [FractalProposalState.REJECTED]: { - Icon: CloseX, tooltipKey: 'stateRejectedTip', - bg: Red._600, - color: 'grayscale.black', + bg: redMinus2, + textColor: redPlus4, + _hover: { bg: redHover }, }, [FractalProposalState.PENDING]: { - Icon: ClockTwo, tooltipKey: 'statePendingTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [FractalProposalState.CLOSED]: { - Icon: ClockTwo, tooltipKey: 'stateClosedTip', - bg: Green._600, - color: 'grayscale.black', + bg: greenMinus4, + textColor: greenPlus2, + _hover: { bg: greenHover }, }, [DAOState.freezeInit]: { - Icon: Check, tooltipKey: 'stateFreezeInitTip', bg: 'blue.400', - color: 'grayscale.black', + textColor: 'grayscale.black', + _hover: { bg: 'grayscale.black' }, }, [DAOState.frozen]: { - Icon: DoubleCheck, tooltipKey: 'stateFrozenTip', bg: 'blue.400', - color: 'grayscale.black', + textColor: 'grayscale.black', + _hover: { bg: 'grayscale.black' }, }, - ownerApproved: { bg: 'sand.700', color: 'grayscale.black' }, + ownerApproved: { bg: 'sand.700', textColor: 'grayscale.black', _hover: { bg: 'grayscale.black' } }, }; type BadgeSize = { [key: string]: { minWidth: string; height: string } }; @@ -97,10 +112,12 @@ const BADGE_SIZES: BadgeSize = { interface IBadge { size: 'sm' | 'base'; labelKey: FractalProposalState | DAOState | string; + // add as undefined to avoid breaking changes + proposal?: FractalProposal; } -export function Badge({ labelKey, size }: IBadge) { - const { Icon, tooltipKey, ...colors } = BADGE_MAPPING[labelKey]; +export function Badge({ labelKey, size, proposal }: IBadge) { + const { tooltipKey, ...colors } = BADGE_MAPPING[labelKey]; const sizes = BADGE_SIZES[size]; const { t } = useTranslation('proposal'); @@ -112,16 +129,32 @@ export function Badge({ labelKey, size }: IBadge) { placement="top" > - {!!Icon && } - {t(labelKey)} + + {t(labelKey)} + {proposal && ( + + )} diff --git a/src/components/ui/proposal/ProposalCountdown.tsx b/src/components/ui/proposal/ProposalCountdown.tsx index c7d0ac17c3..03fe56a695 100644 --- a/src/components/ui/proposal/ProposalCountdown.tsx +++ b/src/components/ui/proposal/ProposalCountdown.tsx @@ -21,9 +21,14 @@ const zeroPad = (num: number) => String(num).padStart(2, '0'); export function ProposalCountdown({ proposal, showIcon = true, + textColor = 'chocolate.200', + textStyle = 'text-base-mono-semibold', // previous default }: { proposal: FractalProposal; showIcon?: boolean; + // custom text color and style + textColor?: string; + textStyle?: string; }) { const totalSecondsLeft = useProposalCountdown(proposal); const { t } = useTranslation('proposal'); @@ -85,8 +90,8 @@ export function ProposalCountdown({ gap={1} > {showDays && `${zeroPad(daysLeft)}:`} {showHours && `${zeroPad(hoursLeft)}:`} From f0e2538c3aeff91f3fb1be8f3c5769522b4ed334 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 19 Mar 2024 13:16:21 -0400 Subject: [PATCH 02/27] create badge to display quorum --- src/components/ui/badges/QuorumBadge.tsx | 96 ++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/components/ui/badges/QuorumBadge.tsx diff --git a/src/components/ui/badges/QuorumBadge.tsx b/src/components/ui/badges/QuorumBadge.tsx new file mode 100644 index 0000000000..ef32af3276 --- /dev/null +++ b/src/components/ui/badges/QuorumBadge.tsx @@ -0,0 +1,96 @@ +import { Box, Flex } from '@chakra-ui/react'; +import { Check } from '@decent-org/fractal-ui'; +import { BigNumber } from 'ethers'; +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useFractal } from '../../../providers/App/AppProvider'; +import { + AzoriusGovernance, + AzoriusProposal, + FractalProposal, + SnapshotProposal, +} from '../../../types'; + +const quorumNotReachedColor = '#838383'; +const quorumReachedColor = '#56A355'; +export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) { + const { governance } = useFractal(); + const { t } = useTranslation('common'); + + const azoriusGovernance = governance as AzoriusGovernance; + const { votesToken, erc721Tokens, votingStrategy } = azoriusGovernance; + + const votesTokenDecimalsDenominator = useMemo( + () => BigNumber.from(10).pow(votesToken?.decimals || 0), + [votesToken?.decimals], + ); + const { votesSummary } = proposal as AzoriusProposal; + const totalVotesCasted = useMemo(() => { + if (votesSummary) { + return votesSummary.yes.add(votesSummary.no).add(votesSummary.abstain); + } + return BigNumber.from(0); + }, [votesSummary]); + // @dev only azorius governance has quorum + if ((proposal as SnapshotProposal).snapshotProposalId || !votingStrategy) { + return null; + } + + if (votesToken !== undefined && erc721Tokens !== undefined) { + return null; + } + + const quorumDisplay = !!votingStrategy.quorumPercentage + ? votingStrategy.quorumPercentage.formatted + : !!votingStrategy.quorumThreshold + ? votingStrategy.quorumThreshold.formatted + : null; + + const strategyQuorum = + erc721Tokens !== undefined + ? votingStrategy.quorumThreshold!.value.toNumber() + : votesToken + ? votingStrategy.quorumPercentage!.value.toNumber() + : 1; + const reachedQuorum = + erc721Tokens !== undefined + ? totalVotesCasted.sub(votesSummary.no).toString() + : votesToken !== undefined + ? totalVotesCasted.sub(votesSummary.no).div(votesTokenDecimalsDenominator).toString() + : '0'; + const totalQuorum = + erc721Tokens !== undefined + ? strategyQuorum.toString() + : votesToken !== undefined + ? votesToken.totalSupply + .div(votesTokenDecimalsDenominator) + .div(100) + .mul(strategyQuorum) + .toString() + : 0; + + const displayColor = !totalVotesCasted.isZero() && reachedQuorum >= totalQuorum ? quorumReachedColor : quorumNotReachedColor; + return ( + <> + + + + {t('quorum', { ns: 'common' })} + {quorumDisplay} + + + + ); +} From 910092cdd067059acb6b56f122c6d9ae565672a2 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:27:20 -0400 Subject: [PATCH 03/27] update badge to use meetQuroum for linearERC20Strategy --- src/components/ui/badges/QuorumBadge.tsx | 50 +++++++++++++++--------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/components/ui/badges/QuorumBadge.tsx b/src/components/ui/badges/QuorumBadge.tsx index ef32af3276..e010a66a4e 100644 --- a/src/components/ui/badges/QuorumBadge.tsx +++ b/src/components/ui/badges/QuorumBadge.tsx @@ -3,6 +3,7 @@ import { Check } from '@decent-org/fractal-ui'; import { BigNumber } from 'ethers'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; +import useSafeContracts from '../../../hooks/safe/useSafeContracts'; import { useFractal } from '../../../providers/App/AppProvider'; import { AzoriusGovernance, @@ -14,16 +15,16 @@ import { const quorumNotReachedColor = '#838383'; const quorumReachedColor = '#56A355'; export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) { - const { governance } = useFractal(); + const { + governance, + governanceContracts: { ozLinearVotingContractAddress }, + } = useFractal(); + const baseContracts = useSafeContracts(); const { t } = useTranslation('common'); const azoriusGovernance = governance as AzoriusGovernance; const { votesToken, erc721Tokens, votingStrategy } = azoriusGovernance; - const votesTokenDecimalsDenominator = useMemo( - () => BigNumber.from(10).pow(votesToken?.decimals || 0), - [votesToken?.decimals], - ); const { votesSummary } = proposal as AzoriusProposal; const totalVotesCasted = useMemo(() => { if (votesSummary) { @@ -31,6 +32,22 @@ export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) } return BigNumber.from(0); }, [votesSummary]); + + const erc20MeetsQuorum = useMemo(async() => { + if (!ozLinearVotingContractAddress || !baseContracts || !votesToken || !votesSummary) { + return undefined; + } + const linearStrategyContract = baseContracts.linearVotingMasterCopyContract.asProvider.attach( + ozLinearVotingContractAddress, + ); + const result = await linearStrategyContract.meetsQuorum( + votesToken.totalSupply, + votesSummary.yes, + votesSummary.abstain, + ); + return result + }, [votesToken, votesSummary, baseContracts, ozLinearVotingContractAddress]); + // @dev only azorius governance has quorum if ((proposal as SnapshotProposal).snapshotProposalId || !votingStrategy) { return null; @@ -49,27 +66,22 @@ export default function QuorumBadge({ proposal }: { proposal: FractalProposal }) const strategyQuorum = erc721Tokens !== undefined ? votingStrategy.quorumThreshold!.value.toNumber() - : votesToken - ? votingStrategy.quorumPercentage!.value.toNumber() - : 1; + : 1; const reachedQuorum = erc721Tokens !== undefined ? totalVotesCasted.sub(votesSummary.no).toString() - : votesToken !== undefined - ? totalVotesCasted.sub(votesSummary.no).div(votesTokenDecimalsDenominator).toString() - : '0'; + : '0'; const totalQuorum = erc721Tokens !== undefined ? strategyQuorum.toString() - : votesToken !== undefined - ? votesToken.totalSupply - .div(votesTokenDecimalsDenominator) - .div(100) - .mul(strategyQuorum) - .toString() - : 0; + : 0; + + const meetsQuorum = erc20MeetsQuorum !== undefined ? erc20MeetsQuorum : reachedQuorum >= totalQuorum; - const displayColor = !totalVotesCasted.isZero() && reachedQuorum >= totalQuorum ? quorumReachedColor : quorumNotReachedColor; + const displayColor = + !totalVotesCasted.isZero() && meetsQuorum + ? quorumReachedColor + : quorumNotReachedColor; return ( <> Date: Tue, 19 Mar 2024 14:27:41 -0400 Subject: [PATCH 04/27] update hover colors for timelock and frozen badges --- src/components/ui/badges/Badge.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/ui/badges/Badge.tsx b/src/components/ui/badges/Badge.tsx index 4fcd0981b5..fbd428eeb3 100644 --- a/src/components/ui/badges/Badge.tsx +++ b/src/components/ui/badges/Badge.tsx @@ -32,7 +32,7 @@ const BADGE_MAPPING: BadgeType = { tooltipKey: 'stateTimelockedTip', bg: 'sand.700', textColor: 'grayscale.black', - _hover: { bg: 'grayscale.black' }, + _hover: { bg: 'sand.600' }, }, [FractalProposalState.EXECUTED]: { tooltipKey: 'stateExecutedTip', @@ -62,7 +62,7 @@ const BADGE_MAPPING: BadgeType = { tooltipKey: 'stateModuleTip', bg: 'sand.700', textColor: 'grayscale.black', - _hover: { bg: '' }, + _hover: { bg: 'sand.600' }, }, [FractalProposalState.EXPIRED]: { tooltipKey: 'stateExpiredTip', @@ -92,15 +92,15 @@ const BADGE_MAPPING: BadgeType = { tooltipKey: 'stateFreezeInitTip', bg: 'blue.400', textColor: 'grayscale.black', - _hover: { bg: 'grayscale.black' }, + _hover: { bg: 'blue.400-hover' }, }, [DAOState.frozen]: { tooltipKey: 'stateFrozenTip', bg: 'blue.400', textColor: 'grayscale.black', - _hover: { bg: 'grayscale.black' }, + _hover: { bg: 'blue.400-hover' }, }, - ownerApproved: { bg: 'sand.700', textColor: 'grayscale.black', _hover: { bg: 'grayscale.black' } }, + ownerApproved: { bg: 'sand.700', textColor: 'grayscale.black', _hover: { bg: 'sand.600' }, }, }; type BadgeSize = { [key: string]: { minWidth: string; height: string } }; From 04c06ecc0f7a97b39c82172f301dd852aa245fa6 Mon Sep 17 00:00:00 2001 From: David Colon <38386583+Da-Colon@users.noreply.github.com> Date: Tue, 19 Mar 2024 16:40:04 -0400 Subject: [PATCH 05/27] update Activity/Proposal description and title to updated designs --- .../Activity/ActivityDescription.tsx | 6 +++--- .../ActivityDescriptionGovernance.tsx | 16 ++++++++++----- src/components/ui/proposal/Markdown.tsx | 20 +++---------------- 3 files changed, 17 insertions(+), 25 deletions(-) diff --git a/src/components/Activity/ActivityDescription.tsx b/src/components/Activity/ActivityDescription.tsx index 88aeaae158..1b518bd46b 100644 --- a/src/components/Activity/ActivityDescription.tsx +++ b/src/components/Activity/ActivityDescription.tsx @@ -17,21 +17,21 @@ export function ActivityDescription({ activity, showFullDescription }: IActivity return ( {description && ( )} diff --git a/src/components/Activity/ActivityDescriptionGovernance.tsx b/src/components/Activity/ActivityDescriptionGovernance.tsx index 7ef99cd291..c717fb252f 100644 --- a/src/components/Activity/ActivityDescriptionGovernance.tsx +++ b/src/components/Activity/ActivityDescriptionGovernance.tsx @@ -1,4 +1,4 @@ -import { Text } from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { useGetMetadata } from '../../hooks/DAO/proposal/useGetMetadata'; import { @@ -58,11 +58,17 @@ export function ProposalTitle({ activity }: { activity: Activity }) { treasuryActivity.transferAddresses && !!treasuryActivity.transferAddresses.length; return ( - <> - {formatId((activity as GovernanceActivity).proposalId)} - {metaData.title ? {metaData.title} : null} + + + {formatId((activity as GovernanceActivity).proposalId)} + {metaData.title ? {metaData.title} : null} + {hasTransfers && {t('proposalDescriptionCont', { ns: 'dashboard' })} } - + ); } diff --git a/src/components/ui/proposal/Markdown.tsx b/src/components/ui/proposal/Markdown.tsx index 3467492e0f..5fc818ceb0 100644 --- a/src/components/ui/proposal/Markdown.tsx +++ b/src/components/ui/proposal/Markdown.tsx @@ -3,7 +3,6 @@ import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown, { Components } from 'react-markdown'; import remarkGfm from 'remark-gfm'; -import removeMd from 'remove-markdown'; import '../../../assets/css/Markdown.css'; function CustomMarkdownImage({ src, alt }: { src?: string; alt?: string }) { @@ -39,13 +38,12 @@ interface IMarkdown { content: string; } -export default function Markdown({ truncate, content, collapsedLines = 6 }: IMarkdown) { +export default function Markdown({ truncate, content, collapsedLines = 6}: IMarkdown) { const { t } = useTranslation('common'); const [collapsed, setCollapsed] = useState(true); const [totalLines, setTotalLines] = useState(0); const [totalLinesError, setTotalLinesError] = useState(false); const markdownTextContainerRef = useRef(null); - const plainText = removeMd(content); useEffect(() => { if ( @@ -83,22 +81,10 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar return uri; }; - if (truncate) { - return ( - - {plainText} - - ); - } - return ( <> @@ -112,7 +98,7 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar - {totalLines > collapsedLines && !totalLinesError && ( + {totalLines > collapsedLines && !totalLinesError && !truncate && ( - - ) - } - eventDate={format(activity.eventDate, DEFAULT_DATE_FORMAT)} - /> + + + ) + } + description={} + /> + ); } diff --git a/src/components/ui/proposal/Markdown.tsx b/src/components/ui/proposal/Markdown.tsx index ac0cf1c15c..f1b63fe2fa 100644 --- a/src/components/ui/proposal/Markdown.tsx +++ b/src/components/ui/proposal/Markdown.tsx @@ -1,4 +1,4 @@ -import { Text, Button, Image } from '@chakra-ui/react'; +import { Button, Image, Box } from '@chakra-ui/react'; import { useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import ReactMarkdown, { Components } from 'react-markdown'; @@ -83,7 +83,7 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar return ( <> - {content} - + {totalLines > collapsedLines && !totalLinesError && !truncate && (