diff --git a/package-lock.json b/package-lock.json index 26e08e9d26..e942c55b88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,6 @@ "react-router-dom": "^6.22.0", "react-toastify": "^9.0.8", "remark-gfm": "^4.0.0", - "remove-markdown": "^0.5.0", "viem": "^2.8.12", "vite": "^5.1.0", "vite-plugin-checker": "^0.6.4", @@ -25565,11 +25564,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remove-markdown": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz", - "integrity": "sha512-x917M80K97K5IN1L8lUvFehsfhR8cYjGQ/yAMRI9E7JIKivtl5Emo5iD13DhMr+VojzMCiYk8V2byNPwT/oapg==" - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", diff --git a/package.json b/package.json index 704e178eda..493d49132a 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,10 @@ "react-router-dom": "^6.22.0", "react-toastify": "^9.0.8", "remark-gfm": "^4.0.0", - "remove-markdown": "^0.5.0", "viem": "^2.8.12", "vite": "^5.1.0", - "vite-plugin-node-polyfills": "^0.21.0", "vite-plugin-checker": "^0.6.4", + "vite-plugin-node-polyfills": "^0.21.0", "wagmi": "^2.5.11", "yup": "^1" }, diff --git a/src/assets/css/Markdown.css b/src/assets/css/Markdown.css index b4ce42947b..cefcd720c5 100644 --- a/src/assets/css/Markdown.css +++ b/src/assets/css/Markdown.css @@ -4,6 +4,7 @@ .markdown-body { min-width: 100%; + max-width: 0; } .markdown-body, .markdown-body > * { diff --git a/src/components/Activity/ActivityDescription.tsx b/src/components/Activity/ActivityDescription.tsx index 88aeaae158..6e4703c4e0 100644 --- a/src/components/Activity/ActivityDescription.tsx +++ b/src/components/Activity/ActivityDescription.tsx @@ -1,4 +1,4 @@ -import { Box, Flex } from '@chakra-ui/react'; +import { Box } from '@chakra-ui/react'; import { useGetMetadata } from '../../hooks/DAO/proposal/useGetMetadata'; import { Activity, FractalProposal, SnapshotProposal } from '../../types'; import Markdown from '../ui/proposal/Markdown'; @@ -12,30 +12,35 @@ interface IActivityDescription { export function ActivityDescription({ activity, showFullDescription }: IActivityDescription) { const metaData = useGetMetadata(activity as FractalProposal); + const snapshotProposal = activity as SnapshotProposal; + const description = snapshotProposal.description || metaData.description; return ( - - + {description && ( )} - {!!activity.transaction && } - + + {!!activity.transaction && } + + ); } diff --git a/src/components/Activity/ActivityDescriptionGovernance.tsx b/src/components/Activity/ActivityDescriptionGovernance.tsx index 7ef99cd291..dcfa243ce4 100644 --- a/src/components/Activity/ActivityDescriptionGovernance.tsx +++ b/src/components/Activity/ActivityDescriptionGovernance.tsx @@ -1,15 +1,17 @@ -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 useDisplayName from '../../hooks/utils/useDisplayName'; import { Activity, GovernanceActivity, MultisigProposal, ActivityEventType, - TreasuryActivity, SnapshotProposal, FractalProposal, + AzoriusProposal, } from '../../types'; +import Avatar from '../ui/page/Header/Avatar'; const formatId = (proposalId: string) => { if (proposalId.startsWith('0x')) { @@ -36,33 +38,77 @@ function OnChainRejectionMessage({ activity }: { activity: Activity }) { ); } -export function ProposalTitle({ activity }: { activity: Activity }) { - const { t } = useTranslation(['common', 'dashboard']); +function ProposalAuthor({ activity }: { activity: Activity }) { + const snapshotProposal = activity as SnapshotProposal; + const azoriusProposal = activity as AzoriusProposal; + const multisigProposal = activity as MultisigProposal; + const isSnapshotProposal = !!snapshotProposal.snapshotProposalId; + const isAzoriusProposal = !!azoriusProposal.proposer; + + const proposer = isAzoriusProposal + ? azoriusProposal.proposer + : isSnapshotProposal + ? snapshotProposal.author + : multisigProposal.confirmations[0].owner; + + const { displayName: author } = useDisplayName(proposer); + return ( + + + {author} + + ); +} + +export function ProposalTitle({ + activity, + showAuthor = false, +}: { + activity: Activity; + showAuthor?: boolean; +}) { const metaData = useGetMetadata(activity as FractalProposal); if (activity.eventType !== ActivityEventType.Governance) { return null; } - if ((activity as SnapshotProposal).snapshotProposalId) { - return ( - <> - {formatId((activity as SnapshotProposal).snapshotProposalId)} - {(activity as SnapshotProposal).title} - - ); - } - - const treasuryActivity = activity as TreasuryActivity; - const hasTransfers = - treasuryActivity.transferAddresses && !!treasuryActivity.transferAddresses.length; + // Check if it's a SnapshotProposal and set variables accordingly + const isSnapshotProposal = (activity as SnapshotProposal).snapshotProposalId !== undefined; + const proposalIdText = isSnapshotProposal + ? formatId((activity as SnapshotProposal).snapshotProposalId) + : formatId((activity as GovernanceActivity).proposalId); + const proposaltitleText = isSnapshotProposal + ? (activity as SnapshotProposal).title + : metaData.title || ''; + const titleText = proposalIdText + ' ' + proposaltitleText; return ( - <> - {formatId((activity as GovernanceActivity).proposalId)} - {metaData.title ? {metaData.title} : null} - {hasTransfers && {t('proposalDescriptionCont', { ns: 'dashboard' })} } - - + + + {titleText} + {showAuthor && } + + + + + ); } diff --git a/src/components/Activity/ActivityDescriptionModule.tsx b/src/components/Activity/ActivityDescriptionModule.tsx index dead54af51..fe1c8de28b 100644 --- a/src/components/Activity/ActivityDescriptionModule.tsx +++ b/src/components/Activity/ActivityDescriptionModule.tsx @@ -1,34 +1,60 @@ -import { Flex, Text } from '@chakra-ui/react'; +import { Box, Flex, Text } from '@chakra-ui/react'; +import { format } from 'date-fns'; import { useTranslation } from 'react-i18next'; -import { FractalProposal } from '../../types'; +import { useFractal } from '../../providers/App/AppProvider'; +import { ActivityEventType, FractalProposal } from '../../types'; +import { DEFAULT_DATE_FORMAT } from '../../utils'; import { ActivityAddress } from './ActivityAddress'; export function ActivityDescriptionModule({ activity }: { activity: FractalProposal }) { - const { t } = useTranslation(['treasury', 'dashboard']); + const { t } = useTranslation(['treasury', 'dashboard', 'common']); + + const { + node: { daoAddress }, + } = useFractal(); + const eventDateLabel = t( + activity.eventType === ActivityEventType.Treasury + ? activity.transaction?.to === daoAddress + ? 'received' + : 'sent' + : 'created', + ); return ( - - {t('moduleDescription', { ns: 'dashboard', count: activity.targets.length })} - {activity.targets.length > 2 ? ( - - {t('addresses', { - ns: 'treasury', - numOfAddresses: activity.targets.length, - })} - - ) : ( - activity.targets.map((address, i, arr) => ( - - )) - )} - + + {t('moduleDescription', { ns: 'dashboard', count: activity.targets.length })} + + {activity.targets.length > 2 + ? t('addresses', { + ns: 'treasury', + numOfAddresses: activity.targets.length, + }) + : activity.targets.map((address, i, arr) => ( + + ))} + + + + {activity.eventDate && ( + + {eventDateLabel} {format(activity.eventDate, DEFAULT_DATE_FORMAT)} + + )} + + ); } diff --git a/src/components/Activity/ActivityDescriptionTreasury.tsx b/src/components/Activity/ActivityDescriptionTreasury.tsx index 9866ab31d7..50a21b9185 100644 --- a/src/components/Activity/ActivityDescriptionTreasury.tsx +++ b/src/components/Activity/ActivityDescriptionTreasury.tsx @@ -1,4 +1,4 @@ -import { Text } from '@chakra-ui/react'; +import { Flex, Text } from '@chakra-ui/react'; import { useTranslation } from 'react-i18next'; import { Activity, TreasuryActivity, ActivityEventType } from '../../types'; import { ActivityAddress } from './ActivityAddress'; @@ -26,27 +26,29 @@ export function ActivityDescriptionTreasury({ activity }: { activity: Activity } ? t('from') : t('to'); + const textString = transferTypeStr + ? transferTypeStr + + ' ' + + treasuryActivity.transferAmountTotals.join(', ') + + ' ' + + transferDirStr + : undefined; + return ( - <> - - {transferTypeStr} {treasuryActivity.transferAmountTotals.join(', ')} {transferDirStr} - - {treasuryActivity.transferAddresses.length > 2 ? ( - - {t('addresses', { + + {textString} + {treasuryActivity.transferAddresses.length > 2 + ? t('addresses', { ns: 'treasury', numOfAddresses: treasuryActivity.transferAddresses.length, - })} - - ) : ( - treasuryActivity.transferAddresses.map((address, i, arr) => ( - - )) - )} - + }) + : treasuryActivity.transferAddresses.map((address, i, arr) => ( + + ))} + ); } diff --git a/src/components/Activity/ActivityGovernance.tsx b/src/components/Activity/ActivityGovernance.tsx deleted file mode 100644 index 812ec81667..0000000000 --- a/src/components/Activity/ActivityGovernance.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Flex } from '@chakra-ui/react'; -import { format } from 'date-fns'; -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { DAO_ROUTES } from '../../constants/routes'; -import { useFractal } from '../../providers/App/AppProvider'; -import { FractalProposal, ActivityEventType, SnapshotProposal } from '../../types'; -import { DEFAULT_DATE_FORMAT } from '../../utils/numberFormats'; -import { ProposalAction } from '../Proposals/ProposalActions/ProposalAction'; -import { VoteContextProvider } from '../Proposals/ProposalVotes/context/VoteContext'; -import { Badge } from '../ui/badges/Badge'; -import { ProposalCountdown } from '../ui/proposal/ProposalCountdown'; - -import { ActivityCard } from './ActivityCard'; -import { ActivityDescription } from './ActivityDescription'; - -export function ActivityGovernance({ activity }: { activity: FractalProposal }) { - const { - node: { safe }, - } = useFractal(); - const { t } = useTranslation(); - - const eventDateLabel = t( - activity.eventType === ActivityEventType.Treasury - ? activity.transaction?.to === safe?.address - ? 'received' - : 'sent' - : 'created', - ); - - const { - node: { daoAddress }, - } = useFractal(); - - return ( - - - ) - } - description={} - RightElement={ - - - - - - - } - eventDate={format(activity.eventDate, DEFAULT_DATE_FORMAT)} - eventDateLabel={eventDateLabel} - isSnapshot={!!(activity as SnapshotProposal).snapshotProposalId} - /> - - ); -} diff --git a/src/components/Activity/ActivityModule.tsx b/src/components/Activity/ActivityModule.tsx index d1cdf1e422..c0bb90cb3e 100644 --- a/src/components/Activity/ActivityModule.tsx +++ b/src/components/Activity/ActivityModule.tsx @@ -1,48 +1,23 @@ -import { Button } from '@chakra-ui/react'; -import { ArrowAngleUp } from '@decent-org/fractal-ui'; -import { format } from 'date-fns'; -import { useTranslation } from 'react-i18next'; import { FractalProposal } from '../../types'; -import { DEFAULT_DATE_FORMAT } from '../../utils/numberFormats'; import { Badge } from '../ui/badges/Badge'; import EtherscanLinkTransaction from '../ui/links/EtherscanLinkTransaction'; import { ActivityCard } from './ActivityCard'; import { ActivityDescriptionModule } from './ActivityDescriptionModule'; export function ActivityModule({ activity }: { activity: FractalProposal }) { - const { t } = useTranslation('common'); return ( - - ) - } - description={} - RightElement={ - !!activity.transactionHash && ( - - - - ) - } - eventDate={format(activity.eventDate, DEFAULT_DATE_FORMAT)} - /> + + + ) + } + description={} + /> + ); } diff --git a/src/components/Proposals/ProposalCard/ProposalCard.tsx b/src/components/Proposals/ProposalCard/ProposalCard.tsx new file mode 100644 index 0000000000..d4796291b0 --- /dev/null +++ b/src/components/Proposals/ProposalCard/ProposalCard.tsx @@ -0,0 +1,76 @@ +import { Box, Flex, Image, Text } from '@chakra-ui/react'; +import { format } from 'date-fns'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { DAO_ROUTES } from '../../../constants/routes'; +import { useFractal } from '../../../providers/App/AppProvider'; +import { ActivityEventType, FractalProposal, SnapshotProposal } from '../../../types'; +import { DEFAULT_DATE_FORMAT } from '../../../utils'; +import { ActivityDescription } from '../../Activity/ActivityDescription'; +import { Badge } from '../../ui/badges/Badge'; +import QuorumBadge from '../../ui/badges/QuorumBadge'; + +function ProposalCard({ proposal }: { proposal: FractalProposal }) { + const { + node: { daoAddress }, + } = useFractal(); + const { t } = useTranslation('common'); + + const eventDateLabel = t( + proposal.eventType === ActivityEventType.Treasury + ? proposal.transaction?.to === daoAddress + ? 'received' + : 'sent' + : 'created', + ); + + const isSnapshotProposal = !!(proposal as SnapshotProposal).snapshotProposalId; + + return ( + + + {/* Top Row */} + + + + {isSnapshotProposal && ( + {t('snapshot')} + )} + + + + + + {proposal.eventDate && ( + + {eventDateLabel} {format(proposal.eventDate, DEFAULT_DATE_FORMAT)} + + )} + + + + ); +} + +export default ProposalCard; diff --git a/src/components/Proposals/ProposalsList.tsx b/src/components/Proposals/ProposalsList.tsx index f180efc871..3323b96814 100644 --- a/src/components/Proposals/ProposalsList.tsx +++ b/src/components/Proposals/ProposalsList.tsx @@ -5,9 +5,9 @@ import { DAO_ROUTES } from '../../constants/routes'; import useSubmitProposal from '../../hooks/DAO/proposal/useSubmitProposal'; import { useFractal } from '../../providers/App/AppProvider'; import { FractalProposal } from '../../types'; -import { ActivityGovernance } from '../Activity/ActivityGovernance'; import { EmptyBox } from '../ui/containers/EmptyBox'; import { InfoBoxLoader } from '../ui/loaders/InfoBoxLoader'; +import ProposalCard from './ProposalCard/ProposalCard'; export function ProposalsList({ proposals }: { proposals: FractalProposal[] }) { const { @@ -27,9 +27,9 @@ export function ProposalsList({ proposals }: { proposals: FractalProposal[] }) { ) : proposals.length > 0 ? ( proposals.map(proposal => ( - )) ) : ( diff --git a/src/components/pages/DaoDashboard/Activities/index.tsx b/src/components/pages/DaoDashboard/Activities/index.tsx index f21e469066..aecd7dd7f8 100644 --- a/src/components/pages/DaoDashboard/Activities/index.tsx +++ b/src/components/pages/DaoDashboard/Activities/index.tsx @@ -3,9 +3,9 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useFractal } from '../../../../providers/App/AppProvider'; import { ActivityEventType, SortBy, TreasuryActivity, FractalProposal } from '../../../../types'; -import { ActivityGovernance } from '../../../Activity/ActivityGovernance'; import { ActivityModule } from '../../../Activity/ActivityModule'; import { ActivityTreasury } from '../../../Activity/ActivityTreasury'; +import ProposalCard from '../../../Proposals/ProposalCard/ProposalCard'; import { EmptyBox } from '../../../ui/containers/EmptyBox'; import { InfoBoxLoader } from '../../../ui/loaders/InfoBoxLoader'; import { Sort } from '../../../ui/utils/Sort'; @@ -48,9 +48,9 @@ export function Activities() { {sortedActivities.map((activity, i) => { if (activity.eventType === ActivityEventType.Governance) { return ( - ); } diff --git a/src/components/ui/badges/Badge.tsx b/src/components/ui/badges/Badge.tsx index 3c0de816ec..db5f21f4d4 100644 --- a/src/components/ui/badges/Badge.tsx +++ b/src/components/ui/badges/Badge.tsx @@ -1,91 +1,130 @@ -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 }; + textColor: string; + }; +}; + +const greenText = '#8BDA8B'; +const greenBG = '#0A320A'; +const greenHoverText = '#78D378'; +const greenHover = '#0E440E'; + +const redText = '#FFB2B2'; +const redBG = '#640E0D'; +const redHoverText = '#FF9999'; +const redHover = '#4D0B0A'; + +const sandBG = '#C18D5A'; +const sandHover = '#B97F46'; +const sandText = '#2C1A08'; +const sandHoverText = '#150D04 '; + +const grayBG = '#9A979D'; +const grayHover = '#8C8990'; + +const freezeBG = '#A3B9EC'; +const freezeHover = '#8DA8E7'; +const freezeText = '#0D2356'; +const freezeHoverText = '#09193E'; + +const frozenBG = '#09193E'; +const frozenText = '#D1DCF5'; +const frozenHoverText = '#BCCCF0'; +const frozenHover = '#17326E'; const BADGE_MAPPING: BadgeType = { [FractalProposalState.ACTIVE]: { - Icon: ActiveTwo, tooltipKey: 'stateActiveTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.TIMELOCKED]: { - Icon: ClockTwo, tooltipKey: 'stateTimelockedTip', - bg: 'sand.700', - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.EXECUTED]: { - Icon: DoubleCheck, tooltipKey: 'stateExecutedTip', - bg: Green._600, - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.EXECUTABLE]: { - Icon: Check, tooltipKey: 'stateExecutableTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.FAILED]: { - Icon: CloseX, tooltipKey: 'stateFailedTip', - bg: Red._600, - color: 'grayscale.black', + bg: redBG, + textColor: redText, + _hover: { bg: redHover, textColor: redHoverText }, }, [FractalProposalState.TIMELOCKABLE]: { - Icon: ClockTwo, tooltipKey: 'stateTimelockableTip', - bg: Green._500, - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.MODULE]: { tooltipKey: 'stateModuleTip', - bg: 'sand.700', - color: 'grayscale.black', + bg: greenBG, + textColor: greenText, + _hover: { bg: greenHover, textColor: greenHoverText }, }, [FractalProposalState.EXPIRED]: { - Icon: ClockTwo, tooltipKey: 'stateExpiredTip', - bg: Red._600, - color: 'grayscale.black', + bg: redBG, + textColor: redText, + _hover: { bg: redHover, textColor: redHoverText }, }, [FractalProposalState.REJECTED]: { - Icon: CloseX, tooltipKey: 'stateRejectedTip', - bg: Red._600, - color: 'grayscale.black', + bg: redBG, + textColor: redText, + _hover: { bg: redHover, textColor: redHoverText }, }, [FractalProposalState.PENDING]: { - Icon: ClockTwo, tooltipKey: 'statePendingTip', - bg: Green._500, - color: 'grayscale.black', + bg: sandBG, + textColor: sandText, + _hover: { bg: sandHover, textColor: sandHoverText }, }, [FractalProposalState.CLOSED]: { - Icon: ClockTwo, tooltipKey: 'stateClosedTip', - bg: Green._600, - color: 'grayscale.black', + bg: grayBG, + textColor: '#000', + _hover: { bg: grayHover, textColor: '#000' }, }, [DAOState.freezeInit]: { - Icon: Check, tooltipKey: 'stateFreezeInitTip', - bg: 'blue.400', - color: 'grayscale.black', + bg: freezeBG, + textColor: freezeText, + _hover: { bg: freezeHover, textColor: freezeHoverText }, }, [DAOState.frozen]: { - Icon: DoubleCheck, tooltipKey: 'stateFrozenTip', - bg: 'blue.400', - color: 'grayscale.black', + bg: frozenBG, + textColor: frozenText, + _hover: { bg: frozenHover, textColor: frozenHoverText }, + }, + ownerApproved: { + bg: 'sand.700', + textColor: sandText, + _hover: { bg: sandBG, textColor: sandHoverText }, }, - ownerApproved: { bg: 'sand.700', color: 'grayscale.black' }, }; type BadgeSize = { [key: string]: { minWidth: string; height: string } }; @@ -97,33 +136,53 @@ const BADGE_SIZES: BadgeSize = { interface IBadge { size: 'sm' | 'base'; labelKey: FractalProposalState | DAOState | string; + 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'); return ( - <> - + - + - {!!Icon && } - {t(labelKey)} - - - + {t(labelKey)} + + {proposal && ( + + )} + + ); } diff --git a/src/components/ui/badges/QuorumBadge.tsx b/src/components/ui/badges/QuorumBadge.tsx new file mode 100644 index 0000000000..73d6435129 --- /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 { votesSummary } = proposal as AzoriusProposal; + const totalVotesCasted = useMemo(() => { + if (votesSummary) { + return votesSummary.yes.add(votesSummary.no).add(votesSummary.abstain); + } + return BigNumber.from(0); + }, [votesSummary]); + + const votesTokenDecimalsDenominator = useMemo( + () => BigNumber.from(10).pow(votesToken?.decimals || 0), + [votesToken?.decimals], + ); + + // @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 !== undefined + ? votingStrategy.quorumPercentage!.value.toNumber() + : 0; + 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() : 0; + + const meetsQuorum = votesToken + ? votesToken.totalSupply + .div(votesTokenDecimalsDenominator) + .div(100) + .mul(strategyQuorum) + .toString() + : reachedQuorum >= totalQuorum; + + const displayColor = + !totalVotesCasted.isZero() && meetsQuorum ? quorumReachedColor : quorumNotReachedColor; + return ( + + + + {t('quorum', { ns: 'common' })} + {quorumDisplay} + + + ); +} diff --git a/src/components/ui/page/Header/Avatar.tsx b/src/components/ui/page/Header/Avatar.tsx index 100a7a015f..074435bb8b 100644 --- a/src/components/ui/page/Header/Avatar.tsx +++ b/src/components/ui/page/Header/Avatar.tsx @@ -3,8 +3,9 @@ import { Jazzicon } from '@ukstv/jazzicon-react'; import { Suspense } from 'react'; import { useImage } from 'react-image'; -type AvatarSize = 'icon' | 'lg'; +type AvatarSize = 'icon' | 'lg' | 'sm'; const avatarSizes: { [size: string]: string } = { + sm: '1rem', icon: '1.5rem', lg: '2rem', }; diff --git a/src/components/ui/proposal/Markdown.tsx b/src/components/ui/proposal/Markdown.tsx index 3467492e0f..f1b63fe2fa 100644 --- a/src/components/ui/proposal/Markdown.tsx +++ b/src/components/ui/proposal/Markdown.tsx @@ -1,9 +1,8 @@ -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'; import remarkGfm from 'remark-gfm'; -import removeMd from 'remove-markdown'; import '../../../assets/css/Markdown.css'; function CustomMarkdownImage({ src, alt }: { src?: string; alt?: string }) { @@ -45,7 +44,6 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar 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 ( <> - @@ -110,9 +96,9 @@ export default function Markdown({ truncate, content, collapsedLines = 6 }: IMar > {content} - + - {totalLines > collapsedLines && !totalLinesError && ( + {totalLines > collapsedLines && !totalLinesError && !truncate && (