Skip to content

Commit

Permalink
Start updating delegation gating messages
Browse files Browse the repository at this point in the history
  • Loading branch information
cgero-eth committed Oct 31, 2023
1 parent 72ec7db commit acc84d6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 102 deletions.
98 changes: 60 additions & 38 deletions src/containers/delegationGatingMenu/delegationGatingMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,48 @@ import {
ButtonText,
IllustrationHuman,
IlluObject,
shortenAddress,
Link,
IconLinkExternal,
} from '@aragon/ods-old';
import {useDaoDetailsQuery} from 'hooks/useDaoDetails';
import {useDaoToken} from 'hooks/useDaoToken';
import {Address, useBalance, useEnsName} from 'wagmi';
import {Address, useBalance} from 'wagmi';
import {CHAIN_METADATA, SupportedNetworks} from 'utils/constants';
import {useDelegatee} from 'services/aragon-sdk/queries/use-delegatee';
import {abbreviateTokenAmount} from 'utils/tokens';
import {useWallet} from 'hooks/useWallet';
import {usePastVotingPower} from 'services/aragon-sdk/queries/use-past-voting-power';
import {TokenVotingProposal} from '@aragon/sdk-client';

export interface IDelegationGatingMenuState {
proposal?: TokenVotingProposal;
}

const getDelegationLabels = (params: {
needsSelfDelegation: boolean;
noVotingPower: boolean;
hasPastVotingPower: boolean;
hasBalance: boolean;
hasPastBalance: boolean;
}) => {
const {needsSelfDelegation, noVotingPower} = params;

let bodyLabel = 'delegationActive';
let ctaLabel = 'delegationActive.CtaLabel';
const {needsSelfDelegation, hasPastVotingPower, hasBalance, hasPastBalance} =
params;

if (needsSelfDelegation) {
bodyLabel = 'delegationInactive';
ctaLabel = 'delegation.ctaLabelDelegateNow';
} else if (noVotingPower) {
bodyLabel = 'delegation.NoVotingPower';
ctaLabel = 'delegation.NoVotingPower.ctaLabel';
}
const firstKey = hasPastBalance ? 'sentence1b' : 'sentence1a';
const secondKey = hasPastBalance
? 'sentence2a'
: hasBalance
? 'sentence2b'
: 'sentence2c';

return {bodyLabel, ctaLabel};
return {firstKey, secondKey};
};

export const DelegationGatingMenu: React.FC = () => {
const {t} = useTranslation();
const {isOpen, close, open} = useGlobalModalContext('delegationGating');
const {isOpen, modalState, close, open} =
useGlobalModalContext<IDelegationGatingMenuState>('delegationGating');

const {proposal} = modalState ?? {};

const {network, address} = useWallet();

Expand All @@ -60,6 +67,23 @@ export const DelegationGatingMenu: React.FC = () => {

const tokenAmount = abbreviateTokenAmount(tokenBalance?.formatted ?? '0');

const {data: pastVotingPower} = usePastVotingPower(
{
tokenAddress: daoToken?.address as string,
address: address as string,
blockNumber: proposal?.creationBlockNumber as number,
},
{enabled: daoToken != null && address != null && proposal != null}
);

const hasBalance = tokenBalance != null && tokenBalance.value > 0;

// TODO: to be fetched from the SDK
const hasPastBalance = true;

const hasPastVotingPower =
pastVotingPower != null && pastVotingPower.gt(constants.Zero);

const {data: delegateData} = useDelegatee(
{tokenAddress: daoToken?.address as string},
{enabled: daoToken != null}
Expand All @@ -70,33 +94,32 @@ export const DelegationGatingMenu: React.FC = () => {
delegateData === null ? (address as string) : delegateData;

// For imported ERC-20 tokens, there's no self-delegation and the delegation data is set to address-zero.
const needsSelfDelegation = delegateData === constants.AddressZero;

// Defines the case when the user is not delegating the tokens to someone else but had no
// voting power when the proposal has been created.
const noVotingPower =
!needsSelfDelegation &&
currentDelegate?.toLowerCase() === address?.toLowerCase();

const {data: delegateEns} = useEnsName({
address: currentDelegate as Address,
enabled: currentDelegate != null,
});

const delegateName = delegateEns ?? shortenAddress(currentDelegate ?? '');
const needsSelfDelegation =
hasBalance && currentDelegate?.toLowerCase() !== address?.toLowerCase();

const handleCtaClick = () => {
if (noVotingPower) {
if (!hasPastVotingPower) {
close();
} else {
open('delegateVoting', {reclaimMode: true});
}
};

const {bodyLabel, ctaLabel} = getDelegationLabels({
noVotingPower,
const {firstKey, secondKey} = getDelegationLabels({
hasPastVotingPower,
needsSelfDelegation,
hasBalance,
hasPastBalance,
});
const firstSentence = t(`modal.delegation.CanVote.${firstKey}`, {
tokenSymbol: daoToken?.symbol,
balance: tokenAmount,
});
const secondSentence = t(`modal.delegation.CanVote.${secondKey}`, {
tokenSymbol: daoToken?.symbol,
});

const ctaLabel =

return (
<ModalBottomSheetSwitcher
Expand All @@ -119,13 +142,12 @@ export const DelegationGatingMenu: React.FC = () => {
<IlluObject object="warning" />
)}
<p className="text-2xl leading-tight text-neutral-800">
{t(`modal.${bodyLabel}.title`)}
{t(`modal.delegation.CantVote.title`)}
</p>
<p className="text-neutral-600">
{t(`modal.${bodyLabel}.desc`, {
balance: tokenAmount,
tokenSymbol: daoToken?.symbol,
walletAddressDelegation: delegateName,
{t('modal.delegation.CanVote.description', {
sentence1: firstSentence,
sentence2: secondSentence,
})}
</p>
</ContentGroup>
Expand Down
11 changes: 10 additions & 1 deletion src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -1268,6 +1268,15 @@
"ctaLabel": "Ok, understood",
"Link": "Learn more",
"LinkURL": "https://aragon.org/how-to/delegate-your-voting-power"
},
"CantVote": {
"title": "You can't vote",
"description": "{{sentence1}} {{sentence2}}",
"sentence1a": "You weren't holding {{tokenSymbol}} and had no voting power when this proposal was created.",
"sentence1b": "You held {{balance}} {{tokenSymbol}} when this proposal was created but had no voting power.",
"sentence2a": "",
"sentence2b": "If you want to participate in future proposals you have to claim your voting power. ",
"sentence2c": "If you want to participate in future proposals you have to obtain {{tokenSymbol}}."
}
},
"delegationInactive": {
Expand Down Expand Up @@ -1651,4 +1660,4 @@
"membersDesc": "These addresses form the governing body that approves and executes proposals passed by the community. Add the addresses who will be members of the execution multisig."
}
}
}
}
81 changes: 18 additions & 63 deletions src/pages/proposal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,11 @@ import {DaoAction, ProposalStatus} from '@aragon/sdk-client-common';
import TipTapLink from '@tiptap/extension-link';
import {useEditor} from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import {BigNumber, constants} from 'ethers';
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {generatePath, useNavigate, useParams} from 'react-router-dom';
import sanitizeHtml from 'sanitize-html';
import styled from 'styled-components';
import {Address} from 'viem';
import {useBalance} from 'wagmi';

import {ExecutionWidget} from 'components/executionWidget';
import ResourceList from 'components/resourceList';
Expand All @@ -47,15 +44,14 @@ import {PluginTypes, usePluginClient} from 'hooks/usePluginClient';
import useScreen from 'hooks/useScreen';
import {useWallet} from 'hooks/useWallet';
import {useWalletCanVote} from 'hooks/useWalletCanVote';
import {usePastVotingPower} from 'services/aragon-sdk/queries/use-past-voting-power';
import {useProposal} from 'services/aragon-sdk/queries/use-proposal';
import {
isMultisigVotingSettings,
isTokenVotingSettings,
useVotingSettings,
} from 'services/aragon-sdk/queries/use-voting-settings';
import {useTokenAsync} from 'services/token/queries/use-token';
import {CHAIN_METADATA, SupportedNetworks} from 'utils/constants';
import {CHAIN_METADATA} from 'utils/constants';
import {featureFlags} from 'utils/featureFlags';
import {
decodeAddMembersToAction,
Expand Down Expand Up @@ -172,23 +168,6 @@ export const Proposal: React.FC = () => {
proposalStatus as string
);

const {data: tokenBalanceData} = useBalance({
address: address as Address,
token: daoToken?.address as Address,
chainId: CHAIN_METADATA[network as SupportedNetworks].id,
enabled: address != null && daoToken != null,
});
const tokenBalance = BigNumber.from(tokenBalanceData?.value ?? 0);

const {data: pastVotingPower = constants.Zero} = usePastVotingPower(
{
address: address as string,
tokenAddress: daoToken?.address as string,
blockNumber: proposal?.creationBlockNumber as number,
},
{enabled: address != null && daoToken != null && proposal != null}
);

const pluginClient = usePluginClient(pluginType);

// ref used to hold "memories" of previous "state"
Expand All @@ -201,12 +180,8 @@ export const Proposal: React.FC = () => {
const [votingInProcess, setVotingInProcess] = useState(false);
const [expandedProposal, setExpandedProposal] = useState(false);

// Display the voting-power gating dialog when user has balance but delegated
// his token to someone else
const shouldDisplayDelegationVoteGating =
!isMultisigPlugin &&
tokenBalance.gt(constants.Zero) &&
pastVotingPower.lte(constants.Zero);
// Display the delegation gating dialog when user cannot vote on token-based proposal
const displayDelegationVoteGating = !isMultisigPlugin && !canVote;

const editor = useEditor({
editable: false,
Expand Down Expand Up @@ -516,13 +491,10 @@ export const Proposal: React.FC = () => {
isTokenVotingSettings(votingSettings) &&
votingSettings.votingMode === VotingMode.VOTE_REPLACEMENT;

let votingDisabled =
(proposal && proposal.status !== ProposalStatus.ACTIVE) ||
const votingDisabled =
(address != null && proposal?.status !== ProposalStatus.ACTIVE) ||
(isMultisigPlugin && voted) ||
(isTokenVotingPlugin && voted && canRevote === false) ||
(canVote === false && shouldDisplayDelegationVoteGating === false);

if (!address) votingDisabled = false;
(isTokenVotingPlugin && voted && !canRevote);

const handleApprovalClick = useCallback(
(tryExecution: boolean) => {
Expand Down Expand Up @@ -553,18 +525,12 @@ export const Proposal: React.FC = () => {
} else if (isOnWrongNetwork) {
open('network');
statusRef.current.wasOnWrongNetwork = true;
} else if (shouldDisplayDelegationVoteGating) {
} else if (displayDelegationVoteGating) {
return open('delegationGating');
} else if (canVote) {
setVotingInProcess(true);
}
}, [
address,
canVote,
shouldDisplayDelegationVoteGating,
isOnWrongNetwork,
open,
]);
}, [address, canVote, displayDelegationVoteGating, isOnWrongNetwork, open]);

// handler for execution
const handleExecuteNowClicked = () => {
Expand All @@ -579,24 +545,17 @@ export const Proposal: React.FC = () => {
}
};

// alert message, only shown when not eligible to vote
let alertMessage = '';
if (
proposal &&
proposal.status === 'Active' && // active proposal
const displayAlertMessage =
proposal?.status === 'Active' && // active proposal
address && // logged in
isOnWrongNetwork === false && // on proper network
voted === false && // haven't voted
canVote === false && // cannot vote
shouldDisplayDelegationVoteGating === false // user delegated tokens
) {
// presence of token delineates token voting proposal
alertMessage = isErc20VotingProposal(proposal)
? t('votingTerminal.status.ineligibleTokenBased', {
token: proposal.token.name,
})
: t('votingTerminal.status.ineligibleWhitelist');
}
!isOnWrongNetwork && // on proper network
!voted && // haven't voted
!canVote && // cannot vote
isMultisigPlugin; // is multisig plugin

const alertMessage = displayAlertMessage
? t('votingTerminal.status.ineligibleWhitelist')
: undefined;

// status steps for proposal
const proposalSteps = proposal
Expand Down Expand Up @@ -770,10 +729,6 @@ const ContentWrapper = styled.div.attrs({
className: 'flex flex-col md:flex-row gap-x-6 gap-y-3',
})``;

// const BadgeContainer = styled.div.attrs({
// className: 'flex flex-wrap gap-x-3',
// })``;

const ProposerLink = styled.p.attrs({
className: 'text-neutral-500',
})``;
Expand Down

0 comments on commit acc84d6

Please sign in to comment.