From 2b8421bfec2a126bcd8d4a29754bea519b07e80d Mon Sep 17 00:00:00 2001 From: Maxime Julian <44675210+therealemjy@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:46:02 +0100 Subject: [PATCH] feat: add indicator on proposals with remote commands awaiting execution (#3626) --- .changeset/fifty-readers-type.md | 5 ++ .../useIsProposalExecutable/index.tsx | 9 +++- .../GovernanceProposal/Status/index.tsx | 50 +++++++++++++++---- .../ProposalList/GovernanceProposal/index.tsx | 24 +-------- .../BscCommand/ActionButton/index.tsx | 2 +- .../Proposal/Commands/BscCommand/index.tsx | 2 +- .../Proposal/Commands/NonBscCommand/index.tsx | 9 ++-- .../pages/Proposal/ProposalSummary/index.tsx | 16 ++++-- apps/evm/src/utilities/index.ts | 1 + .../utilities/isProposalExecutable/index.ts | 14 ++++++ 10 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 .changeset/fifty-readers-type.md rename apps/evm/src/{pages/Proposal/Commands => hooks}/useIsProposalExecutable/index.tsx (70%) create mode 100644 apps/evm/src/utilities/isProposalExecutable/index.ts diff --git a/.changeset/fifty-readers-type.md b/.changeset/fifty-readers-type.md new file mode 100644 index 0000000000..c9b37baa96 --- /dev/null +++ b/.changeset/fifty-readers-type.md @@ -0,0 +1,5 @@ +--- +"@venusprotocol/evm": minor +--- + +add indicator on proposals with remote commands awaiting execution diff --git a/apps/evm/src/pages/Proposal/Commands/useIsProposalExecutable/index.tsx b/apps/evm/src/hooks/useIsProposalExecutable/index.tsx similarity index 70% rename from apps/evm/src/pages/Proposal/Commands/useIsProposalExecutable/index.tsx rename to apps/evm/src/hooks/useIsProposalExecutable/index.tsx index 8ded165bff..bfd826502e 100644 --- a/apps/evm/src/pages/Proposal/Commands/useIsProposalExecutable/index.tsx +++ b/apps/evm/src/hooks/useIsProposalExecutable/index.tsx @@ -1,6 +1,6 @@ -import { isBefore } from 'date-fns/isBefore'; import { useNow } from 'hooks/useNow'; import { useMemo } from 'react'; +import { isProposalExecutable } from 'utilities/isProposalExecutable'; export type UseIsProposalExecutableProps = { executionEtaDate?: Date; @@ -14,7 +14,12 @@ export const useIsProposalExecutable = ({ const now = useNow(); const isExecutable = useMemo( - () => isQueued && !!(executionEtaDate && isBefore(executionEtaDate, now)), + () => + isProposalExecutable({ + executionEtaDate, + isQueued, + now, + }), [executionEtaDate, isQueued, now], ); diff --git a/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/Status/index.tsx b/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/Status/index.tsx index 8c743e03e7..dae5becbae 100644 --- a/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/Status/index.tsx +++ b/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/Status/index.tsx @@ -1,29 +1,49 @@ -import { LabeledProgressCircle } from 'components'; +import { Icon, LabeledProgressCircle } from 'components'; import { useIsFeatureEnabled } from 'hooks/useIsFeatureEnabled'; +import { useNow } from 'hooks/useNow'; import { useTranslation } from 'libs/translations'; import { useMemo } from 'react'; -import { ProposalState } from 'types'; +import { ProposalState, type RemoteProposal, RemoteProposalState } from 'types'; import { cn, getProposalStateLabel } from 'utilities'; +import { isProposalExecutable } from 'utilities/isProposalExecutable'; import { Indicator } from './Indicator'; export interface StatusProps extends React.HTMLAttributes { state: ProposalState; - totalPayloadsCount: number; - executedPayloadsCount: number; + remoteProposals: RemoteProposal[]; } export const Status: React.FC = ({ state, - totalPayloadsCount, - executedPayloadsCount, + remoteProposals, className, ...otherProps }) => { const { t } = useTranslation(); + const now = useNow(); + const isOmnichainGovernanceFeatureEnabled = useIsFeatureEnabled({ name: 'omnichainGovernance', }); + const totalPayloadsCount = 1 + remoteProposals.length; // BSC proposal + remote proposals + const executedPayloadsCount = + (state === ProposalState.Executed ? 1 : 0) + + remoteProposals.filter(remoteProposal => remoteProposal.state === RemoteProposalState.Executed) + .length; + + const shouldShowAwaitingExecutionWarning = + state === ProposalState.Executed && + remoteProposals.some( + remoteProposal => + remoteProposal.state === RemoteProposalState.Queued && + isProposalExecutable({ + now, + isQueued: remoteProposal.state === RemoteProposalState.Queued, + executionEtaDate: remoteProposal.executionEtaDate, + }), + ); + const isFullyExecuted = executedPayloadsCount === totalPayloadsCount; const shouldShowExecutedPayloadsStatus = isOmnichainGovernanceFeatureEnabled && state === ProposalState.Executed && !isFullyExecuted; @@ -50,11 +70,19 @@ export const Status: React.FC = ({ >
{shouldShowExecutedPayloadsStatus ? ( - +
+ + + {shouldShowAwaitingExecutionWarning && ( +
+ +
+ )} +
) : ( )} diff --git a/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/index.tsx b/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/index.tsx index e4a4152bb7..5fef566e5d 100644 --- a/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/index.tsx +++ b/apps/evm/src/pages/Governance/ProposalList/GovernanceProposal/index.tsx @@ -5,14 +5,7 @@ import { useMemo } from 'react'; import { ActiveVotingProgress, Countdown, ProposalTypeChip } from 'components'; import { routes } from 'constants/routing'; import { useTranslation } from 'libs/translations'; -import { - type Proposal, - ProposalState, - ProposalType, - RemoteProposalState, - type Token, - VoteSupport, -} from 'types'; +import { type Proposal, ProposalState, ProposalType, type Token, VoteSupport } from 'types'; import { ProposalCard } from 'containers/ProposalCard'; import { useGetToken } from 'libs/tokens'; @@ -102,20 +95,7 @@ const GovernanceProposalUi: React.FC = ({ ); } - const totalPayloadsCount = 1 + remoteProposals.length; // BSC proposal + remote proposals - const executedPayloadsCount = - (state === ProposalState.Executed ? 1 : 0) + - remoteProposals.filter( - remoteProposal => remoteProposal.state === RemoteProposalState.Executed, - ).length; - - return ( - - ); + return ; }, [state, remoteProposals, forVotesMantissa, againstVotesMantissa, abstainedVotesMantissa, xvs]); return ( diff --git a/apps/evm/src/pages/Proposal/Commands/BscCommand/ActionButton/index.tsx b/apps/evm/src/pages/Proposal/Commands/BscCommand/ActionButton/index.tsx index 9a16dfa2b4..eba2fb7cbf 100644 --- a/apps/evm/src/pages/Proposal/Commands/BscCommand/ActionButton/index.tsx +++ b/apps/evm/src/pages/Proposal/Commands/BscCommand/ActionButton/index.tsx @@ -2,6 +2,7 @@ import { useCancelProposal, useExecuteProposal, useQueueProposal } from 'clients import { Button } from 'components'; import type { ConnectWalletProps } from 'containers/ConnectWallet'; import { ConnectWallet } from 'containers/ConnectWallet'; +import { useIsProposalExecutable } from 'hooks/useIsProposalExecutable'; import { handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { governanceChain, useAccountAddress } from 'libs/wallet'; @@ -9,7 +10,6 @@ import { useMemo } from 'react'; import { ProposalState } from 'types'; import { cn } from 'utilities'; import { useIsProposalCancelableByUser } from '../../useIsProposalCancelableByUser'; -import { useIsProposalExecutable } from '../../useIsProposalExecutable'; export interface ActionButtonProps extends Omit { proposalId: number; diff --git a/apps/evm/src/pages/Proposal/Commands/BscCommand/index.tsx b/apps/evm/src/pages/Proposal/Commands/BscCommand/index.tsx index bd608b2e23..ecf6f8d11e 100644 --- a/apps/evm/src/pages/Proposal/Commands/BscCommand/index.tsx +++ b/apps/evm/src/pages/Proposal/Commands/BscCommand/index.tsx @@ -1,4 +1,5 @@ import { chainMetadata } from '@venusprotocol/chains'; +import { useIsProposalExecutable } from 'hooks/useIsProposalExecutable'; import { useTranslation } from 'libs/translations'; import { governanceChain, useChainId } from 'libs/wallet'; import { useAccountAddress } from 'libs/wallet'; @@ -7,7 +8,6 @@ import { type Proposal, ProposalState } from 'types'; import { Command } from '../Command'; import { Description } from '../Description'; import { useIsProposalCancelableByUser } from '../useIsProposalCancelableByUser'; -import { useIsProposalExecutable } from '../useIsProposalExecutable'; import { ActionButton } from './ActionButton'; import { CurrentStep } from './CurrentStep'; diff --git a/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx b/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx index 9de0abb686..db702c2e07 100644 --- a/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx +++ b/apps/evm/src/pages/Proposal/Commands/NonBscCommand/index.tsx @@ -2,8 +2,7 @@ import { chainMetadata } from '@venusprotocol/chains'; import { useExecuteProposal } from 'clients/api'; import { Button } from 'components'; import { ConnectWallet } from 'containers/ConnectWallet'; -import { isAfter } from 'date-fns/isAfter'; -import { useNow } from 'hooks/useNow'; +import { useIsProposalExecutable } from 'hooks/useIsProposalExecutable'; import { VError, handleError } from 'libs/errors'; import { useTranslation } from 'libs/translations'; import { governanceChain, useChainId } from 'libs/wallet'; @@ -11,7 +10,6 @@ import { useMemo } from 'react'; import { type RemoteProposal, RemoteProposalState } from 'types'; import { Command } from '../Command'; import { Description } from '../Description'; -import { useIsProposalExecutable } from '../useIsProposalExecutable'; import { CurrentStep } from './CurrentStep'; const governanceChainMetadata = chainMetadata[governanceChain.id]; @@ -27,7 +25,6 @@ export const NonBscCommand: React.FC = ({ ...otherProps }) => { const { t } = useTranslation(); - const now = useNow(); const { chainId: currentChainId } = useChainId(); const chain = chainMetadata[remoteProposal.chainId]; @@ -67,7 +64,7 @@ export const NonBscCommand: React.FC = ({ case RemoteProposalState.Canceled: return t('voteProposalUi.command.description.canceled'); case RemoteProposalState.Queued: - if (!remoteProposal.executionEtaDate || isAfter(remoteProposal.executionEtaDate, now)) { + if (!isExecutable) { return t('voteProposalUi.command.description.waitingToBeExecutable'); } @@ -78,7 +75,7 @@ export const NonBscCommand: React.FC = ({ } break; } - }, [t, remoteProposal.state, remoteProposal.executionEtaDate, now, chain, isOnWrongChain]); + }, [t, remoteProposal.state, chain, isOnWrongChain, isExecutable]); return ( { const styles = useStyles(); const { t, Trans } = useTranslation(); + const isVoteProposalFeatureEnabled = useIsFeatureEnabled({ name: 'voteProposal' }); const isOmnichainGovernanceFeatureEnabled = useIsFeatureEnabled({ name: 'omnichainGovernance', @@ -111,7 +112,11 @@ export const ProposalSummaryUi: React.FC ); } + if (!isOmnichainGovernanceFeatureEnabled) { mainTransactionHash = queuedTxHash; } @@ -188,7 +194,7 @@ export const ProposalSummaryUi: React.FC diff --git a/apps/evm/src/utilities/index.ts b/apps/evm/src/utilities/index.ts index e595f6a9f1..6c7b18c7bb 100755 --- a/apps/evm/src/utilities/index.ts +++ b/apps/evm/src/utilities/index.ts @@ -55,3 +55,4 @@ export * from './getProposalType'; export * from './getProposalState'; export * from './getUserVoteSupport'; export * from './getProposalStateLabel'; +export * from './isProposalExecutable'; diff --git a/apps/evm/src/utilities/isProposalExecutable/index.ts b/apps/evm/src/utilities/isProposalExecutable/index.ts new file mode 100644 index 0000000000..7713b65201 --- /dev/null +++ b/apps/evm/src/utilities/isProposalExecutable/index.ts @@ -0,0 +1,14 @@ +import { isBefore } from 'date-fns/isBefore'; + +export type IsProposalExecutableInput = { + now: Date; + isQueued: boolean; + executionEtaDate?: Date; +}; + +export const isProposalExecutable = ({ + now, + isQueued, + executionEtaDate, +}: IsProposalExecutableInput) => + isQueued && !!(executionEtaDate && isBefore(executionEtaDate, now));