feat(governance): proposal details page improvements (#3611)
This commit is contained in:
parent
7f949a276c
commit
313eff1c95
@ -101,11 +101,11 @@ context(
|
|||||||
);
|
);
|
||||||
cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
|
cy.getByTestId('protocol-upgrade-proposal-release-tag').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Vega release tagv1'
|
'Vega release tag: v1'
|
||||||
);
|
);
|
||||||
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
|
cy.getByTestId('protocol-upgrade-proposal-block-height').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
'Upgrade block height2015942'
|
'Upgrade block height: 2015942'
|
||||||
);
|
);
|
||||||
cy.getByTestId('protocol-upgrade-proposal-status').should(
|
cy.getByTestId('protocol-upgrade-proposal-status').should(
|
||||||
'have.text',
|
'have.text',
|
||||||
|
@ -23,6 +23,7 @@ export const ConnectToVega = () => {
|
|||||||
openVegaWalletDialog();
|
openVegaWalletDialog();
|
||||||
}}
|
}}
|
||||||
data-testid="connect-to-vega-wallet-btn"
|
data-testid="connect-to-vega-wallet-btn"
|
||||||
|
variant="primary"
|
||||||
>
|
>
|
||||||
{t('connectVegaWallet')}
|
{t('connectVegaWallet')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -23,7 +23,7 @@ export const Heading = ({
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<h1
|
<h1
|
||||||
className={classNames('font-alpha calt text-5xl', {
|
className={classNames('font-alpha calt text-5xl break-words', {
|
||||||
'mt-0': !marginTop,
|
'mt-0': !marginTop,
|
||||||
'mb-0': !marginBottom,
|
'mb-0': !marginBottom,
|
||||||
})}
|
})}
|
||||||
|
@ -201,7 +201,7 @@
|
|||||||
"NewFreeform": "Freeform",
|
"NewFreeform": "Freeform",
|
||||||
"tokenVotes": "Token votes",
|
"tokenVotes": "Token votes",
|
||||||
"liquidityVotes": "Liquidity votes",
|
"liquidityVotes": "Liquidity votes",
|
||||||
"yourVote": "Your vote",
|
"castYourVote": "Cast your vote",
|
||||||
"for": "For",
|
"for": "For",
|
||||||
"against": "Against",
|
"against": "Against",
|
||||||
"majorityRequired": "Majority Required",
|
"majorityRequired": "Majority Required",
|
||||||
@ -587,7 +587,7 @@
|
|||||||
"tokensAgainstProposal": "Tokens against proposal",
|
"tokensAgainstProposal": "Tokens against proposal",
|
||||||
"participationRequired": "Participation required",
|
"participationRequired": "Participation required",
|
||||||
"numberOfVotingParties": "Number of voting parties",
|
"numberOfVotingParties": "Number of voting parties",
|
||||||
"totalTokensVotes": "Total yes tokens",
|
"totalTokensVotes": "Total tokens voted",
|
||||||
"totalTokenVotedPercentage": "Total tokens voted percentage",
|
"totalTokenVotedPercentage": "Total tokens voted percentage",
|
||||||
"numberOfForVotes": "Number of votes for",
|
"numberOfForVotes": "Number of votes for",
|
||||||
"numberOfAgainstVotes": "Number of votes against",
|
"numberOfAgainstVotes": "Number of votes against",
|
||||||
@ -631,7 +631,10 @@
|
|||||||
"New market": "New market",
|
"New market": "New market",
|
||||||
"Market change": "Market change",
|
"Market change": "Market change",
|
||||||
"Network parameter": "Network parameter",
|
"Network parameter": "Network parameter",
|
||||||
|
"Change": "Change",
|
||||||
"Unknown proposal": "Unknown proposal",
|
"Unknown proposal": "Unknown proposal",
|
||||||
|
"ERC20ContractAddress": "ERC20 contract address",
|
||||||
|
"MaxFaucetAmountMint": "Max faucet amount mint",
|
||||||
"Code": "Code",
|
"Code": "Code",
|
||||||
"settled future": "settled future",
|
"settled future": "settled future",
|
||||||
"Symbol": "Symbol",
|
"Symbol": "Symbol",
|
||||||
@ -677,6 +680,7 @@
|
|||||||
"NewProposal": "New proposal",
|
"NewProposal": "New proposal",
|
||||||
"ProposalTypeQuestion": "What type of proposal would you like to make?",
|
"ProposalTypeQuestion": "What type of proposal would you like to make?",
|
||||||
"NetworkParameterProposal": "Update network parameter proposal",
|
"NetworkParameterProposal": "Update network parameter proposal",
|
||||||
|
"parameter": "parameter",
|
||||||
"NewMarketProposal": "New market proposal",
|
"NewMarketProposal": "New market proposal",
|
||||||
"UpdateMarketProposal": "Update market proposal",
|
"UpdateMarketProposal": "Update market proposal",
|
||||||
"NewAssetProposal": "New asset proposal",
|
"NewAssetProposal": "New asset proposal",
|
||||||
@ -694,6 +698,7 @@
|
|||||||
"UpdateMarket": "Update market",
|
"UpdateMarket": "Update market",
|
||||||
"NewAsset": "New asset",
|
"NewAsset": "New asset",
|
||||||
"UpdateAsset": "Update asset",
|
"UpdateAsset": "Update asset",
|
||||||
|
"AssetID": "Asset ID",
|
||||||
"Freeform": "Freeform",
|
"Freeform": "Freeform",
|
||||||
"RawProposal": "Let me choose (raw proposal)",
|
"RawProposal": "Let me choose (raw proposal)",
|
||||||
"UseMin": "Use minimum",
|
"UseMin": "Use minimum",
|
||||||
@ -737,6 +742,7 @@
|
|||||||
"ProposalNotFound": "Proposal not found",
|
"ProposalNotFound": "Proposal not found",
|
||||||
"ProposalNotFoundDetails": "The proposal you are looking for is not here, it may have been enacted before the last chain restore. You could check the Vega forums/discord instead for information about it.",
|
"ProposalNotFoundDetails": "The proposal you are looking for is not here, it may have been enacted before the last chain restore. You could check the Vega forums/discord instead for information about it.",
|
||||||
"FreeformProposal": "Freeform proposal",
|
"FreeformProposal": "Freeform proposal",
|
||||||
|
"Id": "ID",
|
||||||
"unknownReason": "unknown reason",
|
"unknownReason": "unknown reason",
|
||||||
"votingEnded": "Voting has ended.",
|
"votingEnded": "Voting has ended.",
|
||||||
"STATUS": "STATUS",
|
"STATUS": "STATUS",
|
||||||
@ -791,5 +797,7 @@
|
|||||||
"67% voting power required": "67% voting power required",
|
"67% voting power required": "67% voting power required",
|
||||||
"Token": "Token",
|
"Token": "Token",
|
||||||
"associateVegaNow": "Associate $VEGA now",
|
"associateVegaNow": "Associate $VEGA now",
|
||||||
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app."
|
"disconnectedNotice": "You have been disconnected. Connect your ETH wallet to the {{correctNetwork}} network to use this app.",
|
||||||
|
"connectAVegaWalletToVote": "Connect a Vega wallet with $VEGA tokens to vote on a proposal.",
|
||||||
|
"findOutMoreAboutHowToVote": "Find out more about how to vote on Vega"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import * as Schema from '@vegaprotocol/types';
|
import { Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import type { ProposalInfoLabelVariant } from '../proposal-info-label';
|
||||||
|
|
||||||
export const CurrentProposalState = ({
|
export const CurrentProposalState = ({
|
||||||
proposal,
|
proposal,
|
||||||
@ -9,19 +13,63 @@ export const CurrentProposalState = ({
|
|||||||
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
proposal: ProposalFieldsFragment | ProposalQuery['proposal'];
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
let className = 'text-white';
|
let proposalStatus: ReactNode;
|
||||||
|
let variant = 'tertiary' as ProposalInfoLabelVariant;
|
||||||
|
|
||||||
if (
|
switch (proposal?.state) {
|
||||||
proposal?.state === Schema.ProposalState.STATE_DECLINED ||
|
case ProposalState.STATE_ENACTED: {
|
||||||
proposal?.state === Schema.ProposalState.STATE_FAILED ||
|
proposalStatus = (
|
||||||
proposal?.state === Schema.ProposalState.STATE_REJECTED
|
<>
|
||||||
) {
|
<span className="mr-2">{t('voteState_Enacted')}</span>
|
||||||
className = 'text-danger';
|
<Icon name={'tick'} />
|
||||||
} else if (
|
</>
|
||||||
proposal?.state === Schema.ProposalState.STATE_ENACTED ||
|
);
|
||||||
proposal?.state === Schema.ProposalState.STATE_PASSED
|
break;
|
||||||
) {
|
}
|
||||||
className = 'text-white';
|
case ProposalState.STATE_PASSED: {
|
||||||
|
proposalStatus = (
|
||||||
|
<>
|
||||||
|
<span className="mr-2">{t('voteState_Passed')}</span>
|
||||||
|
<Icon name={'tick'} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||||
|
proposalStatus = (
|
||||||
|
<>
|
||||||
|
<span className="mr-2">{t('voteState_WaitingForNodeVote')}</span>
|
||||||
|
<Icon name={'time'} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProposalState.STATE_OPEN: {
|
||||||
|
variant = 'primary' as ProposalInfoLabelVariant;
|
||||||
|
proposalStatus = <>{t('voteState_Open')}</>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProposalState.STATE_DECLINED: {
|
||||||
|
proposalStatus = (
|
||||||
|
<>
|
||||||
|
<span className="mr-2">{t('voteState_Declined')}</span>
|
||||||
|
<Icon name={'cross'} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ProposalState.STATE_REJECTED: {
|
||||||
|
proposalStatus = (
|
||||||
|
<>
|
||||||
|
<span className="mr-2">{t('voteState_Rejected')}</span>
|
||||||
|
<Icon name={'warning-sign'} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return <span className={className}>{t(`${proposal?.state}`)}</span>;
|
|
||||||
|
return (
|
||||||
|
<ProposalInfoLabel variant={variant}>{proposalStatus}</ProposalInfoLabel>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
@ -16,17 +16,12 @@ it('Renders all data for table', () => {
|
|||||||
render(<ProposalChangeTable proposal={proposal} />);
|
render(<ProposalChangeTable proposal={proposal} />);
|
||||||
expect(screen.getByText('ID')).toBeInTheDocument();
|
expect(screen.getByText('ID')).toBeInTheDocument();
|
||||||
expect(screen.getByText(proposal?.id as string)).toBeInTheDocument();
|
expect(screen.getByText(proposal?.id as string)).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('State')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Open')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('Closes on')).toBeInTheDocument();
|
expect(screen.getByText('Closes on')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(
|
||||||
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
|
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
|
||||||
)
|
)
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Proposed enactment')).toBeInTheDocument();
|
expect(screen.getByText('Proposed enactment')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(
|
||||||
@ -35,17 +30,12 @@ it('Renders all data for table', () => {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Proposed by')).toBeInTheDocument();
|
expect(screen.getByText('Proposed by')).toBeInTheDocument();
|
||||||
expect(screen.getByText(proposal?.party.id ?? '')).toBeInTheDocument();
|
expect(screen.getByText(proposal?.party.id ?? '')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Proposed on')).toBeInTheDocument();
|
expect(screen.getByText('Proposed on')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime)))
|
screen.getByText(formatDateWithLocalTimezone(new Date(proposal?.datetime)))
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Type')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Network parameter')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Changes data based on if data is in future or past', () => {
|
it('Changes data based on if data is in future or past', () => {
|
||||||
@ -53,17 +43,12 @@ it('Changes data based on if data is in future or past', () => {
|
|||||||
state: ProposalState.STATE_ENACTED,
|
state: ProposalState.STATE_ENACTED,
|
||||||
});
|
});
|
||||||
render(<ProposalChangeTable proposal={proposal} />);
|
render(<ProposalChangeTable proposal={proposal} />);
|
||||||
|
|
||||||
expect(screen.getByText('State')).toBeInTheDocument();
|
|
||||||
expect(screen.getByText('Enacted')).toBeInTheDocument();
|
|
||||||
|
|
||||||
expect(screen.getByText('Closed on')).toBeInTheDocument();
|
expect(screen.getByText('Closed on')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(
|
||||||
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
|
formatDateWithLocalTimezone(new Date(proposal?.terms.closingDatetime))
|
||||||
)
|
)
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Enacted on')).toBeInTheDocument();
|
expect(screen.getByText('Enacted on')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(
|
screen.getByText(
|
||||||
@ -104,7 +89,6 @@ it('Renders error details and rejection reason if present', () => {
|
|||||||
render(<ProposalChangeTable proposal={proposal} />);
|
render(<ProposalChangeTable proposal={proposal} />);
|
||||||
expect(screen.getByText('Error details')).toBeInTheDocument();
|
expect(screen.getByText('Error details')).toBeInTheDocument();
|
||||||
expect(screen.getByText(errorDetails)).toBeInTheDocument();
|
expect(screen.getByText(errorDetails)).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByText('Rejection reason')).toBeInTheDocument();
|
expect(screen.getByText('Rejection reason')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText(ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE)
|
screen.getByText(ProposalRejectionReason.PROPOSAL_ERROR_CLOSE_TIME_TOO_LATE)
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
KeyValueTableRow,
|
KeyValueTableRow,
|
||||||
RoundedWrapper,
|
RoundedWrapper,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { CurrentProposalState } from '../current-proposal-state';
|
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
|
||||||
@ -20,16 +19,12 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
|
|||||||
const terms = proposal?.terms;
|
const terms = proposal?.terms;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoundedWrapper>
|
<RoundedWrapper paddingBottom={true}>
|
||||||
<KeyValueTable data-testid="proposal-change-table">
|
<KeyValueTable data-testid="proposal-change-table">
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('id')}
|
{t('id')}
|
||||||
{proposal?.id}
|
{proposal?.id}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('state')}
|
|
||||||
<CurrentProposalState proposal={proposal} />
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{isFuture(new Date(terms?.closingDatetime))
|
{isFuture(new Date(terms?.closingDatetime))
|
||||||
? t('closesOn')
|
? t('closesOn')
|
||||||
@ -50,26 +45,24 @@ export const ProposalChangeTable = ({ proposal }: ProposalChangeTableProps) => {
|
|||||||
{t('proposedBy')}
|
{t('proposedBy')}
|
||||||
<span style={{ wordBreak: 'break-word' }}>{proposal?.party.id}</span>
|
<span style={{ wordBreak: 'break-word' }}>{proposal?.party.id}</span>
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow
|
||||||
|
noBorder={!proposal?.rejectionReason && !proposal?.errorDetails}
|
||||||
|
>
|
||||||
{t('proposedOn')}
|
{t('proposedOn')}
|
||||||
{formatDateWithLocalTimezone(new Date(proposal?.datetime))}
|
{formatDateWithLocalTimezone(new Date(proposal?.datetime))}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
{proposal?.rejectionReason ? (
|
{proposal?.rejectionReason ? (
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow noBorder={!proposal?.errorDetails}>
|
||||||
{t('rejectionReason')}
|
{t('rejectionReason')}
|
||||||
{proposal.rejectionReason}
|
{proposal.rejectionReason}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
) : null}
|
) : null}
|
||||||
{proposal?.errorDetails ? (
|
{proposal?.errorDetails ? (
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow noBorder={true}>
|
||||||
{t('errorDetails')}
|
{t('errorDetails')}
|
||||||
{proposal.errorDetails}
|
{proposal.errorDetails}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
) : null}
|
) : null}
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('type')}
|
|
||||||
{t(`${proposal?.terms.change.__typename}`)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
</KeyValueTable>
|
</KeyValueTable>
|
||||||
</RoundedWrapper>
|
</RoundedWrapper>
|
||||||
);
|
);
|
||||||
|
@ -1,41 +1,45 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { generateProposal } from '../../test-helpers/generate-proposals';
|
import {
|
||||||
|
generateNoVotes,
|
||||||
|
generateProposal,
|
||||||
|
generateYesVotes,
|
||||||
|
} from '../../test-helpers/generate-proposals';
|
||||||
import { ProposalHeader } from './proposal-header';
|
import { ProposalHeader } from './proposal-header';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
|
import { ProposalRejectionReason, ProposalState } from '@vegaprotocol/types';
|
||||||
|
import { lastWeek, nextWeek } from '../../test-helpers/mocks';
|
||||||
|
|
||||||
const renderComponent = (
|
const renderComponent = (
|
||||||
proposal: ProposalQuery['proposal'],
|
proposal: ProposalQuery['proposal'],
|
||||||
isListItem = true
|
isListItem = true
|
||||||
) => <ProposalHeader proposal={proposal} isListItem={isListItem} />;
|
) => render(<ProposalHeader proposal={proposal} isListItem={isListItem} />);
|
||||||
|
|
||||||
describe('Proposal header', () => {
|
describe('Proposal header', () => {
|
||||||
it('Renders New market proposal', () => {
|
it('Renders New market proposal', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
rationale: {
|
||||||
rationale: {
|
title: 'New some market',
|
||||||
title: 'New some market',
|
description: 'A new some market',
|
||||||
description: 'A new some market',
|
},
|
||||||
},
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
__typename: 'NewMarket',
|
||||||
__typename: 'NewMarket',
|
instrument: {
|
||||||
instrument: {
|
__typename: 'InstrumentConfiguration',
|
||||||
__typename: 'InstrumentConfiguration',
|
name: 'Some market',
|
||||||
name: 'Some market',
|
code: 'FX:BTCUSD/DEC99',
|
||||||
code: 'FX:BTCUSD/DEC99',
|
futureProduct: {
|
||||||
futureProduct: {
|
__typename: 'FutureProduct',
|
||||||
__typename: 'FutureProduct',
|
settlementAsset: {
|
||||||
settlementAsset: {
|
__typename: 'Asset',
|
||||||
__typename: 'Asset',
|
symbol: 'tGBP',
|
||||||
symbol: 'tGBP',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'New some market'
|
'New some market'
|
||||||
@ -47,20 +51,18 @@ describe('Proposal header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Renders Update market proposal', () => {
|
it('Renders Update market proposal', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
rationale: {
|
||||||
rationale: {
|
title: 'New market id',
|
||||||
title: 'New market id',
|
},
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'UpdateMarket',
|
||||||
|
marketId: 'MarketId',
|
||||||
},
|
},
|
||||||
terms: {
|
},
|
||||||
change: {
|
})
|
||||||
__typename: 'UpdateMarket',
|
|
||||||
marketId: 'MarketId',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'New market id'
|
'New market id'
|
||||||
@ -77,53 +79,49 @@ describe('Proposal header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Renders New asset proposal - ERC20', () => {
|
it('Renders New asset proposal - ERC20', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
rationale: {
|
||||||
rationale: {
|
title: 'New asset: Fake currency',
|
||||||
title: 'New asset: Fake currency',
|
description: '',
|
||||||
description: '',
|
},
|
||||||
},
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
__typename: 'NewAsset',
|
||||||
__typename: 'NewAsset',
|
name: 'Fake currency',
|
||||||
name: 'Fake currency',
|
symbol: 'FAKE',
|
||||||
symbol: 'FAKE',
|
source: {
|
||||||
source: {
|
__typename: 'ERC20',
|
||||||
__typename: 'ERC20',
|
contractAddress: '0x0',
|
||||||
contractAddress: '0x0',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'New asset: Fake currency'
|
'New asset: Fake currency'
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
|
expect(screen.getByTestId('proposal-type')).toHaveTextContent('New asset');
|
||||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-details')).toHaveTextContent(
|
||||||
'Symbol: FAKE. ERC20 0x0'
|
'Symbol: FAKE. ERC20 contract address: 0x0'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders New asset proposal - BuiltInAsset', () => {
|
it('Renders New asset proposal - BuiltInAsset', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
__typename: 'NewAsset',
|
||||||
__typename: 'NewAsset',
|
name: 'Fake currency',
|
||||||
name: 'Fake currency',
|
symbol: 'BIA',
|
||||||
symbol: 'BIA',
|
source: {
|
||||||
source: {
|
__typename: 'BuiltinAsset',
|
||||||
__typename: 'BuiltinAsset',
|
maxFaucetAmountMint: '300',
|
||||||
maxFaucetAmountMint: '300',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'Unknown proposal'
|
'Unknown proposal'
|
||||||
@ -135,24 +133,22 @@ describe('Proposal header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Renders Update network', () => {
|
it('Renders Update network', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
rationale: {
|
||||||
rationale: {
|
title: 'Network parameter',
|
||||||
title: 'Network parameter',
|
},
|
||||||
},
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
__typename: 'UpdateNetworkParameter',
|
||||||
__typename: 'UpdateNetworkParameter',
|
networkParameter: {
|
||||||
networkParameter: {
|
__typename: 'NetworkParameter',
|
||||||
__typename: 'NetworkParameter',
|
key: 'Network key',
|
||||||
key: 'Network key',
|
value: 'Network value',
|
||||||
value: 'Network value',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'Network parameter'
|
'Network parameter'
|
||||||
@ -165,47 +161,42 @@ describe('Proposal header', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders Freeform network - short rationale', () => {
|
it('Renders Freeform proposal - short rationale', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
id: 'short',
|
||||||
id: 'short',
|
rationale: {
|
||||||
rationale: {
|
title: '0x0',
|
||||||
title: '0x0',
|
},
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'NewFreeform',
|
||||||
},
|
},
|
||||||
terms: {
|
},
|
||||||
change: {
|
})
|
||||||
__typename: 'NewFreeform',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
|
||||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
||||||
expect(
|
expect(
|
||||||
screen.queryByTestId('proposal-description')
|
screen.queryByTestId('proposal-description')
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent('short');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders Freeform proposal - long rationale (105 chars) - listing', () => {
|
it('Renders Freeform proposal - long rationale (105 chars) - listing', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
id: 'long',
|
||||||
id: 'long',
|
rationale: {
|
||||||
rationale: {
|
title: '0x0',
|
||||||
title: '0x0',
|
description:
|
||||||
description:
|
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
|
||||||
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
|
},
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'NewFreeform',
|
||||||
},
|
},
|
||||||
terms: {
|
},
|
||||||
change: {
|
})
|
||||||
__typename: 'NewFreeform',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent('0x0');
|
||||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
||||||
@ -213,27 +204,24 @@ describe('Proposal header', () => {
|
|||||||
expect(
|
expect(
|
||||||
screen.queryByTestId('proposal-description')
|
screen.queryByTestId('proposal-description')
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(screen.getByTestId('proposal-details')).toHaveTextContent('long');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
|
it('Renders Freeform proposal - long rationale (105 chars) - details', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
id: 'long',
|
||||||
id: 'long',
|
rationale: {
|
||||||
rationale: {
|
title: '0x0',
|
||||||
title: '0x0',
|
description:
|
||||||
description:
|
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
|
||||||
'Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean dolor.',
|
},
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'NewFreeform',
|
||||||
},
|
},
|
||||||
terms: {
|
},
|
||||||
change: {
|
}),
|
||||||
__typename: 'NewFreeform',
|
false
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
false
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-description')).toHaveTextContent(
|
||||||
/Class aptent/
|
/Class aptent/
|
||||||
@ -242,43 +230,36 @@ describe('Proposal header', () => {
|
|||||||
|
|
||||||
// Remove once proposals have rationale and re-enable above tests
|
// Remove once proposals have rationale and re-enable above tests
|
||||||
it('Renders Freeform proposal - id for title', () => {
|
it('Renders Freeform proposal - id for title', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
id: 'freeform id',
|
||||||
id: 'freeform id',
|
rationale: {
|
||||||
rationale: {
|
title: 'freeform',
|
||||||
title: 'freeform',
|
},
|
||||||
|
terms: {
|
||||||
|
change: {
|
||||||
|
__typename: 'NewFreeform',
|
||||||
},
|
},
|
||||||
terms: {
|
},
|
||||||
change: {
|
})
|
||||||
__typename: 'NewFreeform',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform');
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent('freeform');
|
||||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
expect(screen.getByTestId('proposal-type')).toHaveTextContent('Freeform');
|
||||||
expect(
|
expect(
|
||||||
screen.queryByTestId('proposal-description')
|
screen.queryByTestId('proposal-description')
|
||||||
).not.toBeInTheDocument();
|
).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId('proposal-details')).toHaveTextContent(
|
|
||||||
'freeform id'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders asset change proposal header', () => {
|
it('Renders asset change proposal header', () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
__typename: 'UpdateAsset',
|
||||||
__typename: 'UpdateAsset',
|
assetId: 'foo',
|
||||||
assetId: 'foo',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-type')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-type')).toHaveTextContent(
|
||||||
'Update asset'
|
'Update asset'
|
||||||
@ -287,20 +268,104 @@ describe('Proposal header', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("Renders unknown proposal if it's a different proposal type", () => {
|
it("Renders unknown proposal if it's a different proposal type", () => {
|
||||||
render(
|
renderComponent(
|
||||||
renderComponent(
|
generateProposal({
|
||||||
generateProposal({
|
terms: {
|
||||||
terms: {
|
change: {
|
||||||
change: {
|
// @ts-ignore unknown proposal
|
||||||
// @ts-ignore unknown proposal
|
__typename: 'Foo',
|
||||||
__typename: 'Foo',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
expect(screen.getByTestId('proposal-title')).toHaveTextContent(
|
||||||
'Unknown proposal'
|
'Unknown proposal'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Enacted', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_ENACTED,
|
||||||
|
terms: {
|
||||||
|
enactmentDatetime: lastWeek.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Passed', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_PASSED,
|
||||||
|
terms: {
|
||||||
|
closingDatetime: lastWeek.toString(),
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Waiting for node vote', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_WAITING_FOR_NODE_VOTE,
|
||||||
|
terms: {
|
||||||
|
enactmentDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
|
||||||
|
'Waiting for node vote'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Open', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_OPEN,
|
||||||
|
votes: {
|
||||||
|
__typename: 'ProposalVotes',
|
||||||
|
yes: generateYesVotes(3000, 1000000000000000000),
|
||||||
|
no: generateNoVotes(0),
|
||||||
|
},
|
||||||
|
terms: {
|
||||||
|
closingDatetime: nextWeek.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Declined - majority not reached', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_DECLINED,
|
||||||
|
terms: {
|
||||||
|
enactmentDatetime: lastWeek.toString(),
|
||||||
|
},
|
||||||
|
votes: {
|
||||||
|
no: generateNoVotes(1, 1000000000000000000),
|
||||||
|
yes: generateYesVotes(1, 1000000000000000000),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Renders proposal state: Rejected', () => {
|
||||||
|
renderComponent(
|
||||||
|
generateProposal({
|
||||||
|
state: ProposalState.STATE_REJECTED,
|
||||||
|
terms: {
|
||||||
|
enactmentDatetime: lastWeek.toString(),
|
||||||
|
},
|
||||||
|
rejectionReason:
|
||||||
|
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Intent, Lozenge } from '@vegaprotocol/ui-toolkit';
|
import { Lozenge } from '@vegaprotocol/ui-toolkit';
|
||||||
import { shorten } from '@vegaprotocol/utils';
|
import { shorten } from '@vegaprotocol/utils';
|
||||||
import { Heading, SubHeading } from '../../../../components/heading';
|
import { Heading, SubHeading } from '../../../../components/heading';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
@ -7,6 +7,8 @@ import type { ProposalFieldsFragment } from '../../proposals/__generated__/Propo
|
|||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
import ReactMarkdown from 'react-markdown';
|
import ReactMarkdown from 'react-markdown';
|
||||||
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
import { truncateMiddle } from '../../../../lib/truncate-middle';
|
||||||
|
import { CurrentProposalState } from '../current-proposal-state';
|
||||||
|
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||||
|
|
||||||
export const ProposalHeader = ({
|
export const ProposalHeader = ({
|
||||||
proposal,
|
proposal,
|
||||||
@ -19,7 +21,7 @@ export const ProposalHeader = ({
|
|||||||
const change = proposal?.terms.change;
|
const change = proposal?.terms.change;
|
||||||
|
|
||||||
let details: ReactNode;
|
let details: ReactNode;
|
||||||
let proposalType: ReactNode;
|
let proposalType = '';
|
||||||
|
|
||||||
let title = proposal?.rationale.title.trim();
|
let title = proposal?.rationale.title.trim();
|
||||||
let description = proposal?.rationale.description.trim();
|
let description = proposal?.rationale.description.trim();
|
||||||
@ -32,10 +34,12 @@ export const ProposalHeader = ({
|
|||||||
|
|
||||||
switch (change?.__typename) {
|
switch (change?.__typename) {
|
||||||
case 'NewMarket': {
|
case 'NewMarket': {
|
||||||
proposalType = t('NewMarket');
|
proposalType = 'NewMarket';
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
{t('Code')}: {change.instrument.code}.{' '}
|
<span>
|
||||||
|
{t('Code')}: {change.instrument.code}.
|
||||||
|
</span>{' '}
|
||||||
{change.instrument.futureProduct?.settlementAsset.symbol ? (
|
{change.instrument.futureProduct?.settlementAsset.symbol ? (
|
||||||
<>
|
<>
|
||||||
<span className="font-semibold">
|
<span className="font-semibold">
|
||||||
@ -51,53 +55,60 @@ export const ProposalHeader = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'UpdateMarket': {
|
case 'UpdateMarket': {
|
||||||
proposalType = t('UpdateMarket');
|
proposalType = 'UpdateMarket';
|
||||||
details = `${t('Market change')}: ${change.marketId}`;
|
details = (
|
||||||
|
<>
|
||||||
|
<span>{t('Market change')}:</span>{' '}
|
||||||
|
<span>{truncateMiddle(change.marketId)}</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'NewAsset': {
|
case 'NewAsset': {
|
||||||
proposalType = t('NewAsset');
|
proposalType = 'NewAsset';
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
{t('Symbol')}: {change.symbol}.{' '}
|
<span>{t('Symbol')}:</span> <Lozenge>{change.symbol}.</Lozenge>{' '}
|
||||||
<Lozenge>
|
{change.source.__typename === 'ERC20' && (
|
||||||
{change.source.__typename === 'ERC20' &&
|
<>
|
||||||
`ERC20 ${change.source.contractAddress}`}
|
<span>{t('ERC20ContractAddress')}:</span>{' '}
|
||||||
{change.source.__typename === 'BuiltinAsset' &&
|
<Lozenge>{change.source.contractAddress}</Lozenge>
|
||||||
`${t('Max faucet amount mint')}: ${
|
</>
|
||||||
change.source.maxFaucetAmountMint
|
)}{' '}
|
||||||
}`}
|
{change.source.__typename === 'BuiltinAsset' && (
|
||||||
</Lozenge>
|
<>
|
||||||
|
<span>{t('MaxFaucetAmountMint')}:</span>{' '}
|
||||||
|
<Lozenge>{change.source.maxFaucetAmountMint}</Lozenge>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'UpdateNetworkParameter': {
|
case 'UpdateNetworkParameter': {
|
||||||
proposalType = t('NetworkParameter');
|
proposalType = 'NetworkParameter';
|
||||||
const parametersClasses = 'font-mono leading-none';
|
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
<span className={`${parametersClasses} mr-2`}>
|
<span>{t('Change')}:</span>{' '}
|
||||||
{change.networkParameter.key}
|
<Lozenge>{change.networkParameter.key}</Lozenge>{' '}
|
||||||
</span>{' '}
|
<span>{t('to')}</span>{' '}
|
||||||
{t('to')}{' '}
|
<span className="whitespace-nowrap">
|
||||||
<span className={`${parametersClasses} ml-2`}>
|
<Lozenge>{change.networkParameter.value}</Lozenge>
|
||||||
{change.networkParameter.value}
|
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'NewFreeform': {
|
case 'NewFreeform': {
|
||||||
proposalType = t('Freeform');
|
proposalType = 'Freeform';
|
||||||
details = `${t('FreeformProposal')}: ${proposal?.id}`;
|
details = <span />;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'UpdateAsset': {
|
case 'UpdateAsset': {
|
||||||
proposalType = t('UpdateAsset');
|
proposalType = 'UpdateAsset';
|
||||||
details = (
|
details = (
|
||||||
<>
|
<>
|
||||||
<span>{t('Asset ID')}:</span>
|
<span>{t('AssetID')}:</span>{' '}
|
||||||
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge>
|
<Lozenge>{truncateMiddle(change.assetId)}</Lozenge>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@ -106,7 +117,7 @@ export const ProposalHeader = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-sm mb-2">
|
<>
|
||||||
<div data-testid="proposal-title">
|
<div data-testid="proposal-title">
|
||||||
{isListItem ? (
|
{isListItem ? (
|
||||||
<header>
|
<header>
|
||||||
@ -118,30 +129,39 @@ export const ProposalHeader = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 mb-4">
|
<div className="flex items-center gap-2 mb-4">
|
||||||
{proposalType && (
|
<div data-testid="proposal-type">
|
||||||
<div data-testid="proposal-type">
|
<ProposalInfoLabel variant="secondary">
|
||||||
<Lozenge variant={Intent.None}>{proposalType}</Lozenge>
|
{t(`${proposalType}`)}
|
||||||
</div>
|
</ProposalInfoLabel>
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
<div data-testid="proposal-status">
|
||||||
{description && !isListItem && (
|
<CurrentProposalState proposal={proposal} />
|
||||||
<div data-testid="proposal-description" className="mb-4">
|
</div>
|
||||||
<ReactMarkdown
|
|
||||||
className="react-markdown-container"
|
|
||||||
/* Prevents HTML embedded in the description from rendering */
|
|
||||||
skipHtml={true}
|
|
||||||
/* Stops users embedding images which could be used for tracking */
|
|
||||||
disallowedElements={['img']}
|
|
||||||
linkTarget="_blank"
|
|
||||||
>
|
|
||||||
{description}
|
|
||||||
</ReactMarkdown>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{details && <div data-testid="proposal-details">{details}</div>}
|
{details && (
|
||||||
</div>
|
<div data-testid="proposal-details" className="break-words my-10">
|
||||||
|
{details}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{description && !isListItem && (
|
||||||
|
<div data-testid="proposal-description">
|
||||||
|
{/*<div className="uppercase mr-2">{t('ProposalDescription')}:</div>*/}
|
||||||
|
<SubHeading title={t('ProposalDescription')} />
|
||||||
|
<ReactMarkdown
|
||||||
|
className="react-markdown-container"
|
||||||
|
/* Prevents HTML embedded in the description from rendering */
|
||||||
|
skipHtml={true}
|
||||||
|
/* Stops users embedding images which could be used for tracking */
|
||||||
|
disallowedElements={['img']}
|
||||||
|
linkTarget="_blank"
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</ReactMarkdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from './proposal-info-label';
|
@ -0,0 +1,35 @@
|
|||||||
|
import classNames from 'classnames';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
export type ProposalInfoLabelVariant =
|
||||||
|
| 'primary'
|
||||||
|
| 'secondary'
|
||||||
|
| 'tertiary'
|
||||||
|
| 'highlight';
|
||||||
|
|
||||||
|
const base = 'rounded-md px-2 py-1 font-alpha';
|
||||||
|
const primary = 'bg-vega-light-150 text-black';
|
||||||
|
const secondary = 'bg-vega-dark-200 text-white';
|
||||||
|
const tertiary = 'bg-vega-dark-150 text-white';
|
||||||
|
const highlight = 'bg-vega-yellow text-black';
|
||||||
|
|
||||||
|
const getClassname = (variant: ProposalInfoLabelVariant) => {
|
||||||
|
return classNames(base, {
|
||||||
|
[primary]: variant === 'primary',
|
||||||
|
[secondary]: variant === 'secondary',
|
||||||
|
[tertiary]: variant === 'tertiary',
|
||||||
|
[highlight]: variant === 'highlight',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ProposalInfoLabelProps {
|
||||||
|
children: ReactNode;
|
||||||
|
variant?: ProposalInfoLabelVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProposalInfoLabel = ({
|
||||||
|
children,
|
||||||
|
variant = 'primary',
|
||||||
|
}: ProposalInfoLabelProps) => {
|
||||||
|
return <div className={getClassname(variant)}>{children}</div>;
|
||||||
|
};
|
@ -1,8 +1,10 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
import { Icon, SyntaxHighlighter } from '@vegaprotocol/ui-toolkit';
|
||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
import type { PartialDeep } from 'type-fest';
|
import type { PartialDeep } from 'type-fest';
|
||||||
import type * as Schema from '@vegaprotocol/types';
|
import type * as Schema from '@vegaprotocol/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
|
||||||
export const ProposalTermsJson = ({
|
export const ProposalTermsJson = ({
|
||||||
terms,
|
terms,
|
||||||
@ -10,10 +12,26 @@ export const ProposalTermsJson = ({
|
|||||||
terms: PartialDeep<Schema.ProposalTerms>;
|
terms: PartialDeep<Schema.ProposalTerms>;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
|
const showDetailsIconClasses = classnames('mb-4', {
|
||||||
|
'rotate-180': showDetails,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
<SubHeading title={t('proposalTerms')} />
|
<button
|
||||||
<SyntaxHighlighter data={terms} />
|
onClick={() => setShowDetails(!showDetails)}
|
||||||
|
data-testid="proposal-terms-toggle"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<SubHeading title={t('proposalTerms')} />
|
||||||
|
<div className={showDetailsIconClasses}>
|
||||||
|
<Icon name="chevron-down" size={8} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{showDetails && <SyntaxHighlighter data={terms} />}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen, fireEvent } from '@testing-library/react';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
import { AppStateProvider } from '../../../../contexts/app-state/app-state-provider';
|
||||||
import { ProposalVotesTable } from './proposal-votes-table';
|
import { ProposalVotesTable } from './proposal-votes-table';
|
||||||
@ -46,6 +46,7 @@ describe('Proposal Votes Table', () => {
|
|||||||
|
|
||||||
it('should show vote breakdown fields, excluding custom update market fields', () => {
|
it('should show vote breakdown fields, excluding custom update market fields', () => {
|
||||||
renderComponent();
|
renderComponent();
|
||||||
|
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||||
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
|
expect(screen.getByText('Expected to pass')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Token majority met')).toBeInTheDocument();
|
expect(screen.getByText('Token majority met')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Token participation met')).toBeInTheDocument();
|
expect(screen.getByText('Token participation met')).toBeInTheDocument();
|
||||||
@ -55,7 +56,7 @@ describe('Proposal Votes Table', () => {
|
|||||||
expect(screen.getByText('Participation required')).toBeInTheDocument();
|
expect(screen.getByText('Participation required')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Majority Required')).toBeInTheDocument();
|
expect(screen.getByText('Majority Required')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
|
expect(screen.getByText('Number of voting parties')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Total yes tokens')).toBeInTheDocument();
|
expect(screen.getByText('Total tokens voted')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('Total tokens voted percentage')
|
screen.getByText('Total tokens voted percentage')
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
@ -70,13 +71,14 @@ describe('Proposal Votes Table', () => {
|
|||||||
|
|
||||||
it('displays different breakdown fields for update market proposal', () => {
|
it('displays different breakdown fields for update market proposal', () => {
|
||||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
renderComponent(updateMarketProposal, updateMarketProposalType);
|
||||||
|
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||||
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
|
expect(screen.getByText('Liquidity majority met')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
|
expect(screen.getByText('Liquidity participation met')).toBeInTheDocument();
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('Liquidity shares for proposal')
|
screen.getByText('Liquidity shares for proposal')
|
||||||
).toBeInTheDocument();
|
).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Number of voting parties')).toBeNull();
|
expect(screen.queryByText('Number of voting parties')).toBeNull();
|
||||||
expect(screen.queryByText('Total yes tokens')).toBeNull();
|
expect(screen.queryByText('Total tokens voted')).toBeNull();
|
||||||
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
|
expect(screen.queryByText('Total tokens voted percentage')).toBeNull();
|
||||||
expect(screen.queryByText('Number of votes for')).toBeNull();
|
expect(screen.queryByText('Number of votes for')).toBeNull();
|
||||||
expect(screen.queryByText('Number of votes against')).toBeNull();
|
expect(screen.queryByText('Number of votes against')).toBeNull();
|
||||||
@ -86,6 +88,7 @@ describe('Proposal Votes Table', () => {
|
|||||||
|
|
||||||
it('displays if an update market proposal will pass by token vote', () => {
|
it('displays if an update market proposal will pass by token vote', () => {
|
||||||
renderComponent(updateMarketProposal, updateMarketProposalType);
|
renderComponent(updateMarketProposal, updateMarketProposalType);
|
||||||
|
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||||
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
|
expect(screen.getByText('👍 by token vote')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -110,6 +113,7 @@ describe('Proposal Votes Table', () => {
|
|||||||
}),
|
}),
|
||||||
updateMarketProposalType
|
updateMarketProposalType
|
||||||
);
|
);
|
||||||
|
fireEvent.click(screen.getByTestId('vote-breakdown-toggle'));
|
||||||
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
|
expect(screen.getByText('👍 by liquidity vote')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
import classnames from 'classnames';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
KeyValueTable,
|
KeyValueTable,
|
||||||
KeyValueTableRow,
|
KeyValueTableRow,
|
||||||
Thumbs,
|
Thumbs,
|
||||||
RoundedWrapper,
|
RoundedWrapper,
|
||||||
|
Icon,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
|
import { formatNumber, formatNumberPercentage } from '@vegaprotocol/utils';
|
||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
@ -26,6 +29,7 @@ export const ProposalVotesTable = ({
|
|||||||
const {
|
const {
|
||||||
appState: { totalSupply },
|
appState: { totalSupply },
|
||||||
} = useAppState();
|
} = useAppState();
|
||||||
|
const [showDetails, setShowDetails] = useState(false);
|
||||||
const {
|
const {
|
||||||
willPassByTokenVote,
|
willPassByTokenVote,
|
||||||
willPassByLPVote,
|
willPassByLPVote,
|
||||||
@ -53,113 +57,130 @@ export const ProposalVotesTable = ({
|
|||||||
? t('byTokenVote')
|
? t('byTokenVote')
|
||||||
: t('byLiquidityVote');
|
: t('byLiquidityVote');
|
||||||
|
|
||||||
|
const showDetailsIconClasses = classnames('mb-4', {
|
||||||
|
'rotate-180': showDetails,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SubHeading title={t('voteBreakdown')} />
|
<button
|
||||||
<RoundedWrapper>
|
onClick={() => setShowDetails(!showDetails)}
|
||||||
<KeyValueTable
|
data-testid="vote-breakdown-toggle"
|
||||||
data-testid="proposal-votes-table"
|
>
|
||||||
numerical={true}
|
<div className="flex items-center gap-3">
|
||||||
headingLevel={4}
|
<SubHeading title={t('voteBreakdown')} />
|
||||||
>
|
<div className={showDetailsIconClasses}>
|
||||||
<KeyValueTableRow>
|
<Icon name="chevron-down" size={8} />
|
||||||
{t('expectedToPass')}
|
</div>
|
||||||
{isUpdateMarket ? (
|
</div>
|
||||||
updateMarketWillPass ? (
|
</button>
|
||||||
<Thumbs up={true} text={updateMarketVotePassMethod} />
|
|
||||||
) : (
|
{showDetails && (
|
||||||
<Thumbs up={false} />
|
<RoundedWrapper marginBottomLarge={true} paddingBottom={true}>
|
||||||
)
|
<KeyValueTable
|
||||||
) : willPassByTokenVote ? (
|
data-testid="proposal-votes-table"
|
||||||
<Thumbs up={true} />
|
numerical={true}
|
||||||
) : (
|
headingLevel={4}
|
||||||
<Thumbs up={false} />
|
>
|
||||||
)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('majorityMet')}
|
|
||||||
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('majorityLPMet')}
|
{t('expectedToPass')}
|
||||||
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
{isUpdateMarket ? (
|
||||||
</KeyValueTableRow>
|
updateMarketWillPass ? (
|
||||||
)}
|
<Thumbs up={true} text={updateMarketVotePassMethod} />
|
||||||
<KeyValueTableRow>
|
) : (
|
||||||
{t('participationMet')}
|
<Thumbs up={false} />
|
||||||
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
)
|
||||||
</KeyValueTableRow>
|
) : willPassByTokenVote ? (
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('participationLPMet')}
|
|
||||||
{participationLPMet ? (
|
|
||||||
<Thumbs up={true} />
|
<Thumbs up={true} />
|
||||||
) : (
|
) : (
|
||||||
<Thumbs up={false} />
|
<Thumbs up={false} />
|
||||||
)}
|
)}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
)}
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('tokenForProposal')}
|
|
||||||
{formatNumber(yesTokens, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{isUpdateMarket && (
|
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('tokenLPForProposal')}
|
{t('majorityMet')}
|
||||||
{formatNumber(yesEquityLikeShareWeight, 2)}
|
{majorityMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
)}
|
{isUpdateMarket && (
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('totalSupply')}
|
|
||||||
{formatNumber(totalSupply, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('tokensAgainstProposal')}
|
|
||||||
{formatNumber(noTokens, 2)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('participationRequired')}
|
|
||||||
{formatNumberPercentage(requiredParticipation)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
<KeyValueTableRow>
|
|
||||||
{t('majorityRequired')}
|
|
||||||
{formatNumberPercentage(requiredMajorityPercentage)}
|
|
||||||
</KeyValueTableRow>
|
|
||||||
{!isUpdateMarket && (
|
|
||||||
<>
|
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('numberOfVotingParties')}
|
{t('majorityLPMet')}
|
||||||
{formatNumber(totalVotes, 0)}
|
{majorityLPMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('participationMet')}
|
||||||
|
{participationMet ? <Thumbs up={true} /> : <Thumbs up={false} />}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{isUpdateMarket && (
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('totalTokensVotes')}
|
{t('participationLPMet')}
|
||||||
{formatNumber(totalTokensVoted, 2)}
|
{participationLPMet ? (
|
||||||
|
<Thumbs up={true} />
|
||||||
|
) : (
|
||||||
|
<Thumbs up={false} />
|
||||||
|
)}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
|
)}
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('tokenForProposal')}
|
||||||
|
{formatNumber(yesTokens, 2)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
{isUpdateMarket && (
|
||||||
<KeyValueTableRow>
|
<KeyValueTableRow>
|
||||||
{t('totalTokenVotedPercentage')}
|
{t('tokenLPForProposal')}
|
||||||
{formatNumberPercentage(totalTokensPercentage, 2)}
|
{formatNumber(yesEquityLikeShareWeight, 2)}
|
||||||
</KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
<KeyValueTableRow>
|
)}
|
||||||
{t('numberOfForVotes')}
|
<KeyValueTableRow>
|
||||||
{formatNumber(yesVotes, 0)}
|
{t('totalSupply')}
|
||||||
</KeyValueTableRow>
|
{formatNumber(totalSupply, 2)}
|
||||||
<KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
{t('numberOfAgainstVotes')}
|
<KeyValueTableRow>
|
||||||
{formatNumber(noVotes, 0)}
|
{t('tokensAgainstProposal')}
|
||||||
</KeyValueTableRow>
|
{formatNumber(noTokens, 2)}
|
||||||
<KeyValueTableRow>
|
</KeyValueTableRow>
|
||||||
{t('yesPercentage')}
|
<KeyValueTableRow>
|
||||||
{formatNumberPercentage(yesPercentage, 2)}
|
{t('participationRequired')}
|
||||||
</KeyValueTableRow>
|
{formatNumberPercentage(requiredParticipation)}
|
||||||
<KeyValueTableRow noBorder={true}>
|
</KeyValueTableRow>
|
||||||
{t('noPercentage')}
|
<KeyValueTableRow>
|
||||||
{formatNumberPercentage(noPercentage, 2)}
|
{t('majorityRequired')}
|
||||||
</KeyValueTableRow>
|
{formatNumberPercentage(requiredMajorityPercentage)}
|
||||||
</>
|
</KeyValueTableRow>
|
||||||
)}
|
{!isUpdateMarket && (
|
||||||
</KeyValueTable>
|
<>
|
||||||
</RoundedWrapper>
|
<KeyValueTableRow>
|
||||||
|
{t('numberOfVotingParties')}
|
||||||
|
{formatNumber(totalVotes, 0)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('totalTokensVotes')}
|
||||||
|
{formatNumber(totalTokensVoted, 2)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('totalTokenVotedPercentage')}
|
||||||
|
{formatNumberPercentage(totalTokensPercentage, 2)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('numberOfForVotes')}
|
||||||
|
{formatNumber(yesVotes, 0)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('numberOfAgainstVotes')}
|
||||||
|
{formatNumber(noVotes, 0)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow>
|
||||||
|
{t('yesPercentage')}
|
||||||
|
{formatNumberPercentage(yesPercentage, 2)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
<KeyValueTableRow noBorder={true}>
|
||||||
|
{t('noPercentage')}
|
||||||
|
{formatNumberPercentage(noPercentage, 2)}
|
||||||
|
</KeyValueTableRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</KeyValueTable>
|
||||||
|
</RoundedWrapper>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -2,7 +2,7 @@ import {
|
|||||||
NetworkParams,
|
NetworkParams,
|
||||||
useNetworkParams,
|
useNetworkParams,
|
||||||
} from '@vegaprotocol/network-parameters';
|
} from '@vegaprotocol/network-parameters';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer, RoundedWrapper } from '@vegaprotocol/ui-toolkit';
|
||||||
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
import { ProposalHeader } from '../proposal-detail-header/proposal-header';
|
||||||
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
|
||||||
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
|
||||||
@ -78,7 +78,7 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
|||||||
<AsyncRenderer data={params} loading={loading} error={error}>
|
<AsyncRenderer data={params} loading={loading} error={error}>
|
||||||
<section data-testid="proposal">
|
<section data-testid="proposal">
|
||||||
<ProposalHeader proposal={proposal} isListItem={false} />
|
<ProposalHeader proposal={proposal} isListItem={false} />
|
||||||
<div className="mb-10">
|
<div className="my-10">
|
||||||
<ProposalChangeTable proposal={proposal} />
|
<ProposalChangeTable proposal={proposal} />
|
||||||
</div>
|
</div>
|
||||||
{proposal.terms.change.__typename === 'NewAsset' &&
|
{proposal.terms.change.__typename === 'NewAsset' &&
|
||||||
@ -91,14 +91,18 @@ export const Proposal = ({ proposal }: ProposalProps) => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<div className="mb-12">
|
<div className="mb-12">
|
||||||
<VoteDetails
|
<RoundedWrapper paddingBottom={true}>
|
||||||
proposal={proposal}
|
<VoteDetails
|
||||||
proposalType={proposalType}
|
proposal={proposal}
|
||||||
minVoterBalance={minVoterBalance}
|
proposalType={proposalType}
|
||||||
spamProtectionMinTokens={params?.spam_protection_voting_min_tokens}
|
minVoterBalance={minVoterBalance}
|
||||||
/>
|
spamProtectionMinTokens={
|
||||||
|
params?.spam_protection_voting_min_tokens
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</RoundedWrapper>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-10">
|
<div className="mb-4">
|
||||||
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
<ProposalVotesTable proposal={proposal} proposalType={proposalType} />
|
||||||
</div>
|
</div>
|
||||||
<ProposalTermsJson terms={proposal.terms} />
|
<ProposalTermsJson terms={proposal.terms} />
|
||||||
|
@ -97,7 +97,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Enacted');
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
format(lastWeek, DATE_FORMAT_DETAILED)
|
format(lastWeek, DATE_FORMAT_DETAILED)
|
||||||
);
|
);
|
||||||
@ -113,7 +112,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Passed');
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
||||||
);
|
);
|
||||||
@ -128,9 +126,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent(
|
|
||||||
'Waiting for node vote'
|
|
||||||
);
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
`Enacts on ${format(nextWeek, DATE_FORMAT_DETAILED)}`
|
||||||
);
|
);
|
||||||
@ -221,7 +216,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
'5 minutes left to vote'
|
'5 minutes left to vote'
|
||||||
);
|
);
|
||||||
@ -236,7 +230,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
'5 hours left to vote'
|
'5 hours left to vote'
|
||||||
);
|
);
|
||||||
@ -251,7 +244,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
expect(screen.getByTestId('vote-details')).toHaveTextContent(
|
||||||
'5 days left to vote'
|
'5 days left to vote'
|
||||||
);
|
);
|
||||||
@ -268,10 +260,7 @@ describe('Proposals list item details', () => {
|
|||||||
networkParamsQueryMock,
|
networkParamsQueryMock,
|
||||||
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
|
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_YES),
|
||||||
]);
|
]);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
expect(await screen.findByText('You voted For')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(await screen.findByText('You voted')).toBeInTheDocument();
|
|
||||||
expect(await screen.findByText('For')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders proposal state: Open - user voted against', async () => {
|
it('Renders proposal state: Open - user voted against', async () => {
|
||||||
@ -285,9 +274,7 @@ describe('Proposals list item details', () => {
|
|||||||
networkParamsQueryMock,
|
networkParamsQueryMock,
|
||||||
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
|
createUserVoteQueryMock(proposal?.id, VoteValue.VALUE_NO),
|
||||||
]);
|
]);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
expect(await screen.findByText('You voted Against')).toBeInTheDocument();
|
||||||
expect(await screen.findByText('You voted')).toBeInTheDocument();
|
|
||||||
expect(await screen.findByText('Against')).toBeInTheDocument();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Renders proposal state: Open - participation not reached', () => {
|
it('Renders proposal state: Open - participation not reached', () => {
|
||||||
@ -303,7 +290,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
'Participation not reached'
|
'Participation not reached'
|
||||||
);
|
);
|
||||||
@ -322,7 +308,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
'Majority not reached'
|
'Majority not reached'
|
||||||
);
|
);
|
||||||
@ -342,7 +327,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Open');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to pass');
|
expect(screen.getByTestId('vote-status')).toHaveTextContent('Set to pass');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -359,7 +343,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
'Participation not reached'
|
'Participation not reached'
|
||||||
);
|
);
|
||||||
@ -378,7 +361,6 @@ describe('Proposals list item details', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Declined');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
'Majority not reached'
|
'Majority not reached'
|
||||||
);
|
);
|
||||||
@ -395,7 +377,6 @@ describe('Proposals list item details', () => {
|
|||||||
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
|
ProposalRejectionReason.PROPOSAL_ERROR_INVALID_FUTURE_PRODUCT,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId('proposal-status')).toHaveTextContent('Rejected');
|
|
||||||
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
expect(screen.getByTestId('vote-status')).toHaveTextContent(
|
||||||
'Invalid future product'
|
'Invalid future product'
|
||||||
);
|
);
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, Icon } from '@vegaprotocol/ui-toolkit';
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVoteInformation } from '../../hooks';
|
import { useVoteInformation } from '../../hooks';
|
||||||
import { useUserVote } from '../vote-details/use-user-vote';
|
import { useUserVote } from '../vote-details/use-user-vote';
|
||||||
import {
|
import { StatusPass } from '../current-proposal-status/current-proposal-status';
|
||||||
StatusPass,
|
|
||||||
StatusFail,
|
|
||||||
} from '../current-proposal-status/current-proposal-status';
|
|
||||||
import { format, formatDistanceToNowStrict } from 'date-fns';
|
import { format, formatDistanceToNowStrict } from 'date-fns';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
import { DATE_FORMAT_DETAILED } from '../../../../lib/date-formats';
|
||||||
@ -22,7 +19,7 @@ const MajorityNotReached = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{t('Majority')} <StatusFail>{t('not reached')}</StatusFail>
|
{t('Majority')} {t('not reached')}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -30,7 +27,7 @@ const ParticipationNotReached = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{t('Participation')} <StatusFail>{t('not reached')}</StatusFail>
|
{t('Participation')} {t('not reached')}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -57,17 +54,11 @@ export const ProposalsListItemDetails = ({
|
|||||||
? t('byTokenVote')
|
? t('byTokenVote')
|
||||||
: t('byLPVote');
|
: t('byLPVote');
|
||||||
|
|
||||||
let proposalStatus: ReactNode;
|
|
||||||
let voteDetails: ReactNode;
|
let voteDetails: ReactNode;
|
||||||
let voteStatus: ReactNode;
|
let voteStatus: ReactNode;
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case ProposalState.STATE_ENACTED: {
|
case ProposalState.STATE_ENACTED: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
{t('voteState_Enacted')} <Icon name={'tick'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteDetails = proposal?.terms.enactmentDatetime && (
|
voteDetails = proposal?.terms.enactmentDatetime && (
|
||||||
<>
|
<>
|
||||||
{format(
|
{format(
|
||||||
@ -79,11 +70,6 @@ export const ProposalsListItemDetails = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_PASSED: {
|
case ProposalState.STATE_PASSED: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
{t('voteState_Passed')} <Icon name={'tick'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
|
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
|
||||||
<>
|
<>
|
||||||
{t('toEnactOn')}{' '}
|
{t('toEnactOn')}{' '}
|
||||||
@ -97,11 +83,6 @@ export const ProposalsListItemDetails = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
case ProposalState.STATE_WAITING_FOR_NODE_VOTE: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
{t('voteState_WaitingForNodeVote')} <Icon name={'time'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
|
voteDetails = proposal?.terms.change.__typename !== 'NewFreeform' && (
|
||||||
<>
|
<>
|
||||||
{t('toEnactOn')}{' '}
|
{t('toEnactOn')}{' '}
|
||||||
@ -115,19 +96,14 @@ export const ProposalsListItemDetails = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_OPEN: {
|
case ProposalState.STATE_OPEN: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
{t('voteState_Open')} <Icon name={'hand'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteDetails = (voteState === 'Yes' && (
|
voteDetails = (voteState === 'Yes' && (
|
||||||
<>
|
<>
|
||||||
{t('youVoted')} <StatusPass>{t('voteState_Yes')}</StatusPass>
|
{t('youVoted')} {t('voteState_Yes')}
|
||||||
</>
|
</>
|
||||||
)) ||
|
)) ||
|
||||||
(voteState === 'No' && (
|
(voteState === 'No' && (
|
||||||
<>
|
<>
|
||||||
{t('youVoted')} <StatusFail>{t('voteState_No')}</StatusFail>
|
{t('youVoted')} {t('voteState_No')}
|
||||||
</>
|
</>
|
||||||
)) || (
|
)) || (
|
||||||
<>
|
<>
|
||||||
@ -148,40 +124,29 @@ export const ProposalsListItemDetails = ({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
|
{t('Set to')} {t('fail')}
|
||||||
</>
|
</>
|
||||||
))) ||
|
))) ||
|
||||||
(!participationMet && <ParticipationNotReached />) ||
|
(!participationMet && <ParticipationNotReached />) ||
|
||||||
(!majorityMet && <MajorityNotReached />) ||
|
(!majorityMet && <MajorityNotReached />) ||
|
||||||
(willPassByTokenVote ? (
|
(willPassByTokenVote ? (
|
||||||
<>
|
<>
|
||||||
{t('Set to')} <StatusPass>{t('pass')}</StatusPass>
|
{t('Set to')} {t('pass')}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{t('Set to')} <StatusFail>{t('fail')}</StatusFail>
|
{t('Set to')} {t('fail')}
|
||||||
</>
|
</>
|
||||||
));
|
));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_DECLINED: {
|
case ProposalState.STATE_DECLINED: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
{t('voteState_Declined')} <Icon name={'cross'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteStatus =
|
voteStatus =
|
||||||
(!participationMet && <ParticipationNotReached />) ||
|
(!participationMet && <ParticipationNotReached />) ||
|
||||||
(!majorityMet && <MajorityNotReached />);
|
(!majorityMet && <MajorityNotReached />);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ProposalState.STATE_REJECTED: {
|
case ProposalState.STATE_REJECTED: {
|
||||||
proposalStatus = (
|
|
||||||
<>
|
|
||||||
<StatusFail>{t('voteState_Rejected')}</StatusFail>{' '}
|
|
||||||
<Icon name={'warning-sign'} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
voteStatus = proposal?.rejectionReason && (
|
voteStatus = proposal?.rejectionReason && (
|
||||||
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
|
<>{t(ProposalRejectionReasonMapping[proposal.rejectionReason])}</>
|
||||||
);
|
);
|
||||||
@ -190,16 +155,10 @@ export const ProposalsListItemDetails = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-[1fr_auto] mt-2 items-start gap-2 text-sm">
|
<div className="grid grid-cols-[1fr_auto] mt-4 items-start gap-2 text-sm">
|
||||||
<div
|
|
||||||
className="col-start-1 row-start-1 flex items-center gap-2 text-white"
|
|
||||||
data-testid="proposal-status"
|
|
||||||
>
|
|
||||||
{proposalStatus}
|
|
||||||
</div>
|
|
||||||
{voteDetails && (
|
{voteDetails && (
|
||||||
<div
|
<div
|
||||||
className="col-start-1 row-start-2 text-neutral-500"
|
className="col-start-1 row-start-2 text-vega-light-300"
|
||||||
data-testid="vote-details"
|
data-testid="vote-details"
|
||||||
>
|
>
|
||||||
{voteDetails}
|
{voteDetails}
|
||||||
@ -216,9 +175,7 @@ export const ProposalsListItemDetails = ({
|
|||||||
{proposal?.id && (
|
{proposal?.id && (
|
||||||
<div className="col-start-2 row-start-2 justify-self-end">
|
<div className="col-start-2 row-start-2 justify-self-end">
|
||||||
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
|
<Link to={`${Routes.PROPOSALS}/${proposal.id}`}>
|
||||||
<Button data-testid="view-proposal-btn" size="sm">
|
<Button data-testid="view-proposal-btn">{t('View')}</Button>
|
||||||
{t('View')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -106,7 +106,7 @@ export const ProposalsList = ({
|
|||||||
{proposals.length > 0 && (
|
{proposals.length > 0 && (
|
||||||
<ProposalsListFilter setFilterString={setFilterString} />
|
<ProposalsListFilter setFilterString={setFilterString} />
|
||||||
)}
|
)}
|
||||||
<section className="-mx-4 p-4 mb-8 bg-neutral-800">
|
<section className="-mx-4 p-4 mb-8 bg-vega-dark-100">
|
||||||
<SubHeading title={t('openProposals')} />
|
<SubHeading title={t('openProposals')} />
|
||||||
{sortedProposals.open.length > 0 ||
|
{sortedProposals.open.length > 0 ||
|
||||||
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
sortedProtocolUpgradeProposals.open.length > 0 ? (
|
||||||
|
@ -3,15 +3,15 @@ import { Link } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Icon,
|
Icon,
|
||||||
Intent,
|
|
||||||
Lozenge,
|
Lozenge,
|
||||||
RoundedWrapper,
|
RoundedWrapper,
|
||||||
} from '@vegaprotocol/ui-toolkit';
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
import { stripFullStops } from '@vegaprotocol/utils';
|
import { stripFullStops } from '@vegaprotocol/utils';
|
||||||
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
|
import { ProtocolUpgradeProposalStatus } from '@vegaprotocol/types';
|
||||||
import { SubHeading } from '../../../../components/heading';
|
import { SubHeading } from '../../../../components/heading';
|
||||||
import type { ReactNode } from 'react';
|
import { ProposalInfoLabel } from '../proposal-info-label';
|
||||||
import Routes from '../../../routes';
|
import Routes from '../../../routes';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
import type { ProtocolUpgradeProposalFieldsFragment } from '@vegaprotocol/proposals';
|
||||||
|
|
||||||
interface ProtocolProposalsListItemProps {
|
interface ProtocolProposalsListItemProps {
|
||||||
@ -29,30 +29,30 @@ export const ProtocolUpgradeProposalsListItem = ({
|
|||||||
switch (proposal.status) {
|
switch (proposal.status) {
|
||||||
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED:
|
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_REJECTED:
|
||||||
proposalStatusIcon = (
|
proposalStatusIcon = (
|
||||||
<div data-testid="protocol-upgrade-proposal-status-icon-rejected">
|
<span data-testid="protocol-upgrade-proposal-status-icon-rejected">
|
||||||
<Icon name={'cross'} />
|
<Icon name={'cross'} />
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
|
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_PENDING:
|
||||||
proposalStatusIcon = (
|
proposalStatusIcon = (
|
||||||
<div data-testid="protocol-upgrade-proposal-status-icon-pending">
|
<span data-testid="protocol-upgrade-proposal-status-icon-pending">
|
||||||
<Icon name={'time'} />
|
<Icon name={'time'} />
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
|
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_APPROVED:
|
||||||
proposalStatusIcon = (
|
proposalStatusIcon = (
|
||||||
<div data-testid="protocol-upgrade-proposal-status-icon-approved">
|
<span data-testid="protocol-upgrade-proposal-status-icon-approved">
|
||||||
<Icon name={'tick'} />
|
<Icon name={'tick'} />
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
|
case ProtocolUpgradeProposalStatus.PROTOCOL_UPGRADE_PROPOSAL_STATUS_UNSPECIFIED:
|
||||||
proposalStatusIcon = (
|
proposalStatusIcon = (
|
||||||
<div data-testid="protocol-upgrade-proposal-status-icon-unspecified">
|
<span data-testid="protocol-upgrade-proposal-status-icon-unspecified">
|
||||||
<Icon name={'disable'} />
|
<Icon name={'disable'} />
|
||||||
</div>
|
</span>
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -71,18 +71,28 @@ export const ProtocolUpgradeProposalsListItem = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<div
|
<div className="flex gap-2">
|
||||||
data-testid="protocol-upgrade-proposal-type"
|
<div
|
||||||
className="flex items-center gap-2 mb-4"
|
data-testid="protocol-upgrade-proposal-type"
|
||||||
>
|
className="flex items-center gap-2 mb-4"
|
||||||
<Lozenge variant={Intent.Success}>{t('networkUpgrade')}</Lozenge>
|
>
|
||||||
|
<ProposalInfoLabel variant="highlight">
|
||||||
|
{t('networkUpgrade')}
|
||||||
|
</ProposalInfoLabel>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-testid="protocol-upgrade-proposal-status">
|
||||||
|
<ProposalInfoLabel>
|
||||||
|
{t(`${proposal.status}`)} {proposalStatusIcon}
|
||||||
|
</ProposalInfoLabel>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
data-testid="protocol-upgrade-proposal-release-tag"
|
data-testid="protocol-upgrade-proposal-release-tag"
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
>
|
>
|
||||||
<span className="pr-2">{t('vegaReleaseTag')}</span>
|
<span>{t('vegaReleaseTag')}:</span>{' '}
|
||||||
<Lozenge>{proposal.vegaReleaseTag}</Lozenge>
|
<Lozenge>{proposal.vegaReleaseTag}</Lozenge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -90,30 +100,18 @@ export const ProtocolUpgradeProposalsListItem = ({
|
|||||||
data-testid="protocol-upgrade-proposal-block-height"
|
data-testid="protocol-upgrade-proposal-block-height"
|
||||||
className="mb-2"
|
className="mb-2"
|
||||||
>
|
>
|
||||||
<span className="pr-2">{t('upgradeBlockHeight')}</span>
|
<span>{t('upgradeBlockHeight')}:</span>{' '}
|
||||||
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
|
<Lozenge>{proposal.upgradeBlockHeight}</Lozenge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-[1fr_auto] mt-3 items-start gap-2">
|
<div className="grid grid-cols-1 mt-3">
|
||||||
<div className="col-start-1 row-start-1 text-white">
|
<div className="justify-self-end">
|
||||||
<div
|
|
||||||
data-testid="protocol-upgrade-proposal-status"
|
|
||||||
className="flex items-center gap-2"
|
|
||||||
>
|
|
||||||
<span>{t(`${proposal.status}`)}</span>
|
|
||||||
<span>{proposalStatusIcon}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-start-2 row-start-2 justify-self-end">
|
|
||||||
<Link
|
<Link
|
||||||
to={`${Routes.PROPOSALS}/protocol-upgrade/${stripFullStops(
|
to={`${Routes.PROPOSALS}/protocol-upgrade/${stripFullStops(
|
||||||
proposal.vegaReleaseTag
|
proposal.vegaReleaseTag
|
||||||
)}`}
|
)}`}
|
||||||
>
|
>
|
||||||
<Button data-testid="view-proposal-btn" size="sm">
|
<Button data-testid="view-proposal-btn">{t('View')}</Button>
|
||||||
{t('View')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,10 +107,6 @@ export const VoteButtons = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentStakeAvailable.isLessThanOrEqualTo(0)) {
|
|
||||||
return t('noGovernanceTokens');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minVoterBalance && spamProtectionMinTokens) {
|
if (minVoterBalance && spamProtectionMinTokens) {
|
||||||
const formattedMinVoterBalance = new BigNumber(
|
const formattedMinVoterBalance = new BigNumber(
|
||||||
addDecimal(minVoterBalance, 18)
|
addDecimal(minVoterBalance, 18)
|
||||||
@ -163,24 +159,30 @@ export const VoteButtons = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
|
{changeVote || (voteState === VoteState.NotCast && proposalVotable) ? (
|
||||||
<div className="flex gap-4" data-testid="vote-buttons">
|
<>
|
||||||
<div className="flex-1">
|
{currentStakeAvailable.isLessThanOrEqualTo(0) && (
|
||||||
|
<p data-testid="no-stake-available">{t('noGovernanceTokens')}</p>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex gap-4" data-testid="vote-buttons">
|
||||||
<Button
|
<Button
|
||||||
data-testid="vote-for"
|
data-testid="vote-for"
|
||||||
onClick={() => submitVote(VoteValue.VALUE_YES)}
|
onClick={() => submitVote(VoteValue.VALUE_YES)}
|
||||||
|
variant="primary"
|
||||||
|
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
|
||||||
>
|
>
|
||||||
{t('voteFor')}
|
{t('voteFor')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<Button
|
<Button
|
||||||
data-testid="vote-against"
|
data-testid="vote-against"
|
||||||
onClick={() => submitVote(VoteValue.VALUE_NO)}
|
onClick={() => submitVote(VoteValue.VALUE_NO)}
|
||||||
|
variant="primary"
|
||||||
|
disabled={currentStakeAvailable.isLessThanOrEqualTo(0)}
|
||||||
>
|
>
|
||||||
{t('voteAgainst')}
|
{t('voteAgainst')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
) : (
|
) : (
|
||||||
(voteState === VoteState.Yes || voteState === VoteState.No) && (
|
(voteState === VoteState.Yes || voteState === VoteState.No) && (
|
||||||
<p data-testid="you-voted">
|
<p data-testid="you-voted">
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
|
import { RoundedWrapper, Icon } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||||
import { ProposalState } from '@vegaprotocol/types';
|
import { ProposalState } from '@vegaprotocol/types';
|
||||||
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
|
import { useVoteSubmit, VoteProgress } from '@vegaprotocol/proposals';
|
||||||
@ -199,10 +200,11 @@ export const VoteDetails = ({
|
|||||||
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
{proposalType === ProposalType.PROPOSAL_UPDATE_MARKET && (
|
||||||
<p>{t('votingThresholdInfo')}</p>
|
<p>{t('votingThresholdInfo')}</p>
|
||||||
)}
|
)}
|
||||||
{pubKey ? (
|
|
||||||
<section className="mt-10">
|
<section className="mt-10">
|
||||||
<SubHeading title={t('yourVote')} />
|
<SubHeading title={t('castYourVote')} />
|
||||||
{proposal && (
|
{pubKey ? (
|
||||||
|
proposal && (
|
||||||
<VoteButtonsContainer
|
<VoteButtonsContainer
|
||||||
voteState={voteState}
|
voteState={voteState}
|
||||||
voteDatetime={voteDatetime}
|
voteDatetime={voteDatetime}
|
||||||
@ -214,11 +216,19 @@ export const VoteDetails = ({
|
|||||||
submit={submit}
|
submit={submit}
|
||||||
dialog={Dialog}
|
dialog={Dialog}
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
</section>
|
) : (
|
||||||
) : (
|
<RoundedWrapper paddingBottom={true}>
|
||||||
<ConnectToVega />
|
<div className="mb-4">
|
||||||
)}
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<Icon name={'info-sign'} />
|
||||||
|
<div>{t('connectAVegaWalletToVote')}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConnectToVega />
|
||||||
|
</RoundedWrapper>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ export const getIntentBorder = (intent = Intent.None) => {
|
|||||||
'border-warning': intent === Intent.Warning,
|
'border-warning': intent === Intent.Warning,
|
||||||
'border-neutral-500': intent === Intent.None,
|
'border-neutral-500': intent === Intent.None,
|
||||||
'border-vega-blue-300': intent === Intent.Primary,
|
'border-vega-blue-300': intent === Intent.Primary,
|
||||||
'border-success': intent === Intent.Success,
|
'border-vega-green': intent === Intent.Success,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import once from 'lodash/once';
|
import once from 'lodash/once';
|
||||||
|
import { format } from 'date-fns';
|
||||||
import { getUserLocale } from '../get-user-locale';
|
import { getUserLocale } from '../get-user-locale';
|
||||||
import { utcToZonedTime, format as tzFormat } from 'date-fns-tz';
|
|
||||||
|
|
||||||
export const isValidDate = (date: Date) =>
|
export const isValidDate = (date: Date) =>
|
||||||
date instanceof Date && !isNaN(date.getTime());
|
date instanceof Date && !isNaN(date.getTime());
|
||||||
@ -53,15 +53,24 @@ export const formatForInput = (date: Date) => {
|
|||||||
return `${year}-${month}-${day}T${hours}:${minutes}:${secs}`;
|
return `${year}-${month}-${day}T${hours}:${minutes}:${secs}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Returns a user's local time zone abbreviation */
|
||||||
|
export const getTimeZoneAbbreviation = (date: Date) => {
|
||||||
|
const formatter = new Intl.DateTimeFormat(undefined, {
|
||||||
|
timeZoneName: 'short',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const timeZoneAbbreviation = parts.find(
|
||||||
|
(part) => part.type === 'timeZoneName'
|
||||||
|
)?.value;
|
||||||
|
return timeZoneAbbreviation || '';
|
||||||
|
};
|
||||||
|
|
||||||
/** Format a user's local date and time with the time zone abbreviation */
|
/** Format a user's local date and time with the time zone abbreviation */
|
||||||
export const formatDateWithLocalTimezone = (
|
export const formatDateWithLocalTimezone = (
|
||||||
date: Date,
|
date: Date,
|
||||||
formatStr = 'dd MMMM yyyy HH:mm (z)'
|
formatStr = 'dd MMMM yyyy HH:mm'
|
||||||
) => {
|
) => {
|
||||||
const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
const formattedDate = format(date, formatStr);
|
||||||
const localDatetime = utcToZonedTime(date, userTimeZone);
|
const timeZoneAbbreviation = getTimeZoneAbbreviation(date);
|
||||||
|
return `${formattedDate} (${timeZoneAbbreviation})`;
|
||||||
return tzFormat(localDatetime, formatStr, {
|
|
||||||
timeZone: userTimeZone,
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user